diff --git a/cmd/root/acp.go b/cmd/root/acp.go index d92cf356c..a495197aa 100644 --- a/cmd/root/acp.go +++ b/cmd/root/acp.go @@ -7,33 +7,39 @@ import ( "github.com/spf13/cobra" "github.com/docker/cagent/pkg/acp" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/telemetry" ) +type acpFlags struct { + runConfig config.RuntimeConfig +} + func newACPCmd() *cobra.Command { + var flags acpFlags + cmd := &cobra.Command{ Use: "acp ", Short: "Start an ACP (Agent Client Protocol) server", Long: `Start an ACP server that exposes the agent via the Agent Client Protocol`, Args: cobra.ExactArgs(1), - RunE: runACPCommand, + RunE: flags.runACPCommand, } - addGatewayFlags(cmd) - addRuntimeConfigFlags(cmd) + addRuntimeConfigFlags(cmd, &flags.runConfig) return cmd } -func runACPCommand(cmd *cobra.Command, args []string) error { +func (f *acpFlags) runACPCommand(cmd *cobra.Command, args []string) error { telemetry.TrackCommand("acp", args) ctx := cmd.Context() agentFilename := args[0] - slog.Debug("Starting ACP server", "agent_file", agentFilename, "debug_mode", debugMode) + slog.Debug("Starting ACP server", "agent_file", agentFilename) - acpAgent := acp.NewAgent(agentFilename, runConfig) + acpAgent := acp.NewAgent(agentFilename, f.runConfig) conn := acpsdk.NewAgentSideConnection(acpAgent, cmd.OutOrStdout(), cmd.InOrStdin()) conn.SetLogger(slog.Default()) acpAgent.SetAgentConnection(conn) diff --git a/cmd/root/api.go b/cmd/root/api.go index e350dfbc4..28bf0b023 100644 --- a/cmd/root/api.go +++ b/cmd/root/api.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/server" "github.com/docker/cagent/pkg/session" "github.com/docker/cagent/pkg/teamloader" @@ -18,6 +19,7 @@ import ( type apiFlags struct { listenAddr string sessionDB string + runConfig config.RuntimeConfig } func newAPICmd() *cobra.Command { @@ -33,8 +35,8 @@ func newAPICmd() *cobra.Command { cmd.PersistentFlags().StringVarP(&flags.listenAddr, "listen", "l", ":8080", "Address to listen on") cmd.PersistentFlags().StringVarP(&flags.sessionDB, "session-db", "s", "session.db", "Path to the session database") - addGatewayFlags(cmd) - addRuntimeConfigFlags(cmd) + + addRuntimeConfigFlags(cmd, &flags.runConfig) return cmd } @@ -63,7 +65,7 @@ func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error { slog.Info("Listening on " + f.listenAddr) } - slog.Debug("Starting server", "agents", agentsPath, "debug_mode", debugMode) + slog.Debug("Starting server", "agents", agentsPath) sessionStore, err := session.NewSQLiteSessionStore(f.sessionDB) if err != nil { @@ -82,7 +84,7 @@ func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error { opts = append(opts, server.WithAgentsDir(filepath.Dir(agentsPath))) } - teams, err := teamloader.LoadTeams(ctx, agentsPath, runConfig) + teams, err := teamloader.LoadTeams(ctx, agentsPath, f.runConfig) if err != nil { return fmt.Errorf("failed to load teams: %w", err) } @@ -94,7 +96,7 @@ func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error { } }() - s, err := server.New(sessionStore, runConfig, teams, opts...) + s, err := server.New(sessionStore, f.runConfig, teams, opts...) if err != nil { return fmt.Errorf("failed to create server: %w", err) } diff --git a/cmd/root/debug.go b/cmd/root/debug.go index 6b4826ea5..1bbbe740d 100644 --- a/cmd/root/debug.go +++ b/cmd/root/debug.go @@ -5,11 +5,18 @@ import ( "github.com/spf13/cobra" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/teamloader" "github.com/docker/cagent/pkg/telemetry" ) +type debugFlags struct { + runConfig config.RuntimeConfig +} + func newDebugCmd() *cobra.Command { + var flags debugFlags + cmd := &cobra.Command{ Use: "debug", } @@ -18,20 +25,22 @@ func newDebugCmd() *cobra.Command { Use: "toolsets ", Short: "Debug the toolsets of an agent", Args: cobra.ExactArgs(1), - RunE: runDebugToolsetsCommand, + RunE: flags.runDebugToolsetsCommand, }) + addRuntimeConfigFlags(cmd, &flags.runConfig) + return cmd } -func runDebugToolsetsCommand(cmd *cobra.Command, args []string) error { +func (f *debugFlags) runDebugToolsetsCommand(cmd *cobra.Command, args []string) error { telemetry.TrackCommand("debug", append([]string{"toolsets"}, args...)) ctx := cmd.Context() agentFilename := args[0] slog.Info("Loading agent", "agent", agentFilename) - team, err := teamloader.Load(ctx, agentFilename, runConfig) + team, err := teamloader.Load(ctx, agentFilename, f.runConfig) if err != nil { return err } diff --git a/cmd/root/eval.go b/cmd/root/eval.go index 0d32c07d4..035ab68ff 100644 --- a/cmd/root/eval.go +++ b/cmd/root/eval.go @@ -5,29 +5,35 @@ import ( "github.com/spf13/cobra" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/evaluation" "github.com/docker/cagent/pkg/teamloader" "github.com/docker/cagent/pkg/telemetry" ) +type evalFlags struct { + runConfig config.RuntimeConfig +} + func newEvalCmd() *cobra.Command { + var flags evalFlags + cmd := &cobra.Command{ Use: "eval ", Short: "Run evaluations for an agent", Args: cobra.ExactArgs(2), - RunE: runEvalCommand, + RunE: flags.runEvalCommand, } - addGatewayFlags(cmd) - addRuntimeConfigFlags(cmd) + addRuntimeConfigFlags(cmd, &flags.runConfig) return cmd } -func runEvalCommand(cmd *cobra.Command, args []string) error { +func (f *evalFlags) runEvalCommand(cmd *cobra.Command, args []string) error { telemetry.TrackCommand("eval", args) - agents, err := teamloader.Load(cmd.Context(), args[0], runConfig) + agents, err := teamloader.Load(cmd.Context(), args[0], f.runConfig) if err != nil { return err } diff --git a/cmd/root/exec.go b/cmd/root/exec.go index 39c9bfb8b..9b9eb8a6d 100644 --- a/cmd/root/exec.go +++ b/cmd/root/exec.go @@ -17,7 +17,7 @@ func newExecCmd() *cobra.Command { RunE: flags.runExecCommand, } - cmd.PersistentFlags().StringVarP(&agentName, "agent", "a", "root", "Name of the agent to run") + cmd.PersistentFlags().StringVarP(&flags.agentName, "agent", "a", "root", "Name of the agent to run") cmd.PersistentFlags().StringVar(&flags.workingDir, "working-dir", "", "Set the working directory for the session (applies to tools and relative paths)") cmd.PersistentFlags().BoolVar(&flags.autoApprove, "yolo", false, "Automatically approve all tool calls without prompting") cmd.PersistentFlags().StringVar(&flags.attachmentPath, "attach", "", "Attach an image file to the message") @@ -25,8 +25,7 @@ func newExecCmd() *cobra.Command { cmd.PersistentFlags().BoolVar(&flags.dryRun, "dry-run", false, "Initialize the agent without executing anything") _ = cmd.PersistentFlags().MarkHidden("dry-run") - addGatewayFlags(cmd) - addRuntimeConfigFlags(cmd) + addRuntimeConfigFlags(cmd, &flags.runConfig) return cmd } diff --git a/cmd/root/flags.go b/cmd/root/flags.go index e45875520..8d8b740ef 100644 --- a/cmd/root/flags.go +++ b/cmd/root/flags.go @@ -6,9 +6,8 @@ import ( "github.com/docker/cagent/pkg/config" ) -var runConfig config.RuntimeConfig - -func addRuntimeConfigFlags(cmd *cobra.Command) { +func addRuntimeConfigFlags(cmd *cobra.Command, runConfig *config.RuntimeConfig) { + addGatewayFlags(cmd, runConfig) cmd.PersistentFlags().StringSliceVar(&runConfig.EnvFiles, "env-from-file", nil, "Set environment variables from file") cmd.PersistentFlags().StringVar(&runConfig.RedirectURI, "redirect-uri", "", "Set the redirect URI for OAuth2 flows") cmd.PersistentFlags().BoolVar(&runConfig.GlobalCodeMode, "code-mode-tools", false, "Provide a single tool to call other tools via Javascript") diff --git a/cmd/root/gateway.go b/cmd/root/gateway.go index b49c0f639..241ecb0eb 100644 --- a/cmd/root/gateway.go +++ b/cmd/root/gateway.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/spf13/cobra" + + "github.com/docker/cagent/pkg/config" ) const ( @@ -19,8 +21,6 @@ type gatewayConfig struct { mainGateway string } -var gwConfig gatewayConfig - func canonize(endpoint string) string { return strings.TrimSpace(strings.TrimSuffix(endpoint, "/")) } @@ -31,7 +31,9 @@ func logEnvvarShadowing(flagValue, varName, flagName string) { } } -func addGatewayFlags(cmd *cobra.Command) { +func addGatewayFlags(cmd *cobra.Command, runConfig *config.RuntimeConfig) { + var gwConfig gatewayConfig + cmd.PersistentFlags().StringVar(&gwConfig.mainGateway, flagGateway, "", "Set the gateway address to use for models and tool calls") cmd.PersistentFlags().StringVar(&runConfig.ModelsGateway, flagModelsGateway, "", "Set the models gateway address") diff --git a/cmd/root/gateway_test.go b/cmd/root/gateway_test.go index cc5893873..39beed322 100644 --- a/cmd/root/gateway_test.go +++ b/cmd/root/gateway_test.go @@ -107,10 +107,6 @@ func TestGatewayLogic(t *testing.T) { t.Setenv(key, value) } - // Reset global variables - runConfig = config.RuntimeConfig{} - gwConfig = gatewayConfig{} - // Create a test command with gateway flags cmd := &cobra.Command{ Use: "test", @@ -121,7 +117,8 @@ func TestGatewayLogic(t *testing.T) { } // Add gateway flags (this is the actual function being tested) - addGatewayFlags(cmd) + runConfig := config.RuntimeConfig{} + addGatewayFlags(cmd, &runConfig) // Set command arguments and execute cmd.SetArgs(tt.args) diff --git a/cmd/root/new.go b/cmd/root/new.go index 5d571ff50..07c2c8b04 100644 --- a/cmd/root/new.go +++ b/cmd/root/new.go @@ -8,6 +8,7 @@ import ( "github.com/spf13/cobra" "github.com/docker/cagent/pkg/cli" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/creator" "github.com/docker/cagent/pkg/input" "github.com/docker/cagent/pkg/runtime" @@ -18,6 +19,7 @@ type newFlags struct { modelParam string maxTokensParam int maxIterationsParam int + runConfig config.RuntimeConfig } func newNewCmd() *cobra.Command { @@ -29,11 +31,13 @@ func newNewCmd() *cobra.Command { Long: `Create a new agent configuration by asking questions and generating a YAML file`, RunE: flags.runNewCommand, } - addGatewayFlags(cmd) + cmd.PersistentFlags().StringVar(&flags.modelParam, "model", "", "Model to use, optionally as provider/model where provider is one of: anthropic, openai, google, dmr. If omitted, provider is auto-selected based on available credentials or gateway") cmd.PersistentFlags().IntVar(&flags.maxTokensParam, "max-tokens", 0, "Override max_tokens for the selected model (0 = default)") cmd.PersistentFlags().IntVar(&flags.maxIterationsParam, "max-iterations", 0, "Maximum number of agentic loop iterations to prevent infinite loops (default: 20 for DMR, unlimited for other providers)") + addRuntimeConfigFlags(cmd, &flags.runConfig) + return cmd } @@ -61,7 +65,7 @@ func (f *newFlags) runNewCommand(cmd *cobra.Command, args []string) error { if derivedProvider != "" { modelProvider = derivedProvider } else { - if runConfig.ModelsGateway == "" { + if f.runConfig.ModelsGateway == "" { // Prefer Anthropic, then OpenAI, then Google based on available API keys // default to DMR if no provider credentials are found switch { @@ -105,7 +109,7 @@ func (f *newFlags) runNewCommand(cmd *cobra.Command, args []string) error { out.Println() } - events, rt, err := creator.StreamCreateAgent(ctx, ".", prompt, runConfig, modelProvider, model, f.maxTokensParam, f.maxIterationsParam) + events, rt, err := creator.StreamCreateAgent(ctx, ".", prompt, f.runConfig, modelProvider, model, f.maxTokensParam, f.maxIterationsParam) if err != nil { return err } diff --git a/cmd/root/root.go b/cmd/root/root.go index 2e762ea58..67bef4126 100644 --- a/cmd/root/root.go +++ b/cmd/root/root.go @@ -33,7 +33,6 @@ func (e RuntimeError) Unwrap() error { } var ( - agentName string debugMode bool enableOtel bool logFilePath string diff --git a/cmd/root/run.go b/cmd/root/run.go index f294b355f..51f4a2a47 100644 --- a/cmd/root/run.go +++ b/cmd/root/run.go @@ -17,6 +17,7 @@ import ( "github.com/docker/cagent/pkg/aliases" "github.com/docker/cagent/pkg/app" "github.com/docker/cagent/pkg/cli" + "github.com/docker/cagent/pkg/config" "github.com/docker/cagent/pkg/content" "github.com/docker/cagent/pkg/remote" "github.com/docker/cagent/pkg/runtime" @@ -28,6 +29,7 @@ import ( ) type runExecFlags struct { + agentName string workingDir string autoApprove bool attachmentPath string @@ -35,6 +37,7 @@ type runExecFlags struct { remoteAddress string modelOverrides []string dryRun bool + runConfig config.RuntimeConfig } func newRunCmd() *cobra.Command { @@ -52,15 +55,15 @@ func newRunCmd() *cobra.Command { RunE: flags.runRunCommand, } - cmd.PersistentFlags().StringVarP(&agentName, "agent", "a", "root", "Name of the agent to run") + cmd.PersistentFlags().StringVarP(&flags.agentName, "agent", "a", "root", "Name of the agent to run") cmd.PersistentFlags().StringVar(&flags.workingDir, "working-dir", "", "Set the working directory for the session (applies to tools and relative paths)") cmd.PersistentFlags().BoolVar(&flags.autoApprove, "yolo", false, "Automatically approve all tool calls without prompting") cmd.PersistentFlags().StringVar(&flags.attachmentPath, "attach", "", "Attach an image file to the message") cmd.PersistentFlags().BoolVar(&flags.useTUI, "tui", true, "Run the agent with a Terminal User Interface (TUI)") cmd.PersistentFlags().StringVar(&flags.remoteAddress, "remote", "", "Use remote runtime with specified address (only supported with TUI)") cmd.PersistentFlags().StringArrayVar(&flags.modelOverrides, "model", nil, "Override agent model: [agent=]provider/model (repeatable)") - addGatewayFlags(cmd) - addRuntimeConfigFlags(cmd) + + addRuntimeConfigFlags(cmd, &flags.runConfig) return cmd } @@ -77,7 +80,7 @@ func (f *runExecFlags) runRunCommand(cmd *cobra.Command, args []string) error { } func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []string, exec bool) error { - slog.Debug("Starting agent", "agent", agentName, "debug_mode", debugMode) + slog.Debug("Starting agent", "agent", f.agentName) if err := f.validateRemoteFlag(exec); err != nil { return err @@ -211,11 +214,11 @@ func (f *runExecFlags) resolveAgentFile(ctx context.Context, agentFilename strin } func (f *runExecFlags) loadAgents(ctx context.Context, agentFilename string) (*team.Team, error) { - if runConfig.RedirectURI == "" { - runConfig.RedirectURI = "http://localhost:8083/oauth-callback" + if f.runConfig.RedirectURI == "" { + f.runConfig.RedirectURI = "http://localhost:8083/oauth-callback" } - t, err := teamloader.Load(ctx, agentFilename, runConfig, teamloader.WithModelOverrides(f.modelOverrides)) + t, err := teamloader.Load(ctx, agentFilename, f.runConfig, teamloader.WithModelOverrides(f.modelOverrides)) if err != nil { return nil, err } @@ -251,19 +254,19 @@ func (f *runExecFlags) createRemoteRuntimeAndSession(ctx context.Context, origin } remoteRt, err := runtime.NewRemoteRuntime(remoteClient, - runtime.WithRemoteCurrentAgent(agentName), + runtime.WithRemoteCurrentAgent(f.agentName), runtime.WithRemoteAgentFilename(originalFilename), ) if err != nil { return nil, nil, fmt.Errorf("failed to create remote runtime: %w", err) } - slog.Debug("Using remote runtime", "address", f.remoteAddress, "agent", agentName) + slog.Debug("Using remote runtime", "address", f.remoteAddress, "agent", f.agentName) return remoteRt, sess, nil } func (f *runExecFlags) createLocalRuntimeAndSession(t *team.Team) (runtime.Runtime, *session.Session, error) { - agent, err := t.Agent(agentName) + agent, err := t.Agent(f.agentName) if err != nil { return nil, nil, err } @@ -274,7 +277,7 @@ func (f *runExecFlags) createLocalRuntimeAndSession(t *team.Team) (runtime.Runti tracer := otel.Tracer(AppName) localRt, err := runtime.New(t, - runtime.WithCurrentAgent(agentName), + runtime.WithCurrentAgent(f.agentName), runtime.WithTracer(tracer), runtime.WithRootSessionID(sess.ID), ) @@ -282,7 +285,7 @@ func (f *runExecFlags) createLocalRuntimeAndSession(t *team.Team) (runtime.Runti return nil, nil, fmt.Errorf("failed to create runtime: %w", err) } - slog.Debug("Using local runtime", "agent", agentName) + slog.Debug("Using local runtime", "agent", f.agentName) return localRt, sess, nil }