@funkai/models 0.3.0 → 0.3.2

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 +34 -33
  2. package/CHANGELOG.md +36 -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 +14 -2
  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 +147 -56
  53. package/src/catalog/index.test.ts +8 -8
  54. package/src/catalog/index.ts +5 -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 +21 -2
  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
@@ -53,29 +53,47 @@ interface ApiProvider {
53
53
  /**
54
54
  * Convert a provider key to a TypeScript constant name.
55
55
  * e.g. "openai" → "OPENAI_MODELS", "meta-llama" → "META_LLAMA_MODELS"
56
+ *
57
+ * @private
56
58
  */
57
59
  function toConstName(provider: string): string {
58
- return `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_MODELS`;
60
+ return `${provider.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}_MODELS`;
59
61
  }
60
62
 
61
63
  /**
62
64
  * Lowercase the first character of a string, preserving the rest as-is.
63
65
  * e.g. "OpenAI" → "openAI", "GoogleVertex" → "googleVertex", "XAI" → "xAI"
66
+ *
67
+ * @private
64
68
  */
65
69
  function lowerFirst(s: string): string {
66
- return s.length === 0 ? s : s[0]!.toLowerCase() + s.slice(1);
70
+ if (s.length === 0) {
71
+ return s;
72
+ }
73
+ const [first] = s;
74
+ if (first === undefined) {
75
+ return s;
76
+ }
77
+ return first.toLowerCase() + s.slice(1);
67
78
  }
68
79
 
69
80
  /**
70
81
  * Return the correct indefinite article ("a" or "an") for a word.
82
+ *
83
+ * @private
71
84
  */
72
85
  function article(word: string): string {
73
- return /^[aeiou]/i.test(word) ? "an" : "a";
86
+ if (/^[aeiou]/i.test(word)) {
87
+ return "an";
88
+ }
89
+ return "a";
74
90
  }
75
91
 
76
92
  /**
77
93
  * Convert per-million-token rate to per-token rate, rounding to
78
94
  * eliminate floating-point noise (e.g. `8.000000000000001e-7`).
95
+ *
96
+ * @private
79
97
  */
80
98
  function toPerToken(perMillion: number): number {
81
99
  return parseFloat((perMillion / 1_000_000).toPrecision(6));
@@ -84,29 +102,56 @@ function toPerToken(perMillion: number): number {
84
102
  /**
85
103
  * Format a number for codegen output, using scientific notation for
86
104
  * very small values.
105
+ *
106
+ * @private
87
107
  */
88
108
  function fmtNum(n: number): string {
89
- if (n === 0) return "0";
90
- if (n < 0.0000001) return n.toExponential();
109
+ if (n === 0) {
110
+ return "0";
111
+ }
112
+ if (n < 0.000_000_1) {
113
+ return n.toExponential();
114
+ }
91
115
  return String(n);
92
116
  }
93
117
 
118
+ /** @private */
119
+ function extractExampleId(model: ApiModel | undefined): string {
120
+ if (model !== undefined && model !== null) {
121
+ return model.id;
122
+ }
123
+ return "example-id";
124
+ }
125
+
94
126
  /**
95
127
  * Build the pricing object literal string for a model.
128
+ *
129
+ * @private
96
130
  */
131
+ function extractCostField(cost: ApiModel["cost"], field: "input" | "output"): number {
132
+ if (cost !== undefined && cost !== null) {
133
+ return cost[field] ?? 0;
134
+ }
135
+ return 0;
136
+ }
137
+
97
138
  function buildPricing(cost: ApiModel["cost"]): string {
98
- const input = toPerToken(cost?.input ?? 0);
99
- const output = toPerToken(cost?.output ?? 0);
139
+ const costInput = extractCostField(cost, "input");
140
+ const costOutput = extractCostField(cost, "output");
141
+ const input = toPerToken(costInput);
142
+ const output = toPerToken(costOutput);
100
143
  const parts: string[] = [`input: ${fmtNum(input)}`, `output: ${fmtNum(output)}`];
101
144
 
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))}`);
145
+ if (cost !== undefined && cost !== null) {
146
+ if (cost.cache_read !== undefined && cost.cache_read !== null && cost.cache_read > 0) {
147
+ parts.push(`cacheRead: ${fmtNum(toPerToken(cost.cache_read))}`);
148
+ }
149
+ if (cost.cache_write !== undefined && cost.cache_write !== null && cost.cache_write > 0) {
150
+ parts.push(`cacheWrite: ${fmtNum(toPerToken(cost.cache_write))}`);
151
+ }
152
+ if (cost.reasoning !== undefined && cost.reasoning !== null && cost.reasoning > 0) {
153
+ parts.push(`reasoning: ${fmtNum(toPerToken(cost.reasoning))}`);
154
+ }
110
155
  }
111
156
 
112
157
  return `{ ${parts.join(", ")} }`;
@@ -114,15 +159,31 @@ function buildPricing(cost: ApiModel["cost"]): string {
114
159
 
115
160
  /**
116
161
  * Build the modalities object literal string.
162
+ *
163
+ * @private
117
164
  */
165
+ function extractModalityField(
166
+ modalities: ApiModel["modalities"],
167
+ field: "input" | "output",
168
+ ): string[] {
169
+ if (modalities !== undefined && modalities !== null) {
170
+ return modalities[field] ?? ["text"];
171
+ }
172
+ return ["text"];
173
+ }
174
+
118
175
  function buildModalities(modalities: ApiModel["modalities"]): string {
119
- const input = JSON.stringify(modalities?.input ?? ["text"]);
120
- const output = JSON.stringify(modalities?.output ?? ["text"]);
176
+ const modalInput = extractModalityField(modalities, "input");
177
+ const modalOutput = extractModalityField(modalities, "output");
178
+ const input = JSON.stringify(modalInput);
179
+ const output = JSON.stringify(modalOutput);
121
180
  return `{ input: ${input}, output: ${output} }`;
122
181
  }
123
182
 
124
183
  /**
125
184
  * Build the capabilities object literal string.
185
+ *
186
+ * @private
126
187
  */
127
188
  function buildCapabilities(m: ApiModel): string {
128
189
  return [
@@ -133,19 +194,44 @@ function buildCapabilities(m: ApiModel): string {
133
194
  ].join(", ");
134
195
  }
135
196
 
197
+ /**
198
+ * Extract context window and max output from a model's limit field.
199
+ *
200
+ * @private
201
+ */
202
+ function getModelLimits(limit: ApiModel["limit"]): { contextWindow: number; maxOutput: number } {
203
+ if (limit === undefined || limit === null) {
204
+ return { contextWindow: 0, maxOutput: 0 };
205
+ }
206
+ const contextWindow = limit.context ?? 0;
207
+ const maxOutput = limit.output ?? 0;
208
+ return { contextWindow, maxOutput };
209
+ }
210
+
136
211
  /**
137
212
  * Escape a string for use in a TypeScript single-quoted string literal.
213
+ *
214
+ * @private
138
215
  */
139
216
  function escapeStr(s: string): string {
140
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
217
+ return s
218
+ .replaceAll("\\", String.raw`\\`)
219
+ .replaceAll("'", String.raw`\'`)
220
+ .replaceAll("\n", String.raw`\n`)
221
+ .replaceAll("\r", String.raw`\r`);
141
222
  }
142
223
 
224
+ /**
225
+ * Check whether the staleness cache file indicates a recent fetch.
226
+ *
227
+ * @private
228
+ */
143
229
  function isFresh(reqPath: string): boolean {
144
230
  if (!existsSync(reqPath)) {
145
231
  return false;
146
232
  }
147
233
  try {
148
- const timestamp = readFileSync(reqPath, "utf-8").trim();
234
+ const timestamp = readFileSync(reqPath, "utf8").trim();
149
235
  const lastRun = new Date(timestamp).getTime();
150
236
  return Date.now() - lastRun < STALE_MS;
151
237
  } catch {
@@ -175,7 +261,7 @@ export default lauf({
175
261
 
176
262
  // Read provider config
177
263
  const providers: Record<string, ProviderEntry> = JSON.parse(
178
- readFileSync(PROVIDERS_PATH, "utf-8"),
264
+ readFileSync(PROVIDERS_PATH, "utf8"),
179
265
  );
180
266
  const providerKeys = Object.keys(providers);
181
267
 
@@ -210,30 +296,30 @@ export default lauf({
210
296
  rmSync(ENTRY_DIR, { recursive: true, force: true });
211
297
  mkdirSync(ENTRY_DIR, { recursive: true });
212
298
 
213
- const providerFiles: { provider: string; constName: string; count: number }[] = [];
214
-
215
- for (const providerKey of providerKeys) {
216
- const apiProvider = apiData[providerKey]!;
217
- const apiModels = apiProvider.models ?? {};
299
+ const providerFiles = providerKeys.flatMap((providerKey) => {
300
+ const apiProviderEntry = apiData[providerKey];
301
+ const providerEntry = providers[providerKey];
302
+ if (apiProviderEntry === undefined || providerEntry === undefined) {
303
+ return [];
304
+ }
305
+ if (apiProviderEntry.models === undefined || apiProviderEntry.models === null) {
306
+ throw new Error(
307
+ `models.dev API returned no models for configured provider: ${providerKey}`,
308
+ );
309
+ }
310
+ const apiModels = apiProviderEntry.models;
218
311
  const constName = toConstName(providerKey);
219
- const lines: string[] = [];
220
-
221
- for (const [, m] of Object.entries(apiModels)) {
312
+ const lines = Object.values(apiModels).map((m) => {
222
313
  const id = escapeStr(m.id);
223
314
  const name = escapeStr(m.name ?? m.id);
224
315
  const family = escapeStr(m.family ?? "");
225
316
  const pricing = buildPricing(m.cost);
226
- const contextWindow = m.limit?.context ?? 0;
227
- const maxOutput = m.limit?.output ?? 0;
317
+ const { contextWindow, maxOutput } = getModelLimits(m.limit);
228
318
  const modalities = buildModalities(m.modalities);
229
319
  const capabilities = buildCapabilities(m);
230
320
 
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
- }
321
+ return ` { id: '${id}', name: '${name}', provider: '${providerKey}', family: '${family}', pricing: ${pricing}, contextWindow: ${contextWindow}, maxOutput: ${maxOutput}, modalities: ${modalities}, capabilities: { ${capabilities} } },`;
322
+ });
237
323
 
238
324
  // Write catalog provider file
239
325
  const catalogContent = `${BANNER}
@@ -246,14 +332,15 @@ ${lines.join("\n")}
246
332
  `;
247
333
 
248
334
  const catalogPath = join(CATALOG_DIR, `${providerKey}.ts`);
249
- writeFileSync(catalogPath, catalogContent, "utf-8");
335
+ writeFileSync(catalogPath, catalogContent, "utf8");
250
336
 
251
337
  // Write per-provider entry point
252
- const prefix = providers[providerKey]!.prefix;
338
+ const { prefix } = providerEntry;
253
339
  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);
340
+ const [firstModel] = Object.values(apiModels);
341
+ const exampleId = escapeStr(extractExampleId(firstModel));
342
+ const providerName = escapeStr(providerEntry.name);
343
+ const art = article(providerEntry.name);
257
344
  const entryContent = `${BANNER}
258
345
 
259
346
  import type { LiteralUnion } from 'type-fest'
@@ -286,6 +373,7 @@ export type ${prefix}ModelId = (typeof ${constName})[number]['id']
286
373
  */
287
374
  export const ${camel}Models = ${constName}
288
375
 
376
+ /** @private */
289
377
  const MODEL_INDEX = new Map<string, ModelDefinition>(${constName}.map((m) => [m.id, m]))
290
378
 
291
379
  /**
@@ -310,11 +398,11 @@ export function ${camel}Model(id: LiteralUnion<${prefix}ModelId, string>): Model
310
398
  `;
311
399
 
312
400
  const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
313
- writeFileSync(entryPath, entryContent, "utf-8");
401
+ writeFileSync(entryPath, entryContent, "utf8");
314
402
 
315
403
  ctx.logger.success(`${providerKey} (${lines.length} models)`);
316
- providerFiles.push({ provider: providerKey, constName, count: lines.length });
317
- }
404
+ return [{ provider: providerKey, constName, count: lines.length }];
405
+ });
318
406
 
319
407
  // Catalog barrel
320
408
  const imports = providerFiles
@@ -325,6 +413,7 @@ export function ${camel}Model(id: LiteralUnion<${prefix}ModelId, string>): Model
325
413
 
326
414
  const catalogBarrel = `${BANNER}
327
415
 
416
+ // oxlint-disable eslint-plugin-import/max-dependencies
328
417
  import type { ModelDefinition } from '../types.js'
329
418
  ${imports}
330
419
 
@@ -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 }> = {
@@ -350,21 +439,23 @@ ${spreads}
350
439
  types: "./dist/index.d.mts",
351
440
  import: "./dist/index.mjs",
352
441
  },
442
+ ...Object.fromEntries(
443
+ providerFiles.map((p) => [
444
+ `./${p.provider}`,
445
+ {
446
+ types: `./dist/providers/${p.provider}.d.mts`,
447
+ import: `./dist/providers/${p.provider}.mjs`,
448
+ },
449
+ ]),
450
+ ),
353
451
  };
354
452
 
355
- for (const p of providerFiles) {
356
- exportsMap[`./${p.provider}`] = {
357
- types: `./dist/providers/${p.provider}.d.mts`,
358
- import: `./dist/providers/${p.provider}.mjs`,
359
- };
360
- }
361
-
362
453
  pkg.exports = exportsMap;
363
- writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
454
+ writeFileSync(PACKAGE_JSON_PATH, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
364
455
  ctx.logger.success("package.json exports map updated");
365
456
 
366
457
  // Staleness timestamp
367
- writeFileSync(REQ_PATH, new Date().toISOString(), "utf-8");
458
+ writeFileSync(REQ_PATH, new Date().toISOString(), "utf8");
368
459
 
369
460
  const totalModels = providerFiles.reduce((sum, p) => sum + p.count, 0);
370
461
  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
  });
@@ -23,6 +23,7 @@ export type ModelId = LiteralUnion<KnownModelId, string>;
23
23
  */
24
24
  export const MODELS = GENERATED_MODELS satisfies readonly ModelDefinition[];
25
25
 
26
+ /** @private */
26
27
  const MODEL_INDEX = new Map<string, ModelDefinition>(MODELS.map((m) => [m.id, m]));
27
28
 
28
29
  /**
@@ -61,5 +62,8 @@ export function model(id: ModelId): ModelDefinition | null {
61
62
  * ```
62
63
  */
63
64
  export function models(filter?: (m: ModelDefinition) => boolean): readonly ModelDefinition[] {
64
- return filter ? MODELS.filter(filter) : MODELS;
65
+ if (filter) {
66
+ return MODELS.filter(filter);
67
+ }
68
+ return MODELS;
65
69
  }