mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
fix(codex): discover effort menus from the host CLI catalog
Worker-start and native chat were still bound to a frozen seed, so unadvertised `minimal` stayed on every menu and a new Codex tier would silently stay unreachable. Probe `codex debug models` the same way commit-message generation already does, use each model's supported_reasoning_levels as the picker, and keep the seed only as a fallback when the probe fails. Co-authored-by: gilje <rp0927@gmail.com>
This commit is contained in:
@@ -36,48 +36,53 @@ describe('orchestration worker launch preferences', () => {
|
||||
it.each([
|
||||
{
|
||||
model: 'gpt-5.6-sol',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
rejected: ['future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
rejected: ['minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.6-terra',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
rejected: ['future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
||||
rejected: ['minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.6-luna',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'],
|
||||
rejected: ['ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh', 'max'],
|
||||
rejected: ['ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.5',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.2-codex',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.4',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.4-mini',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.3-codex-spark',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'gpt-5.2-codex',
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'future-codex-model',
|
||||
accepted: ['minimal', 'low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'future-effort']
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
},
|
||||
{
|
||||
model: 'luna',
|
||||
accepted: ['low', 'medium', 'high', 'xhigh'],
|
||||
rejected: ['max', 'ultra', 'minimal', 'future-effort']
|
||||
}
|
||||
])('enforces the Codex effort ceiling for $model', ({ model, accepted, rejected }) => {
|
||||
const catalog = getAgentSessionOptionCatalog('codex')!
|
||||
@@ -104,6 +109,32 @@ describe('orchestration worker launch preferences', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('passes a supported Codex max/ultra request through instead of capping it', () => {
|
||||
expect(
|
||||
resolveWorkerLaunchPreferences({
|
||||
agent: 'codex',
|
||||
model: 'gpt-5.6-luna',
|
||||
effort: 'max'
|
||||
}).preferences
|
||||
).toEqual({ model: 'gpt-5.6-luna', effort: 'max' })
|
||||
expect(
|
||||
resolveWorkerLaunchPreferences({
|
||||
agent: 'codex',
|
||||
model: 'gpt-5.6-sol',
|
||||
effort: 'ultra'
|
||||
}).preferences
|
||||
).toEqual({ model: 'gpt-5.6-sol', effort: 'ultra' })
|
||||
})
|
||||
|
||||
it('rejects an unsupported Codex effort instead of silently substituting a lower tier', () => {
|
||||
expect(() =>
|
||||
resolveWorkerLaunchPreferences({ agent: 'codex', model: 'gpt-5.6-luna', effort: 'ultra' })
|
||||
).toThrow('does not support effort ultra')
|
||||
expect(() =>
|
||||
resolveWorkerLaunchPreferences({ agent: 'codex', model: 'gpt-5.5', effort: 'max' })
|
||||
).toThrow('does not support effort max')
|
||||
})
|
||||
|
||||
it('rejects effort without a model', () => {
|
||||
expect(() => resolveWorkerLaunchPreferences({ agent: 'codex', effort: 'high' })).toThrow(
|
||||
'--effort requires --model'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AgentType } from '../../../../shared/agent-status-types'
|
||||
import {
|
||||
createClaudeCatalogOptions,
|
||||
createCodexCatalogOptions,
|
||||
getAgentSessionOptionCatalog,
|
||||
type CatalogModel
|
||||
} from '../../../../shared/agent-session-option-catalog'
|
||||
@@ -96,6 +97,11 @@ export async function discoverNativeChatCatalogModels(
|
||||
effortLevelIds: model.thinkingLevels?.map(({ id }) => id) ?? [],
|
||||
supportsFastMode: model.supportsFastMode
|
||||
})
|
||||
: []
|
||||
: agent === 'codex'
|
||||
? createCodexCatalogOptions({
|
||||
effortLevelIds: model.thinkingLevels?.map(({ id }) => id) ?? [],
|
||||
...(model.defaultThinkingLevel ? { defaultEffort: model.defaultThinkingLevel } : {})
|
||||
})
|
||||
: []
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -302,6 +302,80 @@ describe('native chat session option enrichment', () => {
|
||||
).toEqual({ model: 'retired' })
|
||||
})
|
||||
|
||||
it('uses discovered Codex models and their per-model reasoning levels', async () => {
|
||||
mocks.discoverRuntimeCommitMessageModels.mockResolvedValue({
|
||||
success: true,
|
||||
catalogOrigin: 'probe',
|
||||
models: [
|
||||
{
|
||||
id: 'gpt-5.6-sol',
|
||||
label: 'GPT-5.6 Sol',
|
||||
thinkingLevels: [
|
||||
{ id: 'low', label: 'Low' },
|
||||
{ id: 'high', label: 'High' },
|
||||
{ id: 'max', label: 'Max' },
|
||||
{ id: 'ultra', label: 'Ultra' }
|
||||
],
|
||||
defaultThinkingLevel: 'high'
|
||||
},
|
||||
{ id: 'account-only-model', label: 'Account only model' }
|
||||
]
|
||||
})
|
||||
|
||||
const discovered = await discoverNativeChatCatalogModels('codex', {
|
||||
settings: {},
|
||||
worktreeId: 'repo::/worktree',
|
||||
worktreePath: '/worktree'
|
||||
})
|
||||
expect(discovered?.map(({ id }) => id)).toEqual(['gpt-5.6-sol', 'account-only-model'])
|
||||
expect(discovered?.[0].options[0].kind).toMatchObject({
|
||||
type: 'select',
|
||||
choices: [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
{ value: 'ultra', label: 'Ultra' }
|
||||
],
|
||||
defaultValue: 'high'
|
||||
})
|
||||
expect(discovered?.[1].options).toEqual([])
|
||||
|
||||
const listener = vi.fn()
|
||||
subscribeNativeChatEnrichedModels('codex', 'local', listener)
|
||||
ensureNativeChatModelEnrichment({
|
||||
agent: 'codex',
|
||||
hostKey: 'local',
|
||||
discover: async () => discovered
|
||||
})
|
||||
await vi.waitFor(() => expect(listener).toHaveBeenCalledOnce())
|
||||
const enriched = readNativeChatEnrichedModels('codex', 'local')!
|
||||
expect(enriched.map(({ id }) => id)).toEqual(['gpt-5.6-sol', 'account-only-model'])
|
||||
expect(enriched[0].options[0].kind).toMatchObject({
|
||||
choices: [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
{ value: 'ultra', label: 'Ultra' }
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('does not advertise the Codex spec fallback when probing is unavailable', async () => {
|
||||
mocks.discoverRuntimeCommitMessageModels.mockResolvedValue({
|
||||
success: true,
|
||||
catalogOrigin: 'spec',
|
||||
models: [{ id: 'gpt-5.5', label: 'GPT-5.5' }]
|
||||
})
|
||||
|
||||
await expect(
|
||||
discoverNativeChatCatalogModels('codex', {
|
||||
settings: {},
|
||||
worktreeId: 'repo::/worktree',
|
||||
worktreePath: '/worktree'
|
||||
})
|
||||
).resolves.toBeNull()
|
||||
})
|
||||
|
||||
it('does not advertise the Claude spec fallback when probing is unavailable', async () => {
|
||||
mocks.discoverRuntimeCommitMessageModels.mockResolvedValue({
|
||||
success: true,
|
||||
|
||||
@@ -102,7 +102,7 @@ export function ensureNativeChatModelEnrichment(args: {
|
||||
return
|
||||
}
|
||||
entry.models =
|
||||
args.agent === 'claude'
|
||||
args.agent === 'claude' || args.agent === 'codex'
|
||||
? [...discovered]
|
||||
: catalog.discoveredModelsAreAuthoritative
|
||||
? mergeDiscoveredAuthoritativeModels(catalog.models, discovered)
|
||||
|
||||
@@ -4,7 +4,7 @@ import es from './locales/es.json'
|
||||
import ja from './locales/ja.json'
|
||||
import ko from './locales/ko.json'
|
||||
import zh from './locales/zh.json'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from '../../../shared/agent-session-option-catalog-claude-codex'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from '../../../shared/agent-session-option-catalog-codex'
|
||||
|
||||
const localizedCatalogs = { es, ja, ko, zh }
|
||||
const englishSetting = en.auto.components.settings.ExperimentalPane.nativeChat
|
||||
@@ -25,7 +25,17 @@ const codexEffortValues = new Set(
|
||||
|
||||
describe('native chat locale copy', () => {
|
||||
it('covers every Codex effort choice', () => {
|
||||
expect([...codexEffortValues].sort()).toEqual([...localizedEffortValues].sort())
|
||||
const localized = new Set<string>(localizedEffortValues)
|
||||
expect([...codexEffortValues].every((value) => localized.has(value))).toBe(true)
|
||||
expect(localizedEffortValues).toEqual([
|
||||
'minimal',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'xhigh',
|
||||
'max',
|
||||
'ultra'
|
||||
])
|
||||
})
|
||||
|
||||
it.each(Object.entries(localizedCatalogs))(
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
CatalogModel,
|
||||
CatalogOption
|
||||
} from './agent-session-option-catalog-types'
|
||||
import { agentArgOptionTokens, removeAgentArgOption } from './agent-session-option-agent-args'
|
||||
import { removeAgentArgOption } from './agent-session-option-agent-args'
|
||||
import {
|
||||
CLAUDE_MODEL_LIST_ARGS,
|
||||
CLAUDE_MODEL_LIST_STDIN,
|
||||
@@ -11,49 +11,6 @@ import {
|
||||
} from './claude-model-list-probe'
|
||||
import { hasFlag } from './agent-cli-flag-detection'
|
||||
|
||||
function hasCodexEffortOverride(tokens: readonly string[]): boolean {
|
||||
if (hasFlag(tokens, ['--reasoning-effort'])) {
|
||||
return true
|
||||
}
|
||||
const optionTokens = agentArgOptionTokens(tokens)
|
||||
return optionTokens.some((token, index) => {
|
||||
const previous = optionTokens[index - 1]
|
||||
return (
|
||||
(token.startsWith('model_reasoning_effort=') &&
|
||||
(previous === '-c' || previous === '--config')) ||
|
||||
token.startsWith('-cmodel_reasoning_effort=') ||
|
||||
token.startsWith('-c=model_reasoning_effort=') ||
|
||||
token.startsWith('--config=model_reasoning_effort=')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function removeCodexEffortOverride(tokens: readonly string[]): string[] {
|
||||
const withoutFlag = removeAgentArgOption(tokens, ['--reasoning-effort'])
|
||||
const result: string[] = []
|
||||
for (let index = 0; index < withoutFlag.length; index += 1) {
|
||||
const token = withoutFlag[index]
|
||||
if (token === '--') {
|
||||
result.push(...withoutFlag.slice(index))
|
||||
break
|
||||
}
|
||||
const next = withoutFlag[index + 1]
|
||||
if ((token === '-c' || token === '--config') && next?.startsWith('model_reasoning_effort=')) {
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
if (
|
||||
token.startsWith('-cmodel_reasoning_effort=') ||
|
||||
token.startsWith('-c=model_reasoning_effort=') ||
|
||||
token.startsWith('--config=model_reasoning_effort=')
|
||||
) {
|
||||
continue
|
||||
}
|
||||
result.push(token)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const STANDARD_EFFORT_CHOICES = [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
@@ -179,60 +136,3 @@ export const CLAUDE_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = {
|
||||
parse: parseClaudeCatalogModels
|
||||
}
|
||||
}
|
||||
|
||||
const CODEX_EFFORT_CHOICES = [
|
||||
{ value: 'minimal', label: 'Minimal' },
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'xhigh', label: 'Extra high' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
{ value: 'ultra', label: 'Ultra' }
|
||||
]
|
||||
|
||||
// Why: Codex can clamp higher values, so expose only each model's advertised levels.
|
||||
function codexEffort(ceiling: 'xhigh' | 'max' | 'ultra'): CatalogOption {
|
||||
const ceilingIndex = CODEX_EFFORT_CHOICES.findIndex((choice) => choice.value === ceiling)
|
||||
return {
|
||||
id: 'effort',
|
||||
label: 'Reasoning effort',
|
||||
category: 'thought_level',
|
||||
kind: {
|
||||
type: 'select',
|
||||
choices: CODEX_EFFORT_CHOICES.slice(0, ceilingIndex + 1),
|
||||
defaultValue: 'medium'
|
||||
},
|
||||
apply: {
|
||||
launchArgs: (value) => ['-c', `model_reasoning_effort=${String(value)}`],
|
||||
agentArgsOverride: hasCodexEffortOverride,
|
||||
removeAgentArgs: removeCodexEffortOverride,
|
||||
midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const CODEX_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = {
|
||||
supportsWorkerLaunchPreferences: true,
|
||||
// Why: Codex model access depends on auth. Keep this seed short and allow
|
||||
// unknown persisted ids to pass through instead of claiming a complete list.
|
||||
models: [
|
||||
{ id: 'gpt-5.6-sol', label: 'GPT-5.6 Sol', options: [codexEffort('ultra')] },
|
||||
{ id: 'gpt-5.6-terra', label: 'GPT-5.6 Terra', options: [codexEffort('ultra')] },
|
||||
{ id: 'gpt-5.6-luna', label: 'GPT-5.6 Luna', options: [codexEffort('max')] },
|
||||
{ id: 'gpt-5.5', label: 'GPT-5.5', options: [codexEffort('xhigh')] },
|
||||
{
|
||||
id: 'gpt-5.2-codex',
|
||||
label: 'GPT-5.2 Codex',
|
||||
options: [codexEffort('xhigh')]
|
||||
}
|
||||
],
|
||||
modelApply: {
|
||||
launchArgs: (value) => ['-m', String(value)],
|
||||
agentArgsOverride: (tokens) => hasFlag(tokens, ['-m', '--model']),
|
||||
removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']),
|
||||
// Codex classifies multi-character writes as pasted prose; type the bare
|
||||
// command and let its own picker apply the account-supported model.
|
||||
midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' }
|
||||
},
|
||||
unknownModelOptions: [codexEffort('xhigh')]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import { hasFlag } from './agent-cli-flag-detection'
|
||||
import { agentArgOptionTokens, removeAgentArgOption } from './agent-session-option-agent-args'
|
||||
import type {
|
||||
AgentSessionOptionCatalog,
|
||||
CatalogModel,
|
||||
CatalogOption
|
||||
} from './agent-session-option-catalog-types'
|
||||
import { CODEX_MODEL_LIST_COMMAND, parseCodexModelList } from './codex-model-list-probe'
|
||||
import { labelFromModelId } from './model-id-label'
|
||||
|
||||
function hasCodexEffortOverride(tokens: readonly string[]): boolean {
|
||||
if (hasFlag(tokens, ['--reasoning-effort'])) {
|
||||
return true
|
||||
}
|
||||
const optionTokens = agentArgOptionTokens(tokens)
|
||||
return optionTokens.some((token, index) => {
|
||||
const previous = optionTokens[index - 1]
|
||||
return (
|
||||
(token.startsWith('model_reasoning_effort=') &&
|
||||
(previous === '-c' || previous === '--config')) ||
|
||||
token.startsWith('-cmodel_reasoning_effort=') ||
|
||||
token.startsWith('-c=model_reasoning_effort=') ||
|
||||
token.startsWith('--config=model_reasoning_effort=')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function removeCodexEffortOverride(tokens: readonly string[]): string[] {
|
||||
const withoutFlag = removeAgentArgOption(tokens, ['--reasoning-effort'])
|
||||
const result: string[] = []
|
||||
for (let index = 0; index < withoutFlag.length; index += 1) {
|
||||
const token = withoutFlag[index]
|
||||
if (token === '--') {
|
||||
result.push(...withoutFlag.slice(index))
|
||||
break
|
||||
}
|
||||
const next = withoutFlag[index + 1]
|
||||
if ((token === '-c' || token === '--config') && next?.startsWith('model_reasoning_effort=')) {
|
||||
index += 1
|
||||
continue
|
||||
}
|
||||
if (
|
||||
token.startsWith('-cmodel_reasoning_effort=') ||
|
||||
token.startsWith('-c=model_reasoning_effort=') ||
|
||||
token.startsWith('--config=model_reasoning_effort=')
|
||||
) {
|
||||
continue
|
||||
}
|
||||
result.push(token)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const CODEX_EFFORT_CHOICES = [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'xhigh', label: 'Extra high' },
|
||||
{ value: 'max', label: 'Max' },
|
||||
{ value: 'ultra', label: 'Ultra' }
|
||||
]
|
||||
|
||||
function labelCodexEffort(value: string): string {
|
||||
return (
|
||||
CODEX_EFFORT_CHOICES.find((choice) => choice.value === value)?.label ??
|
||||
(value === 'minimal' ? 'Minimal' : labelFromModelId(value))
|
||||
)
|
||||
}
|
||||
|
||||
function codexEffortWithChoices(
|
||||
choices: readonly { value: string; label: string }[],
|
||||
defaultValue: string
|
||||
): CatalogOption {
|
||||
return {
|
||||
id: 'effort',
|
||||
label: 'Reasoning effort',
|
||||
category: 'thought_level',
|
||||
kind: {
|
||||
type: 'select',
|
||||
choices: [...choices],
|
||||
defaultValue
|
||||
},
|
||||
apply: {
|
||||
launchArgs: (value) => ['-c', `model_reasoning_effort=${String(value)}`],
|
||||
agentArgsOverride: hasCodexEffortOverride,
|
||||
removeAgentArgs: removeCodexEffortOverride,
|
||||
midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Why: Codex silently clamps a higher requested tier, so expose only advertised levels.
|
||||
function codexEffort(ceiling: 'xhigh' | 'max' | 'ultra'): CatalogOption {
|
||||
const ceilingIndex = CODEX_EFFORT_CHOICES.findIndex((choice) => choice.value === ceiling)
|
||||
return codexEffortWithChoices(CODEX_EFFORT_CHOICES.slice(0, ceilingIndex + 1), 'medium')
|
||||
}
|
||||
|
||||
export function createCodexCatalogOptions(args: {
|
||||
effortLevelIds: readonly string[]
|
||||
defaultEffort?: string
|
||||
}): CatalogOption[] {
|
||||
const seen = new Set<string>()
|
||||
const choices = args.effortLevelIds.flatMap((id) => {
|
||||
if (!id || seen.has(id)) {
|
||||
return []
|
||||
}
|
||||
seen.add(id)
|
||||
return [{ value: id, label: labelCodexEffort(id) }]
|
||||
})
|
||||
if (choices.length === 0) {
|
||||
return []
|
||||
}
|
||||
const choiceIds = choices.map(({ value }) => value)
|
||||
const requestedDefault = args.defaultEffort
|
||||
const defaultEffort =
|
||||
requestedDefault && choiceIds.includes(requestedDefault)
|
||||
? requestedDefault
|
||||
: choiceIds.includes('medium')
|
||||
? 'medium'
|
||||
: choices[0].value
|
||||
return [codexEffortWithChoices(choices, defaultEffort)]
|
||||
}
|
||||
|
||||
function parseCodexCatalogModels(stdout: string): CatalogModel[] {
|
||||
return parseCodexModelList(stdout).flatMap((model) => {
|
||||
// Hidden rows are API-only (auto-review, aliases). Picking one is a confusing launch.
|
||||
if (model.visibility === 'hide') {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
options: createCodexCatalogOptions({
|
||||
effortLevelIds: model.effortLevels,
|
||||
...(model.defaultEffort ? { defaultEffort: model.defaultEffort } : {})
|
||||
})
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export const CODEX_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = {
|
||||
supportsWorkerLaunchPreferences: true,
|
||||
// Why: Codex model access depends on auth. Seed the advertised picker; a successful
|
||||
// `codex debug models` probe replaces membership and per-model effort menus.
|
||||
models: [
|
||||
{ id: 'gpt-5.6-sol', label: 'GPT-5.6 Sol', options: [codexEffort('ultra')] },
|
||||
{ id: 'gpt-5.6-terra', label: 'GPT-5.6 Terra', options: [codexEffort('ultra')] },
|
||||
{ id: 'gpt-5.6-luna', label: 'GPT-5.6 Luna', options: [codexEffort('max')] },
|
||||
{ id: 'gpt-5.5', label: 'GPT-5.5', options: [codexEffort('xhigh')] },
|
||||
{ id: 'gpt-5.4', label: 'GPT-5.4', options: [codexEffort('xhigh')] },
|
||||
{ id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini', options: [codexEffort('xhigh')] },
|
||||
{ id: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark', options: [codexEffort('xhigh')] }
|
||||
],
|
||||
modelApply: {
|
||||
launchArgs: (value) => ['-m', String(value)],
|
||||
agentArgsOverride: (tokens) => hasFlag(tokens, ['-m', '--model']),
|
||||
removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']),
|
||||
// Codex classifies multi-character writes as pasted prose; type the bare
|
||||
// command and let its own picker apply the account-supported model.
|
||||
midSession: { kind: 'agent-picker', command: '/model', delivery: 'type' }
|
||||
},
|
||||
unknownModelOptions: [codexEffort('xhigh')],
|
||||
// Why: a seeded id the CLI has retired is a fatal `-m`, so a successful probe
|
||||
// must drop it. Option menus come from the probe's supported_reasoning_levels.
|
||||
discoveredModelsAreAuthoritative: true,
|
||||
listModels: {
|
||||
command: CODEX_MODEL_LIST_COMMAND,
|
||||
parse: parseCodexCatalogModels
|
||||
}
|
||||
}
|
||||
@@ -70,9 +70,10 @@ describe('grok session option catalog', () => {
|
||||
expect(effortValues(unknown[0])).toEqual(['low', 'medium', 'high', 'xhigh'])
|
||||
})
|
||||
|
||||
it('treats a successful discovery as authoritative, unlike the other agents', () => {
|
||||
it('treats a successful discovery as authoritative, unlike additive catalogs', () => {
|
||||
expect(GROK_SESSION_OPTION_CATALOG.discoveredModelsAreAuthoritative).toBe(true)
|
||||
for (const agent of ['claude', 'codex', 'gemini', 'cursor'] as const) {
|
||||
expect(getAgentSessionOptionCatalog('codex')?.discoveredModelsAreAuthoritative).toBe(true)
|
||||
for (const agent of ['claude', 'gemini', 'cursor'] as const) {
|
||||
expect(getAgentSessionOptionCatalog(agent)?.discoveredModelsAreAuthoritative).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -99,6 +99,133 @@ describe('agent session option catalog', () => {
|
||||
expect(parsed[2].options).toEqual([])
|
||||
})
|
||||
|
||||
it('seeds Codex effort menus from advertised ceilings, not a shared cap', () => {
|
||||
const catalog = getAgentSessionOptionCatalog('codex')!
|
||||
const effortValues = (modelId: string): string[] => {
|
||||
const option = catalog.models
|
||||
.find((model) => model.id === modelId)
|
||||
?.options.find((candidate) => candidate.id === 'effort')
|
||||
return option?.kind.type === 'select' ? option.kind.choices.map(({ value }) => value) : []
|
||||
}
|
||||
|
||||
expect(catalog.models.map(({ id }) => id)).toEqual([
|
||||
'gpt-5.6-sol',
|
||||
'gpt-5.6-terra',
|
||||
'gpt-5.6-luna',
|
||||
'gpt-5.5',
|
||||
'gpt-5.4',
|
||||
'gpt-5.4-mini',
|
||||
'gpt-5.3-codex-spark'
|
||||
])
|
||||
expect(effortValues('gpt-5.6-sol')).toEqual(['low', 'medium', 'high', 'xhigh', 'max', 'ultra'])
|
||||
expect(effortValues('gpt-5.6-luna')).toEqual(['low', 'medium', 'high', 'xhigh', 'max'])
|
||||
expect(effortValues('gpt-5.5')).toEqual(['low', 'medium', 'high', 'xhigh'])
|
||||
expect(catalog.models.flatMap((model) => effortValues(model.id))).not.toContain('minimal')
|
||||
expect(catalog.models.some((model) => model.id === 'gpt-5.2-codex')).toBe(false)
|
||||
expect(catalog.models.some((model) => model.id === 'luna')).toBe(false)
|
||||
})
|
||||
|
||||
it('parses Codex discovery into model-specific reasoning options', () => {
|
||||
const catalog = getAgentSessionOptionCatalog('codex')!
|
||||
expect(catalog.listModels?.command).toBe('codex debug models')
|
||||
expect(catalog.discoveredModelsAreAuthoritative).toBe(true)
|
||||
|
||||
const parsed = catalog.listModels!.parse(
|
||||
JSON.stringify({
|
||||
models: [
|
||||
{
|
||||
slug: 'gpt-5.6-sol',
|
||||
display_name: 'GPT-5.6-Sol',
|
||||
default_reasoning_level: 'low',
|
||||
visibility: 'list',
|
||||
supported_reasoning_levels: [
|
||||
{ effort: 'low' },
|
||||
{ effort: 'high' },
|
||||
{ effort: 'max' },
|
||||
{ effort: 'ultra' }
|
||||
]
|
||||
},
|
||||
{
|
||||
slug: 'gpt-5.6-luna',
|
||||
display_name: 'GPT-5.6-Luna',
|
||||
default_reasoning_level: 'medium',
|
||||
visibility: 'list',
|
||||
supported_reasoning_levels: [{ effort: 'low' }, { effort: 'max' }]
|
||||
},
|
||||
{
|
||||
slug: 'gpt-5.5',
|
||||
display_name: 'GPT-5.5',
|
||||
visibility: 'list',
|
||||
supported_reasoning_levels: [
|
||||
{ effort: 'low' },
|
||||
{ effort: 'medium' },
|
||||
{ effort: 'high' },
|
||||
{ effort: 'xhigh' }
|
||||
]
|
||||
},
|
||||
{
|
||||
slug: 'codex-auto-review',
|
||||
display_name: 'Codex Auto Review',
|
||||
visibility: 'hide',
|
||||
supported_reasoning_levels: [{ effort: 'max' }]
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
expect(parsed.map(({ id }) => id)).toEqual(['gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-5.5'])
|
||||
expect(
|
||||
parsed[0].options[0].kind.type === 'select'
|
||||
? parsed[0].options[0].kind.choices.map(({ value }) => value)
|
||||
: []
|
||||
).toEqual(['low', 'high', 'max', 'ultra'])
|
||||
expect(parsed[0].options[0].kind).toMatchObject({ defaultValue: 'low' })
|
||||
expect(
|
||||
parsed[1].options[0].kind.type === 'select'
|
||||
? parsed[1].options[0].kind.choices.map(({ value }) => value)
|
||||
: []
|
||||
).toEqual(['low', 'max'])
|
||||
expect(
|
||||
parsed[2].options[0].kind.type === 'select'
|
||||
? parsed[2].options[0].kind.choices.map(({ value }) => value)
|
||||
: []
|
||||
).toEqual(['low', 'medium', 'high', 'xhigh'])
|
||||
})
|
||||
|
||||
it('keeps the Codex seed when model discovery output is malformed', () => {
|
||||
const parse = getAgentSessionOptionCatalog('codex')!.listModels!.parse
|
||||
expect(parse('')).toEqual([])
|
||||
expect(parse('{"models":false}')).toEqual([])
|
||||
expect(parse('garbage')).toEqual([])
|
||||
})
|
||||
|
||||
it('deduplicates Codex models and reasoning levels from discovery', () => {
|
||||
const parse = getAgentSessionOptionCatalog('codex')!.listModels!.parse
|
||||
const parsed = parse(
|
||||
JSON.stringify({
|
||||
models: [
|
||||
{
|
||||
slug: 'gpt-account-frontier',
|
||||
display_name: 'GPT Account Frontier',
|
||||
visibility: 'list',
|
||||
supported_reasoning_levels: [{ effort: 'low' }, { effort: 'low' }, { effort: 'high' }]
|
||||
},
|
||||
{ slug: 'gpt-account-frontier', display_name: 'Duplicate row', visibility: 'list' }
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
expect(parsed).toHaveLength(1)
|
||||
expect(parsed[0].label).toBe('GPT Account Frontier')
|
||||
expect(parsed[0].options[0].kind).toMatchObject({
|
||||
choices: [
|
||||
{ value: 'low', label: 'Low' },
|
||||
{ value: 'high', label: 'High' }
|
||||
],
|
||||
defaultValue: 'low'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps the Claude seed when list_models output is unsupported or malformed', () => {
|
||||
const parse = getAgentSessionOptionCatalog('claude')!.listModels!.parse
|
||||
const unsupported =
|
||||
@@ -142,6 +269,21 @@ describe('agent session option catalog', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('passes a requested Codex effort through launch args instead of capping it', () => {
|
||||
expect(
|
||||
resolveAgentSessionOptionLaunch('codex', { model: 'gpt-5.6-luna', effort: 'max' })
|
||||
).toEqual({
|
||||
args: ['-m', 'gpt-5.6-luna', '-c', 'model_reasoning_effort=max'],
|
||||
appliedValues: { model: 'gpt-5.6-luna', effort: 'max' }
|
||||
})
|
||||
expect(
|
||||
resolveAgentSessionOptionLaunch('codex', { model: 'gpt-5.6-sol', effort: 'ultra' })
|
||||
).toEqual({
|
||||
args: ['-m', 'gpt-5.6-sol', '-c', 'model_reasoning_effort=ultra'],
|
||||
appliedValues: { model: 'gpt-5.6-sol', effort: 'ultra' }
|
||||
})
|
||||
})
|
||||
|
||||
it('passes unknown model and option values through launch mappings', () => {
|
||||
expect(
|
||||
resolveAgentSessionOptionLaunch('claude', {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { AgentType } from './agent-status-types'
|
||||
import {
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CODEX_SESSION_OPTION_CATALOG,
|
||||
createClaudeCatalogOptions
|
||||
} from './agent-session-option-catalog-claude-codex'
|
||||
import {
|
||||
CODEX_SESSION_OPTION_CATALOG,
|
||||
createCodexCatalogOptions
|
||||
} from './agent-session-option-catalog-codex'
|
||||
import {
|
||||
CURSOR_SESSION_OPTION_CATALOG,
|
||||
GEMINI_SESSION_OPTION_CATALOG
|
||||
@@ -26,7 +29,7 @@ export type {
|
||||
CatalogOption,
|
||||
CatalogOptionApply
|
||||
} from './agent-session-option-catalog-types'
|
||||
export { createClaudeCatalogOptions }
|
||||
export { createClaudeCatalogOptions, createCodexCatalogOptions }
|
||||
|
||||
const CATALOGS: AgentSessionOptionCatalogMap = {
|
||||
claude: CLAUDE_SESSION_OPTION_CATALOG,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { CODEX_MODEL_LIST_MAX_JSON_CHARACTERS, parseCodexModelList } from './codex-model-list-probe'
|
||||
|
||||
describe('Codex model list probe', () => {
|
||||
it('parses advertised reasoning levels and keeps hidden rows for callers to filter', () => {
|
||||
expect(
|
||||
parseCodexModelList(
|
||||
JSON.stringify({
|
||||
models: [
|
||||
{
|
||||
slug: 'gpt-5.6-luna',
|
||||
display_name: 'GPT-5.6-Luna',
|
||||
default_reasoning_level: 'medium',
|
||||
visibility: 'list',
|
||||
supported_reasoning_levels: [{ effort: 'low' }, { effort: 'max' }]
|
||||
},
|
||||
{
|
||||
slug: 'codex-auto-review',
|
||||
display_name: 'Codex Auto Review',
|
||||
visibility: 'hide',
|
||||
supported_reasoning_levels: [{ effort: 'max' }]
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
).toEqual([
|
||||
{
|
||||
id: 'gpt-5.6-luna',
|
||||
label: 'GPT-5.6-Luna',
|
||||
effortLevels: ['low', 'max'],
|
||||
defaultEffort: 'medium',
|
||||
visibility: 'list'
|
||||
},
|
||||
{
|
||||
id: 'codex-auto-review',
|
||||
label: 'Codex Auto Review',
|
||||
effortLevels: ['max'],
|
||||
defaultEffort: null,
|
||||
visibility: 'hide'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('returns no models for malformed or empty output so callers keep their seed', () => {
|
||||
expect(parseCodexModelList('')).toEqual([])
|
||||
expect(parseCodexModelList('garbage')).toEqual([])
|
||||
expect(parseCodexModelList('{"models":false}')).toEqual([])
|
||||
})
|
||||
|
||||
it('rejects oversized output before JSON.parse', () => {
|
||||
const parseSpy = vi.spyOn(JSON, 'parse')
|
||||
try {
|
||||
expect(parseCodexModelList(' '.repeat(CODEX_MODEL_LIST_MAX_JSON_CHARACTERS + 1))).toEqual([])
|
||||
expect(parseSpy).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
parseSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit'
|
||||
|
||||
export const CODEX_MODEL_LIST_ARGS = ['debug', 'models'] as const
|
||||
export const CODEX_MODEL_LIST_COMMAND = `codex ${CODEX_MODEL_LIST_ARGS.join(' ')}`
|
||||
export const CODEX_MODEL_LIST_MAX_JSON_CHARACTERS = 4 * 1024 * 1024
|
||||
|
||||
export const CODEX_MODEL_LIST_JSON_STRUCTURE_LIMITS = {
|
||||
structuralTokens: 64 * 1024,
|
||||
nestingDepth: 16
|
||||
} as const
|
||||
|
||||
export type CodexModelListModel = {
|
||||
id: string
|
||||
label: string
|
||||
effortLevels: string[]
|
||||
defaultEffort: string | null
|
||||
/** Absent on older CLI dumps; treat missing as listed. */
|
||||
visibility: string | null
|
||||
}
|
||||
|
||||
function nonEmptyString(value: unknown): string | null {
|
||||
if (typeof value !== 'string') {
|
||||
return null
|
||||
}
|
||||
return value.trim() || null
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function uniqueEffortLevels(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) {
|
||||
return []
|
||||
}
|
||||
const seen = new Set<string>()
|
||||
const levels: string[] = []
|
||||
for (const entry of value) {
|
||||
if (!isRecord(entry)) {
|
||||
continue
|
||||
}
|
||||
const effort = nonEmptyString(entry.effort)
|
||||
if (effort && !seen.has(effort)) {
|
||||
seen.add(effort)
|
||||
levels.push(effort)
|
||||
}
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
export function parseCodexModelList(stdout: string): CodexModelListModel[] {
|
||||
try {
|
||||
if (stdout.length > CODEX_MODEL_LIST_MAX_JSON_CHARACTERS) {
|
||||
return []
|
||||
}
|
||||
assertJsonTextStructureWithinLimits(stdout, CODEX_MODEL_LIST_JSON_STRUCTURE_LIMITS)
|
||||
const parsed: unknown = JSON.parse(stdout)
|
||||
if (!isRecord(parsed) || !Array.isArray(parsed.models)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const seen = new Set<string>()
|
||||
const result: CodexModelListModel[] = []
|
||||
for (const value of parsed.models) {
|
||||
if (!isRecord(value)) {
|
||||
continue
|
||||
}
|
||||
const id = nonEmptyString(value.slug)
|
||||
const label = nonEmptyString(value.display_name)
|
||||
if (!id || !label || seen.has(id)) {
|
||||
continue
|
||||
}
|
||||
seen.add(id)
|
||||
result.push({
|
||||
id,
|
||||
label,
|
||||
effortLevels: uniqueEffortLevels(value.supported_reasoning_levels),
|
||||
defaultEffort: nonEmptyString(value.default_reasoning_level),
|
||||
visibility: nonEmptyString(value.visibility)
|
||||
})
|
||||
}
|
||||
return result
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { TuiAgent } from './tui-agent'
|
||||
import { isTuiAgentEnabled } from './tui-agent-selection'
|
||||
import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit'
|
||||
import {
|
||||
CLAUDE_MODEL_LIST_ARGS,
|
||||
CLAUDE_MODEL_LIST_STDIN,
|
||||
parseClaudeModelList
|
||||
} from './claude-model-list-probe'
|
||||
import {
|
||||
CODEX_MODEL_LIST_ARGS,
|
||||
CODEX_MODEL_LIST_JSON_STRUCTURE_LIMITS,
|
||||
parseCodexModelList
|
||||
} from './codex-model-list-probe'
|
||||
import { labelFromModelId } from './model-id-label'
|
||||
|
||||
/* eslint-disable max-lines -- Why: this is the single registry for non-interactive commit-message agents, their model discovery parsers, and UI capabilities. */
|
||||
@@ -81,10 +85,7 @@ export type CommitMessageAgentCapability = {
|
||||
defaultModelId: string
|
||||
}
|
||||
|
||||
export const COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS = {
|
||||
structuralTokens: 64 * 1024,
|
||||
nestingDepth: 16
|
||||
} as const
|
||||
export const COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS = CODEX_MODEL_LIST_JSON_STRUCTURE_LIMITS
|
||||
|
||||
const BASIC_THINKING_LEVELS: ThinkingLevel[] = [
|
||||
{ id: 'low', label: 'Low' },
|
||||
@@ -172,39 +173,29 @@ export function parseClaudeModels(stdout: string): CommitMessageModel[] {
|
||||
}
|
||||
|
||||
export function parseCodexModels(stdout: string): CommitMessageModel[] {
|
||||
try {
|
||||
assertJsonTextStructureWithinLimits(stdout, COMMIT_MESSAGE_MODEL_JSON_STRUCTURE_LIMITS)
|
||||
const parsed = JSON.parse(stdout) as {
|
||||
models?: {
|
||||
slug?: string
|
||||
display_name?: string
|
||||
supported_reasoning_levels?: { effort?: string }[]
|
||||
default_reasoning_level?: string
|
||||
}[]
|
||||
}
|
||||
return uniqueModels(
|
||||
(parsed.models ?? [])
|
||||
.filter((model) => model.slug && model.display_name)
|
||||
.map((model) => ({
|
||||
id: model.slug!,
|
||||
label: model.display_name!,
|
||||
...(model.supported_reasoning_levels?.length
|
||||
? {
|
||||
thinkingLevels: model.supported_reasoning_levels
|
||||
.map((level) => level.effort)
|
||||
.filter((effort): effort is string => Boolean(effort))
|
||||
.map((effort) => ({
|
||||
id: effort,
|
||||
label: effort === 'xhigh' ? 'Extra High' : labelFromModelId(effort)
|
||||
})),
|
||||
defaultThinkingLevel: model.default_reasoning_level ?? 'low'
|
||||
}
|
||||
: {})
|
||||
}))
|
||||
)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
return uniqueModels(
|
||||
parseCodexModelList(stdout).map((model) => {
|
||||
const defaultFromProbe =
|
||||
model.defaultEffort && model.effortLevels.includes(model.defaultEffort)
|
||||
? model.defaultEffort
|
||||
: undefined
|
||||
return {
|
||||
id: model.id,
|
||||
label: model.label,
|
||||
...(model.effortLevels.length > 0
|
||||
? {
|
||||
thinkingLevels: model.effortLevels.map((effort) => ({
|
||||
id: effort,
|
||||
label: effort === 'xhigh' ? 'Extra High' : labelFromModelId(effort)
|
||||
})),
|
||||
defaultThinkingLevel:
|
||||
defaultFromProbe ??
|
||||
(model.effortLevels.includes('low') ? 'low' : model.effortLevels[0])
|
||||
}
|
||||
: {})
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function parseLineModels(stdout: string): CommitMessageModel[] {
|
||||
@@ -401,7 +392,7 @@ export const COMMIT_MESSAGE_AGENT_SPECS: Partial<Record<TuiAgent, CommitMessageA
|
||||
modelSource: 'dynamic',
|
||||
modelDiscovery: {
|
||||
binary: 'codex',
|
||||
args: ['debug', 'models'],
|
||||
args: [...CODEX_MODEL_LIST_ARGS],
|
||||
parse: parseCodexModels
|
||||
},
|
||||
// Why: ordered to match the official `codex` model picker — descending
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CODEX_SESSION_OPTION_CATALOG
|
||||
} from './agent-session-option-catalog-claude-codex'
|
||||
import { CLAUDE_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-claude-codex'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-codex'
|
||||
import { GROK_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-grok'
|
||||
import {
|
||||
buildNativeChatSessionOptionCommand,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { SessionOptionDescriptor } from './native-chat-session-options'
|
||||
import { mergeDiscoveredAuthoritativeModels } from './agent-session-option-catalog'
|
||||
import {
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CODEX_SESSION_OPTION_CATALOG
|
||||
} from './agent-session-option-catalog-claude-codex'
|
||||
import { CLAUDE_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-claude-codex'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-codex'
|
||||
import { GROK_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-grok'
|
||||
import { resolveAgentSessionOptionLaunch } from './agent-session-option-launch'
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CODEX_SESSION_OPTION_CATALOG
|
||||
} from './agent-session-option-catalog-claude-codex'
|
||||
import { CLAUDE_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-claude-codex'
|
||||
import { CODEX_SESSION_OPTION_CATALOG } from './agent-session-option-catalog-codex'
|
||||
import {
|
||||
applyNativeChatReportedSessionOptions,
|
||||
createNativeChatSessionOptionRecord,
|
||||
|
||||
Reference in New Issue
Block a user