Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/claude/node/test/claudeModelId.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { describe, expect, it } from 'vitest';
7
import { parseClaudeModelId, tryParseClaudeModelId } from '../claudeModelId';
8
9
describe('parseClaudeModelId', () => {
10
describe('parsing SDK model IDs', () => {
11
it('parses claude-{name}-{major}-{minor}-{date}', () => {
12
const result = parseClaudeModelId('claude-opus-4-5-20251101');
13
expect(result).toEqual(expect.objectContaining({
14
name: 'opus',
15
version: '4.5',
16
modifiers: '20251101',
17
}));
18
});
19
20
it('parses claude-{major}-{minor}-{name}-{date} (old format)', () => {
21
const result = parseClaudeModelId('claude-3-5-sonnet-20241022');
22
expect(result).toEqual(expect.objectContaining({
23
name: 'sonnet',
24
version: '3.5',
25
modifiers: '20241022',
26
}));
27
});
28
29
it('parses claude-{name}-{major}-{date}', () => {
30
const result = parseClaudeModelId('claude-sonnet-4-20250514');
31
expect(result).toEqual(expect.objectContaining({
32
name: 'sonnet',
33
version: '4',
34
modifiers: '20250514',
35
}));
36
});
37
38
it('parses claude-{major}-{name}-{date} (old format)', () => {
39
const result = parseClaudeModelId('claude-3-opus-20240229');
40
expect(result).toEqual(expect.objectContaining({
41
name: 'opus',
42
version: '3',
43
modifiers: '20240229',
44
}));
45
});
46
47
it('parses SDK ID without date suffix', () => {
48
const result = parseClaudeModelId('claude-opus-4-5');
49
expect(result).toEqual(expect.objectContaining({
50
name: 'opus',
51
version: '4.5',
52
modifiers: '',
53
}));
54
});
55
});
56
57
describe('parsing endpoint model IDs', () => {
58
it('parses claude-{name}-{major}.{minor}', () => {
59
const result = parseClaudeModelId('claude-opus-4.5');
60
expect(result).toEqual(expect.objectContaining({
61
name: 'opus',
62
version: '4.5',
63
modifiers: '',
64
}));
65
});
66
67
it('parses claude-{name}-{major}', () => {
68
const result = parseClaudeModelId('claude-sonnet-4');
69
expect(result).toEqual(expect.objectContaining({
70
name: 'sonnet',
71
version: '4',
72
modifiers: '',
73
}));
74
});
75
76
it('parses claude-haiku-3.5', () => {
77
const result = parseClaudeModelId('claude-haiku-3.5');
78
expect(result).toEqual(expect.objectContaining({
79
name: 'haiku',
80
version: '3.5',
81
modifiers: '',
82
}));
83
});
84
});
85
86
describe('modifiers (non-date suffixes)', () => {
87
it('parses endpoint ID with 1m context variant (dot version)', () => {
88
const result = parseClaudeModelId('claude-opus-4.6-1m');
89
expect(result).toEqual(expect.objectContaining({
90
name: 'opus',
91
version: '4.6',
92
modifiers: '1m',
93
}));
94
});
95
96
it('parses SDK ID with 1m context variant (dash version)', () => {
97
const result = parseClaudeModelId('claude-opus-4-6-1m');
98
expect(result).toEqual(expect.objectContaining({
99
name: 'opus',
100
version: '4.6',
101
modifiers: '1m',
102
}));
103
});
104
105
it('parses SDK ID with both 1m modifier and date suffix', () => {
106
const result = parseClaudeModelId('claude-opus-4-6-1m-20251101');
107
expect(result).toEqual(expect.objectContaining({
108
name: 'opus',
109
version: '4.6',
110
modifiers: '1m-20251101',
111
}));
112
});
113
114
it('parses single-version ID with modifier', () => {
115
const result = parseClaudeModelId('claude-sonnet-4-1m');
116
expect(result).toEqual(expect.objectContaining({
117
name: 'sonnet',
118
version: '4',
119
modifiers: '1m',
120
}));
121
});
122
123
it('1m on opus converts to correct SDK model ID', () => {
124
expect(parseClaudeModelId('claude-opus-4.6-1m').toSdkModelId()).toBe('claude-opus-4-6-1m');
125
});
126
127
it('1m on opus converts to correct endpoint model ID', () => {
128
expect(parseClaudeModelId('claude-opus-4-6-1m').toEndpointModelId()).toBe('claude-opus-4.6-1m');
129
});
130
131
it('1m on non-opus model is not included in SDK model ID', () => {
132
expect(parseClaudeModelId('claude-sonnet-4-1m').toSdkModelId()).toBe('claude-sonnet-4');
133
});
134
135
it('1m on non-opus model is not included in endpoint model ID', () => {
136
expect(parseClaudeModelId('claude-sonnet-4-1m').toEndpointModelId()).toBe('claude-sonnet-4');
137
});
138
139
it('1m with date suffix on opus keeps only 1m in SDK model ID', () => {
140
expect(parseClaudeModelId('claude-opus-4-6-1m-20251101').toSdkModelId()).toBe('claude-opus-4-6-1m');
141
});
142
143
it('1m with date suffix on opus keeps only 1m in endpoint model ID', () => {
144
expect(parseClaudeModelId('claude-opus-4-6-1m-20251101').toEndpointModelId()).toBe('claude-opus-4.6-1m');
145
});
146
});
147
148
describe('bare model names', () => {
149
it('parses a bare name with no version', () => {
150
const result = parseClaudeModelId('foo');
151
expect(result).toEqual(expect.objectContaining({
152
name: 'foo',
153
version: '',
154
modifiers: '',
155
}));
156
});
157
158
it('toSdkModelId returns the bare name', () => {
159
expect(parseClaudeModelId('foo').toSdkModelId()).toBe('foo');
160
});
161
162
it('toEndpointModelId returns the bare name', () => {
163
expect(parseClaudeModelId('foo').toEndpointModelId()).toBe('foo');
164
});
165
166
it('parses bare "claude" as a bare name', () => {
167
const result = parseClaudeModelId('claude');
168
expect(result).toEqual(expect.objectContaining({
169
name: 'claude',
170
version: '',
171
modifiers: '',
172
}));
173
});
174
});
175
176
describe('unparseable inputs', () => {
177
it('throws for hyphenated non-Claude IDs', () => {
178
expect(() => parseClaudeModelId('gpt-4o')).toThrow(`Unable to parse Claude model ID: 'gpt-4o'`);
179
});
180
181
it('throws for garbage with hyphens', () => {
182
expect(() => parseClaudeModelId('invalid-model-id')).toThrow();
183
});
184
});
185
186
describe('tryParseClaudeModelId', () => {
187
it('returns undefined for hyphenated non-Claude IDs', () => {
188
expect(tryParseClaudeModelId('gpt-4o')).toBeUndefined();
189
});
190
191
it('returns a result for bare names', () => {
192
expect(tryParseClaudeModelId('foo')).toEqual(expect.objectContaining({
193
name: 'foo',
194
version: '',
195
}));
196
});
197
198
it('returns a result for valid Claude IDs', () => {
199
expect(tryParseClaudeModelId('claude-sonnet-4')).toEqual(expect.objectContaining({
200
name: 'sonnet',
201
version: '4',
202
}));
203
});
204
});
205
206
describe('case insensitivity', () => {
207
it('parses uppercase input', () => {
208
const result = parseClaudeModelId('CLAUDE-OPUS-4-5');
209
expect(result).toEqual(expect.objectContaining({
210
name: 'opus',
211
version: '4.5',
212
}));
213
});
214
215
it('parses mixed case input', () => {
216
const result = parseClaudeModelId('Claude-Sonnet-4-20250514');
217
expect(result).toEqual(expect.objectContaining({
218
name: 'sonnet',
219
version: '4',
220
modifiers: '20250514',
221
}));
222
});
223
});
224
225
describe('caching', () => {
226
it('returns the same object for repeated calls', () => {
227
const first = parseClaudeModelId('claude-opus-4-5-20251101');
228
const second = parseClaudeModelId('claude-opus-4-5-20251101');
229
expect(first).toBe(second);
230
});
231
232
it('returns the same object for different casing of the same ID', () => {
233
const lower = parseClaudeModelId('claude-haiku-3-5');
234
const upper = parseClaudeModelId('CLAUDE-HAIKU-3-5');
235
expect(lower).toBe(upper);
236
});
237
});
238
239
describe('toSdkModelId', () => {
240
it('produces dash-separated version for major.minor', () => {
241
expect(parseClaudeModelId('claude-opus-4.5').toSdkModelId()).toBe('claude-opus-4-5');
242
});
243
244
it('produces single-digit version when no minor', () => {
245
expect(parseClaudeModelId('claude-sonnet-4').toSdkModelId()).toBe('claude-sonnet-4');
246
});
247
248
it('normalizes old-format SDK IDs to new format', () => {
249
expect(parseClaudeModelId('claude-3-5-sonnet-20241022').toSdkModelId()).toBe('claude-sonnet-3-5');
250
});
251
252
it('strips date suffix from SDK IDs', () => {
253
expect(parseClaudeModelId('claude-opus-4-5-20251101').toSdkModelId()).toBe('claude-opus-4-5');
254
});
255
});
256
257
describe('toEndpointModelId', () => {
258
it('produces dot-separated version for major.minor', () => {
259
expect(parseClaudeModelId('claude-opus-4-5-20251101').toEndpointModelId()).toBe('claude-opus-4.5');
260
});
261
262
it('produces single-digit version when no minor', () => {
263
expect(parseClaudeModelId('claude-sonnet-4-20250514').toEndpointModelId()).toBe('claude-sonnet-4');
264
});
265
266
it('normalizes old-format SDK IDs', () => {
267
expect(parseClaudeModelId('claude-3-5-sonnet-20241022').toEndpointModelId()).toBe('claude-sonnet-3.5');
268
});
269
270
it('is identity for endpoint-format IDs', () => {
271
expect(parseClaudeModelId('claude-haiku-4.5').toEndpointModelId()).toBe('claude-haiku-4.5');
272
});
273
});
274
});
275
276