Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/test/claudeModelId.spec.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { describe, expect, it } from 'vitest';6import { parseClaudeModelId, tryParseClaudeModelId } from '../claudeModelId';78describe('parseClaudeModelId', () => {9describe('parsing SDK model IDs', () => {10it('parses claude-{name}-{major}-{minor}-{date}', () => {11const result = parseClaudeModelId('claude-opus-4-5-20251101');12expect(result).toEqual(expect.objectContaining({13name: 'opus',14version: '4.5',15modifiers: '20251101',16}));17});1819it('parses claude-{major}-{minor}-{name}-{date} (old format)', () => {20const result = parseClaudeModelId('claude-3-5-sonnet-20241022');21expect(result).toEqual(expect.objectContaining({22name: 'sonnet',23version: '3.5',24modifiers: '20241022',25}));26});2728it('parses claude-{name}-{major}-{date}', () => {29const result = parseClaudeModelId('claude-sonnet-4-20250514');30expect(result).toEqual(expect.objectContaining({31name: 'sonnet',32version: '4',33modifiers: '20250514',34}));35});3637it('parses claude-{major}-{name}-{date} (old format)', () => {38const result = parseClaudeModelId('claude-3-opus-20240229');39expect(result).toEqual(expect.objectContaining({40name: 'opus',41version: '3',42modifiers: '20240229',43}));44});4546it('parses SDK ID without date suffix', () => {47const result = parseClaudeModelId('claude-opus-4-5');48expect(result).toEqual(expect.objectContaining({49name: 'opus',50version: '4.5',51modifiers: '',52}));53});54});5556describe('parsing endpoint model IDs', () => {57it('parses claude-{name}-{major}.{minor}', () => {58const result = parseClaudeModelId('claude-opus-4.5');59expect(result).toEqual(expect.objectContaining({60name: 'opus',61version: '4.5',62modifiers: '',63}));64});6566it('parses claude-{name}-{major}', () => {67const result = parseClaudeModelId('claude-sonnet-4');68expect(result).toEqual(expect.objectContaining({69name: 'sonnet',70version: '4',71modifiers: '',72}));73});7475it('parses claude-haiku-3.5', () => {76const result = parseClaudeModelId('claude-haiku-3.5');77expect(result).toEqual(expect.objectContaining({78name: 'haiku',79version: '3.5',80modifiers: '',81}));82});83});8485describe('modifiers (non-date suffixes)', () => {86it('parses endpoint ID with 1m context variant (dot version)', () => {87const result = parseClaudeModelId('claude-opus-4.6-1m');88expect(result).toEqual(expect.objectContaining({89name: 'opus',90version: '4.6',91modifiers: '1m',92}));93});9495it('parses SDK ID with 1m context variant (dash version)', () => {96const result = parseClaudeModelId('claude-opus-4-6-1m');97expect(result).toEqual(expect.objectContaining({98name: 'opus',99version: '4.6',100modifiers: '1m',101}));102});103104it('parses SDK ID with both 1m modifier and date suffix', () => {105const result = parseClaudeModelId('claude-opus-4-6-1m-20251101');106expect(result).toEqual(expect.objectContaining({107name: 'opus',108version: '4.6',109modifiers: '1m-20251101',110}));111});112113it('parses single-version ID with modifier', () => {114const result = parseClaudeModelId('claude-sonnet-4-1m');115expect(result).toEqual(expect.objectContaining({116name: 'sonnet',117version: '4',118modifiers: '1m',119}));120});121122it('1m on opus converts to correct SDK model ID', () => {123expect(parseClaudeModelId('claude-opus-4.6-1m').toSdkModelId()).toBe('claude-opus-4-6-1m');124});125126it('1m on opus converts to correct endpoint model ID', () => {127expect(parseClaudeModelId('claude-opus-4-6-1m').toEndpointModelId()).toBe('claude-opus-4.6-1m');128});129130it('1m on non-opus model is not included in SDK model ID', () => {131expect(parseClaudeModelId('claude-sonnet-4-1m').toSdkModelId()).toBe('claude-sonnet-4');132});133134it('1m on non-opus model is not included in endpoint model ID', () => {135expect(parseClaudeModelId('claude-sonnet-4-1m').toEndpointModelId()).toBe('claude-sonnet-4');136});137138it('1m with date suffix on opus keeps only 1m in SDK model ID', () => {139expect(parseClaudeModelId('claude-opus-4-6-1m-20251101').toSdkModelId()).toBe('claude-opus-4-6-1m');140});141142it('1m with date suffix on opus keeps only 1m in endpoint model ID', () => {143expect(parseClaudeModelId('claude-opus-4-6-1m-20251101').toEndpointModelId()).toBe('claude-opus-4.6-1m');144});145});146147describe('bare model names', () => {148it('parses a bare name with no version', () => {149const result = parseClaudeModelId('foo');150expect(result).toEqual(expect.objectContaining({151name: 'foo',152version: '',153modifiers: '',154}));155});156157it('toSdkModelId returns the bare name', () => {158expect(parseClaudeModelId('foo').toSdkModelId()).toBe('foo');159});160161it('toEndpointModelId returns the bare name', () => {162expect(parseClaudeModelId('foo').toEndpointModelId()).toBe('foo');163});164165it('parses bare "claude" as a bare name', () => {166const result = parseClaudeModelId('claude');167expect(result).toEqual(expect.objectContaining({168name: 'claude',169version: '',170modifiers: '',171}));172});173});174175describe('unparseable inputs', () => {176it('throws for hyphenated non-Claude IDs', () => {177expect(() => parseClaudeModelId('gpt-4o')).toThrow(`Unable to parse Claude model ID: 'gpt-4o'`);178});179180it('throws for garbage with hyphens', () => {181expect(() => parseClaudeModelId('invalid-model-id')).toThrow();182});183});184185describe('tryParseClaudeModelId', () => {186it('returns undefined for hyphenated non-Claude IDs', () => {187expect(tryParseClaudeModelId('gpt-4o')).toBeUndefined();188});189190it('returns a result for bare names', () => {191expect(tryParseClaudeModelId('foo')).toEqual(expect.objectContaining({192name: 'foo',193version: '',194}));195});196197it('returns a result for valid Claude IDs', () => {198expect(tryParseClaudeModelId('claude-sonnet-4')).toEqual(expect.objectContaining({199name: 'sonnet',200version: '4',201}));202});203});204205describe('case insensitivity', () => {206it('parses uppercase input', () => {207const result = parseClaudeModelId('CLAUDE-OPUS-4-5');208expect(result).toEqual(expect.objectContaining({209name: 'opus',210version: '4.5',211}));212});213214it('parses mixed case input', () => {215const result = parseClaudeModelId('Claude-Sonnet-4-20250514');216expect(result).toEqual(expect.objectContaining({217name: 'sonnet',218version: '4',219modifiers: '20250514',220}));221});222});223224describe('caching', () => {225it('returns the same object for repeated calls', () => {226const first = parseClaudeModelId('claude-opus-4-5-20251101');227const second = parseClaudeModelId('claude-opus-4-5-20251101');228expect(first).toBe(second);229});230231it('returns the same object for different casing of the same ID', () => {232const lower = parseClaudeModelId('claude-haiku-3-5');233const upper = parseClaudeModelId('CLAUDE-HAIKU-3-5');234expect(lower).toBe(upper);235});236});237238describe('toSdkModelId', () => {239it('produces dash-separated version for major.minor', () => {240expect(parseClaudeModelId('claude-opus-4.5').toSdkModelId()).toBe('claude-opus-4-5');241});242243it('produces single-digit version when no minor', () => {244expect(parseClaudeModelId('claude-sonnet-4').toSdkModelId()).toBe('claude-sonnet-4');245});246247it('normalizes old-format SDK IDs to new format', () => {248expect(parseClaudeModelId('claude-3-5-sonnet-20241022').toSdkModelId()).toBe('claude-sonnet-3-5');249});250251it('strips date suffix from SDK IDs', () => {252expect(parseClaudeModelId('claude-opus-4-5-20251101').toSdkModelId()).toBe('claude-opus-4-5');253});254});255256describe('toEndpointModelId', () => {257it('produces dot-separated version for major.minor', () => {258expect(parseClaudeModelId('claude-opus-4-5-20251101').toEndpointModelId()).toBe('claude-opus-4.5');259});260261it('produces single-digit version when no minor', () => {262expect(parseClaudeModelId('claude-sonnet-4-20250514').toEndpointModelId()).toBe('claude-sonnet-4');263});264265it('normalizes old-format SDK IDs', () => {266expect(parseClaudeModelId('claude-3-5-sonnet-20241022').toEndpointModelId()).toBe('claude-sonnet-3.5');267});268269it('is identity for endpoint-format IDs', () => {270expect(parseClaudeModelId('claude-haiku-4.5').toEndpointModelId()).toBe('claude-haiku-4.5');271});272});273});274275276