diff --git a/backend/internal/adapters/agent/grok/grok.go b/backend/internal/adapters/agent/grok/grok.go index ed09212a6f..8f2fc55f85 100644 --- a/backend/internal/adapters/agent/grok/grok.go +++ b/backend/internal/adapters/agent/grok/grok.go @@ -105,6 +105,22 @@ func (p *Plugin) Manifest() adapters.Manifest { } } +// GetConfigSpec reports the per-project agent config keys Grok Build understands. +func (p *Plugin) GetConfigSpec(ctx context.Context) (ports.ConfigSpec, error) { + if err := ctx.Err(); err != nil { + return ports.ConfigSpec{}, err + } + return ports.ConfigSpec{ + Fields: []ports.ConfigField{ + { + Key: "model", + Type: ports.ConfigFieldString, + Description: "Model override passed to `grok -m`.", + }, + }, + }, nil +} + // GetLaunchCommand builds `grok --no-auto-update [--permission-mode ] [-- prompt]`. // Prompt is delivered positionally so Grok starts an interactive coding session. // @@ -118,6 +134,7 @@ func (p *Plugin) GetLaunchCommand(ctx context.Context, cfg ports.LaunchConfig) ( cmd = []string{binary, "--no-auto-update"} appendApprovalFlags(&cmd, cfg.Permissions) + appendModelFlag(&cmd, cfg.Config) systemPrompt, err := launchSystemPromptText(cfg) if err != nil { @@ -179,9 +196,10 @@ func (p *Plugin) GetRestoreCommand(ctx context.Context, cfg ports.RestoreConfig) return nil, false, err } - cmd = make([]string, 0, 4) + cmd = make([]string, 0, 6) cmd = append(cmd, binary, "--no-auto-update") appendApprovalFlags(&cmd, cfg.Permissions) + appendModelFlag(&cmd, cfg.Config) systemPrompt, err := restoreSystemPromptText(cfg) if err != nil { return nil, false, err @@ -228,6 +246,16 @@ func grokClaudeSettingsPath(workspacePath string) string { return filepath.Join(workspacePath, grokClaudeSettingsDirName, grokClaudeSettingsFileName) } +// appendModelFlag appends a trimmed -m flag when a model override is +// configured. -m is the documented model-selection flag for both headless +// and TUI launches (https://docs.x.ai/build/overview: "grok -p \"Hello\" -m +// my-model" / "pick the model in headless mode or in the TUI"). +func appendModelFlag(cmd *[]string, cfg ports.AgentConfig) { + if model := strings.TrimSpace(cfg.Model); model != "" { + *cmd = append(*cmd, "-m", model) + } +} + func appendApprovalFlags(cmd *[]string, permissions ports.PermissionMode) { switch ports.NormalizePermissionMode(permissions) { case ports.PermissionModeDefault: diff --git a/backend/internal/adapters/agent/grok/grok_test.go b/backend/internal/adapters/agent/grok/grok_test.go index d11837e331..4c052a2df2 100644 --- a/backend/internal/adapters/agent/grok/grok_test.go +++ b/backend/internal/adapters/agent/grok/grok_test.go @@ -33,13 +33,71 @@ func TestManifest(t *testing.T) { } } -func TestGetConfigSpecEmpty(t *testing.T) { +func TestGetConfigSpecReportsModelField(t *testing.T) { spec, err := (&Plugin{}).GetConfigSpec(context.Background()) if err != nil { t.Fatalf("err: %v", err) } - if len(spec.Fields) != 0 { - t.Fatalf("expected no fields, got %d", len(spec.Fields)) + want := []ports.ConfigField{ + { + Key: "model", + Type: ports.ConfigFieldString, + Description: "Model override passed to `grok -m`.", + }, + } + if !reflect.DeepEqual(spec.Fields, want) { + t.Fatalf("config fields\nwant: %#v\n got: %#v", want, spec.Fields) + } +} + +func TestGetLaunchCommandAppendsConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "grok"} + cmd, err := plugin.GetLaunchCommand(context.Background(), ports.LaunchConfig{ + Config: ports.AgentConfig{Model: " grok-build "}, + }) + if err != nil { + t.Fatalf("err: %v", err) + } + want := []string{"grok", "--no-auto-update", "-m", "grok-build"} + if !reflect.DeepEqual(cmd, want) { + t.Fatalf("cmd = %#v, want %#v", cmd, want) + } +} + +func TestGetLaunchCommandOmitsBlankConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "grok"} + cmd, err := plugin.GetLaunchCommand(context.Background(), ports.LaunchConfig{ + Config: ports.AgentConfig{Model: " \t "}, + }) + if err != nil { + t.Fatalf("err: %v", err) + } + for _, arg := range cmd { + if arg == "-m" { + t.Fatalf("cmd = %#v contains -m for blank model", cmd) + } + } +} + +func TestGetRestoreCommandAppendsConfiguredModel(t *testing.T) { + plugin := &Plugin{resolvedBinary: "grok"} + cmd, ok, err := plugin.GetRestoreCommand(context.Background(), ports.RestoreConfig{ + Config: ports.AgentConfig{Model: " grok-build "}, + Session: ports.SessionRef{ + Metadata: map[string]string{ + ports.MetadataKeyAgentSessionID: "sess-abc123", + }, + }, + }) + if err != nil { + t.Fatalf("err: %v", err) + } + if !ok { + t.Fatal("ok=false, want true") + } + want := []string{"grok", "--no-auto-update", "-m", "grok-build", "-r", "sess-abc123"} + if !reflect.DeepEqual(cmd, want) { + t.Fatalf("cmd = %#v, want %#v", cmd, want) } }