language-models 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-test.log +18 -0
- package/README.md +56 -142
- package/data/models.json +18805 -0
- package/dist/aliases.d.ts +5 -1
- package/dist/aliases.d.ts.map +1 -0
- package/dist/aliases.js +40 -4
- package/dist/aliases.js.map +1 -0
- package/dist/data/models.json +18805 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +94 -167
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +109 -69803
- package/dist/models.js.map +1 -0
- package/package.json +25 -23
- package/scripts/fetch-models.ts +115 -0
- package/src/aliases.test.ts +319 -0
- package/src/aliases.ts +48 -4
- package/src/index.test.ts +400 -0
- package/src/index.ts +20 -9
- package/src/models.test.ts +392 -0
- package/src/models.ts +174 -0
- package/tsconfig.json +5 -15
- package/vitest.config.ts +4 -14
- package/.editorconfig +0 -10
- package/.gitattributes +0 -4
- package/.releaserc.js +0 -129
- package/LICENSE +0 -21
- package/dist/parser.d.ts +0 -86
- package/dist/parser.js +0 -390
- package/dist/providers.d.ts +0 -1
- package/dist/providers.js +0 -76
- package/dist/types.d.ts +0 -127
- package/dist/types.js +0 -1
- package/eslint.config.js +0 -3
- package/generate/build-models.ts +0 -150
- package/generate/overwrites.ts +0 -12
- package/publish.js +0 -32
- package/roadmap.md +0 -54
- package/src/models.d.ts +0 -170
- package/src/models.js +0 -70434
- package/src/parser.ts +0 -485
- package/src/providers.ts +0 -79
- package/src/types.ts +0 -135
- package/tests/parser.test.ts +0 -11
- package/tests/regex.test.ts +0 -42
- package/tests/selector.test.ts +0 -53
- package/tests/setup.ts +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the language-models package
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the public API exports and end-to-end functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest'
|
|
8
|
+
import {
|
|
9
|
+
resolve,
|
|
10
|
+
resolveWithProvider,
|
|
11
|
+
list,
|
|
12
|
+
get,
|
|
13
|
+
search,
|
|
14
|
+
DIRECT_PROVIDERS,
|
|
15
|
+
ALIASES,
|
|
16
|
+
type ModelInfo,
|
|
17
|
+
type ProviderEndpoint,
|
|
18
|
+
type ResolvedModel,
|
|
19
|
+
type DirectProvider,
|
|
20
|
+
} from './index.js'
|
|
21
|
+
|
|
22
|
+
describe('package exports', () => {
|
|
23
|
+
it('exports resolve function', () => {
|
|
24
|
+
expect(typeof resolve).toBe('function')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('exports resolveWithProvider function', () => {
|
|
28
|
+
expect(typeof resolveWithProvider).toBe('function')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('exports list function', () => {
|
|
32
|
+
expect(typeof list).toBe('function')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('exports get function', () => {
|
|
36
|
+
expect(typeof get).toBe('function')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('exports search function', () => {
|
|
40
|
+
expect(typeof search).toBe('function')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('exports DIRECT_PROVIDERS constant', () => {
|
|
44
|
+
expect(DIRECT_PROVIDERS).toBeDefined()
|
|
45
|
+
expect(Array.isArray(DIRECT_PROVIDERS)).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('exports ALIASES constant', () => {
|
|
49
|
+
expect(ALIASES).toBeDefined()
|
|
50
|
+
expect(typeof ALIASES).toBe('object')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('type exports', () => {
|
|
55
|
+
it('ModelInfo type is available', () => {
|
|
56
|
+
// Type check - if this compiles, the type is exported correctly
|
|
57
|
+
const model: ModelInfo = {
|
|
58
|
+
id: 'test/model',
|
|
59
|
+
name: 'Test Model',
|
|
60
|
+
context_length: 8192,
|
|
61
|
+
pricing: {
|
|
62
|
+
prompt: '0',
|
|
63
|
+
completion: '0',
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
expect(model).toBeDefined()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('ProviderEndpoint type is available', () => {
|
|
70
|
+
const endpoint: ProviderEndpoint = {
|
|
71
|
+
baseUrl: 'https://api.example.com',
|
|
72
|
+
modelId: 'test-model-id',
|
|
73
|
+
}
|
|
74
|
+
expect(endpoint).toBeDefined()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('ResolvedModel type is available', () => {
|
|
78
|
+
const resolved: ResolvedModel = {
|
|
79
|
+
id: 'test/model',
|
|
80
|
+
provider: 'test',
|
|
81
|
+
supportsDirectRouting: false,
|
|
82
|
+
}
|
|
83
|
+
expect(resolved).toBeDefined()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('DirectProvider type is available', () => {
|
|
87
|
+
const provider: DirectProvider = 'anthropic'
|
|
88
|
+
expect(provider).toBeDefined()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('end-to-end workflows', () => {
|
|
93
|
+
describe('simple alias resolution', () => {
|
|
94
|
+
it('resolves opus and gets model info', () => {
|
|
95
|
+
const modelId = resolve('opus')
|
|
96
|
+
expect(modelId).toBe('anthropic/claude-opus-4.5')
|
|
97
|
+
|
|
98
|
+
const model = get(modelId)
|
|
99
|
+
if (model) {
|
|
100
|
+
expect(model.id).toBe(modelId)
|
|
101
|
+
expect(model.name).toContain('Claude')
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('resolves gpt and gets model info', () => {
|
|
106
|
+
const modelId = resolve('gpt')
|
|
107
|
+
expect(modelId).toBe('openai/gpt-4o')
|
|
108
|
+
|
|
109
|
+
const model = get(modelId)
|
|
110
|
+
if (model) {
|
|
111
|
+
expect(model.id).toBe(modelId)
|
|
112
|
+
expect(model.name).toContain('GPT')
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('advanced resolution with provider info', () => {
|
|
118
|
+
it('resolves opus with full provider details', () => {
|
|
119
|
+
const resolved = resolveWithProvider('opus')
|
|
120
|
+
|
|
121
|
+
expect(resolved.id).toBe('anthropic/claude-opus-4.5')
|
|
122
|
+
expect(resolved.provider).toBe('anthropic')
|
|
123
|
+
expect(resolved.supportsDirectRouting).toBe(true)
|
|
124
|
+
|
|
125
|
+
if (resolved.model) {
|
|
126
|
+
expect(resolved.model.name).toBeTruthy()
|
|
127
|
+
expect(resolved.model.context_length).toBeGreaterThan(0)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('resolves with provider model ID if available', () => {
|
|
132
|
+
const resolved = resolveWithProvider('opus')
|
|
133
|
+
if (resolved.model?.provider_model_id) {
|
|
134
|
+
expect(resolved.providerModelId).toBe(resolved.model.provider_model_id)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('search and select workflow', () => {
|
|
140
|
+
it('searches for claude models and selects one', () => {
|
|
141
|
+
const matches = search('claude')
|
|
142
|
+
expect(matches.length).toBeGreaterThan(0)
|
|
143
|
+
|
|
144
|
+
const first = matches[0]
|
|
145
|
+
expect(first.id).toContain('/')
|
|
146
|
+
expect(first.name).toBeTruthy()
|
|
147
|
+
|
|
148
|
+
const retrieved = get(first.id)
|
|
149
|
+
expect(retrieved).toBeDefined()
|
|
150
|
+
expect(retrieved?.id).toBe(first.id)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('searches for openai models', () => {
|
|
154
|
+
const matches = search('openai')
|
|
155
|
+
expect(matches.length).toBeGreaterThan(0)
|
|
156
|
+
|
|
157
|
+
for (const model of matches) {
|
|
158
|
+
expect(model.id).toContain('openai/')
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('listing and filtering', () => {
|
|
164
|
+
it('lists all models and filters by provider', () => {
|
|
165
|
+
const allModels = list()
|
|
166
|
+
expect(allModels.length).toBeGreaterThanOrEqual(0)
|
|
167
|
+
|
|
168
|
+
if (allModels.length > 0) {
|
|
169
|
+
const anthropicModels = allModels.filter(m => m.id.startsWith('anthropic/'))
|
|
170
|
+
const openaiModels = allModels.filter(m => m.id.startsWith('openai/'))
|
|
171
|
+
|
|
172
|
+
if (anthropicModels.length > 0) {
|
|
173
|
+
expect(anthropicModels[0].id).toContain('anthropic/')
|
|
174
|
+
}
|
|
175
|
+
if (openaiModels.length > 0) {
|
|
176
|
+
expect(openaiModels[0].id).toContain('openai/')
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('lists models and checks for direct routing', () => {
|
|
182
|
+
const allModels = list()
|
|
183
|
+
|
|
184
|
+
if (allModels.length > 0) {
|
|
185
|
+
const directModels = allModels.filter(m => {
|
|
186
|
+
const provider = m.id.split('/')[0]
|
|
187
|
+
return (DIRECT_PROVIDERS as readonly string[]).includes(provider)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
for (const model of directModels) {
|
|
191
|
+
const resolved = resolveWithProvider(model.id)
|
|
192
|
+
expect(resolved.supportsDirectRouting).toBe(true)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe('case insensitivity', () => {
|
|
199
|
+
it('resolves aliases case-insensitively', () => {
|
|
200
|
+
const lower = resolve('opus')
|
|
201
|
+
const upper = resolve('OPUS')
|
|
202
|
+
const mixed = resolve('OpUs')
|
|
203
|
+
|
|
204
|
+
expect(lower).toBe(upper)
|
|
205
|
+
expect(lower).toBe(mixed)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('searches case-insensitively', () => {
|
|
209
|
+
const lower = search('claude')
|
|
210
|
+
const upper = search('CLAUDE')
|
|
211
|
+
|
|
212
|
+
expect(lower).toEqual(upper)
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('unknown inputs', () => {
|
|
217
|
+
it('handles unknown alias gracefully', () => {
|
|
218
|
+
const result = resolve('unknown-alias-xyz')
|
|
219
|
+
expect(result).toBe('unknown-alias-xyz')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('handles unknown model ID gracefully', () => {
|
|
223
|
+
const model = get('unknown/model-id')
|
|
224
|
+
expect(model).toBeUndefined()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('handles unknown search gracefully', () => {
|
|
228
|
+
const results = search('this-should-not-exist-xyz123')
|
|
229
|
+
expect(results).toEqual([])
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('resolves unknown with provider info', () => {
|
|
233
|
+
const resolved = resolveWithProvider('unknown/model')
|
|
234
|
+
expect(resolved.id).toBe('unknown/model')
|
|
235
|
+
expect(resolved.provider).toBe('unknown')
|
|
236
|
+
expect(resolved.model).toBeUndefined()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe('README examples', () => {
|
|
241
|
+
it('works with README quick start examples', () => {
|
|
242
|
+
// Examples from README.md Quick Start section
|
|
243
|
+
const opus = resolve('opus')
|
|
244
|
+
expect(opus).toBe('anthropic/claude-opus-4.5')
|
|
245
|
+
|
|
246
|
+
const gpt4o = resolve('gpt-4o')
|
|
247
|
+
expect(gpt4o).toBe('openai/gpt-4o')
|
|
248
|
+
|
|
249
|
+
const llama70b = resolve('llama-70b')
|
|
250
|
+
expect(llama70b).toBe('meta-llama/llama-3.3-70b-instruct')
|
|
251
|
+
|
|
252
|
+
const mistral = resolve('mistral')
|
|
253
|
+
expect(mistral).toBe('mistralai/mistral-large-2411')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('works with README API examples', () => {
|
|
257
|
+
// Examples from README.md API section
|
|
258
|
+
const opus = resolve('opus')
|
|
259
|
+
expect(opus).toBe('anthropic/claude-opus-4.5')
|
|
260
|
+
|
|
261
|
+
const sonnet = resolve('sonnet')
|
|
262
|
+
expect(sonnet).toBe('anthropic/claude-sonnet-4.5')
|
|
263
|
+
|
|
264
|
+
const gpt = resolve('gpt')
|
|
265
|
+
expect(gpt).toBe('openai/gpt-4o')
|
|
266
|
+
|
|
267
|
+
const llama = resolve('llama')
|
|
268
|
+
expect(llama).toBe('meta-llama/llama-4-maverick')
|
|
269
|
+
|
|
270
|
+
// Pass-through example
|
|
271
|
+
const fullId = resolve('anthropic/claude-opus-4.5')
|
|
272
|
+
expect(fullId).toBe('anthropic/claude-opus-4.5')
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('list returns array as shown in README', () => {
|
|
276
|
+
const models = list()
|
|
277
|
+
expect(Array.isArray(models)).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('search returns matching models as shown in README', () => {
|
|
281
|
+
const claudeModels = search('claude')
|
|
282
|
+
expect(Array.isArray(claudeModels)).toBe(true)
|
|
283
|
+
if (claudeModels.length > 0) {
|
|
284
|
+
expect(
|
|
285
|
+
claudeModels.some(m =>
|
|
286
|
+
m.id.includes('claude') || m.name.toLowerCase().includes('claude')
|
|
287
|
+
)
|
|
288
|
+
).toBe(true)
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('data directory integration', () => {
|
|
294
|
+
it('loads models from data/models.json', () => {
|
|
295
|
+
const models = list()
|
|
296
|
+
// Should load from data directory, even if empty
|
|
297
|
+
expect(Array.isArray(models)).toBe(true)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('handles missing data file gracefully', () => {
|
|
301
|
+
// Even without data/models.json, functions should not throw
|
|
302
|
+
expect(() => list()).not.toThrow()
|
|
303
|
+
expect(() => get('test/model')).not.toThrow()
|
|
304
|
+
expect(() => search('test')).not.toThrow()
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
describe('model metadata completeness', () => {
|
|
309
|
+
it('models have pricing information', () => {
|
|
310
|
+
const models = list()
|
|
311
|
+
if (models.length > 0) {
|
|
312
|
+
const model = models[0]
|
|
313
|
+
expect(model.pricing).toBeDefined()
|
|
314
|
+
expect(model.pricing.prompt).toBeDefined()
|
|
315
|
+
expect(model.pricing.completion).toBeDefined()
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('models have context length', () => {
|
|
320
|
+
const models = list()
|
|
321
|
+
if (models.length > 0) {
|
|
322
|
+
for (const model of models.slice(0, 5)) {
|
|
323
|
+
expect(typeof model.context_length).toBe('number')
|
|
324
|
+
expect(model.context_length).toBeGreaterThan(0)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('models may have architecture info', () => {
|
|
330
|
+
const models = list()
|
|
331
|
+
if (models.length > 0) {
|
|
332
|
+
const modelWithArch = models.find(m => m.architecture)
|
|
333
|
+
if (modelWithArch?.architecture) {
|
|
334
|
+
expect(modelWithArch.architecture.modality).toBeDefined()
|
|
335
|
+
expect(Array.isArray(modelWithArch.architecture.input_modalities)).toBe(true)
|
|
336
|
+
expect(Array.isArray(modelWithArch.architecture.output_modalities)).toBe(true)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('provider routing', () => {
|
|
343
|
+
it('correctly identifies direct routing providers', () => {
|
|
344
|
+
const directProviders = ['anthropic', 'openai', 'google']
|
|
345
|
+
|
|
346
|
+
for (const provider of directProviders) {
|
|
347
|
+
const models = list().filter(m => m.id.startsWith(`${provider}/`))
|
|
348
|
+
if (models.length > 0) {
|
|
349
|
+
const resolved = resolveWithProvider(models[0].id)
|
|
350
|
+
expect(resolved.supportsDirectRouting).toBe(true)
|
|
351
|
+
expect(resolved.provider).toBe(provider)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('identifies non-direct routing providers', () => {
|
|
357
|
+
const models = list()
|
|
358
|
+
const nonDirectModel = models.find(m => {
|
|
359
|
+
const provider = m.id.split('/')[0]
|
|
360
|
+
return !(DIRECT_PROVIDERS as readonly string[]).includes(provider)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
if (nonDirectModel) {
|
|
364
|
+
const resolved = resolveWithProvider(nonDirectModel.id)
|
|
365
|
+
expect(resolved.supportsDirectRouting).toBe(false)
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
describe('consistency checks', () => {
|
|
372
|
+
it('all aliases resolve to valid format', () => {
|
|
373
|
+
for (const [alias, modelId] of Object.entries(ALIASES)) {
|
|
374
|
+
expect(modelId).toContain('/')
|
|
375
|
+
const [provider, model] = modelId.split('/')
|
|
376
|
+
expect(provider.length).toBeGreaterThan(0)
|
|
377
|
+
expect(model.length).toBeGreaterThan(0)
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('resolve and resolveWithProvider are consistent', () => {
|
|
382
|
+
const testCases = ['opus', 'gpt', 'gemini', 'llama']
|
|
383
|
+
|
|
384
|
+
for (const alias of testCases) {
|
|
385
|
+
const simpleResolve = resolve(alias)
|
|
386
|
+
const fullResolve = resolveWithProvider(alias)
|
|
387
|
+
|
|
388
|
+
expect(fullResolve.id).toBe(simpleResolve)
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('search results are all valid models', () => {
|
|
393
|
+
const results = search('claude')
|
|
394
|
+
for (const model of results) {
|
|
395
|
+
const retrieved = get(model.id)
|
|
396
|
+
// All search results should be retrievable
|
|
397
|
+
expect(retrieved?.id).toBe(model.id)
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
})
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* language-models - Model listing and resolution
|
|
3
|
+
*
|
|
4
|
+
* Lists all available models and resolves aliases to full model IDs.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
7
8
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export {
|
|
10
|
+
resolve,
|
|
11
|
+
resolveWithProvider,
|
|
12
|
+
list,
|
|
13
|
+
get,
|
|
14
|
+
search,
|
|
15
|
+
DIRECT_PROVIDERS,
|
|
16
|
+
type ModelInfo,
|
|
17
|
+
type ProviderEndpoint,
|
|
18
|
+
type ResolvedModel,
|
|
19
|
+
type DirectProvider
|
|
20
|
+
} from './models.js'
|
|
21
|
+
export { ALIASES } from './aliases.js'
|