@funkai/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.
Files changed (213) hide show
  1. package/.generated/entries.json +23 -0
  2. package/.generated/req.txt +1 -0
  3. package/.turbo/turbo-build.log +145 -0
  4. package/.turbo/turbo-typecheck.log +4 -0
  5. package/CHANGELOG.md +23 -0
  6. package/README.md +95 -0
  7. package/dist/alibaba-B6q4Ng1R.mjs +957 -0
  8. package/dist/alibaba-B6q4Ng1R.mjs.map +1 -0
  9. package/dist/amazon-bedrock-Cv9AHQBH.mjs +2070 -0
  10. package/dist/amazon-bedrock-Cv9AHQBH.mjs.map +1 -0
  11. package/dist/anthropic-yB7ST97_.mjs +651 -0
  12. package/dist/anthropic-yB7ST97_.mjs.map +1 -0
  13. package/dist/cerebras-COfl7XM-.mjs +95 -0
  14. package/dist/cerebras-COfl7XM-.mjs.map +1 -0
  15. package/dist/cohere-B7TgO0hT.mjs +271 -0
  16. package/dist/cohere-B7TgO0hT.mjs.map +1 -0
  17. package/dist/deepinfra-B0GxUwCG.mjs +636 -0
  18. package/dist/deepinfra-B0GxUwCG.mjs.map +1 -0
  19. package/dist/deepseek-D64ZEsvS.mjs +50 -0
  20. package/dist/deepseek-D64ZEsvS.mjs.map +1 -0
  21. package/dist/fireworks-ai-DJYvdAi_.mjs +304 -0
  22. package/dist/fireworks-ai-DJYvdAi_.mjs.map +1 -0
  23. package/dist/google-BypRl349.mjs +833 -0
  24. package/dist/google-BypRl349.mjs.map +1 -0
  25. package/dist/google-vertex-DbS-zTGD.mjs +730 -0
  26. package/dist/google-vertex-DbS-zTGD.mjs.map +1 -0
  27. package/dist/groq-ei_PerYi.mjs +381 -0
  28. package/dist/groq-ei_PerYi.mjs.map +1 -0
  29. package/dist/huggingface-DaM1EeLP.mjs +456 -0
  30. package/dist/huggingface-DaM1EeLP.mjs.map +1 -0
  31. package/dist/inception-CspEzqNV.mjs +101 -0
  32. package/dist/inception-CspEzqNV.mjs.map +1 -0
  33. package/dist/index.d.mts +30314 -0
  34. package/dist/index.d.mts.map +1 -0
  35. package/dist/index.mjs +271 -0
  36. package/dist/index.mjs.map +1 -0
  37. package/dist/llama-Cf3-koap.mjs +161 -0
  38. package/dist/llama-Cf3-koap.mjs.map +1 -0
  39. package/dist/mistral-BI9MdAO4.mjs +579 -0
  40. package/dist/mistral-BI9MdAO4.mjs.map +1 -0
  41. package/dist/nvidia-COHacuoa.mjs +1625 -0
  42. package/dist/nvidia-COHacuoa.mjs.map +1 -0
  43. package/dist/openai-C0nCfZUq.mjs +1023 -0
  44. package/dist/openai-C0nCfZUq.mjs.map +1 -0
  45. package/dist/openrouter-DSFzxKQb.mjs +4608 -0
  46. package/dist/openrouter-DSFzxKQb.mjs.map +1 -0
  47. package/dist/perplexity-zeZ2WlBU.mjs +96 -0
  48. package/dist/perplexity-zeZ2WlBU.mjs.map +1 -0
  49. package/dist/providers/alibaba.d.mts +1795 -0
  50. package/dist/providers/alibaba.d.mts.map +1 -0
  51. package/dist/providers/alibaba.mjs +39 -0
  52. package/dist/providers/alibaba.mjs.map +1 -0
  53. package/dist/providers/amazon-bedrock.d.mts +3713 -0
  54. package/dist/providers/amazon-bedrock.d.mts.map +1 -0
  55. package/dist/providers/amazon-bedrock.mjs +39 -0
  56. package/dist/providers/amazon-bedrock.mjs.map +1 -0
  57. package/dist/providers/anthropic.d.mts +1109 -0
  58. package/dist/providers/anthropic.d.mts.map +1 -0
  59. package/dist/providers/anthropic.mjs +39 -0
  60. package/dist/providers/anthropic.mjs.map +1 -0
  61. package/dist/providers/cerebras.d.mts +219 -0
  62. package/dist/providers/cerebras.d.mts.map +1 -0
  63. package/dist/providers/cerebras.mjs +39 -0
  64. package/dist/providers/cerebras.mjs.map +1 -0
  65. package/dist/providers/cohere.d.mts +555 -0
  66. package/dist/providers/cohere.d.mts.map +1 -0
  67. package/dist/providers/cohere.mjs +39 -0
  68. package/dist/providers/cohere.mjs.map +1 -0
  69. package/dist/providers/deepinfra.d.mts +1245 -0
  70. package/dist/providers/deepinfra.d.mts.map +1 -0
  71. package/dist/providers/deepinfra.mjs +39 -0
  72. package/dist/providers/deepinfra.mjs.map +1 -0
  73. package/dist/providers/deepseek.d.mts +139 -0
  74. package/dist/providers/deepseek.d.mts.map +1 -0
  75. package/dist/providers/deepseek.mjs +39 -0
  76. package/dist/providers/deepseek.mjs.map +1 -0
  77. package/dist/providers/fireworks-ai.d.mts +611 -0
  78. package/dist/providers/fireworks-ai.d.mts.map +1 -0
  79. package/dist/providers/fireworks-ai.mjs +39 -0
  80. package/dist/providers/fireworks-ai.mjs.map +1 -0
  81. package/dist/providers/google-vertex.d.mts +1227 -0
  82. package/dist/providers/google-vertex.d.mts.map +1 -0
  83. package/dist/providers/google-vertex.mjs +39 -0
  84. package/dist/providers/google-vertex.mjs.map +1 -0
  85. package/dist/providers/google.d.mts +1359 -0
  86. package/dist/providers/google.d.mts.map +1 -0
  87. package/dist/providers/google.mjs +39 -0
  88. package/dist/providers/google.mjs.map +1 -0
  89. package/dist/providers/groq.d.mts +765 -0
  90. package/dist/providers/groq.d.mts.map +1 -0
  91. package/dist/providers/groq.mjs +39 -0
  92. package/dist/providers/groq.mjs.map +1 -0
  93. package/dist/providers/huggingface.d.mts +901 -0
  94. package/dist/providers/huggingface.d.mts.map +1 -0
  95. package/dist/providers/huggingface.mjs +39 -0
  96. package/dist/providers/huggingface.mjs.map +1 -0
  97. package/dist/providers/inception.d.mts +231 -0
  98. package/dist/providers/inception.d.mts.map +1 -0
  99. package/dist/providers/inception.mjs +39 -0
  100. package/dist/providers/inception.mjs.map +1 -0
  101. package/dist/providers/llama.d.mts +345 -0
  102. package/dist/providers/llama.d.mts.map +1 -0
  103. package/dist/providers/llama.mjs +39 -0
  104. package/dist/providers/llama.mjs.map +1 -0
  105. package/dist/providers/mistral.d.mts +1143 -0
  106. package/dist/providers/mistral.d.mts.map +1 -0
  107. package/dist/providers/mistral.mjs +39 -0
  108. package/dist/providers/mistral.mjs.map +1 -0
  109. package/dist/providers/nvidia.d.mts +3117 -0
  110. package/dist/providers/nvidia.d.mts.map +1 -0
  111. package/dist/providers/nvidia.mjs +39 -0
  112. package/dist/providers/nvidia.mjs.map +1 -0
  113. package/dist/providers/openai.d.mts +1963 -0
  114. package/dist/providers/openai.d.mts.map +1 -0
  115. package/dist/providers/openai.mjs +39 -0
  116. package/dist/providers/openai.mjs.map +1 -0
  117. package/dist/providers/openrouter.d.mts +8531 -0
  118. package/dist/providers/openrouter.d.mts.map +1 -0
  119. package/dist/providers/openrouter.mjs +39 -0
  120. package/dist/providers/openrouter.mjs.map +1 -0
  121. package/dist/providers/perplexity.d.mts +221 -0
  122. package/dist/providers/perplexity.d.mts.map +1 -0
  123. package/dist/providers/perplexity.mjs +39 -0
  124. package/dist/providers/perplexity.mjs.map +1 -0
  125. package/dist/providers/togetherai.d.mts +767 -0
  126. package/dist/providers/togetherai.d.mts.map +1 -0
  127. package/dist/providers/togetherai.mjs +39 -0
  128. package/dist/providers/togetherai.mjs.map +1 -0
  129. package/dist/providers/xai.d.mts +1161 -0
  130. package/dist/providers/xai.d.mts.map +1 -0
  131. package/dist/providers/xai.mjs +39 -0
  132. package/dist/providers/xai.mjs.map +1 -0
  133. package/dist/togetherai-BvcxUfPE.mjs +382 -0
  134. package/dist/togetherai-BvcxUfPE.mjs.map +1 -0
  135. package/dist/types-DjdaZckF.d.mts +71 -0
  136. package/dist/types-DjdaZckF.d.mts.map +1 -0
  137. package/dist/xai-fSuAkQJo.mjs +587 -0
  138. package/dist/xai-fSuAkQJo.mjs.map +1 -0
  139. package/docs/catalog/filtering.md +102 -0
  140. package/docs/catalog/overview.md +168 -0
  141. package/docs/catalog/providers.md +73 -0
  142. package/docs/cost/overview.md +125 -0
  143. package/docs/guides/filter-models.md +113 -0
  144. package/docs/guides/setup-resolver.md +106 -0
  145. package/docs/guides/track-costs.md +133 -0
  146. package/docs/overview.md +139 -0
  147. package/docs/provider/configuration.md +100 -0
  148. package/docs/provider/openrouter.md +105 -0
  149. package/docs/provider/overview.md +131 -0
  150. package/docs/troubleshooting.md +100 -0
  151. package/package.json +142 -0
  152. package/providers.json +39 -0
  153. package/scripts/generate-models.ts +392 -0
  154. package/src/catalog/index.test.ts +124 -0
  155. package/src/catalog/index.ts +65 -0
  156. package/src/catalog/providers/alibaba.ts +468 -0
  157. package/src/catalog/providers/amazon-bedrock.ts +941 -0
  158. package/src/catalog/providers/anthropic.ts +270 -0
  159. package/src/catalog/providers/cerebras.ts +61 -0
  160. package/src/catalog/providers/cohere.ts +149 -0
  161. package/src/catalog/providers/deepinfra.ts +325 -0
  162. package/src/catalog/providers/deepseek.ts +39 -0
  163. package/src/catalog/providers/fireworks-ai.ts +160 -0
  164. package/src/catalog/providers/google-vertex.ts +314 -0
  165. package/src/catalog/providers/google.ts +347 -0
  166. package/src/catalog/providers/groq.ts +204 -0
  167. package/src/catalog/providers/huggingface.ts +237 -0
  168. package/src/catalog/providers/inception.ts +61 -0
  169. package/src/catalog/providers/index.ts +59 -0
  170. package/src/catalog/providers/llama.ts +94 -0
  171. package/src/catalog/providers/mistral.ts +303 -0
  172. package/src/catalog/providers/nvidia.ts +820 -0
  173. package/src/catalog/providers/openai.ts +501 -0
  174. package/src/catalog/providers/openrouter.ts +2201 -0
  175. package/src/catalog/providers/perplexity.ts +61 -0
  176. package/src/catalog/providers/togetherai.ts +204 -0
  177. package/src/catalog/providers/xai.ts +292 -0
  178. package/src/catalog/types.ts +86 -0
  179. package/src/cost/calculate.test.ts +157 -0
  180. package/src/cost/calculate.ts +43 -0
  181. package/src/cost/index.ts +2 -0
  182. package/src/cost/types.ts +25 -0
  183. package/src/index.ts +25 -0
  184. package/src/provider/index.ts +9 -0
  185. package/src/provider/openrouter.test.ts +125 -0
  186. package/src/provider/openrouter.ts +110 -0
  187. package/src/provider/resolver.test.ts +138 -0
  188. package/src/provider/resolver.ts +125 -0
  189. package/src/provider/types.ts +39 -0
  190. package/src/providers/alibaba.ts +65 -0
  191. package/src/providers/amazon-bedrock.ts +67 -0
  192. package/src/providers/anthropic.ts +65 -0
  193. package/src/providers/cerebras.ts +65 -0
  194. package/src/providers/cohere.ts +65 -0
  195. package/src/providers/deepinfra.ts +65 -0
  196. package/src/providers/deepseek.ts +65 -0
  197. package/src/providers/fireworks-ai.ts +65 -0
  198. package/src/providers/google-vertex.ts +67 -0
  199. package/src/providers/google.ts +65 -0
  200. package/src/providers/groq.ts +65 -0
  201. package/src/providers/huggingface.ts +67 -0
  202. package/src/providers/inception.ts +65 -0
  203. package/src/providers/llama.ts +65 -0
  204. package/src/providers/mistral.ts +65 -0
  205. package/src/providers/nvidia.ts +65 -0
  206. package/src/providers/openai.ts +65 -0
  207. package/src/providers/openrouter.ts +67 -0
  208. package/src/providers/perplexity.ts +67 -0
  209. package/src/providers/togetherai.ts +65 -0
  210. package/src/providers/xai.ts +65 -0
  211. package/tsconfig.json +25 -0
  212. package/tsdown.config.ts +23 -0
  213. package/vitest.config.ts +29 -0
package/package.json ADDED
@@ -0,0 +1,142 @@
1
+ {
2
+ "name": "@funkai/models",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Model catalog, provider resolution, and cost calculations for the funkai AI SDK",
6
+ "keywords": [
7
+ "ai",
8
+ "ai-sdk",
9
+ "models",
10
+ "openrouter",
11
+ "pricing",
12
+ "providers",
13
+ "typescript"
14
+ ],
15
+ "homepage": "https://github.com/joggrdocs/funkai/tree/main/packages/models#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/joggrdocs/funkai/issues"
18
+ },
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/joggrdocs/funkai.git",
23
+ "directory": "packages/models"
24
+ },
25
+ "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.mts",
29
+ "import": "./dist/index.mjs"
30
+ },
31
+ "./openai": {
32
+ "types": "./dist/providers/openai.d.mts",
33
+ "import": "./dist/providers/openai.mjs"
34
+ },
35
+ "./anthropic": {
36
+ "types": "./dist/providers/anthropic.d.mts",
37
+ "import": "./dist/providers/anthropic.mjs"
38
+ },
39
+ "./google": {
40
+ "types": "./dist/providers/google.d.mts",
41
+ "import": "./dist/providers/google.mjs"
42
+ },
43
+ "./google-vertex": {
44
+ "types": "./dist/providers/google-vertex.d.mts",
45
+ "import": "./dist/providers/google-vertex.mjs"
46
+ },
47
+ "./mistral": {
48
+ "types": "./dist/providers/mistral.d.mts",
49
+ "import": "./dist/providers/mistral.mjs"
50
+ },
51
+ "./amazon-bedrock": {
52
+ "types": "./dist/providers/amazon-bedrock.d.mts",
53
+ "import": "./dist/providers/amazon-bedrock.mjs"
54
+ },
55
+ "./groq": {
56
+ "types": "./dist/providers/groq.d.mts",
57
+ "import": "./dist/providers/groq.mjs"
58
+ },
59
+ "./deepseek": {
60
+ "types": "./dist/providers/deepseek.d.mts",
61
+ "import": "./dist/providers/deepseek.mjs"
62
+ },
63
+ "./xai": {
64
+ "types": "./dist/providers/xai.d.mts",
65
+ "import": "./dist/providers/xai.mjs"
66
+ },
67
+ "./cohere": {
68
+ "types": "./dist/providers/cohere.d.mts",
69
+ "import": "./dist/providers/cohere.mjs"
70
+ },
71
+ "./fireworks-ai": {
72
+ "types": "./dist/providers/fireworks-ai.d.mts",
73
+ "import": "./dist/providers/fireworks-ai.mjs"
74
+ },
75
+ "./togetherai": {
76
+ "types": "./dist/providers/togetherai.d.mts",
77
+ "import": "./dist/providers/togetherai.mjs"
78
+ },
79
+ "./deepinfra": {
80
+ "types": "./dist/providers/deepinfra.d.mts",
81
+ "import": "./dist/providers/deepinfra.mjs"
82
+ },
83
+ "./cerebras": {
84
+ "types": "./dist/providers/cerebras.d.mts",
85
+ "import": "./dist/providers/cerebras.mjs"
86
+ },
87
+ "./perplexity": {
88
+ "types": "./dist/providers/perplexity.d.mts",
89
+ "import": "./dist/providers/perplexity.mjs"
90
+ },
91
+ "./openrouter": {
92
+ "types": "./dist/providers/openrouter.d.mts",
93
+ "import": "./dist/providers/openrouter.mjs"
94
+ },
95
+ "./llama": {
96
+ "types": "./dist/providers/llama.d.mts",
97
+ "import": "./dist/providers/llama.mjs"
98
+ },
99
+ "./alibaba": {
100
+ "types": "./dist/providers/alibaba.d.mts",
101
+ "import": "./dist/providers/alibaba.mjs"
102
+ },
103
+ "./nvidia": {
104
+ "types": "./dist/providers/nvidia.d.mts",
105
+ "import": "./dist/providers/nvidia.mjs"
106
+ },
107
+ "./huggingface": {
108
+ "types": "./dist/providers/huggingface.d.mts",
109
+ "import": "./dist/providers/huggingface.mjs"
110
+ },
111
+ "./inception": {
112
+ "types": "./dist/providers/inception.d.mts",
113
+ "import": "./dist/providers/inception.mjs"
114
+ }
115
+ },
116
+ "scripts": {
117
+ "generate:models": "lauf run @funkai/models/generate-models",
118
+ "build": "tsdown",
119
+ "typecheck": "tsc --noEmit",
120
+ "test": "vitest run --typecheck",
121
+ "test:coverage": "vitest run --coverage"
122
+ },
123
+ "dependencies": {
124
+ "@openrouter/ai-sdk-provider": "^2.3.1",
125
+ "ai": "^6.0.116",
126
+ "type-fest": "^5.4.4"
127
+ },
128
+ "devDependencies": {
129
+ "@types/node": "catalog:",
130
+ "@vitest/coverage-v8": "catalog:",
131
+ "tsdown": "catalog:",
132
+ "tsx": "catalog:",
133
+ "typescript": "catalog:",
134
+ "vitest": "catalog:"
135
+ },
136
+ "publishConfig": {
137
+ "access": "public"
138
+ },
139
+ "engines": {
140
+ "node": ">=24.0.0"
141
+ }
142
+ }
package/providers.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "openai": { "name": "OpenAI", "prefix": "OpenAI", "sdk": "@ai-sdk/openai" },
3
+ "anthropic": { "name": "Anthropic", "prefix": "Anthropic", "sdk": "@ai-sdk/anthropic" },
4
+ "google": { "name": "Google", "prefix": "Google", "sdk": "@ai-sdk/google" },
5
+ "google-vertex": {
6
+ "name": "Google Vertex AI",
7
+ "prefix": "GoogleVertex",
8
+ "sdk": "@ai-sdk/google-vertex"
9
+ },
10
+ "mistral": { "name": "Mistral", "prefix": "Mistral", "sdk": "@ai-sdk/mistral" },
11
+ "amazon-bedrock": {
12
+ "name": "Amazon Bedrock",
13
+ "prefix": "AmazonBedrock",
14
+ "sdk": "@ai-sdk/amazon-bedrock"
15
+ },
16
+ "groq": { "name": "Groq", "prefix": "Groq", "sdk": "@ai-sdk/groq" },
17
+ "deepseek": { "name": "DeepSeek", "prefix": "DeepSeek", "sdk": "@ai-sdk/deepseek" },
18
+ "xai": { "name": "xAI", "prefix": "XAI", "sdk": "@ai-sdk/xai" },
19
+ "cohere": { "name": "Cohere", "prefix": "Cohere", "sdk": "@ai-sdk/cohere" },
20
+ "fireworks-ai": { "name": "Fireworks AI", "prefix": "Fireworks", "sdk": "@ai-sdk/fireworks" },
21
+ "togetherai": { "name": "Together AI", "prefix": "Together", "sdk": "@ai-sdk/togetherai" },
22
+ "deepinfra": { "name": "DeepInfra", "prefix": "DeepInfra", "sdk": "@ai-sdk/deepinfra" },
23
+ "cerebras": { "name": "Cerebras", "prefix": "Cerebras", "sdk": "@ai-sdk/cerebras" },
24
+ "perplexity": { "name": "Perplexity", "prefix": "Perplexity", "sdk": "@ai-sdk/perplexity" },
25
+ "openrouter": {
26
+ "name": "OpenRouter",
27
+ "prefix": "OpenRouter",
28
+ "sdk": "@openrouter/ai-sdk-provider"
29
+ },
30
+ "llama": { "name": "Meta Llama", "prefix": "Llama", "sdk": "@ai-sdk/openai-compatible" },
31
+ "alibaba": { "name": "Alibaba (Qwen)", "prefix": "Alibaba", "sdk": "@ai-sdk/alibaba" },
32
+ "nvidia": { "name": "NVIDIA NIM", "prefix": "Nvidia", "sdk": "@ai-sdk/openai-compatible" },
33
+ "huggingface": { "name": "Hugging Face", "prefix": "HuggingFace", "sdk": "@ai-sdk/huggingface" },
34
+ "inception": {
35
+ "name": "Inception (Mercury)",
36
+ "prefix": "Inception",
37
+ "sdk": "@ai-sdk/openai-compatible"
38
+ }
39
+ }
@@ -0,0 +1,392 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { lauf, z } from "laufen";
5
+
6
+ const API_URL = "https://models.dev/api.json";
7
+ const STALE_MS = 24 * 60 * 60 * 1000;
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Banner
11
+ // ---------------------------------------------------------------------------
12
+
13
+ const BANNER = `// ──────────────────────────────────────────────────────────────
14
+ // ███████╗██╗ ██╗███╗ ██╗██╗ ██╗ █████╗ ██╗
15
+ // ██╔════╝██║ ██║████╗ ██║██║ ██╔╝██╔══██╗██║
16
+ // █████╗ ██║ ██║██╔██╗ ██║█████╔╝ ███████║██║
17
+ // ██╔══╝ ██║ ██║██║╚██╗██║██╔═██╗ ██╔══██║██║
18
+ // ██║ ╚██████╔╝██║ ╚████║██║ ██╗██║ ██║██║
19
+ // ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝
20
+ //
21
+ // AUTO-GENERATED — DO NOT EDIT
22
+ // Source: https://models.dev
23
+ // Update: pnpm --filter=@funkai/models generate:models
24
+ // ──────────────────────────────────────────────────────────────`;
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Types
28
+ // ---------------------------------------------------------------------------
29
+
30
+ interface ProviderEntry {
31
+ name: string;
32
+ prefix: string;
33
+ sdk: string;
34
+ }
35
+
36
+ interface ApiModel {
37
+ id: string;
38
+ name?: string;
39
+ family?: string;
40
+ reasoning?: boolean;
41
+ tool_call?: boolean;
42
+ attachment?: boolean;
43
+ structured_output?: boolean;
44
+ modalities?: { input?: string[]; output?: string[] };
45
+ cost?: {
46
+ input?: number;
47
+ output?: number;
48
+ cache_read?: number;
49
+ cache_write?: number;
50
+ reasoning?: number;
51
+ };
52
+ limit?: { context?: number; output?: number };
53
+ }
54
+
55
+ interface ApiProvider {
56
+ id: string;
57
+ name: string;
58
+ models: Record<string, ApiModel>;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Helpers
63
+ // ---------------------------------------------------------------------------
64
+
65
+ /**
66
+ * Convert a provider key to a TypeScript constant name.
67
+ * e.g. "openai" → "OPENAI_MODELS", "meta-llama" → "META_LLAMA_MODELS"
68
+ */
69
+ function toConstName(provider: string): string {
70
+ return `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_MODELS`;
71
+ }
72
+
73
+ /**
74
+ * Lowercase the first character of a string, preserving the rest as-is.
75
+ * e.g. "OpenAI" → "openAI", "GoogleVertex" → "googleVertex", "XAI" → "xAI"
76
+ */
77
+ function lowerFirst(s: string): string {
78
+ return s.length === 0 ? s : s[0]!.toLowerCase() + s.slice(1);
79
+ }
80
+
81
+ /**
82
+ * Return the correct indefinite article ("a" or "an") for a word.
83
+ */
84
+ function article(word: string): string {
85
+ return /^[aeiou]/i.test(word) ? "an" : "a";
86
+ }
87
+
88
+ /**
89
+ * Convert per-million-token rate to per-token rate, rounding to
90
+ * eliminate floating-point noise (e.g. `8.000000000000001e-7`).
91
+ */
92
+ function toPerToken(perMillion: number): number {
93
+ return parseFloat((perMillion / 1_000_000).toPrecision(6));
94
+ }
95
+
96
+ /**
97
+ * Format a number for codegen output, using scientific notation for
98
+ * very small values.
99
+ */
100
+ function fmtNum(n: number): string {
101
+ if (n === 0) return "0";
102
+ if (n < 0.0000001) return n.toExponential();
103
+ return String(n);
104
+ }
105
+
106
+ /**
107
+ * Build the pricing object literal string for a model.
108
+ */
109
+ function buildPricing(cost: ApiModel["cost"]): string {
110
+ const input = toPerToken(cost?.input ?? 0);
111
+ const output = toPerToken(cost?.output ?? 0);
112
+ const parts: string[] = [`input: ${fmtNum(input)}`, `output: ${fmtNum(output)}`];
113
+
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))}`);
122
+ }
123
+
124
+ return `{ ${parts.join(", ")} }`;
125
+ }
126
+
127
+ /**
128
+ * Build the modalities object literal string.
129
+ */
130
+ function buildModalities(modalities: ApiModel["modalities"]): string {
131
+ const input = JSON.stringify(modalities?.input ?? ["text"]);
132
+ const output = JSON.stringify(modalities?.output ?? ["text"]);
133
+ return `{ input: ${input}, output: ${output} }`;
134
+ }
135
+
136
+ /**
137
+ * Build the capabilities object literal string.
138
+ */
139
+ function buildCapabilities(m: ApiModel): string {
140
+ return [
141
+ `reasoning: ${Boolean(m.reasoning)}`,
142
+ `toolCall: ${Boolean(m.tool_call)}`,
143
+ `attachment: ${Boolean(m.attachment)}`,
144
+ `structuredOutput: ${Boolean(m.structured_output)}`,
145
+ ].join(", ");
146
+ }
147
+
148
+ /**
149
+ * Escape a string for use in a TypeScript single-quoted string literal.
150
+ */
151
+ function escapeStr(s: string): string {
152
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Staleness check
157
+ // ---------------------------------------------------------------------------
158
+
159
+ function isFresh(reqPath: string): boolean {
160
+ if (!existsSync(reqPath)) {
161
+ return false;
162
+ }
163
+ try {
164
+ const timestamp = readFileSync(reqPath, "utf-8").trim();
165
+ const lastRun = new Date(timestamp).getTime();
166
+ return Date.now() - lastRun < STALE_MS;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+
172
+ // ---------------------------------------------------------------------------
173
+ // Script
174
+ // ---------------------------------------------------------------------------
175
+
176
+ export default lauf({
177
+ description: "Fetch model data from models.dev and generate TypeScript catalog files",
178
+ args: {
179
+ force: z.boolean().default(false).describe("Force-fetch ignoring staleness cache"),
180
+ },
181
+ async run(ctx) {
182
+ const PACKAGE_ROOT = ctx.dir.package;
183
+ const PROVIDERS_PATH = join(PACKAGE_ROOT, "providers.json");
184
+ const CATALOG_DIR = join(PACKAGE_ROOT, "src", "catalog", "providers");
185
+ const ENTRY_DIR = join(PACKAGE_ROOT, "src", "providers");
186
+ const GENERATED_DIR = join(PACKAGE_ROOT, ".generated");
187
+ const REQ_PATH = join(GENERATED_DIR, "req.txt");
188
+ const ENTRIES_PATH = join(GENERATED_DIR, "entries.json");
189
+ const PACKAGE_JSON_PATH = join(PACKAGE_ROOT, "package.json");
190
+
191
+ if (!ctx.args.force && isFresh(REQ_PATH)) {
192
+ ctx.logger.info("skipping — last fetch was less than 24h ago");
193
+ return;
194
+ }
195
+
196
+ // Read provider config
197
+ const providers: Record<string, ProviderEntry> = JSON.parse(
198
+ readFileSync(PROVIDERS_PATH, "utf-8"),
199
+ );
200
+ const providerKeys = Object.keys(providers);
201
+
202
+ if (providerKeys.length === 0) {
203
+ throw new Error("providers.json has no providers");
204
+ }
205
+
206
+ // Fetch models.dev API
207
+ ctx.spinner.start("Fetching models from models.dev");
208
+ const response = await fetch(API_URL, { signal: AbortSignal.timeout(30_000) });
209
+ if (!response.ok) {
210
+ throw new Error(`Failed to fetch ${API_URL}: ${response.status} ${response.statusText}`);
211
+ }
212
+ const apiData: Record<string, ApiProvider> = await response.json();
213
+ ctx.spinner.stop(`${Object.keys(apiData).length} providers from API`);
214
+
215
+ // Fail fast if any configured provider is missing from the API
216
+ const missingProviders = providerKeys.filter((key) => !apiData[key]);
217
+ if (missingProviders.length > 0) {
218
+ throw new Error(
219
+ `models.dev API is missing configured providers: ${missingProviders.join(", ")}`,
220
+ );
221
+ }
222
+
223
+ mkdirSync(GENERATED_DIR, { recursive: true });
224
+
225
+ // Clean and recreate catalog providers dir
226
+ rmSync(CATALOG_DIR, { recursive: true, force: true });
227
+ mkdirSync(CATALOG_DIR, { recursive: true });
228
+
229
+ // Clean and recreate entry points dir
230
+ rmSync(ENTRY_DIR, { recursive: true, force: true });
231
+ mkdirSync(ENTRY_DIR, { recursive: true });
232
+
233
+ const providerFiles: { provider: string; constName: string; count: number }[] = [];
234
+
235
+ 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}
260
+
261
+ import type { ModelDefinition } from '../types.js'
262
+
263
+ export const ${constName} = [
264
+ ${lines.join("\n")}
265
+ ] as const satisfies readonly ModelDefinition[]
266
+ `;
267
+
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}
278
+
279
+ import type { LiteralUnion } from 'type-fest'
280
+ import type { ModelDefinition } from '../catalog/types.js'
281
+ import { ${constName} } from '../catalog/providers/${providerKey}.js'
282
+
283
+ /**
284
+ * Known model identifiers for ${providerName}.
285
+ *
286
+ * @example
287
+ * \`\`\`typescript
288
+ * import type { ${prefix}ModelId } from '@funkai/models/${providerKey}'
289
+ *
290
+ * const id: ${prefix}ModelId = '${exampleId}'
291
+ * \`\`\`
292
+ */
293
+ export type ${prefix}ModelId = (typeof ${constName})[number]['id']
294
+
295
+ /**
296
+ * All ${providerName} models in the catalog.
297
+ *
298
+ * @example
299
+ * \`\`\`typescript
300
+ * import { ${camel}Models } from '@funkai/models/${providerKey}'
301
+ *
302
+ * for (const m of ${camel}Models) {
303
+ * console.log(m.id, m.pricing.input)
304
+ * }
305
+ * \`\`\`
306
+ */
307
+ export const ${camel}Models = ${constName}
308
+
309
+ const MODEL_INDEX = new Map<string, ModelDefinition>(${constName}.map((m) => [m.id, m]))
310
+
311
+ /**
312
+ * Look up ${art} ${providerName} model by ID.
313
+ *
314
+ * @param id - The provider-native model identifier.
315
+ * @returns The matching model definition, or \`null\`.
316
+ *
317
+ * @example
318
+ * \`\`\`typescript
319
+ * import { ${camel}Model } from '@funkai/models/${providerKey}'
320
+ *
321
+ * const m = ${camel}Model('${exampleId}')
322
+ * if (m) {
323
+ * console.log(m.pricing.input)
324
+ * }
325
+ * \`\`\`
326
+ */
327
+ export function ${camel}Model(id: LiteralUnion<${prefix}ModelId, string>): ModelDefinition | null {
328
+ return MODEL_INDEX.get(id) ?? null
329
+ }
330
+ `;
331
+
332
+ const entryPath = join(ENTRY_DIR, `${providerKey}.ts`);
333
+ writeFileSync(entryPath, entryContent, "utf-8");
334
+
335
+ ctx.logger.success(`${providerKey} (${lines.length} models)`);
336
+ providerFiles.push({ provider: providerKey, constName, count: lines.length });
337
+ }
338
+
339
+ // Catalog barrel
340
+ const imports = providerFiles
341
+ .map((p) => `import { ${p.constName} } from './${p.provider}.js'`)
342
+ .join("\n");
343
+
344
+ const spreads = providerFiles.map((p) => ` ...${p.constName},`).join("\n");
345
+
346
+ const catalogBarrel = `${BANNER}
347
+
348
+ import type { ModelDefinition } from '../types.js'
349
+ ${imports}
350
+
351
+ export const MODELS = [
352
+ ${spreads}
353
+ ] as const satisfies readonly ModelDefinition[]
354
+ `;
355
+
356
+ writeFileSync(join(CATALOG_DIR, "index.ts"), catalogBarrel, "utf-8");
357
+ ctx.logger.success("catalog/providers/index.ts (barrel)");
358
+
359
+ // Write generated entries list for tsdown config
360
+ const entryPoints = providerFiles.map((p) => `src/providers/${p.provider}.ts`);
361
+ writeFileSync(ENTRIES_PATH, JSON.stringify(entryPoints, null, 2), "utf-8");
362
+ ctx.logger.success(".generated/entries.json");
363
+
364
+ // Update package.json exports map
365
+ const pkgRaw = readFileSync(PACKAGE_JSON_PATH, "utf-8");
366
+ const pkg = JSON.parse(pkgRaw);
367
+
368
+ const exportsMap: Record<string, { types: string; import: string }> = {
369
+ ".": {
370
+ types: "./dist/index.d.mts",
371
+ import: "./dist/index.mjs",
372
+ },
373
+ };
374
+
375
+ for (const p of providerFiles) {
376
+ exportsMap[`./${p.provider}`] = {
377
+ types: `./dist/providers/${p.provider}.d.mts`,
378
+ import: `./dist/providers/${p.provider}.mjs`,
379
+ };
380
+ }
381
+
382
+ pkg.exports = exportsMap;
383
+ writeFileSync(PACKAGE_JSON_PATH, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
384
+ ctx.logger.success("package.json exports map updated");
385
+
386
+ // Staleness timestamp
387
+ writeFileSync(REQ_PATH, new Date().toISOString(), "utf-8");
388
+
389
+ const totalModels = providerFiles.reduce((sum, p) => sum + p.count, 0);
390
+ ctx.logger.info(`done (${providerFiles.length} providers, ${totalModels} models)`);
391
+ },
392
+ });