66 "encoding/json"
77 "errors"
88 "fmt"
9+ "io"
910 "log/slog"
1011 "net"
1112 "net/http"
@@ -15,9 +16,11 @@ import (
1516 "strings"
1617
1718 "github.com/sashabaranov/go-openai"
19+ "golang.org/x/term"
1820
1921 "github.com/docker/cagent/pkg/chat"
2022 latest "github.com/docker/cagent/pkg/config/v2"
23+ "github.com/docker/cagent/pkg/input"
2124 "github.com/docker/cagent/pkg/model/provider/base"
2225 "github.com/docker/cagent/pkg/model/provider/options"
2326 "github.com/docker/cagent/pkg/tools"
@@ -51,6 +54,12 @@ func NewClient(ctx context.Context, cfg *latest.ModelConfig, opts ...options.Opt
5154 endpoint , engine , err := getDockerModelEndpointAndEngine (ctx )
5255 if err != nil {
5356 slog .Debug ("docker model status query failed" , "error" , err )
57+ } else {
58+ // Auto-pull the model if needed
59+ if err := pullDockerModelIfNeeded (ctx , cfg .Model ); err != nil {
60+ slog .Debug ("docker model pull failed" , "error" , err )
61+ return nil , err
62+ }
5463 }
5564
5665 clientConfig := openai .DefaultConfig ("" )
@@ -430,6 +439,61 @@ func parseDMRProviderOpts(cfg *latest.ModelConfig) (contextSize int, runtimeFlag
430439 return contextSize , runtimeFlags
431440}
432441
442+ func pullDockerModelIfNeeded (ctx context.Context , model string ) error {
443+ // Check if running in interactive mode (stdin is a terminal)
444+ interactive := term .IsTerminal (int (os .Stdin .Fd ()))
445+ if ! interactive {
446+ // In non-interactive mode (CI / Servers), do not attempt to pull the model
447+ return nil
448+ }
449+
450+ if modelExists (ctx , model ) {
451+ slog .Debug ("Model already exists, skipping pull" , "model" , model )
452+ return nil
453+ }
454+
455+ // Prompt user for confirmation in interactive mode
456+ fmt .Printf ("\n Model %s not found locally.\n " , model )
457+ fmt .Printf ("Do you want to pull it now? ([y]es/[n]o): " )
458+
459+ response , err := input .ReadLine (ctx , os .Stdin )
460+ if err != nil {
461+ return fmt .Errorf ("failed to read user input: %w" , err )
462+ }
463+
464+ response = strings .TrimSpace (strings .ToLower (response ))
465+ if response != "y" && response != "yes" {
466+ return fmt .Errorf ("model pull declined by user" )
467+ }
468+
469+ // Pull the model
470+ slog .Info ("Pulling DMR model" , "model" , model )
471+ fmt .Printf ("Pulling model %s...\n " , model )
472+ cmd := exec .CommandContext (ctx , "docker" , "model" , "pull" , model )
473+ cmd .Stdout = os .Stdout
474+ cmd .Stderr = os .Stderr
475+ if err := cmd .Run (); err != nil {
476+ return fmt .Errorf ("failed to pull model %s: %w" , model , err )
477+ }
478+
479+ slog .Info ("Model pulled successfully" , "model" , model )
480+ fmt .Printf ("Model %s pulled successfully.\n " , model )
481+
482+ return nil
483+ }
484+
485+ func modelExists (ctx context.Context , model string ) bool {
486+ cmd := exec .CommandContext (ctx , "docker" , "model" , "inspect" , model )
487+ var stderr bytes.Buffer
488+ cmd .Stdout = io .Discard
489+ cmd .Stderr = & stderr
490+ if err := cmd .Run (); err != nil {
491+ slog .Debug ("Model does not exist" , "model" , model , "error" , strings .TrimSpace (stderr .String ()))
492+ return false
493+ }
494+ return true
495+ }
496+
433497func configureDockerModel (ctx context.Context , model string , contextSize int , runtimeFlags []string ) error {
434498 args := buildDockerModelConfigureArgs (model , contextSize , runtimeFlags )
435499
0 commit comments