Skip to content

Commit 69eef25

Browse files
akcorcaclaude
andcommitted
Fix per-agent default models to use valid upstream ids
Gemini CLI 0.37.x only registers `gemini-3-flash-base` (and friends); the previous examples pointed users at `gemini3-flash`, which 404s as ModelNotFoundError. Introduce DefaultAgentModels so `sah run` picks `codex=gpt-5.4-mini`, `gemini=gemini-3-flash-base`, `claude=sonnet` when neither --model nor --models is supplied, and align every help string and example accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2b7894e commit 69eef25

3 files changed

Lines changed: 45 additions & 7 deletions

File tree

cmd/sah/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Commands:
7272
Examples:
7373
sah auth login
7474
sah run --rotate-installed
75-
sah run --agents codex,gemini,claude --models codex=gpt-5.4-mini,gemini=gemini3-flash,claude=sonnet
75+
sah run --agents codex,gemini,claude --models codex=gpt-5.4-mini,gemini=gemini-3-flash-base,claude=sonnet
7676
sah daemon install --agents codex,claude --interval 30m
7777
sah me
7878
`)
@@ -173,7 +173,7 @@ func runCmd(args []string) error {
173173
agents := fs.String("agents", "", "Comma-separated round-robin agent order, e.g. codex,gemini,claude")
174174
rotateInstalled := fs.Bool("rotate-installed", false, "Rotate through every supported agent CLI installed on this Mac")
175175
model := fs.String("model", "", "Optional model override passed to the agent CLI")
176-
models := fs.String("models", "", "Per-agent model overrides, e.g. codex=gpt-5.4-mini,gemini=gemini3-flash,claude=sonnet")
176+
models := fs.String("models", "", "Per-agent model overrides, e.g. codex=gpt-5.4-mini,gemini=gemini-3-flash-base,claude=sonnet")
177177
interval := fs.String("interval", "", "Polling interval")
178178
timeout := fs.String("timeout", "", "Per-assignment agent timeout")
179179
taskType := fs.String("task-type", "", "Optional task type filter")
@@ -351,7 +351,7 @@ func parseDaemonInstallOptions(args []string) (daemonInstallOptions, error) {
351351
fs.StringVar(&options.agents, "agents", "", "Comma-separated round-robin agent order for the daemon")
352352
fs.BoolVar(&options.rotateInstalled, "rotate-installed", false, "Rotate through every installed supported agent CLI")
353353
fs.StringVar(&options.model, "model", "", "Default model override")
354-
fs.StringVar(&options.models, "models", "", "Per-agent model overrides, e.g. codex=gpt-5.4-mini,gemini=gemini3-flash,claude=sonnet")
354+
fs.StringVar(&options.models, "models", "", "Per-agent model overrides, e.g. codex=gpt-5.4-mini,gemini=gemini-3-flash-base,claude=sonnet")
355355
fs.StringVar(&options.interval, "interval", "", "Default polling interval")
356356
fs.StringVar(&options.timeout, "timeout", "", "Default per-assignment timeout")
357357
fs.StringVar(&options.baseURL, "base-url", "", "SCIENCE@home base URL")

internal/sah/agent_selection.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,29 @@ func MergeAgentModels(base map[string]string, overrides map[string]string) map[s
140140
return merged
141141
}
142142

143+
// DefaultAgentModels is the per-agent model used when neither --models nor
144+
// --model is supplied. Keep these in sync with what each upstream CLI actually
145+
// accepts today: Gemini CLI 0.37.x only exposes `gemini-3-flash-base` (and
146+
// friends) -- `gemini3-flash` / `gemini-3-flash` are NOT valid ids and cause
147+
// ModelNotFoundError.
148+
var DefaultAgentModels = map[string]string{
149+
"codex": "gpt-5.4-mini",
150+
"gemini": "gemini-3-flash-base",
151+
"claude": "sonnet",
152+
}
153+
143154
func ModelForAgent(agentName string, fallback string, overrides map[string]string) string {
144-
if model, ok := normalizeAgentModels(overrides)[normalizeAgentName(agentName)]; ok {
155+
name := normalizeAgentName(agentName)
156+
if model, ok := normalizeAgentModels(overrides)[name]; ok {
157+
return model
158+
}
159+
if trimmed := strings.TrimSpace(fallback); trimmed != "" {
160+
return trimmed
161+
}
162+
if model, ok := DefaultAgentModels[name]; ok {
145163
return model
146164
}
147-
return strings.TrimSpace(fallback)
165+
return ""
148166
}
149167

150168
func DescribeAgentMode(config Config, options WorkerOptions) string {

internal/sah/agent_selection_test.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ func TestParseAgentList(t *testing.T) {
1010
}
1111

1212
func TestParseAgentModels(t *testing.T) {
13-
models, err := ParseAgentModels("codex=gpt-5.4-mini, gemini=gemini3-flash, claude=sonnet")
13+
models, err := ParseAgentModels("codex=gpt-5.4-mini, gemini=gemini-3-flash-base, claude=sonnet")
1414
if err != nil {
1515
t.Fatalf("ParseAgentModels returned error: %v", err)
1616
}
17-
if models["codex"] != "gpt-5.4-mini" || models["gemini"] != "gemini3-flash" || models["claude"] != "sonnet" {
17+
if models["codex"] != "gpt-5.4-mini" || models["gemini"] != "gemini-3-flash-base" || models["claude"] != "sonnet" {
1818
t.Fatalf("unexpected models: %#v", models)
1919
}
2020
}
@@ -25,3 +25,23 @@ func TestModelForAgentPrefersOverride(t *testing.T) {
2525
t.Fatalf("unexpected model: %q", model)
2626
}
2727
}
28+
29+
func TestModelForAgentFallsBackToGlobal(t *testing.T) {
30+
model := ModelForAgent("gemini", "some-global", nil)
31+
if model != "some-global" {
32+
t.Fatalf("expected global fallback, got %q", model)
33+
}
34+
}
35+
36+
func TestModelForAgentUsesBuiltinDefaults(t *testing.T) {
37+
cases := map[string]string{
38+
"codex": "gpt-5.4-mini",
39+
"gemini": "gemini-3-flash-base",
40+
"claude": "sonnet",
41+
}
42+
for agent, want := range cases {
43+
if got := ModelForAgent(agent, "", nil); got != want {
44+
t.Fatalf("%s: expected %q, got %q", agent, want, got)
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)