diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index f44f6f49..4d1aae68 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -350,7 +350,7 @@ async function handleCreate(args: string[]): Promise { } // Step 4: Model configuration (use preset default if available) - const defaultModel = preset?.defaultModel || 'claude-sonnet-4-5-20250929'; + const defaultModel = preset?.defaultModel || 'claude-sonnet-4-6'; let model = parsedArgs.model || openRouterModel || preset?.defaultModel; if (!model && !parsedArgs.yes && !preset) { model = await InteractivePrompt.input('Default model (ANTHROPIC_MODEL)', { diff --git a/tests/unit/cliproxy/model-catalog.test.js b/tests/unit/cliproxy/model-catalog.test.js index ee9f5040..0b2b47f2 100644 --- a/tests/unit/cliproxy/model-catalog.test.js +++ b/tests/unit/cliproxy/model-catalog.test.js @@ -113,7 +113,6 @@ describe('Model Catalog', () => { assert.strictEqual(MODEL_CATALOG.claude.provider, 'claude'); assert.strictEqual(MODEL_CATALOG.claude.displayName, 'Claude (Anthropic)'); }); - it('has correct default model', () => { const { MODEL_CATALOG } = modelCatalog; assert.strictEqual(MODEL_CATALOG.claude.defaultModel, 'claude-sonnet-4-6'); @@ -125,6 +124,13 @@ describe('Model Catalog', () => { assert(sonnet, 'Should include Claude Sonnet 4.6'); assert.strictEqual(sonnet.name, 'Claude Sonnet 4.6'); }); + + it('retains previous 4.5 snapshot models for explicit selection', () => { + const { MODEL_CATALOG } = modelCatalog; + const ids = MODEL_CATALOG.claude.models.map((m) => m.id); + assert(ids.includes('claude-opus-4-5-20251101')); + assert(ids.includes('claude-sonnet-4-5-20250929')); + }); }); describe('Gemini models', () => { diff --git a/ui/src/components/profiles/profile-dialog.tsx b/ui/src/components/profiles/profile-dialog.tsx index b6873580..f880a6e4 100644 --- a/ui/src/components/profiles/profile-dialog.tsx +++ b/ui/src/components/profiles/profile-dialog.tsx @@ -18,7 +18,7 @@ import type { Profile } from '@/lib/api-client'; import { ChevronDown, ChevronRight } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -const DEFAULT_MODEL = 'claude-sonnet-4-5-20250929'; +const DEFAULT_MODEL = 'claude-sonnet-4-6'; const schema = z.object({ name: z diff --git a/ui/src/lib/preset-utils.ts b/ui/src/lib/preset-utils.ts index 1121d5ab..dec9ba6a 100644 --- a/ui/src/lib/preset-utils.ts +++ b/ui/src/lib/preset-utils.ts @@ -27,7 +27,7 @@ async function fetchEffectiveApiKey(): Promise { /** * Apply default preset for a provider to its settings - * Uses the first model's presetMapping or falls back to using defaultModel for all tiers + * Uses the catalog default model's preset mapping or falls back to using defaultModel for all tiers * * @param provider - The provider ID (e.g., 'gemini', 'codex', 'agy') * @param port - Optional custom port (defaults to CLIPROXY_DEFAULT_PORT) @@ -40,9 +40,9 @@ export async function applyDefaultPreset( const catalog = MODEL_CATALOGS[provider]; if (!catalog) return { success: false }; - // Get the first (recommended) model's preset mapping - const firstModel = catalog.models[0]; - const mapping = firstModel?.presetMapping || { + const defaultModelEntry = + catalog.models.find((model) => model.id === catalog.defaultModel) || catalog.models[0]; + const mapping = defaultModelEntry?.presetMapping || { default: catalog.defaultModel, opus: catalog.defaultModel, sonnet: catalog.defaultModel, @@ -72,7 +72,7 @@ export async function applyDefaultPreset( }); return { success: res.ok, - presetName: firstModel?.name || catalog.defaultModel, + presetName: defaultModelEntry?.name || catalog.defaultModel, }; } catch { return { success: false }; diff --git a/ui/tests/unit/ui/lib/preset-utils.test.ts b/ui/tests/unit/ui/lib/preset-utils.test.ts new file mode 100644 index 00000000..460c1e1f --- /dev/null +++ b/ui/tests/unit/ui/lib/preset-utils.test.ts @@ -0,0 +1,44 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { MODEL_CATALOGS } from '@/lib/model-catalogs'; +import { applyDefaultPreset } from '@/lib/preset-utils'; + +describe('claude preset utils', () => { + afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it('keeps the claude catalog default on Sonnet 4.6', () => { + const claudeCatalog = MODEL_CATALOGS.claude; + + expect(claudeCatalog.defaultModel).toBe('claude-sonnet-4-6'); + expect(claudeCatalog.models.map((model) => model.id)).toContain('claude-sonnet-4-6'); + }); + + it('applies the default claude preset from the catalog default model mapping', async () => { + const fetchMock = vi + .fn() + .mockResolvedValueOnce({ + ok: true, + json: async () => ({ apiKey: { value: 'managed-key' } }), + }) + .mockResolvedValueOnce({ ok: true }); + + vi.stubGlobal('fetch', fetchMock); + + const result = await applyDefaultPreset('claude'); + + expect(result).toEqual({ success: true, presetName: 'Claude Sonnet 4.6' }); + + const [, requestInit] = fetchMock.mock.calls[1] ?? []; + const body = JSON.parse(String(requestInit?.body)); + + expect(body.settings.env).toMatchObject({ + ANTHROPIC_MODEL: 'claude-sonnet-4-6', + ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-6', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-6', + ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-haiku-4-5-20251001', + }); + }); +});