@funkai/models 0.3.0 → 0.3.1

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.
Files changed (83) hide show
  1. package/.turbo/turbo-build.log +33 -33
  2. package/CHANGELOG.md +6 -0
  3. package/dist/alibaba-B6q4Ng1R.mjs.map +1 -1
  4. package/dist/amazon-bedrock-Cv9AHQBH.mjs.map +1 -1
  5. package/dist/anthropic-yB7ST97_.mjs.map +1 -1
  6. package/dist/cerebras-COfl7XM-.mjs.map +1 -1
  7. package/dist/cohere-B7TgO0hT.mjs.map +1 -1
  8. package/dist/deepinfra-B0GxUwCG.mjs.map +1 -1
  9. package/dist/deepseek-D64ZEsvS.mjs.map +1 -1
  10. package/dist/fireworks-ai-DJYvdAi_.mjs.map +1 -1
  11. package/dist/google-BypRl349.mjs.map +1 -1
  12. package/dist/google-vertex-DbS-zTGD.mjs.map +1 -1
  13. package/dist/groq-ei_PerYi.mjs.map +1 -1
  14. package/dist/huggingface-DaM1EeLP.mjs.map +1 -1
  15. package/dist/inception-CspEzqNV.mjs.map +1 -1
  16. package/dist/index.d.mts +1 -1
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +2 -1
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/llama-Cf3-koap.mjs.map +1 -1
  21. package/dist/mistral-BI9MdAO4.mjs.map +1 -1
  22. package/dist/nvidia-COHacuoa.mjs.map +1 -1
  23. package/dist/openai-C0nCfZUq.mjs.map +1 -1
  24. package/dist/openrouter-DSFzxKQb.mjs.map +1 -1
  25. package/dist/perplexity-zeZ2WlBU.mjs.map +1 -1
  26. package/dist/providers/alibaba.d.mts +1 -1
  27. package/dist/providers/amazon-bedrock.d.mts +1 -1
  28. package/dist/providers/anthropic.d.mts +1 -1
  29. package/dist/providers/cerebras.d.mts +1 -1
  30. package/dist/providers/cohere.d.mts +1 -1
  31. package/dist/providers/deepinfra.d.mts +1 -1
  32. package/dist/providers/deepseek.d.mts +1 -1
  33. package/dist/providers/fireworks-ai.d.mts +1 -1
  34. package/dist/providers/google-vertex.d.mts +1 -1
  35. package/dist/providers/google.d.mts +1 -1
  36. package/dist/providers/groq.d.mts +1 -1
  37. package/dist/providers/huggingface.d.mts +1 -1
  38. package/dist/providers/inception.d.mts +1 -1
  39. package/dist/providers/llama.d.mts +1 -1
  40. package/dist/providers/mistral.d.mts +1 -1
  41. package/dist/providers/nvidia.d.mts +1 -1
  42. package/dist/providers/openai.d.mts +1 -1
  43. package/dist/providers/openrouter.d.mts +1 -1
  44. package/dist/providers/perplexity.d.mts +1 -1
  45. package/dist/providers/togetherai.d.mts +1 -1
  46. package/dist/providers/xai.d.mts +1 -1
  47. package/dist/togetherai-BvcxUfPE.mjs.map +1 -1
  48. package/dist/{types-DjdaZckF.d.mts → types-DIzolT_s.d.mts} +61 -21
  49. package/dist/types-DIzolT_s.d.mts.map +1 -0
  50. package/dist/xai-fSuAkQJo.mjs.map +1 -1
  51. package/package.json +6 -3
  52. package/scripts/generate-models.ts +152 -63
  53. package/src/catalog/index.test.ts +8 -8
  54. package/src/catalog/index.ts +4 -1
  55. package/src/catalog/providers/alibaba.ts +91 -91
  56. package/src/catalog/providers/amazon-bedrock.ts +205 -185
  57. package/src/catalog/providers/anthropic.ts +87 -62
  58. package/src/catalog/providers/cerebras.ts +9 -9
  59. package/src/catalog/providers/cohere.ts +16 -16
  60. package/src/catalog/providers/deepinfra.ts +71 -71
  61. package/src/catalog/providers/deepseek.ts +3 -3
  62. package/src/catalog/providers/fireworks-ai.ts +36 -36
  63. package/src/catalog/providers/google-vertex.ts +62 -62
  64. package/src/catalog/providers/google.ts +69 -69
  65. package/src/catalog/providers/groq.ts +24 -24
  66. package/src/catalog/providers/huggingface.ts +52 -52
  67. package/src/catalog/providers/inception.ts +9 -9
  68. package/src/catalog/providers/index.ts +1 -0
  69. package/src/catalog/providers/llama.ts +7 -7
  70. package/src/catalog/providers/mistral.ts +60 -60
  71. package/src/catalog/providers/nvidia.ts +84 -84
  72. package/src/catalog/providers/openai.ts +115 -115
  73. package/src/catalog/providers/openrouter.ts +448 -433
  74. package/src/catalog/providers/perplexity.ts +9 -9
  75. package/src/catalog/providers/togetherai.ts +47 -47
  76. package/src/catalog/providers/xai.ts +49 -49
  77. package/src/catalog/types.ts +60 -20
  78. package/src/cost/calculate.test.ts +11 -11
  79. package/src/provider/registry.ts +1 -1
  80. package/src/provider/types.ts +1 -1
  81. package/tsconfig.json +2 -1
  82. package/tsdown.config.ts +7 -3
  83. package/dist/types-DjdaZckF.d.mts.map +0 -1
@@ -55,7 +55,7 @@ interface ApiProvider {
55
55
  * e.g. "openai" → "OPENAI_MODELS", "meta-llama" → "META_LLAMA_MODELS"
56
56
  */
57
57
  function toConstName(provider: string): string {
58
- return `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_MODELS`;
58
+ return `${provider.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}_MODELS`;
59
59
  }
60
60
 
61
61
  /**
@@ -63,14 +63,24 @@ function toConstName(provider: string): string {
63
63
  * e.g. "OpenAI" → "openAI", "GoogleVertex" → "googleVertex", "XAI" → "xAI"
64
64
  */
65
65
  function lowerFirst(s: string): string {
66
- return s.length === 0 ? s : s[0]!.toLowerCase() + s.slice(1);
66
+ if (s.length === 0) {
67
+ return s;
68
+ }
69
+ const [first] = s;
70
+ if (first === undefined) {
71
+ return s;
72
+ }
73
+ return first.toLowerCase() + s.slice(1);
67
74
  }
68
75
 
69
76
  /**
70
77
  * Return the correct indefinite article ("a" or "an") for a word.
71
78
  */
72
79
  function article(word: string): string {
73
- return /^[aeiou]/i.test(word) ? "an" : "a";
80
+ if (/^[aeiou]/i.test(word)) {
81
+ return "an";
82
+ }
83
+ return "a";
74
84
  }
75
85
 
76
86
  /**
@@ -86,8 +96,12 @@ function toPerToken(perMillion: number): number {
86
96
  * very small values.
87
97
  */
88
98
  function fmtNum(n: number): string {
89
- if (n === 0) return "0";
90
- if (n < 0.0000001) return n.toExponential();
99
+ if (n === 0) {
100
+ return "0";
101
+ }
102
+ if (n < 0.000_000_1) {
103
+ return n.toExponential();
104
+ }
91
105
  return String(n);
92
106
  }
93
107
 
@@ -95,18 +109,32 @@ function fmtNum(n: number): string {
95
109
  * Build the pricing object literal string for a model.
96
110
  */
97
111
  function buildPricing(cost: ApiModel["cost"]): string {
98
- const input = toPerToken(cost?.input ?? 0);
99
- const output = toPerToken(cost?.output ?? 0);
112
+ const costInput: number = (() => {
113
+ if (cost !== undefined && cost !== null && cost.input !== undefined && cost.input !== null) {
114
+ return cost.input;
115
+ }
116
+ return 0;
117
+ })();
118
+ const costOutput: number = (() => {
119
+ if (cost !== undefined && cost !== null && cost.output !== undefined && cost.output !== null) {
120
+ return cost.output;
121
+ }
122
+ return 0;
123
+ })();
124
+ const input = toPerToken(costInput);
125
+ const output = toPerToken(costOutput);
100
126
  const parts: string[] = [`input: ${fmtNum(input)}`, `output: ${fmtNum(output)}`];
101
127
 
102
- if (cost?.cache_read != null && cost.cache_read > 0) {
103
- parts.push(`cacheRead: ${fmtNum(toPerToken(cost.cache_read))}`);
104
- }
105
- if (cost?.cache_write != null && cost.cache_write > 0) {
106
- parts.push(`cacheWrite: ${fmtNum(toPerToken(cost.cache_write))}`);
107
- }
108
- if (cost?.reasoning != null && cost.reasoning > 0) {
109
- parts.push(`reasoning: ${fmtNum(toPerToken(cost.reasoning))}`);
128
+ if (cost !== undefined && cost !== null) {
129
+ if (cost.cache_read !== undefined && cost.cache_read !== null && cost.cache_read > 0) {
130
+ parts.push(`cacheRead: ${fmtNum(toPerToken(cost.cache_read))}`);
131
+ }
132
+ if (cost.cache_write !== undefined && cost.cache_write !== null && cost.cache_write > 0) {
133
+ parts.push(`cacheWrite: ${fmtNum(toPerToken(cost.cache_write))}`);
134
+ }
135
+ if (cost.reasoning !== undefined && cost.reasoning !== null && cost.reasoning > 0) {
136
+ parts.push(`reasoning: ${fmtNum(toPerToken(cost.reasoning))}`);
137
+ }
110
138
  }
111
139
 
112
140
  return `{ ${parts.join(", ")} }`;
@@ -116,8 +144,30 @@ function buildPricing(cost: ApiModel["cost"]): string {
116
144
  * Build the modalities object literal string.
117
145
  */
118
146
  function buildModalities(modalities: ApiModel["modalities"]): string {
119
- const input = JSON.stringify(modalities?.input ?? ["text"]);
120
- const output = JSON.stringify(modalities?.output ?? ["text"]);
147
+ const modalInput: string[] = (() => {
148
+ if (
149
+ modalities !== undefined &&
150
+ modalities !== null &&
151
+ modalities.input !== undefined &&
152
+ modalities.input !== null
153
+ ) {
154
+ return modalities.input;
155
+ }
156
+ return ["text"];
157
+ })();
158
+ const modalOutput: string[] = (() => {
159
+ if (
160
+ modalities !== undefined &&
161
+ modalities !== null &&
162
+ modalities.output !== undefined &&
163
+ modalities.output !== null
164
+ ) {
165
+ return modalities.output;
166
+ }
167
+ return ["text"];
168
+ })();
169
+ const input = JSON.stringify(modalInput);
170
+ const output = JSON.stringify(modalOutput);
121
171
  return `{ input: ${input}, output: ${output} }`;
122
172
  }
123
173
 
@@ -133,11 +183,37 @@ function buildCapabilities(m: ApiModel): string {
133
183
  ].join(", ");
134
184
  }
135
185
 
186
+ /**
187
+ * Extract context window and max output from a model's limit field.
188
+ */
189
+ function getModelLimits(limit: ApiModel["limit"]): { contextWindow: number; maxOutput: number } {
190
+ if (limit === undefined || limit === null) {
191
+ return { contextWindow: 0, maxOutput: 0 };
192
+ }
193
+ const contextWindow: number = (() => {
194
+ if (limit.context !== undefined && limit.context !== null) {
195
+ return limit.context;
196
+ }
197
+ return 0;
198
+ })();
199
+ const maxOutput: number = (() => {
200
+ if (limit.output !== undefined && limit.output !== null) {
201
+ return limit.output;
202
+ }
203
+ return 0;
204
+ })();
205
+ return { contextWindow, maxOutput };
206
+ }
207
+
136
208
  /**
137
209
  * Escape a string for use in a TypeScript single-quoted string literal.
138
210
  */
139
211
  function escapeStr(s: string): string {
140
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
212
+ return s
213
+ .replaceAll("\\", String.raw`\\`)
214
+ .replaceAll("'", String.raw`\'`)
215
+ .replaceAll("\n", String.raw`\n`)
216
+ .replaceAll("\r", String.raw`\r`);
141
217
  }
142
218
 
143
219
  function isFresh(reqPath: string): boolean {
@@ -145,7 +221,7 @@ function isFresh(reqPath: string): boolean {
145
221
  return false;
146
222
  }
147
223
  try {
148
- const timestamp = readFileSync(reqPath, "utf-8").trim();
224
+ const timestamp = readFileSync(reqPath, "utf8").trim();
149
225
  const lastRun = new Date(timestamp).getTime();
150
226
  return Date.now() - lastRun < STALE_MS;
151
227
  } catch {
@@ -175,7 +251,7 @@ export default lauf({
175
251
 
176
252
  // Read provider config
177
253
  const providers: Record<string, ProviderEntry> = JSON.parse(
178
- readFileSync(PROVIDERS_PATH, "utf-8"),
254
+ readFileSync(PROVIDERS_PATH, "utf8"),
179
255
  );
180
256
  const providerKeys = Object.keys(providers);
181
257
 
@@ -213,30 +289,34 @@ export default lauf({
213
289
  const providerFiles: { provider: string; constName: string; count: number }[] = [];
214
290
 
215
291
  for (const providerKey of providerKeys) {
216
- const apiProvider = apiData[providerKey]!;
217
- const apiModels = apiProvider.models ?? {};
218
- const constName = toConstName(providerKey);
219
- const lines: string[] = [];
220
-
221
- for (const [, m] of Object.entries(apiModels)) {
222
- const id = escapeStr(m.id);
223
- const name = escapeStr(m.name ?? m.id);
224
- const family = escapeStr(m.family ?? "");
225
- const pricing = buildPricing(m.cost);
226
- const contextWindow = m.limit?.context ?? 0;
227
- const maxOutput = m.limit?.output ?? 0;
228
- const modalities = buildModalities(m.modalities);
229
- const capabilities = buildCapabilities(m);
230
-
231
- lines.push(
232
- ` { id: '${id}', name: '${name}', provider: '${providerKey}', family: '${family}', ` +
233
- `pricing: ${pricing}, contextWindow: ${contextWindow}, maxOutput: ${maxOutput}, ` +
234
- `modalities: ${modalities}, capabilities: { ${capabilities} } },`,
235
- );
236
- }
237
-
238
- // Write catalog provider file
239
- const catalogContent = `${BANNER}
292
+ const apiProviderEntry = apiData[providerKey];
293
+ const providerEntry = providers[providerKey];
294
+ if (apiProviderEntry !== undefined && providerEntry !== undefined) {
295
+ if (apiProviderEntry.models === undefined || apiProviderEntry.models === null) {
296
+ throw new Error(
297
+ `models.dev API returned no models for configured provider: ${providerKey}`,
298
+ );
299
+ }
300
+ const apiModels = apiProviderEntry.models;
301
+ const constName = toConstName(providerKey);
302
+ const lines: string[] = [];
303
+
304
+ for (const [, m] of Object.entries(apiModels)) {
305
+ const id = escapeStr(m.id);
306
+ const name = escapeStr(m.name ?? m.id);
307
+ const family = escapeStr(m.family ?? "");
308
+ const pricing = buildPricing(m.cost);
309
+ const { contextWindow, maxOutput } = getModelLimits(m.limit);
310
+ const modalities = buildModalities(m.modalities);
311
+ const capabilities = buildCapabilities(m);
312
+
313
+ lines.push(
314
+ ` { id: '${id}', name: '${name}', provider: '${providerKey}', family: '${family}', pricing: ${pricing}, contextWindow: ${contextWindow}, maxOutput: ${maxOutput}, modalities: ${modalities}, capabilities: { ${capabilities} } },`,
315
+ );
316
+ }
317
+
318
+ // Write catalog provider file
319
+ const catalogContent = `${BANNER}
240
320
 
241
321
  import type { ModelDefinition } from '../types.js'
242
322
 
@@ -245,16 +325,24 @@ ${lines.join("\n")}
245
325
  ] as const satisfies readonly ModelDefinition[]
246
326
  `;
247
327
 
248
- const catalogPath = join(CATALOG_DIR, `${providerKey}.ts`);
249
- writeFileSync(catalogPath, catalogContent, "utf-8");
250
-
251
- // Write per-provider entry point
252
- const prefix = providers[providerKey]!.prefix;
253
- const camel = lowerFirst(prefix);
254
- const exampleId = escapeStr(Object.values(apiModels)[0]?.id ?? "example-id");
255
- const providerName = escapeStr(providers[providerKey]!.name);
256
- const art = article(providers[providerKey]!.name);
257
- const entryContent = `${BANNER}
328
+ const catalogPath = join(CATALOG_DIR, `${providerKey}.ts`);
329
+ writeFileSync(catalogPath, catalogContent, "utf8");
330
+
331
+ // Write per-provider entry point
332
+ const { prefix } = providerEntry;
333
+ const camel = lowerFirst(prefix);
334
+ const [firstModel] = Object.values(apiModels);
335
+ const exampleId = escapeStr(
336
+ (() => {
337
+ if (firstModel !== undefined) {
338
+ return firstModel.id;
339
+ }
340
+ return "example-id";
341
+ })(),
342
+ );
343
+ const providerName = escapeStr(providerEntry.name);
344
+ const art = article(providerEntry.name);
345
+ const entryContent = `${BANNER}
258
346
 
259
347
  import type { LiteralUnion } from 'type-fest'
260
348
  import type { ModelDefinition } from '../catalog/types.js'
@@ -309,11 +397,12 @@ export function ${camel}Model(id: LiteralUnion<${prefix}ModelId, string>): Model
309
397
  }
310
398
  `;
311
399
 
312
- const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
313
- writeFileSync(entryPath, entryContent, "utf-8");
400
+ const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
401
+ writeFileSync(entryPath, entryContent, "utf8");
314
402
 
315
- ctx.logger.success(`${providerKey} (${lines.length} models)`);
316
- providerFiles.push({ provider: providerKey, constName, count: lines.length });
403
+ ctx.logger.success(`${providerKey} (${lines.length} models)`);
404
+ providerFiles.push({ provider: providerKey, constName, count: lines.length });
405
+ }
317
406
  }
318
407
 
319
408
  // Catalog barrel
@@ -333,16 +422,16 @@ ${spreads}
333
422
  ] as const satisfies readonly ModelDefinition[]
334
423
  `;
335
424
 
336
- writeFileSync(join(CATALOG_DIR, "index.ts"), catalogBarrel, "utf-8");
425
+ writeFileSync(join(CATALOG_DIR, "index.ts"), catalogBarrel, "utf8");
337
426
  ctx.logger.success("catalog/providers/index.ts (barrel)");
338
427
 
339
428
  // Write generated entries list for tsdown config
340
429
  const entryPoints = providerFiles.map((p) => `src/providers/${p.provider}.ts`);
341
- writeFileSync(ENTRIES_PATH, JSON.stringify(entryPoints, null, 2), "utf-8");
430
+ writeFileSync(ENTRIES_PATH, JSON.stringify(entryPoints, null, 2), "utf8");
342
431
  ctx.logger.success(".generated/entries.json");
343
432
 
344
433
  // Update package.json exports map
345
- const pkgRaw = readFileSync(PACKAGE_JSON_PATH, "utf-8");
434
+ const pkgRaw = readFileSync(PACKAGE_JSON_PATH, "utf8");
346
435
  const pkg = JSON.parse(pkgRaw);
347
436
 
348
437
  const exportsMap: Record<string, { types: string; import: string }> = {
@@ -360,11 +449,11 @@ ${spreads}
360
449
  }
361
450
 
362
451
  pkg.exports = exportsMap;
363
- writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
452
+ writeFileSync(PACKAGE_JSON_PATH, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
364
453
  ctx.logger.success("package.json exports map updated");
365
454
 
366
455
  // Staleness timestamp
367
- writeFileSync(REQ_PATH, new Date().toISOString(), "utf-8");
456
+ writeFileSync(REQ_PATH, new Date().toISOString(), "utf8");
368
457
 
369
458
  const totalModels = providerFiles.reduce((sum, p) => sum + p.count, 0);
370
459
  ctx.logger.info(`done (${providerFiles.length} providers, ${totalModels} models)`);
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
 
3
3
  import { model, models, MODELS } from "@/catalog/index.js";
4
4
 
5
- describe("MODELS", () => {
5
+ describe("MODELS catalog", () => {
6
6
  it("is a non-empty array", () => {
7
7
  expect(MODELS.length).toBeGreaterThan(0);
8
8
  });
@@ -14,8 +14,8 @@ describe("MODELS", () => {
14
14
  expect(typeof m.provider).toBe("string");
15
15
  expect(typeof m.pricing.input).toBe("number");
16
16
  expect(typeof m.pricing.output).toBe("number");
17
- expect(Array.isArray(m.modalities.input)).toBe(true);
18
- expect(Array.isArray(m.modalities.output)).toBe(true);
17
+ expect(Array.isArray(m.modalities.input)).toBeTruthy();
18
+ expect(Array.isArray(m.modalities.output)).toBeTruthy();
19
19
  expect(typeof m.capabilities.reasoning).toBe("boolean");
20
20
  }
21
21
  });
@@ -30,7 +30,7 @@ describe("MODELS", () => {
30
30
  const seen = new Map<string, Set<string>>();
31
31
  for (const m of MODELS) {
32
32
  const providerSet = seen.get(m.provider) ?? new Set<string>();
33
- expect(providerSet.has(m.id)).toBe(false);
33
+ expect(providerSet.has(m.id)).toBeFalsy();
34
34
  providerSet.add(m.id);
35
35
  seen.set(m.provider, providerSet);
36
36
  }
@@ -58,7 +58,7 @@ describe("model()", () => {
58
58
  const result = model("o1");
59
59
 
60
60
  expect(result).not.toBeNull();
61
- expect(result!.capabilities.reasoning).toBe(true);
61
+ expect(result!.capabilities.reasoning).toBeTruthy();
62
62
  });
63
63
 
64
64
  it("returns model with correct modalities", () => {
@@ -82,7 +82,7 @@ describe("models()", () => {
82
82
 
83
83
  expect(reasoningModels.length).toBeGreaterThan(0);
84
84
  for (const m of reasoningModels) {
85
- expect(m.capabilities.reasoning).toBe(true);
85
+ expect(m.capabilities.reasoning).toBeTruthy();
86
86
  }
87
87
  });
88
88
 
@@ -102,11 +102,11 @@ describe("models()", () => {
102
102
  });
103
103
 
104
104
  it("supports arbitrary filter predicates", () => {
105
- const result = models((m) => m.pricing.input > 0.000001);
105
+ const result = models((m) => m.pricing.input > 0.000_001);
106
106
 
107
107
  expect(result.length).toBeGreaterThan(0);
108
108
  for (const m of result) {
109
- expect(m.pricing.input).toBeGreaterThan(0.000001);
109
+ expect(m.pricing.input).toBeGreaterThan(0.000_001);
110
110
  }
111
111
  });
112
112
  });
@@ -61,5 +61,8 @@ export function model(id: ModelId): ModelDefinition | null {
61
61
  * ```
62
62
  */
63
63
  export function models(filter?: (m: ModelDefinition) => boolean): readonly ModelDefinition[] {
64
- return filter ? MODELS.filter(filter) : MODELS;
64
+ if (filter) {
65
+ return MODELS.filter(filter);
66
+ }
67
+ return MODELS;
65
68
  }