diff --git a/internal/forker/launcher.go b/internal/forker/launcher.go index b4f6d2f..a8de278 100644 --- a/internal/forker/launcher.go +++ b/internal/forker/launcher.go @@ -16,15 +16,33 @@ type LaunchConfig struct { } func Launch(cfg LaunchConfig) error { - agent := strings.ToLower(cfg.Agent) + launcher, err := launcherForAgent(cfg.Agent) + if err != nil { + return err + } - switch { - case strings.Contains(agent, "claude"): + switch launcher { + case "claude": return launchClaude(cfg) - case strings.Contains(agent, "codex") || strings.Contains(agent, "o4") || strings.Contains(agent, "gpt"): + case "codex": return launchCodex(cfg) default: - return launchClaude(cfg) + panic("unreachable launcher") + } +} + +func launcherForAgent(agent string) (string, error) { + agent = strings.ToLower(strings.TrimSpace(agent)) + + switch { + case strings.HasPrefix(agent, "claude"): + return "claude", nil + case strings.HasPrefix(agent, "codex"), + strings.HasPrefix(agent, "o4"), + strings.HasPrefix(agent, "gpt"): + return "codex", nil + default: + return "", fmt.Errorf("unsupported agent %q: expected a claude, codex, o4, or gpt agent", agent) } } diff --git a/internal/forker/launcher_test.go b/internal/forker/launcher_test.go new file mode 100644 index 0000000..2bd93df --- /dev/null +++ b/internal/forker/launcher_test.go @@ -0,0 +1,47 @@ +package forker + +import ( + "strings" + "testing" +) + +func TestLauncherForAgent(t *testing.T) { + tests := []struct { + name string + agent string + want string + wantError string + }{ + {name: "claude model", agent: "claude-sonnet-4-6", want: "claude"}, + {name: "claude case and whitespace", agent: " Claude-Opus-4-6 ", want: "claude"}, + {name: "codex agent", agent: "codex", want: "codex"}, + {name: "o4 model", agent: "o4-mini", want: "codex"}, + {name: "gpt model", agent: "gpt-5.5", want: "codex"}, + {name: "embedded o4 substring", agent: "proto4", wantError: "unsupported agent"}, + {name: "embedded gpt substring", agent: "not-gpt", wantError: "unsupported agent"}, + {name: "unknown agent", agent: "gemini-pro", wantError: "unsupported agent"}, + {name: "empty agent", agent: "", wantError: "unsupported agent"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := launcherForAgent(tt.agent) + if tt.wantError != "" { + if err == nil { + t.Fatalf("launcherForAgent(%q) returned %q, want error containing %q", tt.agent, got, tt.wantError) + } + if !strings.Contains(err.Error(), tt.wantError) { + t.Fatalf("launcherForAgent(%q) error = %q, want substring %q", tt.agent, err, tt.wantError) + } + return + } + + if err != nil { + t.Fatalf("launcherForAgent(%q) returned unexpected error: %v", tt.agent, err) + } + if got != tt.want { + t.Fatalf("launcherForAgent(%q) = %q, want %q", tt.agent, got, tt.want) + } + }) + } +}