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
1 change: 1 addition & 0 deletions packages/@ant/model-provider/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './types/index.js'
// Provider model mappings
export { resolveOpenAIModel } from './providers/openai/modelMapping.js'
export { resolveGrokModel } from './providers/grok/modelMapping.js'
export { resolveCodexModel } from './providers/codex/modelMapping.js'
export { resolveGeminiModel } from './providers/gemini/modelMapping.js'

// Gemini provider utilities
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { afterEach, describe, expect, test } from 'bun:test'
import { resolveCodexModel } from '../modelMapping.js'

describe('resolveCodexModel', () => {
const savedCodexModel = process.env.CODEX_MODEL

afterEach(() => {
if (savedCodexModel === undefined) {
delete process.env.CODEX_MODEL
} else {
process.env.CODEX_MODEL = savedCodexModel
}
})

test('uses CODEX_MODEL as the default for family aliases', () => {
process.env.CODEX_MODEL = 'deepseek-v4-pro[1m]'

expect(resolveCodexModel('sonnet')).toBe('deepseek-v4-pro[1m]')
})

test('does not let CODEX_MODEL override an explicit model selection', () => {
process.env.CODEX_MODEL = 'deepseek-v4-pro[1m]'

expect(resolveCodexModel('gpt-5.5')).toBe('gpt-5.5')
})
})
69 changes: 69 additions & 0 deletions packages/@ant/model-provider/src/providers/codex/modelMapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Default mapping from Anthropic model names to Codex model names.
*
* Users can override per-family via CODEX_DEFAULT_{FAMILY}_MODEL env vars.
* CODEX_MODEL is a default model used only when the caller did not select a
* concrete model.
*/
const DEFAULT_MODEL_MAP: Record<string, string> = {
'claude-sonnet-4-20250514': 'claude-sonnet-4-20250514',
'claude-sonnet-4-5-20250929': 'claude-sonnet-4-5-20250929',
'claude-sonnet-4-6': 'claude-sonnet-4-6',
'claude-opus-4-20250514': 'claude-opus-4-20250514',
'claude-opus-4-1-20250805': 'claude-opus-4-1-20250805',
'claude-opus-4-5-20251101': 'claude-opus-4-5-20251101',
'claude-opus-4-6': 'claude-opus-4-6',
'claude-opus-4-7': 'claude-opus-4-7',
'claude-haiku-4-5-20251001': 'claude-haiku-4-5-20251001',
'claude-3-5-haiku-20241022': 'claude-3-5-haiku-20241022',
'claude-3-7-sonnet-20250219': 'claude-3-7-sonnet-20250219',
'claude-3-5-sonnet-20241022': 'claude-3-5-sonnet-20241022',
}

const DEFAULT_FAMILY_MAP: Record<string, string> = {
opus: 'claude-opus-4-7',
sonnet: 'claude-sonnet-4-6',
haiku: 'claude-haiku-4-5-20251001',
}

function getModelFamily(model: string): 'haiku' | 'sonnet' | 'opus' | null {
if (/haiku/i.test(model)) return 'haiku'
if (/opus/i.test(model)) return 'opus'
if (/sonnet/i.test(model)) return 'sonnet'
return null
}

/**
* Resolve the Codex model name for a given Anthropic model.
*/
export function resolveCodexModel(anthropicModel: string): string {
if (
process.env.CODEX_MODEL &&
['sonnet', 'opus', 'haiku'].includes(anthropicModel)
) {
return process.env.CODEX_MODEL
}

const cleanModel = anthropicModel.replace(/\[1m\]$/, '')
const family = getModelFamily(cleanModel)

if (family) {
const codexEnvVar = `CODEX_DEFAULT_${family.toUpperCase()}_MODEL`
const codexOverride = process.env[codexEnvVar]
if (codexOverride) return codexOverride

const anthropicEnvVar = `ANTHROPIC_DEFAULT_${family.toUpperCase()}_MODEL`
const anthropicOverride = process.env[anthropicEnvVar]
if (anthropicOverride) return anthropicOverride
}

if (DEFAULT_MODEL_MAP[cleanModel]) {
return DEFAULT_MODEL_MAP[cleanModel]
}

if (family && DEFAULT_FAMILY_MAP[family]) {
return DEFAULT_FAMILY_MAP[family]
}

return cleanModel
}
4 changes: 4 additions & 0 deletions src/commands/logout/logout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,19 @@ export async function performLogout({ clearOnboarding = false }): Promise<void>

function clearChatGPTSettingsAuthMode(): void {
delete process.env.OPENAI_AUTH_MODE;
delete process.env.CODEX_AUTH_MODE;
const userSettings = getSettingsForSource('userSettings') ?? {};
const env = userSettings.env ?? {};
const hasOpenAICompatibleConfig =
Boolean(env.OPENAI_API_KEY ?? process.env.OPENAI_API_KEY) &&
Boolean(env.OPENAI_BASE_URL ?? process.env.OPENAI_BASE_URL);
const hasCodexCompatibleConfig = Boolean(env.CODEX_API_KEY ?? process.env.CODEX_API_KEY);
const settingsUpdate: Parameters<typeof updateSettingsForSource>[1] = {
...(userSettings.modelType === 'openai' && !hasOpenAICompatibleConfig ? { modelType: undefined } : {}),
...(userSettings.modelType === 'codex' && !hasCodexCompatibleConfig ? { modelType: undefined } : {}),
env: {
OPENAI_AUTH_MODE: undefined,
CODEX_AUTH_MODE: undefined,
} as unknown as Record<string, string>,
};
updateSettingsForSource('userSettings', settingsUpdate);
Expand Down
28 changes: 25 additions & 3 deletions src/commands/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ function getEnvVarForProvider(provider: string): string {
return 'CLAUDE_CODE_USE_GEMINI'
case 'grok':
return 'CLAUDE_CODE_USE_GROK'
case 'codex':
return 'CLAUDE_CODE_USE_CODEX'
default:
throw new Error(`Unknown provider: ${provider}`)
}
Expand Down Expand Up @@ -55,6 +57,7 @@ const call: LocalCommandCall = async (args, _context) => {
delete process.env.CLAUDE_CODE_USE_OPENAI
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.CLAUDE_CODE_USE_GROK
delete process.env.CLAUDE_CODE_USE_CODEX
return {
type: 'text',
value: 'API provider cleared (will use environment variables).',
Expand All @@ -67,6 +70,7 @@ const call: LocalCommandCall = async (args, _context) => {
'openai',
'gemini',
'grok',
'codex',
'bedrock',
'vertex',
'foundry',
Expand Down Expand Up @@ -109,6 +113,20 @@ const call: LocalCommandCall = async (args, _context) => {
}
}

// Check env vars when switching to codex (including settings.env)
if (arg === 'codex') {
const mergedEnv = getMergedEnv()
const hasKey = !!mergedEnv.CODEX_API_KEY
const hasChatGPTAuth = mergedEnv.CODEX_AUTH_MODE === 'chatgpt'
if (!hasKey && !hasChatGPTAuth) {
updateSettingsForSource('userSettings', { modelType: 'codex' })
return {
type: 'text',
value: `Switched to Codex provider.\nWarning: Missing env var: CODEX_API_KEY\nConfigure it via /login or set manually.`,
}
}
}

// Check env vars when switching to gemini (including settings.env)
if (arg === 'gemini') {
const mergedEnv = getMergedEnv()
Expand All @@ -130,7 +148,8 @@ const call: LocalCommandCall = async (args, _context) => {
arg === 'anthropic' ||
arg === 'openai' ||
arg === 'gemini' ||
arg === 'grok'
arg === 'grok' ||
arg === 'codex'
) {
// Clear any cloud provider env vars to avoid conflicts
delete process.env.CLAUDE_CODE_USE_BEDROCK
Expand All @@ -139,6 +158,7 @@ const call: LocalCommandCall = async (args, _context) => {
delete process.env.CLAUDE_CODE_USE_OPENAI
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.CLAUDE_CODE_USE_GROK
delete process.env.CLAUDE_CODE_USE_CODEX
// Update settings.json
updateSettingsForSource('userSettings', { modelType: arg })
// Ensure settings.env gets applied to process.env
Expand All @@ -151,6 +171,7 @@ const call: LocalCommandCall = async (args, _context) => {
delete process.env.OPENAI_BASE_URL
delete process.env.CLAUDE_CODE_USE_GEMINI
delete process.env.CLAUDE_CODE_USE_GROK
delete process.env.CLAUDE_CODE_USE_CODEX
process.env[getEnvVarForProvider(arg)] = '1'
// Do not modify settings.json - cloud providers controlled solely by env vars
applyConfigEnvironmentVariables()
Expand All @@ -165,9 +186,10 @@ const provider = {
type: 'local',
name: 'provider',
description:
'Switch API provider (anthropic/openai/gemini/grok/bedrock/vertex/foundry)',
'Switch API provider (anthropic/openai/gemini/grok/codex/bedrock/vertex/foundry)',
aliases: ['api'],
argumentHint: '[anthropic|openai|gemini|grok|bedrock|vertex|foundry|unset]',
argumentHint:
'[anthropic|openai|gemini|grok|codex|bedrock|vertex|foundry|unset]',
supportsNonInteractive: true,
load: () => Promise.resolve({ call }),
} satisfies Command
Expand Down
Loading
Loading