Files
orca/src/shared/commit-message-agent-specs-primary.ts
T

224 lines
7.2 KiB
TypeScript

import type { TuiAgent } from './tui-agent'
import type {
CommitMessageAgentSpec,
CommitMessageModel,
ThinkingLevel
} from './commit-message-agent-spec'
import { CLAUDE_MODEL_LIST_ARGS, CLAUDE_MODEL_LIST_STDIN } from './claude-model-list-probe'
type PrimaryAgentSpecDeps = {
CLAUDE_THINKING_LEVELS: ThinkingLevel[]
OPENAI_THINKING_LEVELS: ThinkingLevel[]
parseClaudeModels: (stdout: string) => CommitMessageModel[]
parseCodexModels: (stdout: string) => CommitMessageModel[]
parseLineModels: (stdout: string) => CommitMessageModel[]
parsePiModels: (stdout: string) => CommitMessageModel[]
withOpenAiThinking: (
id: string
) => Pick<CommitMessageModel, 'thinkingLevels' | 'defaultThinkingLevel'>
}
export function buildPrimaryCommitMessageAgentSpecs({
CLAUDE_THINKING_LEVELS,
OPENAI_THINKING_LEVELS,
parseClaudeModels,
parseCodexModels,
parseLineModels,
parsePiModels,
withOpenAiThinking
}: PrimaryAgentSpecDeps): Partial<Record<TuiAgent, CommitMessageAgentSpec>> {
return {
claude: {
id: 'claude',
label: 'Claude',
binary: 'claude',
// Why: diffs can be large and `claude -p` reads from stdin natively when no
// positional prompt is provided.
promptDelivery: 'stdin',
buildArgs: ({ model, thinkingLevel }) => [
'-p',
'--output-format',
'text',
'--model',
model,
'--permission-mode',
'plan',
...(thinkingLevel ? ['--effort', thinkingLevel] : [])
],
modelSource: 'dynamic',
// Why: the Claude CLI has no listing subcommand; one list_models control
// request over --print stream-json returns the /model picker catalog.
// Older CLIs answer with a control error and exit 0, keeping the fallback.
modelDiscovery: {
binary: 'claude',
args: [...CLAUDE_MODEL_LIST_ARGS],
stdinPayload: CLAUDE_MODEL_LIST_STDIN,
parse: parseClaudeModels
},
models: [
{
// Why: Claude Code aliases track the account/provider's supported
// model IDs; hardcoded version IDs can be rejected by Bedrock/Vertex.
id: 'haiku',
label: 'Haiku'
},
{
id: 'sonnet',
label: 'Sonnet',
thinkingLevels: CLAUDE_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
id: 'opus',
label: 'Opus',
thinkingLevels: CLAUDE_THINKING_LEVELS,
defaultThinkingLevel: 'low'
}
],
defaultModelId: 'sonnet'
},
codex: {
id: 'codex',
label: 'Codex',
binary: 'codex',
// Why: `codex exec` reads stdin when no prompt arg is supplied. Commit
// prompts include large staged diffs, so argv would exceed Windows and
// some SSH/POSIX command-line limits.
promptDelivery: 'stdin',
buildArgs: ({ model, thinkingLevel }) => [
'exec',
// Why: commit-message generation needs text only, not a persisted agent
// session or workspace writes. Match the safe git-text mode used by
// local-first coding agents.
'--ephemeral',
'--skip-git-repo-check',
'-s',
'read-only',
'--model',
model,
...(thinkingLevel ? ['-c', `model_reasoning_effort=${thinkingLevel}`] : [])
],
// `-c` is intentionally absent: Codex accepts repeated overrides.
singletonOptions: [['--model', '-m']],
modelSource: 'dynamic',
modelDiscovery: {
binary: 'codex',
args: ['debug', 'models'],
parse: parseCodexModels
},
// Why: ordered to match the official `codex` model picker — descending
// by version so the frontier model lands on top and legacy models trail.
models: [
{
id: 'gpt-5.5',
label: 'GPT-5.5',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
id: 'gpt-5.4',
label: 'GPT-5.4',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
id: 'gpt-5.4-mini',
label: 'GPT-5.4 Mini',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
id: 'gpt-5.3-codex',
label: 'GPT-5.3 Codex',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
// Why: Codex's Spark variant accepts `model_reasoning_effort` (the
// CLI banner reports "reasoning effort: medium" by default); the
// gating that surfaces "model not supported" is on the account
// tier, not the effort flag.
id: 'gpt-5.3-codex-spark',
label: 'GPT-5.3 Codex Spark',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
},
{
id: 'gpt-5.2',
label: 'GPT-5.2',
thinkingLevels: OPENAI_THINKING_LEVELS,
defaultThinkingLevel: 'low'
}
],
defaultModelId: 'gpt-5.5'
},
opencode: {
id: 'opencode',
label: 'OpenCode',
binary: 'opencode',
// Why: Source Control AI prompts can include large staged diffs; OpenCode
// accepts the prompt on stdin, which avoids cross-platform argv limits.
promptDelivery: 'stdin',
buildArgs: ({ model, thinkingLevel }) => [
'run',
'--model',
model,
'--agent',
'build',
'--format',
'default',
...(thinkingLevel ? ['--variant', thinkingLevel] : [])
],
singletonOptions: [['--model', '-m'], ['--agent'], ['--format'], ['--variant']],
modelSource: 'dynamic',
modelDiscovery: { binary: 'opencode', args: ['models'], parse: parseLineModels },
models: [
{
// Why: OpenCode's hosted GPT models can require workspace billing even
// when `opencode models` lists them. This free model is available in
// discovery and works as a usable out-of-the-box default.
id: 'opencode/deepseek-v4-flash-free',
label: 'OpenCode DeepSeek V4 Flash Free'
},
{
id: 'opencode/gpt-5.4-mini',
label: 'OpenCode GPT 5.4 Mini',
...withOpenAiThinking('gpt-5.4-mini')
}
],
defaultModelId: 'opencode/deepseek-v4-flash-free'
},
pi: {
id: 'pi',
label: 'Pi',
binary: 'pi',
promptDelivery: 'stdin',
buildArgs: ({ model, thinkingLevel }) => [
'--print',
'--no-session',
'--no-tools',
'--no-extensions',
'--no-skills',
'--no-context-files',
'--mode',
'text',
'--model',
model,
...(thinkingLevel ? ['--thinking', thinkingLevel] : [])
],
modelSource: 'dynamic',
modelDiscovery: { binary: 'pi', args: ['--list-models'], parse: parsePiModels },
models: [
{
// Why: Pi commonly authenticates through GitHub Copilot locally; using
// that provider avoids selecting a raw OpenAI model when no key exists.
id: 'github-copilot/gpt-5.4-mini',
label: 'Github Copilot GPT 5.4 Mini',
...withOpenAiThinking('gpt-5.4-mini')
}
],
defaultModelId: 'github-copilot/gpt-5.4-mini'
}
}
}