Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions internal/forker/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
47 changes: 47 additions & 0 deletions internal/forker/launcher_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}