From 7b73658f8700c7d6f3804f00df29de4c14243028 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Sat, 14 Feb 2026 16:57:04 +0700 Subject: [PATCH] feat(cursor): sync model mapping across config and raw settings - add cursor model fields to unified config defaults and YAML comments - expand Cursor model catalog/defaults and return configured current model - keep cursor.settings.json env model fields in sync with config updates - align unit tests for new model defaults and route merge behavior Refs #555 --- src/config/unified-config-loader.ts | 6 + src/config/unified-config-types.ts | 9 ++ src/cursor/cursor-models.ts | 129 ++++++++++++--- src/web-server/routes/cursor-routes.ts | 3 +- .../routes/cursor-settings-routes.ts | 149 ++++++++++++++++-- tests/unit/cursor/cursor-models.test.ts | 23 +-- .../web-server/cursor-settings-routes.test.ts | 57 ++++++- 7 files changed, 322 insertions(+), 54 deletions(-) diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index 773259c4..790117b0 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -306,6 +306,10 @@ function mergeWithDefaults(partial: Partial): UnifiedConfig { port: partial.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port, auto_start: partial.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start, ghost_mode: partial.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode, + model: partial.cursor?.model ?? DEFAULT_CURSOR_CONFIG.model, + opus_model: partial.cursor?.opus_model, + sonnet_model: partial.cursor?.sonnet_model, + haiku_model: partial.cursor?.haiku_model, }, // Global env - injected into all non-Claude subscription profiles global_env: { @@ -603,6 +607,8 @@ function generateYamlWithComments(config: UnifiedConfig): string { lines.push('# port: Port for cursor proxy daemon (default: 20129)'); lines.push('# auto_start: Auto-start daemon when CCS starts (default: false)'); lines.push('# ghost_mode: Disable telemetry for privacy (default: true)'); + lines.push('# model: Default model ID (used for ANTHROPIC_MODEL)'); + lines.push('# opus_model/sonnet_model/haiku_model: Optional tier model mapping'); lines.push('# ----------------------------------------------------------------------------'); lines.push( yaml.dump({ cursor: config.cursor }, { indent: 2, lineWidth: -1, quotingType: '"' }).trim() diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index a9faf6d6..6a7797d0 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -304,6 +304,14 @@ export interface CursorConfig { auto_start: boolean; /** Enable ghost mode to disable telemetry (default: true) */ ghost_mode: boolean; + /** Default model ID used by Cursor integration */ + model: string; + /** Optional tier mapping for Claude-compatible model routing */ + opus_model?: string; + /** Optional tier mapping for Claude-compatible model routing */ + sonnet_model?: string; + /** Optional tier mapping for Claude-compatible model routing */ + haiku_model?: string; } /** @@ -723,6 +731,7 @@ export const DEFAULT_CURSOR_CONFIG: CursorConfig = { port: 20129, auto_start: false, ghost_mode: true, + model: 'gpt-5.3-codex', }; /** diff --git a/src/cursor/cursor-models.ts b/src/cursor/cursor-models.ts index de878cdc..c1355fa0 100644 --- a/src/cursor/cursor-models.ts +++ b/src/cursor/cursor-models.ts @@ -13,61 +13,143 @@ import { isDaemonRunning } from './cursor-daemon'; export const DEFAULT_CURSOR_PORT = 20129; /** Default model ID */ -export const DEFAULT_CURSOR_MODEL = 'gpt-4.1'; +export const DEFAULT_CURSOR_MODEL = 'gpt-5.3-codex'; /** * Default models available through Cursor IDE. * Used as fallback when daemon is not reachable. - * Source: Cursor IDE supported models (Feb 2025) + * Source: Cursor docs model catalog (Feb 2026) */ export const DEFAULT_CURSOR_MODELS: CursorModel[] = [ // Anthropic Models { - id: 'claude-sonnet-4', - name: 'Claude Sonnet 4', + id: 'claude-4.6-opus', + name: 'Claude 4.6 Opus', provider: 'anthropic', }, { - id: 'claude-sonnet-4.5', - name: 'Claude Sonnet 4.5', + id: 'claude-4.6-opus-fast-mode', + name: 'Claude 4.6 Opus (Fast mode)', provider: 'anthropic', }, { - id: 'claude-opus-4', - name: 'Claude Opus 4', + id: 'claude-4.5-sonnet', + name: 'Claude 4.5 Sonnet', provider: 'anthropic', }, + { + id: 'claude-4.5-opus', + name: 'Claude 4.5 Opus', + provider: 'anthropic', + }, + { + id: 'claude-4.5-haiku', + name: 'Claude 4.5 Haiku', + provider: 'anthropic', + }, + { + id: 'claude-4-sonnet', + name: 'Claude 4 Sonnet', + provider: 'anthropic', + }, + { + id: 'claude-4-sonnet-1m', + name: 'Claude 4 Sonnet 1M', + provider: 'anthropic', + }, + + // Cursor Models + { + id: 'composer-1.5', + name: 'Composer 1.5', + provider: 'cursor', + }, + { + id: 'composer-1', + name: 'Composer 1', + provider: 'cursor', + }, // OpenAI Models { - id: 'gpt-4.1', - name: 'GPT-4.1', + id: 'gpt-5.3-codex', + name: 'GPT-5.3 Codex', provider: 'openai', isDefault: true, }, + { + id: 'gpt-5.2-codex', + name: 'GPT-5.2 Codex', + provider: 'openai', + }, + { + id: 'gpt-5.2', + name: 'GPT-5.2', + provider: 'openai', + }, + { + id: 'gpt-5.1-codex', + name: 'GPT-5.1 Codex', + provider: 'openai', + }, + { + id: 'gpt-5.1-codex-max', + name: 'GPT-5.1 Codex Max', + provider: 'openai', + }, + { + id: 'gpt-5.1-codex-mini', + name: 'GPT-5.1 Codex Mini', + provider: 'openai', + }, + { + id: 'gpt-5-codex', + name: 'GPT-5-Codex', + provider: 'openai', + }, + { + id: 'gpt-5', + name: 'GPT-5', + provider: 'openai', + }, + { + id: 'gpt-5-fast', + name: 'GPT-5 Fast', + provider: 'openai', + }, { id: 'gpt-5-mini', name: 'GPT-5 Mini', provider: 'openai', }, - { - id: 'o3-mini', - name: 'O3 Mini', - provider: 'openai', - }, // Google Models { - id: 'gemini-2.5-pro', - name: 'Gemini 2.5 Pro', + id: 'gemini-3-pro', + name: 'Gemini 3 Pro', + provider: 'google', + }, + { + id: 'gemini-3-pro-image-preview', + name: 'Gemini 3 Pro Image Preview', + provider: 'google', + }, + { + id: 'gemini-3-flash', + name: 'Gemini 3 Flash', + provider: 'google', + }, + { + id: 'gemini-2.5-flash', + name: 'Gemini 2.5 Flash', provider: 'google', }, - // Cursor Custom Models + // xAI Models { - id: 'cursor-small', - name: 'Cursor Small', - provider: 'cursor', + id: 'grok-code', + name: 'Grok Code', + provider: 'xai', }, ]; @@ -154,7 +236,7 @@ export async function getAvailableModels(port: number): Promise { /** * Get the default model. - * Uses gpt-4.1 as it's commonly available. + * Uses GPT-5.3 Codex as default. */ export function getDefaultModel(): string { return DEFAULT_CURSOR_MODEL; @@ -167,7 +249,8 @@ export function detectProvider(modelId: string): string { if (modelId.includes('claude')) return 'anthropic'; if (modelId.includes('gpt') || /^o[1-9]\d*(-|$)/.test(modelId)) return 'openai'; if (modelId.includes('gemini')) return 'google'; - if (modelId.includes('cursor')) return 'cursor'; + if (modelId.includes('cursor') || modelId.includes('composer')) return 'cursor'; + if (modelId.includes('grok')) return 'xai'; return 'unknown'; } diff --git a/src/web-server/routes/cursor-routes.ts b/src/web-server/routes/cursor-routes.ts index 0ba13344..b28c3705 100644 --- a/src/web-server/routes/cursor-routes.ts +++ b/src/web-server/routes/cursor-routes.ts @@ -7,7 +7,6 @@ import { Router } from 'express'; import { getDaemonStatus, getAvailableModels, - getDefaultModel, startDaemon, stopDaemon, checkAuthStatus, @@ -150,7 +149,7 @@ router.get('/models', async (_req: Request, res: Response): Promise => { try { const cursorConfig = getCursorConfig(); const models = await getAvailableModels(cursorConfig.port); - res.json({ models, current: getDefaultModel() }); + res.json({ models, current: cursorConfig.model }); } catch (error) { res.status(500).json({ error: (error as Error).message }); } diff --git a/src/web-server/routes/cursor-settings-routes.ts b/src/web-server/routes/cursor-settings-routes.ts index ce1d229a..25bbd685 100644 --- a/src/web-server/routes/cursor-settings-routes.ts +++ b/src/web-server/routes/cursor-settings-routes.ts @@ -13,6 +13,7 @@ import { saveUnifiedConfig, getCursorConfig, } from '../../config/unified-config-loader'; +import type { CursorConfig } from '../../config/unified-config-types'; const router = Router(); @@ -34,8 +35,78 @@ function parseLocalCursorPort(settings: unknown): number | null { } } +function parseRequiredModel(value: unknown): string | null { + if (typeof value !== 'string') return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +} + +function parseOptionalModel(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : undefined; +} + +function getDefaultCursorSettings(cursorConfig: CursorConfig): { env: Record } { + const model = cursorConfig.model || DEFAULT_CURSOR_CONFIG.model; + return { + env: { + ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorConfig.port}`, + ANTHROPIC_AUTH_TOKEN: 'cursor-managed', + ANTHROPIC_MODEL: model, + ANTHROPIC_DEFAULT_OPUS_MODEL: cursorConfig.opus_model || model, + ANTHROPIC_DEFAULT_SONNET_MODEL: cursorConfig.sonnet_model || model, + ANTHROPIC_DEFAULT_HAIKU_MODEL: cursorConfig.haiku_model || model, + }, + }; +} + +function syncRawSettingsFromCursorConfig(cursorConfig: CursorConfig): void { + const settingsPath = path.join(getCcsDir(), 'cursor.settings.json'); + + let settings: Record = {}; + if (fs.existsSync(settingsPath)) { + try { + const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8')); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + settings = parsed as Record; + } + } catch { + settings = {}; + } + } + + const envSource = settings.env; + const env = + typeof envSource === 'object' && envSource !== null && !Array.isArray(envSource) + ? { ...(envSource as Record) } + : {}; + + const model = cursorConfig.model || DEFAULT_CURSOR_CONFIG.model; + const localPort = parseLocalCursorPort({ env }); + if (!env.ANTHROPIC_BASE_URL || localPort !== null) { + env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${cursorConfig.port}`; + } + if (!env.ANTHROPIC_AUTH_TOKEN) { + env.ANTHROPIC_AUTH_TOKEN = 'cursor-managed'; + } + env.ANTHROPIC_MODEL = model; + env.ANTHROPIC_DEFAULT_OPUS_MODEL = cursorConfig.opus_model || model; + env.ANTHROPIC_DEFAULT_SONNET_MODEL = cursorConfig.sonnet_model || model; + env.ANTHROPIC_DEFAULT_HAIKU_MODEL = cursorConfig.haiku_model || model; + + const nextSettings = { + ...settings, + env, + }; + + const tempPath = settingsPath + '.tmp'; + fs.writeFileSync(tempPath, JSON.stringify(nextSettings, null, 2) + '\n'); + fs.renameSync(tempPath, settingsPath); +} + /** - * GET /api/cursor/settings - Get cursor config (port, auto_start, ghost_mode) + * GET /api/cursor/settings - Get cursor config */ router.get('/', (_req: Request, res: Response): void => { try { @@ -82,8 +153,40 @@ router.put('/', (req: Request, res: Response): void => { res.status(400).json({ error: 'ghost_mode must be a boolean' }); return; } + if ('model' in updates && !parseRequiredModel(updates.model)) { + res.status(400).json({ error: 'model must be a non-empty string' }); + return; + } + if ( + 'opus_model' in updates && + updates.opus_model !== undefined && + updates.opus_model !== null && + typeof updates.opus_model !== 'string' + ) { + res.status(400).json({ error: 'opus_model must be a string' }); + return; + } + if ( + 'sonnet_model' in updates && + updates.sonnet_model !== undefined && + updates.sonnet_model !== null && + typeof updates.sonnet_model !== 'string' + ) { + res.status(400).json({ error: 'sonnet_model must be a string' }); + return; + } + if ( + 'haiku_model' in updates && + updates.haiku_model !== undefined && + updates.haiku_model !== null && + typeof updates.haiku_model !== 'string' + ) { + res.status(400).json({ error: 'haiku_model must be a string' }); + return; + } const config = loadOrCreateUnifiedConfig(); + const normalizedModel = parseRequiredModel(updates.model); // Merge updates with existing config // Only known fields are merged — unknown properties are ignored @@ -94,9 +197,23 @@ router.put('/', (req: Request, res: Response): void => { updates.auto_start ?? config.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start, ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode, + model: normalizedModel ?? config.cursor?.model ?? DEFAULT_CURSOR_CONFIG.model, + opus_model: + 'opus_model' in updates + ? parseOptionalModel(updates.opus_model) + : config.cursor?.opus_model, + sonnet_model: + 'sonnet_model' in updates + ? parseOptionalModel(updates.sonnet_model) + : config.cursor?.sonnet_model, + haiku_model: + 'haiku_model' in updates + ? parseOptionalModel(updates.haiku_model) + : config.cursor?.haiku_model, }; saveUnifiedConfig(config); + syncRawSettingsFromCursorConfig(config.cursor); res.json({ success: true, cursor: config.cursor }); } catch (error) { res.status(500).json({ error: (error as Error).message }); @@ -114,14 +231,7 @@ router.get('/raw', (_req: Request, res: Response): void => { // If file doesn't exist, return default structure if (!fs.existsSync(settingsPath)) { - // Create settings structure matching Cursor pattern - // Use 127.0.0.1 instead of localhost for more reliable local connections - const defaultSettings = { - env: { - ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorConfig.port}`, - ANTHROPIC_AUTH_TOKEN: 'cursor-managed', - }, - }; + const defaultSettings = getDefaultCursorSettings(cursorConfig); res.json({ settings: defaultSettings, @@ -186,14 +296,19 @@ router.put('/raw', (req: Request, res: Response): void => { // Keep unified config aligned with raw settings edits (parity with Copilot raw editor). const parsedPort = parseLocalCursorPort(settings); - if (parsedPort) { - const config = loadOrCreateUnifiedConfig(); - config.cursor = { - ...(config.cursor ?? DEFAULT_CURSOR_CONFIG), - port: parsedPort, - }; - saveUnifiedConfig(config); - } + const config = loadOrCreateUnifiedConfig(); + const env = (settings as { env?: Record }).env ?? {}; + const model = parseRequiredModel(env.ANTHROPIC_MODEL) ?? config.cursor?.model; + + config.cursor = { + ...(config.cursor ?? DEFAULT_CURSOR_CONFIG), + ...(parsedPort !== null ? { port: parsedPort } : {}), + ...(model ? { model } : {}), + opus_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_OPUS_MODEL), + sonnet_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_SONNET_MODEL), + haiku_model: parseOptionalModel(env.ANTHROPIC_DEFAULT_HAIKU_MODEL), + }; + saveUnifiedConfig(config); const stat = fs.statSync(settingsPath); res.json({ success: true, mtime: stat.mtimeMs }); diff --git a/tests/unit/cursor/cursor-models.test.ts b/tests/unit/cursor/cursor-models.test.ts index e510a590..e5127f02 100644 --- a/tests/unit/cursor/cursor-models.test.ts +++ b/tests/unit/cursor/cursor-models.test.ts @@ -35,8 +35,8 @@ describe('DEFAULT_CURSOR_PORT', () => { }); describe('DEFAULT_CURSOR_MODEL', () => { - it('is gpt-4.1', () => { - expect(DEFAULT_CURSOR_MODEL).toBe('gpt-4.1'); + it('is gpt-5.3-codex', () => { + expect(DEFAULT_CURSOR_MODEL).toBe('gpt-5.3-codex'); }); }); @@ -48,12 +48,12 @@ describe('getDefaultModel', () => { describe('detectProvider', () => { it('detects anthropic models', () => { - expect(detectProvider('claude-sonnet-4')).toBe('anthropic'); - expect(detectProvider('claude-opus-4')).toBe('anthropic'); + expect(detectProvider('claude-4.5-sonnet')).toBe('anthropic'); + expect(detectProvider('claude-4.6-opus')).toBe('anthropic'); }); it('detects openai models', () => { - expect(detectProvider('gpt-4.1')).toBe('openai'); + expect(detectProvider('gpt-5.3-codex')).toBe('openai'); expect(detectProvider('gpt-5-mini')).toBe('openai'); expect(detectProvider('o3-mini')).toBe('openai'); }); @@ -65,11 +65,16 @@ describe('detectProvider', () => { }); it('detects google models', () => { - expect(detectProvider('gemini-2.5-pro')).toBe('google'); + expect(detectProvider('gemini-3-pro')).toBe('google'); }); it('detects cursor models', () => { - expect(detectProvider('cursor-small')).toBe('cursor'); + expect(detectProvider('composer-1.5')).toBe('cursor'); + expect(detectProvider('cursor-model')).toBe('cursor'); + }); + + it('detects xai models', () => { + expect(detectProvider('grok-code')).toBe('xai'); }); it('defaults to unknown for unrecognized models', () => { @@ -79,8 +84,8 @@ describe('detectProvider', () => { describe('formatModelName', () => { it('returns catalog name for known models', () => { - expect(formatModelName('claude-sonnet-4')).toBe('Claude Sonnet 4'); - expect(formatModelName('gpt-4.1')).toBe('GPT-4.1'); + expect(formatModelName('claude-4.6-opus')).toBe('Claude 4.6 Opus'); + expect(formatModelName('gpt-5.3-codex')).toBe('GPT-5.3 Codex'); }); it('converts kebab-case to title case for unknown models', () => { diff --git a/tests/unit/web-server/cursor-settings-routes.test.ts b/tests/unit/web-server/cursor-settings-routes.test.ts index c2e5dfac..fb163a7a 100644 --- a/tests/unit/web-server/cursor-settings-routes.test.ts +++ b/tests/unit/web-server/cursor-settings-routes.test.ts @@ -128,6 +128,13 @@ describe('Cursor Settings Routes Logic', () => { port: updates.port ?? config.cursor?.port ?? 3000, auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false, ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false, + model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex', + opus_model: + updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model, + sonnet_model: + updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model, + haiku_model: + updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model, }; expect(cursorConfig.port).toBe(5000); @@ -137,7 +144,13 @@ describe('Cursor Settings Routes Logic', () => { it('updates port only', () => { const config = loadOrCreateUnifiedConfig(); - config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false }; + config.cursor = { + enabled: false, + port: 3000, + auto_start: false, + ghost_mode: false, + model: 'gpt-5.3-codex', + }; saveUnifiedConfig(config); const updates = { port: 4000 }; @@ -146,6 +159,13 @@ describe('Cursor Settings Routes Logic', () => { port: updates.port ?? config.cursor?.port ?? 3000, auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false, ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false, + model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex', + opus_model: + updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model, + sonnet_model: + updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model, + haiku_model: + updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model, }; expect(cursorConfig.port).toBe(4000); @@ -155,7 +175,13 @@ describe('Cursor Settings Routes Logic', () => { it('updates auto_start only', () => { const config = loadOrCreateUnifiedConfig(); - config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false }; + config.cursor = { + enabled: false, + port: 3000, + auto_start: false, + ghost_mode: false, + model: 'gpt-5.3-codex', + }; saveUnifiedConfig(config); const updates = { auto_start: true }; @@ -164,6 +190,13 @@ describe('Cursor Settings Routes Logic', () => { port: updates.port ?? config.cursor?.port ?? 3000, auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false, ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false, + model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex', + opus_model: + updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model, + sonnet_model: + updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model, + haiku_model: + updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model, }; expect(cursorConfig.port).toBe(3000); @@ -173,7 +206,13 @@ describe('Cursor Settings Routes Logic', () => { it('updates ghost_mode only', () => { const config = loadOrCreateUnifiedConfig(); - config.cursor = { enabled: false, port: 3000, auto_start: false, ghost_mode: false }; + config.cursor = { + enabled: false, + port: 3000, + auto_start: false, + ghost_mode: false, + model: 'gpt-5.3-codex', + }; saveUnifiedConfig(config); const updates = { ghost_mode: true }; @@ -182,6 +221,13 @@ describe('Cursor Settings Routes Logic', () => { port: updates.port ?? config.cursor?.port ?? 3000, auto_start: updates.auto_start ?? config.cursor?.auto_start ?? false, ghost_mode: updates.ghost_mode ?? config.cursor?.ghost_mode ?? false, + model: updates.model ?? config.cursor?.model ?? 'gpt-5.3-codex', + opus_model: + updates.opus_model !== undefined ? updates.opus_model : config.cursor?.opus_model, + sonnet_model: + updates.sonnet_model !== undefined ? updates.sonnet_model : config.cursor?.sonnet_model, + haiku_model: + updates.haiku_model !== undefined ? updates.haiku_model : config.cursor?.haiku_model, }; expect(cursorConfig.port).toBe(3000); @@ -203,11 +249,16 @@ describe('Cursor Settings Routes Logic', () => { env: { ANTHROPIC_BASE_URL: `http://127.0.0.1:${cursorPort}`, ANTHROPIC_AUTH_TOKEN: 'cursor-managed', + ANTHROPIC_MODEL: 'gpt-5.3-codex', + ANTHROPIC_DEFAULT_OPUS_MODEL: 'gpt-5.3-codex', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.3-codex', + ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gpt-5.3-codex', }, }; expect(defaultSettings.env.ANTHROPIC_BASE_URL).toContain('http://127.0.0.1:'); expect(defaultSettings.env.ANTHROPIC_AUTH_TOKEN).toBe('cursor-managed'); + expect(defaultSettings.env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex'); }); it('reads existing file', () => {