Files
orca/src/shared/commit-message-agent-spec.test.ts
T
HopkinsandNeil cd1fe07182 feat(kimi): add K3 to the commit-message model picker (#11386)
Adds kimi-code/k3 (Kimi K3) to the static Kimi model list, which is also
the allowlist, so planning with K3 no longer fails with "not available".
Drops the test that only restated the catalog.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-10-28 01:38:47 -07:00

751 lines
23 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import {
COMMIT_MESSAGE_AGENT_SPECS,
CUSTOM_AGENT_ID,
getCommitMessageAgentCapability,
getCommitMessageAgentSpec,
getCommitMessageModel,
isCustomAgentId,
listCommitMessageAgentCapabilities,
listCommitMessageAgentIds,
resolveCommitMessageAgentChoice
} from './commit-message-agent-spec'
import {
COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS,
parseAntigravityModels,
parseClaudeModels,
parseCodexModels,
parseCursorModels,
parseLineModels,
parsePiModels
} from './commit-message-model-parsers'
afterEach(() => {
vi.restoreAllMocks()
})
describe('COMMIT_MESSAGE_AGENT_SPECS', () => {
it('uses the expected defaults for core agents', () => {
expect(COMMIT_MESSAGE_AGENT_SPECS.claude?.defaultModelId).toBe('sonnet')
expect(COMMIT_MESSAGE_AGENT_SPECS.codex?.defaultModelId).toBe('gpt-5.6-terra')
expect(getCommitMessageModel('codex', 'gpt-5.6-terra')?.defaultThinkingLevel).toBe('low')
expect(COMMIT_MESSAGE_AGENT_SPECS.pi?.defaultModelId).toBe('default')
})
it('passes only flags Kimi Code accepts for non-interactive generation', () => {
// Why: Kimi Code rejects --print/--quiet/--thinking/--no-thinking as unknown options (#11669, #14771).
const spec = COMMIT_MESSAGE_AGENT_SPECS.kimi
expect(spec?.promptDelivery).toBe('argv')
expect(
spec?.buildArgs({
prompt: 'Name a branch for adding login',
model: 'kimi-code/kimi-for-coding',
thinkingLevel: 'on'
})
).toEqual([
'--prompt',
'Name a branch for adding login',
'--output-format',
'stream-json',
'--model',
'kimi-code/kimi-for-coding'
])
})
it('uses Muse exec for non-interactive Source Control AI generation', () => {
const spec = COMMIT_MESSAGE_AGENT_SPECS.muse
expect(spec).toBeDefined()
expect(spec?.promptDelivery).toBe('argv')
expect(spec?.buildArgs({ prompt: 'Write a concise commit message', model: 'default' })).toEqual(
[
'exec',
'--no-session-log',
'--approval-mode',
'never',
'--disable-sandbox',
'--disable-shell',
'--disable-write',
'--disable-web-tools',
'--',
'Write a concise commit message'
]
)
})
it('omits Kimi --model for the config default and an empty model', () => {
const spec = COMMIT_MESSAGE_AGENT_SPECS.kimi!
for (const model of ['default', '']) {
expect(spec.buildArgs({ prompt: 'PROMPT', model })).not.toContain('--model')
}
})
it('treats disabled default agents as unavailable for implicit Source Control AI choices', () => {
expect(resolveCommitMessageAgentChoice(null, 'codex', ['codex'])).toBe('claude')
expect(resolveCommitMessageAgentChoice(null, null, ['claude'])).toBeNull()
expect(resolveCommitMessageAgentChoice('codex', null, ['codex'])).toBe('codex')
})
it('gives every model with thinking levels a valid default', () => {
for (const spec of Object.values(COMMIT_MESSAGE_AGENT_SPECS)) {
if (!spec) {
continue
}
for (const model of spec.models) {
if (model.thinkingLevels) {
expect(model.defaultThinkingLevel).toBeDefined()
expect(model.thinkingLevels.some((l) => l.id === model.defaultThinkingLevel)).toBe(true)
}
}
}
})
it('exposes thinking levels on the Spark variant (it accepts model_reasoning_effort)', () => {
const spark = getCommitMessageModel('codex', 'gpt-5.3-codex-spark')
expect(spark).toBeDefined()
expect(spark?.thinkingLevels?.map((l) => l.id)).toEqual(['low', 'medium', 'high', 'xhigh'])
expect(spark?.defaultThinkingLevel).toBe('low')
})
it('omits thinking levels on Claude Haiku (non-reasoning model)', () => {
const haiku = getCommitMessageModel('claude', 'haiku')
expect(haiku).toBeDefined()
expect(haiku?.thinkingLevels).toBeUndefined()
expect(haiku?.defaultThinkingLevel).toBeUndefined()
})
it('identifies the custom sentinel via isCustomAgentId', () => {
expect(isCustomAgentId(CUSTOM_AGENT_ID)).toBe(true)
expect(isCustomAgentId('claude')).toBe(false)
expect(isCustomAgentId('codex')).toBe(false)
expect(isCustomAgentId(null)).toBe(false)
expect(isCustomAgentId(undefined)).toBe(false)
})
it('does not list "custom" alongside preset agent ids', () => {
expect(listCommitMessageAgentIds()).not.toContain(CUSTOM_AGENT_ID)
})
it('orders seeded Codex models by version descending', () => {
const ids = COMMIT_MESSAGE_AGENT_SPECS.codex?.models.map((m) => m.id)
expect(ids).toEqual([
'gpt-5.6-terra',
'gpt-5.5',
'gpt-5.4',
'gpt-5.4-mini',
'gpt-5.3-codex',
'gpt-5.3-codex-spark',
'gpt-5.2'
])
})
it('exposes UI capabilities without spawn details', () => {
const capabilities = listCommitMessageAgentCapabilities()
expect(capabilities.map((capability) => capability.id)).toContain('opencode')
const codex = getCommitMessageAgentCapability('codex')
expect(codex).toMatchObject({
id: 'codex',
label: 'Codex',
modelSource: 'dynamic',
defaultModelId: 'gpt-5.6-terra'
})
expect(codex).not.toHaveProperty('binary')
expect(codex).not.toHaveProperty('buildArgs')
})
})
describe('buildArgs (Claude)', () => {
const spec = getCommitMessageAgentSpec('claude')!
it('passes -p, output format, and model on every call', () => {
const args = spec.buildArgs({ prompt: '', model: 'haiku' })
expect(args).toEqual([
'-p',
'--output-format',
'text',
'--model',
'haiku',
'--permission-mode',
'plan',
'--strict-mcp-config',
'--settings',
'{"disableAllHooks":true}'
])
})
it('appends --effort when a thinking level is supplied', () => {
const args = spec.buildArgs({
prompt: '',
model: 'sonnet',
thinkingLevel: 'high'
})
expect(args).toEqual([
'-p',
'--output-format',
'text',
'--model',
'sonnet',
'--permission-mode',
'plan',
'--effort',
'high',
'--strict-mcp-config',
'--settings',
'{"disableAllHooks":true}'
])
})
it('omits --effort when thinkingLevel is not provided', () => {
const args = spec.buildArgs({ prompt: '', model: 'opus' })
expect(args).not.toContain('--effort')
})
})
describe('buildArgs (Jcode)', () => {
const spec = getCommitMessageAgentSpec('jcode')!
it('builds a jcode run argv with the model and prompt', () => {
const args = spec.buildArgs({ prompt: 'name this branch', model: 'claude-haiku-4-5' })
expect(args).toEqual([
'--no-update',
'--quiet',
'--no-selfdev',
'--tool-profile',
'none',
'--model',
'claude-haiku-4-5',
'run',
'--json',
'name this branch'
])
})
it('omits --model for the config-default choice', () => {
const args = spec.buildArgs({ prompt: 'name this branch', model: 'default' })
expect(args).toEqual([
'--no-update',
'--quiet',
'--no-selfdev',
'--tool-profile',
'none',
'run',
'--json',
'name this branch'
])
})
it('exposes no tools to a prompt that is a staged patch', () => {
// Staged patches are attacker-influenced; jcode defaults expose shell/read/write/MCP unlike read-only sibling generators.
const args = spec.buildArgs({ prompt: 'name this branch', model: 'default' })
expect(args.slice(args.indexOf('--tool-profile'), args.indexOf('--tool-profile') + 2)).toEqual([
'--tool-profile',
'none'
])
expect(args.indexOf('--tool-profile')).toBeLessThan(args.indexOf('run'))
})
it('keeps every jcode flag ahead of the subcommand', () => {
// jcode global flags must precede run; keep model in the same position for one argv shape.
const args = spec.buildArgs({ prompt: 'name this branch', model: 'claude-haiku-4-5' })
const runIndex = args.indexOf('run')
expect(runIndex).toBeGreaterThan(0)
expect(args.slice(0, runIndex)).toEqual([
'--no-update',
'--quiet',
'--no-selfdev',
'--tool-profile',
'none',
'--model',
'claude-haiku-4-5'
])
expect(args.slice(runIndex)).toEqual(['run', '--json', 'name this branch'])
})
it('discovers models from `jcode model list`', () => {
expect(spec.modelSource).toBe('dynamic')
expect(spec.modelDiscovery?.binary).toBe('jcode')
expect(spec.modelDiscovery?.args).toEqual(['--no-update', '--quiet', 'model', 'list'])
// Real `jcode model list` output: one bare id per line.
expect(
spec.modelDiscovery?.parse('claude-opus-5-5\nclaude-haiku-4-5\ngemini-2.5-pro\n')
).toEqual([
{ id: 'claude-opus-5-5', label: 'Claude Opus 5 5' },
{ id: 'claude-haiku-4-5', label: 'Claude Haiku 4 5' },
{ id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' }
])
})
it('defaults the model to the jcode config default', () => {
expect(spec.defaultModelId).toBe('default')
})
})
describe('model discovery parsers', () => {
it('parses Claude list_models output into commit-message models', () => {
const stdout = `${JSON.stringify({
type: 'control_response',
response: {
subtype: 'success',
request_id: 'orca-model-discovery',
response: {
models: [
{
value: 'default',
displayName: 'Default (recommended)',
supportsEffort: true,
supportedEffortLevels: ['low', 'medium', 'high', 'xhigh', 'max']
},
{
value: 'opus[1m]',
displayName: 'Opus (1M context)',
description: 'Opus 5 with 1M context · $5/$25 per Mtok',
supportsEffort: true,
supportedEffortLevels: ['low', 'medium', 'high', 'xhigh', 'max'],
supportsFastMode: true
},
{ value: 'haiku', displayName: 'Haiku' }
]
}
}
})}\n`
expect(parseClaudeModels(stdout)).toEqual([
{
id: 'opus[1m]',
label: 'Opus (1M context)',
description: 'Opus 5 with 1M context · $5/$25 per Mtok',
thinkingLevels: [
{ id: 'low', label: 'Low' },
{ id: 'medium', label: 'Medium' },
{ id: 'high', label: 'High' },
{ id: 'xhigh', label: 'Extra High' },
{ id: 'max', label: 'Max' }
],
defaultThinkingLevel: 'low',
supportsFastMode: true
},
{ id: 'haiku', label: 'Haiku' }
])
})
it('returns no Claude models when the CLI lacks list_models so the seed stays', () => {
expect(
parseClaudeModels(
'{"type":"control_response","response":{"subtype":"error","request_id":"orca-model-discovery","error":"Unsupported control request subtype: list_models"}}\n'
)
).toEqual([])
})
it('declares stdin-driven dynamic discovery for Claude', () => {
const discovery = COMMIT_MESSAGE_AGENT_SPECS.claude?.modelDiscovery
expect(COMMIT_MESSAGE_AGENT_SPECS.claude?.modelSource).toBe('dynamic')
expect(discovery?.binary).toBe('claude')
expect(discovery?.args).toEqual([
'-p',
'--input-format',
'stream-json',
'--output-format',
'stream-json',
'--verbose',
'--strict-mcp-config',
'--settings',
'{"disableAllHooks":true}'
])
const payload = JSON.parse(discovery?.stdinPayload ?? '') as {
type?: string
request?: { subtype?: string }
}
expect(payload.type).toBe('control_request')
expect(payload.request?.subtype).toBe('list_models')
expect(discovery?.stdinPayload?.endsWith('\n')).toBe(true)
})
it('parses Codex model JSON', () => {
expect(
parseCodexModels(
JSON.stringify({
models: [
{
slug: 'gpt-5.5',
display_name: 'GPT-5.5',
default_reasoning_level: 'low',
supported_reasoning_levels: [{ effort: 'low' }, { effort: 'high' }]
}
]
})
)
).toEqual([
{
id: 'gpt-5.5',
label: 'GPT-5.5',
thinkingLevels: [
{ id: 'low', label: 'Low' },
{ id: 'high', label: 'High' }
],
defaultThinkingLevel: 'low'
}
])
})
it('rejects excessive Codex model nesting before JSON.parse', () => {
const parseSpy = vi.spyOn(JSON, 'parse')
const depth = COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS.nestingDepth + 1
try {
expect(parseCodexModels(`${'['.repeat(depth)}0${']'.repeat(depth)}`)).toEqual([])
expect(parseSpy).not.toHaveBeenCalled()
} finally {
parseSpy.mockRestore()
}
})
it('parses one-model-per-line output', () => {
expect(parseLineModels('opencode/gpt-5.4-mini\n\nopenai/gpt-5.5\n').map((m) => m.id)).toEqual([
'opencode/gpt-5.4-mini',
'openai/gpt-5.5'
])
})
it('parses Pi model table output with provider-qualified ids', () => {
const output = [
'provider model context max-out thinking images',
'github-copilot gpt-5.4-mini 400K 128K yes yes',
'github-copilot gpt-4o 128K 4.1K no yes'
].join('\n')
expect(parsePiModels(output)).toEqual([
{
id: 'github-copilot/gpt-5.4-mini',
label: 'Github Copilot GPT 5.4 Mini',
thinkingLevels: [
{ id: 'off', label: 'Off' },
{ id: 'low', label: 'Low' },
{ id: 'medium', label: 'Medium' },
{ id: 'high', label: 'High' },
{ id: 'xhigh', label: 'Extra High' }
],
defaultThinkingLevel: 'low'
},
{
id: 'github-copilot/gpt-4o',
label: 'Github Copilot GPT 4O'
}
])
})
it('parses Cursor model output', () => {
expect(parseCursorModels('auto - Auto\ngpt-5.2 - GPT-5.2\n')).toEqual([
{ id: 'auto', label: 'Auto' },
{
id: 'gpt-5.2',
label: 'GPT-5.2',
thinkingLevels: [
{ id: 'low', label: 'Low' },
{ id: 'medium', label: 'Medium' },
{ id: 'high', label: 'High' },
{ id: 'xhigh', label: 'Extra High' }
],
defaultThinkingLevel: 'low'
}
])
})
it('parses Antigravity model output', () => {
const output = [
'Gemini 3.5 Flash (Medium)',
'Gemini 3.5 Flash (High)',
'Gemini 3.5 Flash (Low)',
'Gemini 3.1 Pro (Low)',
'Gemini 3.1 Pro (High)',
'Claude Sonnet 4.6 (Thinking)',
'Claude Opus 4.6 (Thinking)',
'GPT-OSS 120B (Medium)'
].join('\n')
expect(parseAntigravityModels(output)).toEqual([
{ id: 'Gemini 3.5 Flash (Medium)', label: 'Gemini 3.5 Flash (Medium)' },
{ id: 'Gemini 3.5 Flash (High)', label: 'Gemini 3.5 Flash (High)' },
{ id: 'Gemini 3.5 Flash (Low)', label: 'Gemini 3.5 Flash (Low)' },
{ id: 'Gemini 3.1 Pro (Low)', label: 'Gemini 3.1 Pro (Low)' },
{ id: 'Gemini 3.1 Pro (High)', label: 'Gemini 3.1 Pro (High)' },
{ id: 'Claude Sonnet 4.6 (Thinking)', label: 'Claude Sonnet 4.6 (Thinking)' },
{ id: 'Claude Opus 4.6 (Thinking)', label: 'Claude Opus 4.6 (Thinking)' },
{ id: 'GPT-OSS 120B (Medium)', label: 'GPT-OSS 120B (Medium)' }
])
})
it('parses CRLF-heavy dynamic model outputs without full line-array splitting', () => {
const splitSpy = vi.spyOn(String.prototype, 'split')
const noise = 'ignored model with spaces\r\n'.repeat(10_000)
const blankNoise = '\r\n'.repeat(10_000)
expect(parseLineModels(`${noise}opencode/gpt-5.4-mini\r\nopenai/gpt-5.5\r\n`)).toEqual([
{
id: 'opencode/gpt-5.4-mini',
label: 'Opencode GPT 5.4 Mini',
thinkingLevels: [
{ id: 'low', label: 'Low' },
{ id: 'medium', label: 'Medium' },
{ id: 'high', label: 'High' },
{ id: 'xhigh', label: 'Extra High' }
],
defaultThinkingLevel: 'low'
},
{
id: 'openai/gpt-5.5',
label: 'Openai GPT 5.5',
thinkingLevels: [
{ id: 'low', label: 'Low' },
{ id: 'medium', label: 'Medium' },
{ id: 'high', label: 'High' },
{ id: 'xhigh', label: 'Extra High' }
],
defaultThinkingLevel: 'low'
}
])
expect(
parsePiModels(
`${noise}provider model context max-out thinking images\r\ngithub-copilot gpt-5.4-mini 400K 128K yes yes\r\n`
)[0]?.id
).toBe('github-copilot/gpt-5.4-mini')
expect(parseCursorModels(`${noise}auto - Auto\r\ngpt-5.2 - GPT-5.2\r\n`)).toHaveLength(2)
expect(parseAntigravityModels(`${blankNoise}Gemini 3.5 Flash (Medium)\r\n`)).toEqual([
{ id: 'Gemini 3.5 Flash (Medium)', label: 'Gemini 3.5 Flash (Medium)' }
])
const usedFullLineSplit = splitSpy.mock.calls.some(
([separator]) =>
(typeof separator === 'string' && separator === '\n') ||
(separator instanceof RegExp && separator.source === '\\r?\\n')
)
const usedWhitespaceFieldSplit = splitSpy.mock.calls.some(
([separator]) => separator instanceof RegExp && separator.source === '\\s+'
)
expect(usedFullLineSplit).toBe(false)
expect(usedWhitespaceFieldSplit).toBe(false)
})
})
describe('buildArgs (Codex)', () => {
const spec = getCommitMessageAgentSpec('codex')!
it('runs `codex exec` without passing the prompt via argv', () => {
const args = spec.buildArgs({
prompt: 'PROMPT',
model: 'gpt-5.4-mini'
})
expect(args[0]).toBe('exec')
expect(args).toEqual([
'exec',
'--ephemeral',
'--skip-git-repo-check',
'-s',
'read-only',
'--model',
'gpt-5.4-mini'
])
expect(args).toContain('--model')
expect(args).not.toContain('PROMPT')
expect(spec.promptDelivery).toBe('stdin')
})
it('emits -c model_reasoning_effort=<level> when thinking level is supplied', () => {
const args = spec.buildArgs({
prompt: 'PROMPT',
model: 'gpt-5.4',
thinkingLevel: 'medium'
})
expect(args).toContain('-c')
expect(args).toContain('model_reasoning_effort=medium')
})
it('omits the -c flag when no thinking level is supplied', () => {
const args = spec.buildArgs({ prompt: 'PROMPT', model: 'gpt-5.4-mini' })
expect(args).not.toContain('-c')
})
})
describe('buildArgs (OpenCode)', () => {
const spec = getCommitMessageAgentSpec('opencode')!
it('runs `opencode run` without passing the prompt via argv', () => {
const prompt = `PROMPT ${'x'.repeat(1024)}`
const args = spec.buildArgs({
prompt,
model: 'opencode/deepseek-v4-flash-free'
})
expect(args).toEqual([
'run',
'--model',
'opencode/deepseek-v4-flash-free',
'--agent',
'build',
'--format',
'json'
])
expect(args).not.toContain(prompt)
expect(args).not.toContain('')
expect(spec.promptDelivery).toBe('stdin')
})
it('emits --variant <level> when thinking level is supplied', () => {
const args = spec.buildArgs({
prompt: 'PROMPT',
model: 'opencode/gpt-5.4-mini',
thinkingLevel: 'high'
})
expect(args).toEqual([
'run',
'--model',
'opencode/gpt-5.4-mini',
'--agent',
'build',
'--format',
'json',
'--variant',
'high'
])
})
it('omits --variant when no thinking level is supplied', () => {
const args = spec.buildArgs({
prompt: 'PROMPT',
model: 'opencode/gpt-5.4-mini'
})
expect(args).not.toContain('--variant')
})
})
describe('buildArgs (OpenCode 2)', () => {
const spec = getCommitMessageAgentSpec('opencode2')!
it('runs `opencode2 run` with stdin delivery', () => {
const prompt = `PROMPT ${'x'.repeat(1024)}`
const args = spec.buildArgs({
prompt,
model: 'opencode/deepseek-v4-flash-free'
})
expect(args).toEqual([
'run',
'--model',
'opencode/deepseek-v4-flash-free',
'--agent',
'build',
'--format',
'json'
])
expect(args).not.toContain(prompt)
expect(args).not.toContain('')
expect(spec.promptDelivery).toBe('stdin')
})
it('inlines the thinking variant as model#variant (v1 --variant is removed in v2)', () => {
const args = spec.buildArgs({
prompt: 'PROMPT',
model: 'opencode/gpt-5.4-mini',
thinkingLevel: 'high'
})
expect(args).toEqual([
'run',
'--model',
'opencode/gpt-5.4-mini#high',
'--agent',
'build',
'--format',
'json'
])
expect(args).not.toContain('--variant')
})
})
describe('buildArgs (Antigravity)', () => {
const spec = getCommitMessageAgentSpec('antigravity')!
it('runs agy in stream input mode with sandbox and model flags separate', () => {
expect(
spec.buildArgs({ prompt: 'real commit prompt', model: 'Gemini 3.5 Flash (Medium)' })
).toEqual([
'--print=',
'--input-format=stream-json',
'--output-format=stream-json',
'--sandbox',
'--model',
'Gemini 3.5 Flash (Medium)'
])
expect(spec.promptDelivery).toBe('stdin')
})
it('uses dynamic model discovery via agy models', () => {
expect(spec.modelSource).toBe('dynamic')
expect(spec.modelDiscovery?.binary).toBe('agy')
expect(spec.modelDiscovery?.args).toEqual(['models'])
})
it('uses the configured CLI model instead of a bundled model that can retire', () => {
expect(spec.defaultModelId).toBe('default')
expect(
spec.buildArgs({ prompt: 'Generate a commit message', model: spec.defaultModelId })
).toEqual([
'--print=',
'--input-format=stream-json',
'--output-format=stream-json',
'--sandbox'
])
})
it('passes only a nonempty requested effort', () => {
expect(spec.buildArgs({ prompt: 'P', model: 'default', thinkingLevel: '' })).not.toContain(
'--effort'
)
expect(spec.buildArgs({ prompt: 'P', model: 'default', thinkingLevel: 'high' })).toEqual([
'--print=',
'--input-format=stream-json',
'--output-format=stream-json',
'--sandbox',
'--effort',
'high'
])
})
it('parses current tab-separated IDs without treating progress text as a model', () => {
expect(
parseAntigravityModels(
[
'Fetching available models...',
'id\tLabel',
'gemini-3.8-flash-medium\tGemini 3.8 Flash (Medium)',
'claude-sonnet-4-6\tClaude Sonnet 4.6 (Thinking)',
'gemini-3.8-flash-medium\tGemini 3.8 Flash (Medium)',
''
].join('\r\n')
)
).toEqual([
{ id: 'gemini-3.8-flash-medium', label: 'Gemini 3.8 Flash (Medium)' },
{ id: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6 (Thinking)' }
])
})
})
describe('Pi Source Control AI model selection', () => {
it('leaves provider selection to Pi for the config default', () => {
const args = getCommitMessageAgentSpec('pi')!.buildArgs({
prompt: 'Name a branch',
model: 'default'
})
expect(args).not.toContain('--model')
})
it('passes an explicit discovered Pi model through', () => {
const args = getCommitMessageAgentSpec('pi')!.buildArgs({
prompt: 'Name a branch',
model: 'openai-codex/gpt-5.5'
})
const modelFlagIndex = args.indexOf('--model')
expect(modelFlagIndex).toBeGreaterThanOrEqual(0)
expect(args[modelFlagIndex + 1]).toBe('openai-codex/gpt-5.5')
})
})