From 8459415e51249e9b91c2a754feda1a49c257e7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Tue, 16 Dec 2025 12:13:02 +0100 Subject: [PATCH 1/5] gets back runtime flags when configuring models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ignacio López Luna --- pkg/compose/model.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index 8561bd68a9..06ea638483 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -29,7 +29,6 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/containerd/errdefs" "github.com/docker/cli/cli-plugins/manager" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -154,21 +153,21 @@ func (m *modelAPI) PullModel(ctx context.Context, model types.ModelConfig, quiet } func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, events api.EventProcessor) error { - if len(config.RuntimeFlags) != 0 { - logrus.Warnf("Runtime flags are not supported and will be ignored for model %s", config.Model) - config.RuntimeFlags = nil - } events.On(api.Resource{ ID: config.Name, Status: api.Working, Text: api.StatusConfiguring, }) - // configure [--context-size=] MODEL + // configure [--context-size=] MODEL [-- ] args := []string{"configure"} if config.ContextSize > 0 { args = append(args, "--context-size", strconv.Itoa(config.ContextSize)) } args = append(args, config.Model) + if len(config.RuntimeFlags) != 0 { + args = append(args, "--") + args = append(args, config.RuntimeFlags...) + } cmd := exec.CommandContext(ctx, m.path, args...) err := m.prepare(ctx, cmd) if err != nil { From 6a52878984ce4731f7fdfed7316e3605dcfecde5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Wed, 17 Dec 2025 17:16:35 +0100 Subject: [PATCH 2/5] Only append RuntimeFlags if docker model CLI version is >= v1.0.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ignacio López Luna --- pkg/compose/model.go | 114 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index 06ea638483..e2279acc04 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -164,7 +164,8 @@ func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, args = append(args, "--context-size", strconv.Itoa(config.ContextSize)) } args = append(args, config.Model) - if len(config.RuntimeFlags) != 0 { + // Only append RuntimeFlags if docker model CLI version is >= v1.0.6 + if len(config.RuntimeFlags) != 0 && m.supportsRuntimeFlags(ctx) { args = append(args, "--") args = append(args, config.RuntimeFlags...) } @@ -272,3 +273,114 @@ func (m *modelAPI) ListModels(ctx context.Context) ([]string, error) { } return availableModels, nil } + +// getModelVersion retrieves the docker model CLI version +func (m *modelAPI) getModelVersion(ctx context.Context) (string, error) { + cmd := exec.CommandContext(ctx, m.path, "version") + err := m.prepare(ctx, cmd) + if err != nil { + return "", err + } + + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting docker model version: %w", err) + } + + // Parse output like: "Docker Model Runner version v1.0.4" + // We need to extract the version string (e.g., "v1.0.4") + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + for _, line := range lines { + if strings.Contains(line, "version") { + parts := strings.Fields(line) + for i, part := range parts { + if part == "version" && i+1 < len(parts) { + return parts[i+1], nil + } + } + } + } + + return "", fmt.Errorf("could not parse docker model version from output: %s", string(output)) +} + +// supportsRuntimeFlags checks if the docker model version supports runtime flags +// Runtime flags are supported in version >= v1.0.6 +func (m *modelAPI) supportsRuntimeFlags(ctx context.Context) bool { + versionStr, err := m.getModelVersion(ctx) + if err != nil { + // If we can't determine the version, don't append runtime flags to be safe + return false + } + + // Parse version strings + currentVersion, err := parseVersion(versionStr) + if err != nil { + return false + } + + minVersion, err := parseVersion("1.0.6") + if err != nil { + return false + } + + return !currentVersion.LessThan(minVersion) +} + +// parseVersion parses a semantic version string +// Strips build metadata and prerelease suffixes (e.g., "1.0.6-dirty" or "1.0.6+build") +func parseVersion(versionStr string) (*semVersion, error) { + // Remove 'v' prefix if present + versionStr = strings.TrimPrefix(versionStr, "v") + + // Strip build metadata or prerelease suffix after "-" or "+" + // Examples: "1.0.6-dirty" -> "1.0.6", "1.0.6+build" -> "1.0.6" + if idx := strings.IndexAny(versionStr, "-+"); idx != -1 { + versionStr = versionStr[:idx] + } + + parts := strings.Split(versionStr, ".") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid version format: %s", versionStr) + } + + var v semVersion + var err error + + v.major, err = strconv.Atoi(parts[0]) + if err != nil { + return nil, fmt.Errorf("invalid major version: %s", parts[0]) + } + + v.minor, err = strconv.Atoi(parts[1]) + if err != nil { + return nil, fmt.Errorf("invalid minor version: %s", parts[1]) + } + + if len(parts) > 2 { + v.patch, err = strconv.Atoi(parts[2]) + if err != nil { + return nil, fmt.Errorf("invalid patch version: %s", parts[2]) + } + } + + return &v, nil +} + +// semVersion represents a semantic version +type semVersion struct { + major int + minor int + patch int +} + +// LessThan compares two semantic versions +func (v *semVersion) LessThan(other *semVersion) bool { + if v.major != other.major { + return v.major < other.major + } + if v.minor != other.minor { + return v.minor < other.minor + } + return v.patch < other.patch +} From 3cdd1e6da8440ac77bde6185f68ed7f73363ac96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Wed, 17 Dec 2025 17:38:07 +0100 Subject: [PATCH 3/5] use github.com/docker/docker/api/types/versions for comparing versions and store plugin version obtained by pluginManager in newModelAPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ignacio López Luna --- pkg/compose/model.go | 113 +++++-------------------------------------- 1 file changed, 13 insertions(+), 100 deletions(-) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index e2279acc04..ddc62f4f01 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -29,6 +29,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/containerd/errdefs" "github.com/docker/cli/cli-plugins/manager" + "github.com/docker/docker/api/types/versions" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -71,6 +72,7 @@ func (s *composeService) ensureModels(ctx context.Context, project *types.Projec type modelAPI struct { path string + version string // cached plugin version env []string prepare func(ctx context.Context, cmd *exec.Cmd) error cleanup func() @@ -89,7 +91,8 @@ func (s *composeService) newModelAPI(project *types.Project) (*modelAPI, error) return nil, err } return &modelAPI{ - path: dockerModel.Path, + path: dockerModel.Path, + version: dockerModel.Version, prepare: func(ctx context.Context, cmd *exec.Cmd) error { return s.prepareShellOut(ctx, project.Environment, cmd) }, @@ -165,7 +168,7 @@ func (m *modelAPI) ConfigureModel(ctx context.Context, config types.ModelConfig, } args = append(args, config.Model) // Only append RuntimeFlags if docker model CLI version is >= v1.0.6 - if len(config.RuntimeFlags) != 0 && m.supportsRuntimeFlags(ctx) { + if len(config.RuntimeFlags) != 0 && m.supportsRuntimeFlags() { args = append(args, "--") args = append(args, config.RuntimeFlags...) } @@ -274,113 +277,23 @@ func (m *modelAPI) ListModels(ctx context.Context) ([]string, error) { return availableModels, nil } -// getModelVersion retrieves the docker model CLI version -func (m *modelAPI) getModelVersion(ctx context.Context) (string, error) { - cmd := exec.CommandContext(ctx, m.path, "version") - err := m.prepare(ctx, cmd) - if err != nil { - return "", err - } - - output, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("error getting docker model version: %w", err) - } - - // Parse output like: "Docker Model Runner version v1.0.4" - // We need to extract the version string (e.g., "v1.0.4") - lines := strings.Split(strings.TrimSpace(string(output)), "\n") - for _, line := range lines { - if strings.Contains(line, "version") { - parts := strings.Fields(line) - for i, part := range parts { - if part == "version" && i+1 < len(parts) { - return parts[i+1], nil - } - } - } - } - - return "", fmt.Errorf("could not parse docker model version from output: %s", string(output)) -} - // supportsRuntimeFlags checks if the docker model version supports runtime flags // Runtime flags are supported in version >= v1.0.6 -func (m *modelAPI) supportsRuntimeFlags(ctx context.Context) bool { - versionStr, err := m.getModelVersion(ctx) - if err != nil { - // If we can't determine the version, don't append runtime flags to be safe - return false - } - - // Parse version strings - currentVersion, err := parseVersion(versionStr) - if err != nil { - return false - } - - minVersion, err := parseVersion("1.0.6") - if err != nil { +func (m *modelAPI) supportsRuntimeFlags() bool { + // If version is not cached, don't append runtime flags to be safe + if m.version == "" { return false } - return !currentVersion.LessThan(minVersion) -} - -// parseVersion parses a semantic version string -// Strips build metadata and prerelease suffixes (e.g., "1.0.6-dirty" or "1.0.6+build") -func parseVersion(versionStr string) (*semVersion, error) { - // Remove 'v' prefix if present - versionStr = strings.TrimPrefix(versionStr, "v") + // Strip 'v' prefix if present (e.g., "v1.0.6" -> "1.0.6") + versionStr := strings.TrimPrefix(m.version, "v") // Strip build metadata or prerelease suffix after "-" or "+" - // Examples: "1.0.6-dirty" -> "1.0.6", "1.0.6+build" -> "1.0.6" + // This is necessary because versions.LessThan treats "1.0.6-dirty" < "1.0.6" per semver rules + // but we want to compare the base version numbers only if idx := strings.IndexAny(versionStr, "-+"); idx != -1 { versionStr = versionStr[:idx] } - parts := strings.Split(versionStr, ".") - if len(parts) < 2 { - return nil, fmt.Errorf("invalid version format: %s", versionStr) - } - - var v semVersion - var err error - - v.major, err = strconv.Atoi(parts[0]) - if err != nil { - return nil, fmt.Errorf("invalid major version: %s", parts[0]) - } - - v.minor, err = strconv.Atoi(parts[1]) - if err != nil { - return nil, fmt.Errorf("invalid minor version: %s", parts[1]) - } - - if len(parts) > 2 { - v.patch, err = strconv.Atoi(parts[2]) - if err != nil { - return nil, fmt.Errorf("invalid patch version: %s", parts[2]) - } - } - - return &v, nil -} - -// semVersion represents a semantic version -type semVersion struct { - major int - minor int - patch int -} - -// LessThan compares two semantic versions -func (v *semVersion) LessThan(other *semVersion) bool { - if v.major != other.major { - return v.major < other.major - } - if v.minor != other.minor { - return v.minor < other.minor - } - return v.patch < other.patch + return !versions.LessThan(versionStr, "1.0.6") } From db04dc5c56f07c890bdfac5915e84c6feb288fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Thu, 18 Dec 2025 11:50:15 +0100 Subject: [PATCH 4/5] do not strip build metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ignacio López Luna --- pkg/compose/model.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index ddc62f4f01..3bd86384ed 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -287,13 +287,5 @@ func (m *modelAPI) supportsRuntimeFlags() bool { // Strip 'v' prefix if present (e.g., "v1.0.6" -> "1.0.6") versionStr := strings.TrimPrefix(m.version, "v") - - // Strip build metadata or prerelease suffix after "-" or "+" - // This is necessary because versions.LessThan treats "1.0.6-dirty" < "1.0.6" per semver rules - // but we want to compare the base version numbers only - if idx := strings.IndexAny(versionStr, "-+"); idx != -1 { - versionStr = versionStr[:idx] - } - return !versions.LessThan(versionStr, "1.0.6") } From 5cdf55a14cd4c56f3f3d8fc24ef5e4b1697c5892 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20L=C3=B3pez=20Luna?= Date: Thu, 18 Dec 2025 15:00:38 +0100 Subject: [PATCH 5/5] remove duplicated version field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ignacio López Luna --- pkg/compose/model.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/compose/model.go b/pkg/compose/model.go index f75cc99b40..e4614d199c 100644 --- a/pkg/compose/model.go +++ b/pkg/compose/model.go @@ -72,7 +72,6 @@ func (s *composeService) ensureModels(ctx context.Context, project *types.Projec type modelAPI struct { path string - version string // cached plugin version env []string prepare func(ctx context.Context, cmd *exec.Cmd) error cleanup func()