@funkai/models 0.2.1 → 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 (90) hide show
  1. package/.turbo/turbo-build.log +35 -36
  2. package/CHANGELOG.md +18 -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 +41 -87
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +20 -115
  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 +7 -6
  52. package/scripts/generate-models.ts +152 -83
  53. package/src/catalog/index.test.ts +8 -20
  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 -19
  79. package/src/index.ts +2 -8
  80. package/src/provider/index.ts +2 -8
  81. package/src/provider/registry.test.ts +87 -0
  82. package/src/provider/registry.ts +93 -0
  83. package/src/provider/types.ts +1 -1
  84. package/tsconfig.json +2 -1
  85. package/tsdown.config.ts +7 -4
  86. package/dist/types-DjdaZckF.d.mts.map +0 -1
  87. package/src/provider/openrouter.test.ts +0 -125
  88. package/src/provider/openrouter.ts +0 -110
  89. package/src/provider/resolver.test.ts +0 -138
  90. package/src/provider/resolver.ts +0 -125
@@ -6,10 +6,6 @@ import { lauf, z } from "laufen";
6
6
  const API_URL = "https://models.dev/api.json";
7
7
  const STALE_MS = 24 * 60 * 60 * 1000;
8
8
 
9
- // ---------------------------------------------------------------------------
10
- // Banner
11
- // ---------------------------------------------------------------------------
12
-
13
9
  const BANNER = `// ──────────────────────────────────────────────────────────────
14
10
  // ███████╗██╗ ██╗███╗ ██╗██╗ ██╗ █████╗ ██╗
15
11
  // ██╔════╝██║ ██║████╗ ██║██║ ██╔╝██╔══██╗██║
@@ -23,10 +19,6 @@ const BANNER = `// ────────────────────
23
19
  // Update: pnpm --filter=@funkai/models generate:models
24
20
  // ──────────────────────────────────────────────────────────────`;
25
21
 
26
- // ---------------------------------------------------------------------------
27
- // Types
28
- // ---------------------------------------------------------------------------
29
-
30
22
  interface ProviderEntry {
31
23
  name: string;
32
24
  prefix: string;
@@ -58,16 +50,12 @@ interface ApiProvider {
58
50
  models: Record<string, ApiModel>;
59
51
  }
60
52
 
61
- // ---------------------------------------------------------------------------
62
- // Helpers
63
- // ---------------------------------------------------------------------------
64
-
65
53
  /**
66
54
  * Convert a provider key to a TypeScript constant name.
67
55
  * e.g. "openai" → "OPENAI_MODELS", "meta-llama" → "META_LLAMA_MODELS"
68
56
  */
69
57
  function toConstName(provider: string): string {
70
- return `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_MODELS`;
58
+ return `${provider.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}_MODELS`;
71
59
  }
72
60
 
73
61
  /**
@@ -75,14 +63,24 @@ function toConstName(provider: string): string {
75
63
  * e.g. "OpenAI" → "openAI", "GoogleVertex" → "googleVertex", "XAI" → "xAI"
76
64
  */
77
65
  function lowerFirst(s: string): string {
78
- 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);
79
74
  }
80
75
 
81
76
  /**
82
77
  * Return the correct indefinite article ("a" or "an") for a word.
83
78
  */
84
79
  function article(word: string): string {
85
- return /^[aeiou]/i.test(word) ? "an" : "a";
80
+ if (/^[aeiou]/i.test(word)) {
81
+ return "an";
82
+ }
83
+ return "a";
86
84
  }
87
85
 
88
86
  /**
@@ -98,8 +96,12 @@ function toPerToken(perMillion: number): number {
98
96
  * very small values.
99
97
  */
100
98
  function fmtNum(n: number): string {
101
- if (n === 0) return "0";
102
- 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
+ }
103
105
  return String(n);
104
106
  }
105
107
 
@@ -107,18 +109,32 @@ function fmtNum(n: number): string {
107
109
  * Build the pricing object literal string for a model.
108
110
  */
109
111
  function buildPricing(cost: ApiModel["cost"]): string {
110
- const input = toPerToken(cost?.input ?? 0);
111
- 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);
112
126
  const parts: string[] = [`input: ${fmtNum(input)}`, `output: ${fmtNum(output)}`];
113
127
 
114
- if (cost?.cache_read != null && cost.cache_read > 0) {
115
- parts.push(`cacheRead: ${fmtNum(toPerToken(cost.cache_read))}`);
116
- }
117
- if (cost?.cache_write != null && cost.cache_write > 0) {
118
- parts.push(`cacheWrite: ${fmtNum(toPerToken(cost.cache_write))}`);
119
- }
120
- if (cost?.reasoning != null && cost.reasoning > 0) {
121
- 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
+ }
122
138
  }
123
139
 
124
140
  return `{ ${parts.join(", ")} }`;
@@ -128,8 +144,30 @@ function buildPricing(cost: ApiModel["cost"]): string {
128
144
  * Build the modalities object literal string.
129
145
  */
130
146
  function buildModalities(modalities: ApiModel["modalities"]): string {
131
- const input = JSON.stringify(modalities?.input ?? ["text"]);
132
- 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);
133
171
  return `{ input: ${input}, output: ${output} }`;
134
172
  }
135
173
 
@@ -145,23 +183,45 @@ function buildCapabilities(m: ApiModel): string {
145
183
  ].join(", ");
146
184
  }
147
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
+
148
208
  /**
149
209
  * Escape a string for use in a TypeScript single-quoted string literal.
150
210
  */
151
211
  function escapeStr(s: string): string {
152
- 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`);
153
217
  }
154
218
 
155
- // ---------------------------------------------------------------------------
156
- // Staleness check
157
- // ---------------------------------------------------------------------------
158
-
159
219
  function isFresh(reqPath: string): boolean {
160
220
  if (!existsSync(reqPath)) {
161
221
  return false;
162
222
  }
163
223
  try {
164
- const timestamp = readFileSync(reqPath, "utf-8").trim();
224
+ const timestamp = readFileSync(reqPath, "utf8").trim();
165
225
  const lastRun = new Date(timestamp).getTime();
166
226
  return Date.now() - lastRun < STALE_MS;
167
227
  } catch {
@@ -169,10 +229,6 @@ function isFresh(reqPath: string): boolean {
169
229
  }
170
230
  }
171
231
 
172
- // ---------------------------------------------------------------------------
173
- // Script
174
- // ---------------------------------------------------------------------------
175
-
176
232
  export default lauf({
177
233
  description: "Fetch model data from models.dev and generate TypeScript catalog files",
178
234
  args: {
@@ -195,7 +251,7 @@ export default lauf({
195
251
 
196
252
  // Read provider config
197
253
  const providers: Record<string, ProviderEntry> = JSON.parse(
198
- readFileSync(PROVIDERS_PATH, "utf-8"),
254
+ readFileSync(PROVIDERS_PATH, "utf8"),
199
255
  );
200
256
  const providerKeys = Object.keys(providers);
201
257
 
@@ -233,30 +289,34 @@ export default lauf({
233
289
  const providerFiles: { provider: string; constName: string; count: number }[] = [];
234
290
 
235
291
  for (const providerKey of providerKeys) {
236
- const apiProvider = apiData[providerKey]!;
237
- const apiModels = apiProvider.models ?? {};
238
- const constName = toConstName(providerKey);
239
- const lines: string[] = [];
240
-
241
- for (const [, m] of Object.entries(apiModels)) {
242
- const id = escapeStr(m.id);
243
- const name = escapeStr(m.name ?? m.id);
244
- const family = escapeStr(m.family ?? "");
245
- const pricing = buildPricing(m.cost);
246
- const contextWindow = m.limit?.context ?? 0;
247
- const maxOutput = m.limit?.output ?? 0;
248
- const modalities = buildModalities(m.modalities);
249
- const capabilities = buildCapabilities(m);
250
-
251
- lines.push(
252
- ` { id: '${id}', name: '${name}', provider: '${providerKey}', family: '${family}', ` +
253
- `pricing: ${pricing}, contextWindow: ${contextWindow}, maxOutput: ${maxOutput}, ` +
254
- `modalities: ${modalities}, capabilities: { ${capabilities} } },`,
255
- );
256
- }
257
-
258
- // Write catalog provider file
259
- 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}
260
320
 
261
321
  import type { ModelDefinition } from '../types.js'
262
322
 
@@ -265,16 +325,24 @@ ${lines.join("\n")}
265
325
  ] as const satisfies readonly ModelDefinition[]
266
326
  `;
267
327
 
268
- const catalogPath = join(CATALOG_DIR, `${providerKey}.ts`);
269
- writeFileSync(catalogPath, catalogContent, "utf-8");
270
-
271
- // Write per-provider entry point
272
- const prefix = providers[providerKey]!.prefix;
273
- const camel = lowerFirst(prefix);
274
- const exampleId = escapeStr(Object.values(apiModels)[0]?.id ?? "example-id");
275
- const providerName = escapeStr(providers[providerKey]!.name);
276
- const art = article(providers[providerKey]!.name);
277
- 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}
278
346
 
279
347
  import type { LiteralUnion } from 'type-fest'
280
348
  import type { ModelDefinition } from '../catalog/types.js'
@@ -329,11 +397,12 @@ export function ${camel}Model(id: LiteralUnion<${prefix}ModelId, string>): Model
329
397
  }
330
398
  `;
331
399
 
332
- const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
333
- writeFileSync(entryPath, entryContent, "utf-8");
400
+ const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
401
+ writeFileSync(entryPath, entryContent, "utf8");
334
402
 
335
- ctx.logger.success(`${providerKey} (${lines.length} models)`);
336
- 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
+ }
337
406
  }
338
407
 
339
408
  // Catalog barrel
@@ -353,16 +422,16 @@ ${spreads}
353
422
  ] as const satisfies readonly ModelDefinition[]
354
423
  `;
355
424
 
356
- writeFileSync(join(CATALOG_DIR, "index.ts"), catalogBarrel, "utf-8");
425
+ writeFileSync(join(CATALOG_DIR, "index.ts"), catalogBarrel, "utf8");
357
426
  ctx.logger.success("catalog/providers/index.ts (barrel)");
358
427
 
359
428
  // Write generated entries list for tsdown config
360
429
  const entryPoints = providerFiles.map((p) => `src/providers/${p.provider}.ts`);
361
- writeFileSync(ENTRIES_PATH, JSON.stringify(entryPoints, null, 2), "utf-8");
430
+ writeFileSync(ENTRIES_PATH, JSON.stringify(entryPoints, null, 2), "utf8");
362
431
  ctx.logger.success(".generated/entries.json");
363
432
 
364
433
  // Update package.json exports map
365
- const pkgRaw = readFileSync(PACKAGE_JSON_PATH, "utf-8");
434
+ const pkgRaw = readFileSync(PACKAGE_JSON_PATH, "utf8");
366
435
  const pkg = JSON.parse(pkgRaw);
367
436
 
368
437
  const exportsMap: Record<string, { types: string; import: string }> = {
@@ -380,11 +449,11 @@ ${spreads}
380
449
  }
381
450
 
382
451
  pkg.exports = exportsMap;
383
- writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
452
+ writeFileSync(PACKAGE_JSON_PATH, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
384
453
  ctx.logger.success("package.json exports map updated");
385
454
 
386
455
  // Staleness timestamp
387
- writeFileSync(REQ_PATH, new Date().toISOString(), "utf-8");
456
+ writeFileSync(REQ_PATH, new Date().toISOString(), "utf8");
388
457
 
389
458
  const totalModels = providerFiles.reduce((sum, p) => sum + p.count, 0);
390
459
  ctx.logger.info(`done (${providerFiles.length} providers, ${totalModels} models)`);
@@ -2,11 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
 
3
3
  import { model, models, MODELS } from "@/catalog/index.js";
4
4
 
5
- // ---------------------------------------------------------------------------
6
- // MODELS constant
7
- // ---------------------------------------------------------------------------
8
-
9
- describe("MODELS", () => {
5
+ describe("MODELS catalog", () => {
10
6
  it("is a non-empty array", () => {
11
7
  expect(MODELS.length).toBeGreaterThan(0);
12
8
  });
@@ -18,8 +14,8 @@ describe("MODELS", () => {
18
14
  expect(typeof m.provider).toBe("string");
19
15
  expect(typeof m.pricing.input).toBe("number");
20
16
  expect(typeof m.pricing.output).toBe("number");
21
- expect(Array.isArray(m.modalities.input)).toBe(true);
22
- expect(Array.isArray(m.modalities.output)).toBe(true);
17
+ expect(Array.isArray(m.modalities.input)).toBeTruthy();
18
+ expect(Array.isArray(m.modalities.output)).toBeTruthy();
23
19
  expect(typeof m.capabilities.reasoning).toBe("boolean");
24
20
  }
25
21
  });
@@ -34,17 +30,13 @@ describe("MODELS", () => {
34
30
  const seen = new Map<string, Set<string>>();
35
31
  for (const m of MODELS) {
36
32
  const providerSet = seen.get(m.provider) ?? new Set<string>();
37
- expect(providerSet.has(m.id)).toBe(false);
33
+ expect(providerSet.has(m.id)).toBeFalsy();
38
34
  providerSet.add(m.id);
39
35
  seen.set(m.provider, providerSet);
40
36
  }
41
37
  });
42
38
  });
43
39
 
44
- // ---------------------------------------------------------------------------
45
- // model()
46
- // ---------------------------------------------------------------------------
47
-
48
40
  describe("model()", () => {
49
41
  it("returns the model definition for a known ID", () => {
50
42
  const result = model("gpt-4o-mini");
@@ -66,7 +58,7 @@ describe("model()", () => {
66
58
  const result = model("o1");
67
59
 
68
60
  expect(result).not.toBeNull();
69
- expect(result!.capabilities.reasoning).toBe(true);
61
+ expect(result!.capabilities.reasoning).toBeTruthy();
70
62
  });
71
63
 
72
64
  it("returns model with correct modalities", () => {
@@ -78,10 +70,6 @@ describe("model()", () => {
78
70
  });
79
71
  });
80
72
 
81
- // ---------------------------------------------------------------------------
82
- // models()
83
- // ---------------------------------------------------------------------------
84
-
85
73
  describe("models()", () => {
86
74
  it("returns all models when called without filter", () => {
87
75
  const result = models();
@@ -94,7 +82,7 @@ describe("models()", () => {
94
82
 
95
83
  expect(reasoningModels.length).toBeGreaterThan(0);
96
84
  for (const m of reasoningModels) {
97
- expect(m.capabilities.reasoning).toBe(true);
85
+ expect(m.capabilities.reasoning).toBeTruthy();
98
86
  }
99
87
  });
100
88
 
@@ -114,11 +102,11 @@ describe("models()", () => {
114
102
  });
115
103
 
116
104
  it("supports arbitrary filter predicates", () => {
117
- const result = models((m) => m.pricing.input > 0.000001);
105
+ const result = models((m) => m.pricing.input > 0.000_001);
118
106
 
119
107
  expect(result.length).toBeGreaterThan(0);
120
108
  for (const m of result) {
121
- expect(m.pricing.input).toBeGreaterThan(0.000001);
109
+ expect(m.pricing.input).toBeGreaterThan(0.000_001);
122
110
  }
123
111
  });
124
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
  }