language-models 0.1.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/.editorconfig +10 -0
- package/.gitattributes +4 -0
- package/.releaserc.js +129 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/dist/aliases.d.ts +1 -0
- package/dist/aliases.js +5 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/models.d.ts +170 -0
- package/dist/models.js +69803 -0
- package/dist/parser.d.ts +86 -0
- package/dist/parser.js +390 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.js +76 -0
- package/dist/types.d.ts +127 -0
- package/dist/types.js +1 -0
- package/eslint.config.js +3 -0
- package/generate/build-models.ts +150 -0
- package/generate/overwrites.ts +12 -0
- package/package.json +32 -0
- package/publish.js +32 -0
- package/roadmap.md +54 -0
- package/src/aliases.ts +5 -0
- package/src/index.ts +10 -0
- package/src/models.d.ts +170 -0
- package/src/models.js +70434 -0
- package/src/parser.ts +485 -0
- package/src/providers.ts +79 -0
- package/src/types.ts +135 -0
- package/tests/parser.test.ts +11 -0
- package/tests/regex.test.ts +42 -0
- package/tests/selector.test.ts +53 -0
- package/tests/setup.ts +0 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +21 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import camelCase from 'camelcase'
|
|
2
|
+
import { aliases } from './aliases'
|
|
3
|
+
import { Model, Provider } from './types'
|
|
4
|
+
import rawModels from './models'
|
|
5
|
+
|
|
6
|
+
type InternalModel = Model
|
|
7
|
+
|
|
8
|
+
const allModels = rawModels as unknown as {
|
|
9
|
+
models: Model[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type ParsedModelIdentifier = {
|
|
13
|
+
provider?: string
|
|
14
|
+
author?: string
|
|
15
|
+
model?: string
|
|
16
|
+
systemConfig?: Record<string, string | number>
|
|
17
|
+
alias?: string
|
|
18
|
+
priorities?: string[]
|
|
19
|
+
providerConstraints?: {
|
|
20
|
+
field: string
|
|
21
|
+
value: string
|
|
22
|
+
type: 'gt' | 'lt' | 'eq'
|
|
23
|
+
}[]
|
|
24
|
+
tools?: Record<string, string | number | boolean | Record<string, unknown>>
|
|
25
|
+
capabilities?: Record<string, string | number | boolean>
|
|
26
|
+
outputFormat?: string
|
|
27
|
+
outputSchema?: string
|
|
28
|
+
// Anything the parser doesnt know what to do with
|
|
29
|
+
// Mostly used to pass tools, schemas, and other parameters to a higher level
|
|
30
|
+
unasignedParameters?: Record<string, string | number | boolean>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Priorities may be on its own, which indicates to just find the best value for that priority
|
|
34
|
+
// or it may be in one of the following formats:
|
|
35
|
+
// cost:<1 -> A model that has a cost of less than $1 per million tokens
|
|
36
|
+
// latency:<50 -> A model with a latency of less than 50ms
|
|
37
|
+
// throughput:>250 -> A model with a throughput higher than 250 tokens per second
|
|
38
|
+
// Additional notes: if just cost is specified, then it will be treated as outputCost
|
|
39
|
+
const priorities = ['cost', 'latency', 'throughput', 'inputCost', 'outputCost']
|
|
40
|
+
const capabilities = ['reasoning', 'thinking', 'tools', 'structuredOutput', 'responseFormat', 'pdf']
|
|
41
|
+
const defaultTools = ['exec', 'online']
|
|
42
|
+
const systemConfiguration = ['seed', 'thread', 'temperature', 'topP', 'topK']
|
|
43
|
+
// Any of the following is an output format, anything else that starts with a capital letter is an output *schema*
|
|
44
|
+
const outputFormats = ['Object', 'ObjectArray', 'Text', 'TextArray', 'Markdown', 'Code']
|
|
45
|
+
|
|
46
|
+
export function parse(modelIdentifier: string): ParsedModelIdentifier {
|
|
47
|
+
const output: ParsedModelIdentifier = {
|
|
48
|
+
model: '',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Locate all paratheses, even nested ones
|
|
52
|
+
const parentheses = modelIdentifier.match(/\(([^)]+)\)/g)
|
|
53
|
+
|
|
54
|
+
output.model = modelIdentifier.split('(')[0]
|
|
55
|
+
const modelName = output.model.includes('/') ? output.model.split('/')[1] : output.model
|
|
56
|
+
|
|
57
|
+
if (aliases[modelName]) {
|
|
58
|
+
output.model = aliases[modelName]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (output.model.includes('/')) {
|
|
62
|
+
const [author, model] = output.model.split('/')
|
|
63
|
+
output.author = author
|
|
64
|
+
output.model = model
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (output.model.includes('@')) {
|
|
68
|
+
// @ indicates a specific provider
|
|
69
|
+
const [model, provider] = output.model.split('@')
|
|
70
|
+
output.model = model
|
|
71
|
+
output.provider = provider
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// If there are no parentheses, then we can just return the model identifier
|
|
75
|
+
if (!parentheses) {
|
|
76
|
+
return output
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Defaults storage allows us to set defaults for settings if they are missing from the input
|
|
80
|
+
// The main use case is for output formats, where we want to set the format based on the schema
|
|
81
|
+
// but only if the format isnt already set. Since we only have access to a single expression at once,
|
|
82
|
+
// we need to store the defaults and apply them later.
|
|
83
|
+
const defaultStorage: Record<string, string | number | boolean> = {}
|
|
84
|
+
|
|
85
|
+
// Split by comma, each part is a new parameter that needs to be stored in the right
|
|
86
|
+
// place in the output object
|
|
87
|
+
// For each parathesis, we need to parse the contents
|
|
88
|
+
for (const parenthesis of parentheses) {
|
|
89
|
+
const contents = parenthesis.slice(1, -1)
|
|
90
|
+
|
|
91
|
+
const parts = contents.split(',')
|
|
92
|
+
|
|
93
|
+
for (const part of parts) {
|
|
94
|
+
// Not all parts will have a colon, if not, treat it as a boolean with a value of true.
|
|
95
|
+
const [key, value] = part
|
|
96
|
+
// Cheat we can do to make the parsing easier
|
|
97
|
+
.replace('<', ':<')
|
|
98
|
+
.replace('>', ':>')
|
|
99
|
+
.split(':')
|
|
100
|
+
|
|
101
|
+
if (!key) {
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let notAKnownParameter = false
|
|
106
|
+
|
|
107
|
+
switch (true) {
|
|
108
|
+
case defaultTools.includes(key):
|
|
109
|
+
output.tools = output.tools || {}
|
|
110
|
+
output.tools[key] = value || true
|
|
111
|
+
break
|
|
112
|
+
case systemConfiguration.includes(key):
|
|
113
|
+
output.systemConfig = {
|
|
114
|
+
...output.systemConfig,
|
|
115
|
+
[key]: value,
|
|
116
|
+
}
|
|
117
|
+
break
|
|
118
|
+
case priorities.includes(key):
|
|
119
|
+
if (value) {
|
|
120
|
+
output.providerConstraints = output.providerConstraints || []
|
|
121
|
+
output.providerConstraints.push({
|
|
122
|
+
field: key,
|
|
123
|
+
value: value.replace('>', '').replace('<', ''),
|
|
124
|
+
type: value.startsWith('>') ? 'gt' : value.startsWith('<') ? 'lt' : 'eq',
|
|
125
|
+
})
|
|
126
|
+
} else {
|
|
127
|
+
output.priorities = [...(output.priorities || []), key]
|
|
128
|
+
}
|
|
129
|
+
break
|
|
130
|
+
case capabilities.includes(key):
|
|
131
|
+
output.capabilities = {
|
|
132
|
+
...output.capabilities,
|
|
133
|
+
[key]: value || true,
|
|
134
|
+
}
|
|
135
|
+
break
|
|
136
|
+
case outputFormats.includes(key):
|
|
137
|
+
output.outputFormat = key == 'Code' && !!value ? `Code:${value}` : key
|
|
138
|
+
break
|
|
139
|
+
default:
|
|
140
|
+
notAKnownParameter = true
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!notAKnownParameter) {
|
|
145
|
+
// No need to process any further
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// If it starts with a capital letter, then it is a Schema
|
|
150
|
+
if (key[0] === key[0].toUpperCase()) {
|
|
151
|
+
const schema = key
|
|
152
|
+
|
|
153
|
+
if (schema.includes('[]')) {
|
|
154
|
+
defaultStorage.outputFormat = 'ObjectArray'
|
|
155
|
+
} else {
|
|
156
|
+
defaultStorage.outputFormat = 'Object'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
output.outputSchema = schema.replace('[]', '')
|
|
160
|
+
} else if (value?.includes('>') || value?.includes('<')) {
|
|
161
|
+
// This is most likely a benchmark constraint
|
|
162
|
+
output.providerConstraints = output.providerConstraints || []
|
|
163
|
+
output.providerConstraints.push({
|
|
164
|
+
field: key,
|
|
165
|
+
value: value.replace('>', '').replace('<', ''),
|
|
166
|
+
type: value.startsWith('>') ? 'gt' : 'lt',
|
|
167
|
+
})
|
|
168
|
+
} else {
|
|
169
|
+
output.tools = output.tools || {}
|
|
170
|
+
output.tools = {
|
|
171
|
+
...output.tools,
|
|
172
|
+
[key]: value || true,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Custom rules / requirements
|
|
179
|
+
// If someone has tools, they need to have the tools capability
|
|
180
|
+
if (Object.values(output.tools || {}).length > 0) {
|
|
181
|
+
if (!output.capabilities?.tools) {
|
|
182
|
+
output.capabilities = {
|
|
183
|
+
...output.capabilities,
|
|
184
|
+
tools: true,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Finally, apply the defaults
|
|
190
|
+
Object.entries(defaultStorage).forEach(([key, value]) => {
|
|
191
|
+
const keyToCheck = key as keyof ParsedModelIdentifier
|
|
192
|
+
if (output[keyToCheck] === undefined) {
|
|
193
|
+
// @ts-expect-error - We know these assignments are safe based on our defaultStorage logic
|
|
194
|
+
output[keyToCheck] = value
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
return output
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function constructModelIdentifier(parsed: ParsedModelIdentifier): string {
|
|
202
|
+
const modelAlias = aliases[parsed.model || ''] || parsed.model || ''
|
|
203
|
+
let identifier = `${modelAlias}`
|
|
204
|
+
|
|
205
|
+
if (parsed.provider) {
|
|
206
|
+
identifier += `@${parsed.provider}`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const args: string[] = []
|
|
210
|
+
|
|
211
|
+
const formatArgument = (key: string, value: string | number | boolean) => {
|
|
212
|
+
// If the value is a boolean, then we can just return the key
|
|
213
|
+
if (typeof value === 'boolean') {
|
|
214
|
+
return value ? key : ''
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(key, value)
|
|
218
|
+
|
|
219
|
+
// Otherwise, we need to format the value
|
|
220
|
+
return `${key}:${value}`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const keysToFormat = ['capabilities', 'tools', 'systemConfig'] as const
|
|
224
|
+
|
|
225
|
+
keysToFormat.forEach((key) => {
|
|
226
|
+
if (parsed[key]) {
|
|
227
|
+
Object.entries(parsed[key]).forEach(([key, value]) => {
|
|
228
|
+
args.push(formatArgument(key, value as string | number | boolean))
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (parsed.priorities) {
|
|
234
|
+
parsed.priorities.forEach((priority) => {
|
|
235
|
+
args.push(priority)
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Provider constraints are a bit more complex as they are stored as { field: string, value: string }[]
|
|
240
|
+
if (parsed.providerConstraints) {
|
|
241
|
+
parsed.providerConstraints.forEach((constraint) => {
|
|
242
|
+
args.push(`${constraint.field}${constraint.type === 'gt' ? '>' : constraint.type === 'lt' ? '<' : ':'}${constraint.value}`)
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (args.length) {
|
|
247
|
+
identifier += `(${args.join(',')})`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return identifier
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function modelToIdentifier(model: Model): string {
|
|
254
|
+
return constructModelIdentifier({
|
|
255
|
+
model: model.slug.split('/')[1],
|
|
256
|
+
provider: model.slug.split('/')[0],
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Its a model, but with a provider attached
|
|
261
|
+
type ResolvedModel = Model & {
|
|
262
|
+
provider: Provider
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// A function that takes a model and a provider and returns a boolean
|
|
266
|
+
type FilterChainCallback = (model: InternalModel, provider: Provider) => boolean
|
|
267
|
+
|
|
268
|
+
export function filterModels(
|
|
269
|
+
modelIdentifier: string,
|
|
270
|
+
modelsToFilter?: InternalModel[],
|
|
271
|
+
): {
|
|
272
|
+
models: ResolvedModel[]
|
|
273
|
+
parsed: ParsedModelIdentifier
|
|
274
|
+
} {
|
|
275
|
+
const parsed = parse(modelIdentifier)
|
|
276
|
+
|
|
277
|
+
const modelAndProviders = []
|
|
278
|
+
|
|
279
|
+
let modelsToFilterMixin = modelsToFilter
|
|
280
|
+
|
|
281
|
+
if (metaModels.find((m) => m.name === parsed.model) && !modelsToFilter?.length) {
|
|
282
|
+
const metaModelChildren = metaModels.find((m) => m.name === parsed.model)?.models
|
|
283
|
+
modelsToFilterMixin = allModels.models.filter((m) => metaModelChildren?.includes(m.slug.split('/')[1])) as InternalModel[]
|
|
284
|
+
|
|
285
|
+
// Because the parser has no model knowledge, it thinks the meta model name is the model name
|
|
286
|
+
// Which will always return false.
|
|
287
|
+
delete parsed.model
|
|
288
|
+
} else if (modelsToFilter?.length) {
|
|
289
|
+
modelsToFilterMixin = modelsToFilter as InternalModel[]
|
|
290
|
+
} else {
|
|
291
|
+
modelsToFilterMixin = allModels.models as InternalModel[]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const filterChain: FilterChainCallback[] = []
|
|
295
|
+
|
|
296
|
+
if (parsed.model) {
|
|
297
|
+
filterChain.push(function modelFilter(model) {
|
|
298
|
+
// Wildcard search for any model that matches everything else.
|
|
299
|
+
if (parsed.model == '*') {
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Return true if we're looking for claude-3.7-sonnet
|
|
304
|
+
// Fixes the issue where we need :thinking to be supported
|
|
305
|
+
if (parsed.model === 'claude-3.7-sonnet' && model.slug.includes('claude-3.7-sonnet') && !model.slug.includes('beta')) {
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return model.slug.split('/')[1] === parsed.model
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// We're using named functions here so we can console.log the filter chain
|
|
314
|
+
// and see what filter is being applied and when
|
|
315
|
+
if (parsed.provider) {
|
|
316
|
+
filterChain.push(function providerFilter(model, provider) {
|
|
317
|
+
return provider?.slug === parsed.provider
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (parsed?.providerConstraints?.length) {
|
|
322
|
+
// Since the provider isnt defined, we need to filter based on the provider constraints
|
|
323
|
+
filterChain.push(function providerConstraintFilter(model, provider) {
|
|
324
|
+
return (
|
|
325
|
+
parsed?.providerConstraints?.every((constraint) => {
|
|
326
|
+
if (!provider) {
|
|
327
|
+
return false
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
let fieldToCheck = constraint.field as keyof typeof provider
|
|
331
|
+
|
|
332
|
+
if (constraint.field === 'cost') {
|
|
333
|
+
fieldToCheck = 'outputCost'
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
switch (constraint.type) {
|
|
337
|
+
case 'gt':
|
|
338
|
+
return Number(provider[fieldToCheck]) > parseFloat(constraint.value)
|
|
339
|
+
case 'lt':
|
|
340
|
+
return Number(provider[fieldToCheck]) < parseFloat(constraint.value)
|
|
341
|
+
case 'eq':
|
|
342
|
+
return Number(provider[fieldToCheck]) === parseFloat(constraint.value)
|
|
343
|
+
default:
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
}) || false
|
|
347
|
+
)
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (parsed.capabilities) {
|
|
352
|
+
filterChain.push(function capabilitiesFilter(model, provider) {
|
|
353
|
+
return Object.entries(parsed?.capabilities || {}).every(([key, value]) => {
|
|
354
|
+
return provider?.supportedParameters?.includes(camelCase(key))
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
for (const model of modelsToFilterMixin as InternalModel[]) {
|
|
360
|
+
for (const provider of model.providers || []) {
|
|
361
|
+
if (filterChain.every((f) => f(model, provider))) {
|
|
362
|
+
modelAndProviders.push({
|
|
363
|
+
model,
|
|
364
|
+
provider: provider,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const orderBy = (fields: string[]) => (a: any, b: any) =>
|
|
371
|
+
fields
|
|
372
|
+
.map((o) => {
|
|
373
|
+
let dir = 1
|
|
374
|
+
if (o[0] === '-') {
|
|
375
|
+
dir = -1
|
|
376
|
+
o = o.substring(1)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Support for dot notation to access nested properties
|
|
380
|
+
const getNestedValue = (obj: any, path: string): any => {
|
|
381
|
+
return path.split('.').reduce((prev, curr) => (prev && prev[curr] !== undefined ? prev[curr] : undefined), obj)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const aVal = getNestedValue(a, o)
|
|
385
|
+
const bVal = getNestedValue(b, o)
|
|
386
|
+
|
|
387
|
+
return aVal > bVal ? dir : aVal < bVal ? -dir : 0
|
|
388
|
+
})
|
|
389
|
+
.reduce((p: number, n: number) => (p ? p : n), 0)
|
|
390
|
+
|
|
391
|
+
let sortingStrategy = orderBy(parsed?.priorities?.map((f) => `provider.${f}`) || [])
|
|
392
|
+
|
|
393
|
+
// Re-join back on model, replacing the providers with the filtered providers
|
|
394
|
+
return {
|
|
395
|
+
models: modelAndProviders
|
|
396
|
+
.map((x) => ({
|
|
397
|
+
...x.model,
|
|
398
|
+
provider: x.provider,
|
|
399
|
+
}))
|
|
400
|
+
.map((x) => {
|
|
401
|
+
delete x.providers
|
|
402
|
+
delete x.endpoint
|
|
403
|
+
return x
|
|
404
|
+
})
|
|
405
|
+
.sort(sortingStrategy) as ResolvedModel[],
|
|
406
|
+
parsed,
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Helper function to get the first model that matches a model identifier
|
|
412
|
+
* @param modelIdentifier
|
|
413
|
+
* @returns ResolvedModel
|
|
414
|
+
*/
|
|
415
|
+
export function getModel(modelIdentifier: string, augments: Record<string, string | number | boolean | string[]> = {}) {
|
|
416
|
+
// Inject the augments into the model string inside the parentheses
|
|
417
|
+
// Keeping the content of the parentheses intact
|
|
418
|
+
const parentheses = modelIdentifier.match(/\(([^)]+)\)/)
|
|
419
|
+
|
|
420
|
+
const augmentsString: string[] = []
|
|
421
|
+
|
|
422
|
+
Object.entries(augments).forEach(([key, value]) => {
|
|
423
|
+
if (key == 'seed') augmentsString.push(`seed:${value}`)
|
|
424
|
+
else if (key == 'temperature') augmentsString.push(`temperature:${value}`)
|
|
425
|
+
else if (key == 'topP') augmentsString.push(`topP:${value}`)
|
|
426
|
+
else if (key == 'topK') augmentsString.push(`topK:${value}`)
|
|
427
|
+
else if (key == 'thread') augmentsString.push(`thread:${value}`)
|
|
428
|
+
else if (key == 'requiredCapabilities') {
|
|
429
|
+
for (const capability of value as string[]) {
|
|
430
|
+
augmentsString.push(capability)
|
|
431
|
+
}
|
|
432
|
+
} else augmentsString.push(`${key}:${value}`)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
if (parentheses) {
|
|
436
|
+
modelIdentifier = modelIdentifier.replace(parentheses[0], `(${parentheses[1]},${augmentsString.filter(Boolean).join(',')})`)
|
|
437
|
+
} else {
|
|
438
|
+
if (augmentsString.length) {
|
|
439
|
+
modelIdentifier += `(${augmentsString.join(',')})`
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const parsed = parse(modelIdentifier)
|
|
444
|
+
|
|
445
|
+
const { models } = filterModels(modelIdentifier)
|
|
446
|
+
return {
|
|
447
|
+
...models[0],
|
|
448
|
+
parsed: {
|
|
449
|
+
...parsed,
|
|
450
|
+
...augments,
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function getModels(modelIdentifier: string) {
|
|
456
|
+
// Split the modelIdentifier by comma, ignoring commas inside parentheses
|
|
457
|
+
let result = []
|
|
458
|
+
let segment = ''
|
|
459
|
+
let depth = 0
|
|
460
|
+
|
|
461
|
+
for (const char of modelIdentifier) {
|
|
462
|
+
if (char === '(') depth++
|
|
463
|
+
else if (char === ')') depth--
|
|
464
|
+
else if (char === ',' && depth === 0) {
|
|
465
|
+
result.push(segment.trim())
|
|
466
|
+
segment = ''
|
|
467
|
+
continue
|
|
468
|
+
}
|
|
469
|
+
segment += char
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (segment.trim()) result.push(segment.trim())
|
|
473
|
+
|
|
474
|
+
// Resolve each segment
|
|
475
|
+
return result.map((r) => getModel(r)) as (Model & { parsed: ParsedModelIdentifier })[]
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// TODO: Move this to a database or another source of truth
|
|
479
|
+
// This isnt a good place for this data.
|
|
480
|
+
const metaModels = [
|
|
481
|
+
{
|
|
482
|
+
name: 'frontier',
|
|
483
|
+
models: ['gemini-2.0-flash-001', 'deepseek-r1'],
|
|
484
|
+
},
|
|
485
|
+
]
|
package/src/providers.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import camelCase from 'camelcase'
|
|
2
|
+
|
|
3
|
+
const allProviders = [
|
|
4
|
+
'OpenAI',
|
|
5
|
+
'Anthropic',
|
|
6
|
+
'Google',
|
|
7
|
+
'Google AI Studio',
|
|
8
|
+
'Amazon Bedrock',
|
|
9
|
+
'Groq',
|
|
10
|
+
'SambaNova',
|
|
11
|
+
'Cohere',
|
|
12
|
+
'Mistral',
|
|
13
|
+
'Together',
|
|
14
|
+
'Together 2',
|
|
15
|
+
'Fireworks',
|
|
16
|
+
'DeepInfra',
|
|
17
|
+
'Lepton',
|
|
18
|
+
'Novita',
|
|
19
|
+
'Avian',
|
|
20
|
+
'Lambda',
|
|
21
|
+
'Azure',
|
|
22
|
+
'Modal',
|
|
23
|
+
'AnyScale',
|
|
24
|
+
'Replicate',
|
|
25
|
+
'Perplexity',
|
|
26
|
+
'Recursal',
|
|
27
|
+
'OctoAI',
|
|
28
|
+
'DeepSeek',
|
|
29
|
+
'Infermatic',
|
|
30
|
+
'AI21',
|
|
31
|
+
'Featherless',
|
|
32
|
+
'Inflection',
|
|
33
|
+
'xAI',
|
|
34
|
+
'Cloudflare',
|
|
35
|
+
'SF Compute',
|
|
36
|
+
'Minimax',
|
|
37
|
+
'Nineteen',
|
|
38
|
+
'Liquid',
|
|
39
|
+
'Stealth',
|
|
40
|
+
'NCompass',
|
|
41
|
+
'InferenceNet',
|
|
42
|
+
'Friendli',
|
|
43
|
+
'AionLabs',
|
|
44
|
+
'Alibaba',
|
|
45
|
+
'Nebius',
|
|
46
|
+
'Chutes',
|
|
47
|
+
'Kluster',
|
|
48
|
+
'Crusoe',
|
|
49
|
+
'Targon',
|
|
50
|
+
'Ubicloud',
|
|
51
|
+
'Parasail',
|
|
52
|
+
'Phala',
|
|
53
|
+
'Cent-ML',
|
|
54
|
+
'Venice',
|
|
55
|
+
'OpenInference',
|
|
56
|
+
'Atoma',
|
|
57
|
+
'01.AI',
|
|
58
|
+
'HuggingFace',
|
|
59
|
+
'Mancer',
|
|
60
|
+
'Mancer 2',
|
|
61
|
+
'Hyperbolic',
|
|
62
|
+
'Hyperbolic 2',
|
|
63
|
+
'Lynn 2',
|
|
64
|
+
'Lynn',
|
|
65
|
+
'Reflection',
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
export const getProviderName = (provider: string) => {
|
|
69
|
+
// Reverse a camelCase string into the provider's name
|
|
70
|
+
|
|
71
|
+
switch (provider) {
|
|
72
|
+
case 'vertex':
|
|
73
|
+
return 'Google'
|
|
74
|
+
case 'google':
|
|
75
|
+
return 'Google AI Studio'
|
|
76
|
+
default:
|
|
77
|
+
return allProviders.find((p) => camelCase(p) === provider)
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export type Endpoint = {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
contextLength: number
|
|
5
|
+
model: Model
|
|
6
|
+
modelVariantSlug: string
|
|
7
|
+
modelVariantPermaslug: string
|
|
8
|
+
providerName: string
|
|
9
|
+
providerInfo: ProviderInfo
|
|
10
|
+
providerDisplayName: string
|
|
11
|
+
providerModelId: string
|
|
12
|
+
providerGroup: string
|
|
13
|
+
isCloaked: boolean
|
|
14
|
+
quantization: null
|
|
15
|
+
variant: string
|
|
16
|
+
isSelfHosted: boolean
|
|
17
|
+
canAbort: boolean
|
|
18
|
+
maxPromptTokens: null
|
|
19
|
+
maxCompletionTokens: number
|
|
20
|
+
maxPromptImages: null
|
|
21
|
+
maxTokensPerImage: null
|
|
22
|
+
supportedParameters: string[]
|
|
23
|
+
isByok: boolean
|
|
24
|
+
moderationRequired: boolean
|
|
25
|
+
dataPolicy: DataPolicy
|
|
26
|
+
pricing: Pricing
|
|
27
|
+
isHidden: boolean
|
|
28
|
+
isDeranked: boolean
|
|
29
|
+
isDisabled: boolean
|
|
30
|
+
supportsToolParameters: boolean
|
|
31
|
+
supportsReasoning: boolean
|
|
32
|
+
supportsMultipart: boolean
|
|
33
|
+
limitRpm: number
|
|
34
|
+
limitRpd: null
|
|
35
|
+
hasCompletions: boolean
|
|
36
|
+
hasChatCompletions: boolean
|
|
37
|
+
features: Features
|
|
38
|
+
providerRegion: null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type Model = {
|
|
42
|
+
slug: string
|
|
43
|
+
hfSlug?: string | null
|
|
44
|
+
updatedAt: string
|
|
45
|
+
createdAt: string
|
|
46
|
+
hfUpdatedAt: null
|
|
47
|
+
name: string
|
|
48
|
+
shortName: string
|
|
49
|
+
author: string
|
|
50
|
+
description: string
|
|
51
|
+
modelVersionGroupId: string
|
|
52
|
+
contextLength: number
|
|
53
|
+
inputModalities: string[]
|
|
54
|
+
outputModalities: string[]
|
|
55
|
+
hasTextOutput: boolean
|
|
56
|
+
group: string
|
|
57
|
+
instructType: null
|
|
58
|
+
defaultSystem: null
|
|
59
|
+
defaultStops: any[]
|
|
60
|
+
hidden: boolean
|
|
61
|
+
router: null
|
|
62
|
+
warningMessage: null
|
|
63
|
+
permaslug: string
|
|
64
|
+
reasoningConfig: null
|
|
65
|
+
endpoint?: Endpoint | Provider
|
|
66
|
+
sorting?: Sorting
|
|
67
|
+
providers?: Provider[]
|
|
68
|
+
provider?: Provider
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type DataPolicy = {
|
|
72
|
+
termsOfServiceUrl: string
|
|
73
|
+
privacyPolicyUrl: string
|
|
74
|
+
training: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type Features = {}
|
|
78
|
+
|
|
79
|
+
export type Pricing = {
|
|
80
|
+
prompt: string
|
|
81
|
+
completion: string
|
|
82
|
+
image: string
|
|
83
|
+
request: string
|
|
84
|
+
inputCacheRead: string
|
|
85
|
+
inputCacheWrite: string
|
|
86
|
+
webSearch: string
|
|
87
|
+
internalReasoning: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type ProviderInfo = {
|
|
91
|
+
name: string
|
|
92
|
+
displayName: string
|
|
93
|
+
baseUrl: string
|
|
94
|
+
dataPolicy: DataPolicy
|
|
95
|
+
headquarters: string
|
|
96
|
+
hasChatCompletions: boolean
|
|
97
|
+
hasCompletions: boolean
|
|
98
|
+
isAbortable: boolean
|
|
99
|
+
moderationRequired: boolean
|
|
100
|
+
group: string
|
|
101
|
+
editors: any[]
|
|
102
|
+
owners: any[]
|
|
103
|
+
isMultipartSupported: boolean
|
|
104
|
+
statusPageUrl: null
|
|
105
|
+
byokEnabled: boolean
|
|
106
|
+
isPrimaryProvider: boolean
|
|
107
|
+
icon: Icon
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type Icon = {
|
|
111
|
+
url: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type Provider = {
|
|
115
|
+
name: string
|
|
116
|
+
slug: string
|
|
117
|
+
quantization: string | null
|
|
118
|
+
context: number
|
|
119
|
+
maxCompletionTokens: number
|
|
120
|
+
pricing: Pricing
|
|
121
|
+
supportedParameters: string[]
|
|
122
|
+
inputCost: number
|
|
123
|
+
outputCost: number
|
|
124
|
+
throughput: number
|
|
125
|
+
latency: number
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type Sorting = {
|
|
129
|
+
topWeekly: number
|
|
130
|
+
newest: number
|
|
131
|
+
throughputHighToLow: number
|
|
132
|
+
latencyLowToHigh: number
|
|
133
|
+
pricingLowToHigh: number
|
|
134
|
+
pricingHighToLow: number
|
|
135
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
import { parse } from '../src/parser'
|
|
3
|
+
|
|
4
|
+
describe('parser', () => {
|
|
5
|
+
it('should parse a model', () => {
|
|
6
|
+
const model = parse('gemini')
|
|
7
|
+
expect(model.author).toBe('google')
|
|
8
|
+
expect(model.model).toBe('gemini-2.0-flash-001')
|
|
9
|
+
expect(model.capabilities || []).toEqual([])
|
|
10
|
+
})
|
|
11
|
+
})
|