gsd-pi 2.42.0-dev.97e9e30 → 2.42.0-dev.eedc83f

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 (167) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +15 -1
  3. package/dist/resource-loader.js +39 -6
  4. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  5. package/dist/resources/extensions/gsd/auto-prompts.js +1 -1
  6. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  7. package/dist/resources/extensions/gsd/detection.js +19 -0
  8. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  9. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  10. package/dist/resources/extensions/gsd/forensics.js +84 -0
  11. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  12. package/dist/resources/extensions/gsd/git-service.js +68 -2
  13. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
  14. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  15. package/dist/resources/extensions/gsd/preferences.js +59 -8
  16. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  17. package/dist/resources/extensions/gsd/repo-identity.js +46 -5
  18. package/dist/resources/extensions/gsd/service-tier.js +13 -4
  19. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  20. package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
  21. package/dist/resources/extensions/mcp-client/index.js +2 -1
  22. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  23. package/dist/web/standalone/.next/BUILD_ID +1 -1
  24. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  25. package/dist/web/standalone/.next/build-manifest.json +2 -2
  26. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  44. package/dist/web/standalone/.next/server/app/index.html +1 -1
  45. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  52. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  53. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  54. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  55. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  56. package/dist/web-mode.d.ts +2 -0
  57. package/dist/web-mode.js +40 -4
  58. package/package.json +1 -1
  59. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  60. package/packages/pi-agent-core/dist/agent.js +2 -0
  61. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  62. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  63. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  64. package/packages/pi-agent-core/dist/types.js.map +1 -1
  65. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  66. package/packages/pi-agent-core/src/agent.ts +3 -0
  67. package/packages/pi-agent-core/src/types.ts +6 -0
  68. package/packages/pi-agent-core/tsconfig.json +1 -1
  69. package/packages/pi-ai/dist/models.d.ts +5 -3
  70. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  72. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  73. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  74. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  75. package/packages/pi-ai/dist/models.js.map +1 -1
  76. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  78. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  79. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  80. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  81. package/packages/pi-ai/src/models.ts +7 -4
  82. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  83. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  85. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  87. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  89. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  91. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  94. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  97. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  100. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  102. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  104. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  106. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  108. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  111. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  121. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  122. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  123. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  124. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  125. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  126. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  127. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  128. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  129. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  130. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  131. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  132. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
  133. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  134. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  135. package/src/resources/extensions/gsd/auto-prompts.ts +1 -1
  136. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  137. package/src/resources/extensions/gsd/detection.ts +19 -0
  138. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  139. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  140. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  141. package/src/resources/extensions/gsd/forensics.ts +92 -0
  142. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  143. package/src/resources/extensions/gsd/git-service.ts +71 -2
  144. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
  145. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  146. package/src/resources/extensions/gsd/preferences.ts +62 -6
  147. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  148. package/src/resources/extensions/gsd/repo-identity.ts +48 -5
  149. package/src/resources/extensions/gsd/service-tier.ts +17 -4
  150. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  151. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  152. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  153. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  154. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  155. package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
  156. package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
  157. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  158. package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
  159. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  160. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  161. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
  162. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
  163. package/src/resources/extensions/gsd/worktree-resolver.ts +2 -2
  164. package/src/resources/extensions/mcp-client/index.ts +5 -1
  165. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  166. /package/dist/web/standalone/.next/static/{PXrI5DoWsm7rwAVnEU2rD → JUBX5FUR73jiViQU5a-Cx}/_buildManifest.js +0 -0
  167. /package/dist/web/standalone/.next/static/{PXrI5DoWsm7rwAVnEU2rD → JUBX5FUR73jiViQU5a-Cx}/_ssgManifest.js +0 -0
@@ -0,0 +1,1543 @@
1
+ #!/usr/bin/env tsx
2
+
3
+ import { writeFileSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { Api, KnownProvider, Model } from "../src/types.js";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const packageRoot = join(__dirname, "..");
11
+
12
+ interface ModelsDevModel {
13
+ id: string;
14
+ name: string;
15
+ tool_call?: boolean;
16
+ reasoning?: boolean;
17
+ limit?: {
18
+ context?: number;
19
+ output?: number;
20
+ };
21
+ cost?: {
22
+ input?: number;
23
+ output?: number;
24
+ cache_read?: number;
25
+ cache_write?: number;
26
+ };
27
+ modalities?: {
28
+ input?: string[];
29
+ };
30
+ provider?: {
31
+ npm?: string;
32
+ };
33
+ }
34
+
35
+ interface AiGatewayModel {
36
+ id: string;
37
+ name?: string;
38
+ context_window?: number;
39
+ max_tokens?: number;
40
+ tags?: string[];
41
+ pricing?: {
42
+ input?: string | number;
43
+ output?: string | number;
44
+ input_cache_read?: string | number;
45
+ input_cache_write?: string | number;
46
+ };
47
+ }
48
+
49
+ const COPILOT_STATIC_HEADERS = {
50
+ "User-Agent": "GitHubCopilotChat/0.35.0",
51
+ "Editor-Version": "vscode/1.107.0",
52
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
53
+ "Copilot-Integration-Id": "vscode-chat",
54
+ } as const;
55
+
56
+ const AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1";
57
+ const AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
58
+
59
+ async function fetchOpenRouterModels(): Promise<Model<any>[]> {
60
+ try {
61
+ console.log("Fetching models from OpenRouter API...");
62
+ const response = await fetch("https://openrouter.ai/api/v1/models");
63
+ const data = await response.json();
64
+
65
+ const models: Model<any>[] = [];
66
+
67
+ for (const model of data.data) {
68
+ // Only include models that support tools
69
+ if (!model.supported_parameters?.includes("tools")) continue;
70
+
71
+ // Parse provider from model ID
72
+ let provider: KnownProvider = "openrouter";
73
+ let modelKey = model.id;
74
+
75
+ modelKey = model.id; // Keep full ID for OpenRouter
76
+
77
+ // Parse input modalities
78
+ const input: ("text" | "image")[] = ["text"];
79
+ if (model.architecture?.modality?.includes("image")) {
80
+ input.push("image");
81
+ }
82
+
83
+ // Convert pricing from $/token to $/million tokens
84
+ const inputCost = parseFloat(model.pricing?.prompt || "0") * 1_000_000;
85
+ const outputCost = parseFloat(model.pricing?.completion || "0") * 1_000_000;
86
+ const cacheReadCost = parseFloat(model.pricing?.input_cache_read || "0") * 1_000_000;
87
+ const cacheWriteCost = parseFloat(model.pricing?.input_cache_write || "0") * 1_000_000;
88
+
89
+ const normalizedModel: Model<any> = {
90
+ id: modelKey,
91
+ name: model.name,
92
+ api: "openai-completions",
93
+ baseUrl: "https://openrouter.ai/api/v1",
94
+ provider,
95
+ reasoning: model.supported_parameters?.includes("reasoning") || false,
96
+ input,
97
+ cost: {
98
+ input: inputCost,
99
+ output: outputCost,
100
+ cacheRead: cacheReadCost,
101
+ cacheWrite: cacheWriteCost,
102
+ },
103
+ contextWindow: model.context_length || 4096,
104
+ maxTokens: model.top_provider?.max_completion_tokens || 4096,
105
+ };
106
+ models.push(normalizedModel);
107
+ }
108
+
109
+ console.log(`Fetched ${models.length} tool-capable models from OpenRouter`);
110
+ return models;
111
+ } catch (error) {
112
+ console.error("Failed to fetch OpenRouter models:", error);
113
+ return [];
114
+ }
115
+ }
116
+
117
+ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
118
+ try {
119
+ console.log("Fetching models from Vercel AI Gateway API...");
120
+ const response = await fetch(`${AI_GATEWAY_MODELS_URL}/models`);
121
+ const data = await response.json();
122
+ const models: Model<any>[] = [];
123
+
124
+ const toNumber = (value: string | number | undefined): number => {
125
+ if (typeof value === "number") {
126
+ return Number.isFinite(value) ? value : 0;
127
+ }
128
+ const parsed = parseFloat(value ?? "0");
129
+ return Number.isFinite(parsed) ? parsed : 0;
130
+ };
131
+
132
+ const items = Array.isArray(data.data) ? (data.data as AiGatewayModel[]) : [];
133
+ for (const model of items) {
134
+ const tags = Array.isArray(model.tags) ? model.tags : [];
135
+ // Only include models that support tools
136
+ if (!tags.includes("tool-use")) continue;
137
+
138
+ const input: ("text" | "image")[] = ["text"];
139
+ if (tags.includes("vision")) {
140
+ input.push("image");
141
+ }
142
+
143
+ const inputCost = toNumber(model.pricing?.input) * 1_000_000;
144
+ const outputCost = toNumber(model.pricing?.output) * 1_000_000;
145
+ const cacheReadCost = toNumber(model.pricing?.input_cache_read) * 1_000_000;
146
+ const cacheWriteCost = toNumber(model.pricing?.input_cache_write) * 1_000_000;
147
+
148
+ models.push({
149
+ id: model.id,
150
+ name: model.name || model.id,
151
+ api: "anthropic-messages",
152
+ baseUrl: AI_GATEWAY_BASE_URL,
153
+ provider: "vercel-ai-gateway",
154
+ reasoning: tags.includes("reasoning"),
155
+ input,
156
+ cost: {
157
+ input: inputCost,
158
+ output: outputCost,
159
+ cacheRead: cacheReadCost,
160
+ cacheWrite: cacheWriteCost,
161
+ },
162
+ contextWindow: model.context_window || 4096,
163
+ maxTokens: model.max_tokens || 4096,
164
+ });
165
+ }
166
+
167
+ console.log(`Fetched ${models.length} tool-capable models from Vercel AI Gateway`);
168
+ return models;
169
+ } catch (error) {
170
+ console.error("Failed to fetch Vercel AI Gateway models:", error);
171
+ return [];
172
+ }
173
+ }
174
+
175
+ async function loadModelsDevData(): Promise<Model<any>[]> {
176
+ try {
177
+ console.log("Fetching models from models.dev API...");
178
+ const response = await fetch("https://models.dev/api.json");
179
+ const data = await response.json();
180
+
181
+ const models: Model<any>[] = [];
182
+
183
+ // Process Amazon Bedrock models
184
+ if (data["amazon-bedrock"]?.models) {
185
+ for (const [modelId, model] of Object.entries(data["amazon-bedrock"].models)) {
186
+ const m = model as ModelsDevModel;
187
+ if (m.tool_call !== true) continue;
188
+
189
+ let id = modelId;
190
+
191
+ if (id.startsWith("ai21.jamba")) {
192
+ // These models doesn't support tool use in streaming mode
193
+ continue;
194
+ }
195
+
196
+ if (id.startsWith("mistral.mistral-7b-instruct-v0")) {
197
+ // These models doesn't support system messages
198
+ continue;
199
+ }
200
+
201
+ models.push({
202
+ id,
203
+ name: m.name || id,
204
+ api: "bedrock-converse-stream" as const,
205
+ provider: "amazon-bedrock" as const,
206
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
207
+ reasoning: m.reasoning === true,
208
+ input: (m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"]) as ("text" | "image")[],
209
+ cost: {
210
+ input: m.cost?.input || 0,
211
+ output: m.cost?.output || 0,
212
+ cacheRead: m.cost?.cache_read || 0,
213
+ cacheWrite: m.cost?.cache_write || 0,
214
+ },
215
+ contextWindow: m.limit?.context || 4096,
216
+ maxTokens: m.limit?.output || 4096,
217
+ });
218
+ }
219
+ }
220
+
221
+ // Process Anthropic models
222
+ if (data.anthropic?.models) {
223
+ for (const [modelId, model] of Object.entries(data.anthropic.models)) {
224
+ const m = model as ModelsDevModel;
225
+ if (m.tool_call !== true) continue;
226
+
227
+ models.push({
228
+ id: modelId,
229
+ name: m.name || modelId,
230
+ api: "anthropic-messages",
231
+ provider: "anthropic",
232
+ baseUrl: "https://api.anthropic.com",
233
+ reasoning: m.reasoning === true,
234
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
235
+ cost: {
236
+ input: m.cost?.input || 0,
237
+ output: m.cost?.output || 0,
238
+ cacheRead: m.cost?.cache_read || 0,
239
+ cacheWrite: m.cost?.cache_write || 0,
240
+ },
241
+ contextWindow: m.limit?.context || 4096,
242
+ maxTokens: m.limit?.output || 4096,
243
+ });
244
+ }
245
+ }
246
+
247
+ // Process Google models
248
+ if (data.google?.models) {
249
+ for (const [modelId, model] of Object.entries(data.google.models)) {
250
+ const m = model as ModelsDevModel;
251
+ if (m.tool_call !== true) continue;
252
+
253
+ models.push({
254
+ id: modelId,
255
+ name: m.name || modelId,
256
+ api: "google-generative-ai",
257
+ provider: "google",
258
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
259
+ reasoning: m.reasoning === true,
260
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
261
+ cost: {
262
+ input: m.cost?.input || 0,
263
+ output: m.cost?.output || 0,
264
+ cacheRead: m.cost?.cache_read || 0,
265
+ cacheWrite: m.cost?.cache_write || 0,
266
+ },
267
+ contextWindow: m.limit?.context || 4096,
268
+ maxTokens: m.limit?.output || 4096,
269
+ });
270
+ }
271
+ }
272
+
273
+ // Process OpenAI models
274
+ if (data.openai?.models) {
275
+ for (const [modelId, model] of Object.entries(data.openai.models)) {
276
+ const m = model as ModelsDevModel;
277
+ if (m.tool_call !== true) continue;
278
+
279
+ models.push({
280
+ id: modelId,
281
+ name: m.name || modelId,
282
+ api: "openai-responses",
283
+ provider: "openai",
284
+ baseUrl: "https://api.openai.com/v1",
285
+ reasoning: m.reasoning === true,
286
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
287
+ cost: {
288
+ input: m.cost?.input || 0,
289
+ output: m.cost?.output || 0,
290
+ cacheRead: m.cost?.cache_read || 0,
291
+ cacheWrite: m.cost?.cache_write || 0,
292
+ },
293
+ contextWindow: m.limit?.context || 4096,
294
+ maxTokens: m.limit?.output || 4096,
295
+ });
296
+ }
297
+ }
298
+
299
+ // Process Groq models
300
+ if (data.groq?.models) {
301
+ for (const [modelId, model] of Object.entries(data.groq.models)) {
302
+ const m = model as ModelsDevModel;
303
+ if (m.tool_call !== true) continue;
304
+
305
+ models.push({
306
+ id: modelId,
307
+ name: m.name || modelId,
308
+ api: "openai-completions",
309
+ provider: "groq",
310
+ baseUrl: "https://api.groq.com/openai/v1",
311
+ reasoning: m.reasoning === true,
312
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
313
+ cost: {
314
+ input: m.cost?.input || 0,
315
+ output: m.cost?.output || 0,
316
+ cacheRead: m.cost?.cache_read || 0,
317
+ cacheWrite: m.cost?.cache_write || 0,
318
+ },
319
+ contextWindow: m.limit?.context || 4096,
320
+ maxTokens: m.limit?.output || 4096,
321
+ });
322
+ }
323
+ }
324
+
325
+ // Process Cerebras models
326
+ if (data.cerebras?.models) {
327
+ for (const [modelId, model] of Object.entries(data.cerebras.models)) {
328
+ const m = model as ModelsDevModel;
329
+ if (m.tool_call !== true) continue;
330
+
331
+ models.push({
332
+ id: modelId,
333
+ name: m.name || modelId,
334
+ api: "openai-completions",
335
+ provider: "cerebras",
336
+ baseUrl: "https://api.cerebras.ai/v1",
337
+ reasoning: m.reasoning === true,
338
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
339
+ cost: {
340
+ input: m.cost?.input || 0,
341
+ output: m.cost?.output || 0,
342
+ cacheRead: m.cost?.cache_read || 0,
343
+ cacheWrite: m.cost?.cache_write || 0,
344
+ },
345
+ contextWindow: m.limit?.context || 4096,
346
+ maxTokens: m.limit?.output || 4096,
347
+ });
348
+ }
349
+ }
350
+
351
+ // Process xAi models
352
+ if (data.xai?.models) {
353
+ for (const [modelId, model] of Object.entries(data.xai.models)) {
354
+ const m = model as ModelsDevModel;
355
+ if (m.tool_call !== true) continue;
356
+
357
+ models.push({
358
+ id: modelId,
359
+ name: m.name || modelId,
360
+ api: "openai-completions",
361
+ provider: "xai",
362
+ baseUrl: "https://api.x.ai/v1",
363
+ reasoning: m.reasoning === true,
364
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
365
+ cost: {
366
+ input: m.cost?.input || 0,
367
+ output: m.cost?.output || 0,
368
+ cacheRead: m.cost?.cache_read || 0,
369
+ cacheWrite: m.cost?.cache_write || 0,
370
+ },
371
+ contextWindow: m.limit?.context || 4096,
372
+ maxTokens: m.limit?.output || 4096,
373
+ });
374
+ }
375
+ }
376
+
377
+ // Process zAi models
378
+ if (data.zai?.models) {
379
+ for (const [modelId, model] of Object.entries(data.zai.models)) {
380
+ const m = model as ModelsDevModel;
381
+ if (m.tool_call !== true) continue;
382
+ const supportsImage = m.modalities?.input?.includes("image")
383
+
384
+ models.push({
385
+ id: modelId,
386
+ name: m.name || modelId,
387
+ api: "openai-completions",
388
+ provider: "zai",
389
+ baseUrl: "https://api.z.ai/api/coding/paas/v4",
390
+ reasoning: m.reasoning === true,
391
+ input: supportsImage ? ["text", "image"] : ["text"],
392
+ cost: {
393
+ input: m.cost?.input || 0,
394
+ output: m.cost?.output || 0,
395
+ cacheRead: m.cost?.cache_read || 0,
396
+ cacheWrite: m.cost?.cache_write || 0,
397
+ },
398
+ compat: {
399
+ supportsDeveloperRole: false,
400
+ thinkingFormat: "zai",
401
+ },
402
+ contextWindow: m.limit?.context || 4096,
403
+ maxTokens: m.limit?.output || 4096,
404
+ });
405
+ }
406
+ }
407
+
408
+ // Process Mistral models
409
+ if (data.mistral?.models) {
410
+ for (const [modelId, model] of Object.entries(data.mistral.models)) {
411
+ const m = model as ModelsDevModel;
412
+ if (m.tool_call !== true) continue;
413
+
414
+ models.push({
415
+ id: modelId,
416
+ name: m.name || modelId,
417
+ api: "mistral-conversations",
418
+ provider: "mistral",
419
+ baseUrl: "https://api.mistral.ai",
420
+ reasoning: m.reasoning === true,
421
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
422
+ cost: {
423
+ input: m.cost?.input || 0,
424
+ output: m.cost?.output || 0,
425
+ cacheRead: m.cost?.cache_read || 0,
426
+ cacheWrite: m.cost?.cache_write || 0,
427
+ },
428
+ contextWindow: m.limit?.context || 4096,
429
+ maxTokens: m.limit?.output || 4096,
430
+ });
431
+ }
432
+ }
433
+
434
+ // Process Hugging Face models
435
+ if (data.huggingface?.models) {
436
+ for (const [modelId, model] of Object.entries(data.huggingface.models)) {
437
+ const m = model as ModelsDevModel;
438
+ if (m.tool_call !== true) continue;
439
+
440
+ models.push({
441
+ id: modelId,
442
+ name: m.name || modelId,
443
+ api: "openai-completions",
444
+ provider: "huggingface",
445
+ baseUrl: "https://router.huggingface.co/v1",
446
+ reasoning: m.reasoning === true,
447
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
448
+ cost: {
449
+ input: m.cost?.input || 0,
450
+ output: m.cost?.output || 0,
451
+ cacheRead: m.cost?.cache_read || 0,
452
+ cacheWrite: m.cost?.cache_write || 0,
453
+ },
454
+ compat: {
455
+ supportsDeveloperRole: false,
456
+ },
457
+ contextWindow: m.limit?.context || 4096,
458
+ maxTokens: m.limit?.output || 4096,
459
+ });
460
+ }
461
+ }
462
+
463
+ // Process OpenCode models (Zen and Go)
464
+ // API mapping based on provider.npm field:
465
+ // - @ai-sdk/openai → openai-responses
466
+ // - @ai-sdk/anthropic → anthropic-messages
467
+ // - @ai-sdk/google → google-generative-ai
468
+ // - null/undefined/@ai-sdk/openai-compatible → openai-completions
469
+ const opencodeVariants = [
470
+ { key: "opencode", provider: "opencode", basePath: "https://opencode.ai/zen" },
471
+ { key: "opencode-go", provider: "opencode-go", basePath: "https://opencode.ai/zen/go" },
472
+ ] as const;
473
+
474
+ for (const variant of opencodeVariants) {
475
+ if (!data[variant.key]?.models) continue;
476
+
477
+ for (const [modelId, model] of Object.entries(data[variant.key].models)) {
478
+ const m = model as ModelsDevModel & { status?: string };
479
+ if (m.tool_call !== true) continue;
480
+ if (m.status === "deprecated") continue;
481
+
482
+ const npm = m.provider?.npm;
483
+ let api: Api;
484
+ let baseUrl: string;
485
+
486
+ if (npm === "@ai-sdk/openai") {
487
+ api = "openai-responses";
488
+ baseUrl = `${variant.basePath}/v1`;
489
+ } else if (npm === "@ai-sdk/anthropic") {
490
+ api = "anthropic-messages";
491
+ // Anthropic SDK appends /v1/messages to baseURL
492
+ baseUrl = variant.basePath;
493
+ } else if (npm === "@ai-sdk/google") {
494
+ api = "google-generative-ai";
495
+ baseUrl = `${variant.basePath}/v1`;
496
+ } else {
497
+ // null, undefined, or @ai-sdk/openai-compatible
498
+ api = "openai-completions";
499
+ baseUrl = `${variant.basePath}/v1`;
500
+ }
501
+
502
+ models.push({
503
+ id: modelId,
504
+ name: m.name || modelId,
505
+ api,
506
+ provider: variant.provider,
507
+ baseUrl,
508
+ reasoning: m.reasoning === true,
509
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
510
+ cost: {
511
+ input: m.cost?.input || 0,
512
+ output: m.cost?.output || 0,
513
+ cacheRead: m.cost?.cache_read || 0,
514
+ cacheWrite: m.cost?.cache_write || 0,
515
+ },
516
+ contextWindow: m.limit?.context || 4096,
517
+ maxTokens: m.limit?.output || 4096,
518
+ });
519
+ }
520
+ }
521
+
522
+ // Process GitHub Copilot models
523
+ if (data["github-copilot"]?.models) {
524
+ for (const [modelId, model] of Object.entries(data["github-copilot"].models)) {
525
+ const m = model as ModelsDevModel & { status?: string };
526
+ if (m.tool_call !== true) continue;
527
+ if (m.status === "deprecated") continue;
528
+
529
+ // Claude 4.x models route to Anthropic Messages API
530
+ const isCopilotClaude4 = /^claude-(haiku|sonnet|opus)-4([.\-]|$)/.test(modelId);
531
+ // gpt-5 models require responses API, others use completions
532
+ const needsResponsesApi = modelId.startsWith("gpt-5") || modelId.startsWith("oswe");
533
+
534
+ const api: Api = isCopilotClaude4
535
+ ? "anthropic-messages"
536
+ : needsResponsesApi
537
+ ? "openai-responses"
538
+ : "openai-completions";
539
+
540
+ const copilotModel: Model<any> = {
541
+ id: modelId,
542
+ name: m.name || modelId,
543
+ api,
544
+ provider: "github-copilot",
545
+ baseUrl: "https://api.individual.githubcopilot.com",
546
+ reasoning: m.reasoning === true,
547
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
548
+ cost: {
549
+ input: m.cost?.input || 0,
550
+ output: m.cost?.output || 0,
551
+ cacheRead: m.cost?.cache_read || 0,
552
+ cacheWrite: m.cost?.cache_write || 0,
553
+ },
554
+ contextWindow: m.limit?.context || 128000,
555
+ maxTokens: m.limit?.output || 8192,
556
+ headers: { ...COPILOT_STATIC_HEADERS },
557
+ // compat only applies to openai-completions
558
+ ...(api === "openai-completions" ? {
559
+ compat: {
560
+ supportsStore: false,
561
+ supportsDeveloperRole: false,
562
+ supportsReasoningEffort: false,
563
+ },
564
+ } : {}),
565
+ };
566
+
567
+ models.push(copilotModel);
568
+ }
569
+ }
570
+
571
+ // Process MiniMax models
572
+ const minimaxVariants = [
573
+ { key: "minimax", provider: "minimax", baseUrl: "https://api.minimax.io/anthropic" },
574
+ { key: "minimax-cn", provider: "minimax-cn", baseUrl: "https://api.minimaxi.com/anthropic" },
575
+ ] as const;
576
+
577
+ for (const { key, provider, baseUrl } of minimaxVariants) {
578
+ if (data[key]?.models) {
579
+ for (const [modelId, model] of Object.entries(data[key].models)) {
580
+ const m = model as ModelsDevModel;
581
+ if (m.tool_call !== true) continue;
582
+
583
+ models.push({
584
+ id: modelId,
585
+ name: m.name || modelId,
586
+ api: "anthropic-messages",
587
+ provider,
588
+ // MiniMax's Anthropic-compatible API - SDK appends /v1/messages
589
+ baseUrl,
590
+ reasoning: m.reasoning === true,
591
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
592
+ cost: {
593
+ input: m.cost?.input || 0,
594
+ output: m.cost?.output || 0,
595
+ cacheRead: m.cost?.cache_read || 0,
596
+ cacheWrite: m.cost?.cache_write || 0,
597
+ },
598
+ contextWindow: m.limit?.context || 4096,
599
+ maxTokens: m.limit?.output || 4096,
600
+ });
601
+ }
602
+ }
603
+ }
604
+
605
+ // Process Kimi For Coding models
606
+ if (data["kimi-for-coding"]?.models) {
607
+ for (const [modelId, model] of Object.entries(data["kimi-for-coding"].models)) {
608
+ const m = model as ModelsDevModel;
609
+ if (m.tool_call !== true) continue;
610
+
611
+ models.push({
612
+ id: modelId,
613
+ name: m.name || modelId,
614
+ api: "anthropic-messages",
615
+ provider: "kimi-coding",
616
+ // Kimi For Coding's Anthropic-compatible API - SDK appends /v1/messages
617
+ baseUrl: "https://api.kimi.com/coding",
618
+ reasoning: m.reasoning === true,
619
+ input: m.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
620
+ cost: {
621
+ input: m.cost?.input || 0,
622
+ output: m.cost?.output || 0,
623
+ cacheRead: m.cost?.cache_read || 0,
624
+ cacheWrite: m.cost?.cache_write || 0,
625
+ },
626
+ contextWindow: m.limit?.context || 4096,
627
+ maxTokens: m.limit?.output || 4096,
628
+ });
629
+ }
630
+ }
631
+
632
+ console.log(`Loaded ${models.length} tool-capable models from models.dev`);
633
+ return models;
634
+ } catch (error) {
635
+ console.error("Failed to load models.dev data:", error);
636
+ return [];
637
+ }
638
+ }
639
+
640
+ async function generateModels() {
641
+ // Fetch models from both sources
642
+ // models.dev: Anthropic, Google, OpenAI, Groq, Cerebras
643
+ // OpenRouter: xAI and other providers (excluding Anthropic, Google, OpenAI)
644
+ // AI Gateway: OpenAI-compatible catalog with tool-capable models
645
+ const modelsDevModels = await loadModelsDevData();
646
+ const openRouterModels = await fetchOpenRouterModels();
647
+ const aiGatewayModels = await fetchAiGatewayModels();
648
+
649
+ // Combine models (models.dev has priority)
650
+ const allModels = [...modelsDevModels, ...openRouterModels, ...aiGatewayModels].filter(
651
+ (model) =>
652
+ !((model.provider === "opencode" || model.provider === "opencode-go") && model.id === "gpt-5.3-codex-spark"),
653
+ );
654
+
655
+ // Fix incorrect cache pricing for Claude Opus 4.5 from models.dev
656
+ // models.dev has 3x the correct pricing (1.5/18.75 instead of 0.5/6.25)
657
+ const opus45 = allModels.find(m => m.provider === "anthropic" && m.id === "claude-opus-4-5");
658
+ if (opus45) {
659
+ opus45.cost.cacheRead = 0.5;
660
+ opus45.cost.cacheWrite = 6.25;
661
+ }
662
+
663
+ // Temporary overrides until upstream model metadata is corrected.
664
+ for (const candidate of allModels) {
665
+ if (candidate.provider === "amazon-bedrock" && candidate.id.includes("anthropic.claude-opus-4-6-v1")) {
666
+ candidate.cost.cacheRead = 0.5;
667
+ candidate.cost.cacheWrite = 6.25;
668
+ candidate.contextWindow = 1000000;
669
+ }
670
+ if (candidate.provider === "amazon-bedrock" && candidate.id.includes("anthropic.claude-sonnet-4-6")) {
671
+ candidate.contextWindow = 1000000;
672
+ }
673
+ if (
674
+ (candidate.provider === "anthropic" ||
675
+ candidate.provider === "opencode" ||
676
+ candidate.provider === "opencode-go") &&
677
+ (candidate.id === "claude-opus-4-6" ||
678
+ candidate.id === "claude-sonnet-4-6" ||
679
+ candidate.id === "claude-opus-4.6" ||
680
+ candidate.id === "claude-sonnet-4.6")
681
+ ) {
682
+ candidate.contextWindow = 1000000;
683
+ }
684
+ if (
685
+ candidate.provider === "google-antigravity" &&
686
+ (candidate.id === "claude-opus-4-6-thinking" || candidate.id === "claude-sonnet-4-6")
687
+ ) {
688
+ candidate.contextWindow = 1000000;
689
+ }
690
+ // OpenCode variants list Claude Sonnet 4/4.5 with 1M context, actual limit is 200K
691
+ if (
692
+ (candidate.provider === "opencode" || candidate.provider === "opencode-go") &&
693
+ (candidate.id === "claude-sonnet-4-5" || candidate.id === "claude-sonnet-4")
694
+ ) {
695
+ candidate.contextWindow = 200000;
696
+ }
697
+ if ((candidate.provider === "opencode" || candidate.provider === "opencode-go") && candidate.id === "gpt-5.4") {
698
+ candidate.contextWindow = 272000;
699
+ candidate.maxTokens = 128000;
700
+ }
701
+ if (candidate.provider === "openai" && candidate.id === "gpt-5.4") {
702
+ candidate.contextWindow = 272000;
703
+ candidate.maxTokens = 128000;
704
+ }
705
+ // Keep selected OpenRouter model metadata stable until upstream settles.
706
+ if (candidate.provider === "openrouter" && candidate.id === "moonshotai/kimi-k2.5") {
707
+ candidate.cost.input = 0.41;
708
+ candidate.cost.output = 2.06;
709
+ candidate.cost.cacheRead = 0.07;
710
+ candidate.maxTokens = 4096;
711
+ }
712
+ if (candidate.provider === "openrouter" && candidate.id === "z-ai/glm-5") {
713
+ candidate.cost.input = 0.6;
714
+ candidate.cost.output = 1.9;
715
+ candidate.cost.cacheRead = 0.119;
716
+ }
717
+ }
718
+
719
+
720
+ // Add missing EU Opus 4.6 profile
721
+ if (!allModels.some((m) => m.provider === "amazon-bedrock" && m.id === "eu.anthropic.claude-opus-4-6-v1")) {
722
+ allModels.push({
723
+ id: "eu.anthropic.claude-opus-4-6-v1",
724
+ name: "Claude Opus 4.6 (EU)",
725
+ api: "bedrock-converse-stream",
726
+ provider: "amazon-bedrock",
727
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
728
+ reasoning: true,
729
+ input: ["text", "image"],
730
+ cost: {
731
+ input: 5,
732
+ output: 25,
733
+ cacheRead: 0.5,
734
+ cacheWrite: 6.25,
735
+ },
736
+ contextWindow: 1000000,
737
+ maxTokens: 128000,
738
+ });
739
+ }
740
+
741
+ // Add missing Claude Opus 4.6
742
+ if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-opus-4-6")) {
743
+ allModels.push({
744
+ id: "claude-opus-4-6",
745
+ name: "Claude Opus 4.6",
746
+ api: "anthropic-messages",
747
+ baseUrl: "https://api.anthropic.com",
748
+ provider: "anthropic",
749
+ reasoning: true,
750
+ input: ["text", "image"],
751
+ cost: {
752
+ input: 5,
753
+ output: 25,
754
+ cacheRead: 0.5,
755
+ cacheWrite: 6.25,
756
+ },
757
+ contextWindow: 1000000,
758
+ maxTokens: 128000,
759
+ });
760
+ }
761
+
762
+ // Add missing Claude Sonnet 4.6
763
+ if (!allModels.some(m => m.provider === "anthropic" && m.id === "claude-sonnet-4-6")) {
764
+ allModels.push({
765
+ id: "claude-sonnet-4-6",
766
+ name: "Claude Sonnet 4.6",
767
+ api: "anthropic-messages",
768
+ baseUrl: "https://api.anthropic.com",
769
+ provider: "anthropic",
770
+ reasoning: true,
771
+ input: ["text", "image"],
772
+ cost: {
773
+ input: 3,
774
+ output: 15,
775
+ cacheRead: 0.3,
776
+ cacheWrite: 3.75,
777
+ },
778
+ contextWindow: 1000000,
779
+ maxTokens: 64000,
780
+ });
781
+ }
782
+
783
+ // Add missing Gemini 3.1 Flash Lite Preview until models.dev includes it.
784
+ if (!allModels.some((m) => m.provider === "google" && m.id === "gemini-3.1-flash-lite-preview")) {
785
+ allModels.push({
786
+ id: "gemini-3.1-flash-lite-preview",
787
+ name: "Gemini 3.1 Flash Lite Preview",
788
+ api: "google-generative-ai",
789
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
790
+ provider: "google",
791
+ reasoning: true,
792
+ input: ["text", "image"],
793
+ cost: {
794
+ input: 0,
795
+ output: 0,
796
+ cacheRead: 0,
797
+ cacheWrite: 0,
798
+ },
799
+ contextWindow: 1048576,
800
+ maxTokens: 65536,
801
+ });
802
+ }
803
+
804
+ // Add missing gpt models
805
+ if (!allModels.some(m => m.provider === "openai" && m.id === "gpt-5-chat-latest")) {
806
+ allModels.push({
807
+ id: "gpt-5-chat-latest",
808
+ name: "GPT-5 Chat Latest",
809
+ api: "openai-responses",
810
+ baseUrl: "https://api.openai.com/v1",
811
+ provider: "openai",
812
+ reasoning: false,
813
+ input: ["text", "image"],
814
+ cost: {
815
+ input: 1.25,
816
+ output: 10,
817
+ cacheRead: 0.125,
818
+ cacheWrite: 0,
819
+ },
820
+ contextWindow: 128000,
821
+ maxTokens: 16384,
822
+ });
823
+ }
824
+
825
+ if (!allModels.some(m => m.provider === "openai" && m.id === "gpt-5.1-codex")) {
826
+ allModels.push({
827
+ id: "gpt-5.1-codex",
828
+ name: "GPT-5.1 Codex",
829
+ api: "openai-responses",
830
+ baseUrl: "https://api.openai.com/v1",
831
+ provider: "openai",
832
+ reasoning: true,
833
+ input: ["text", "image"],
834
+ cost: {
835
+ input: 1.25,
836
+ output: 5,
837
+ cacheRead: 0.125,
838
+ cacheWrite: 1.25,
839
+ },
840
+ contextWindow: 400000,
841
+ maxTokens: 128000,
842
+ });
843
+ }
844
+
845
+ if (!allModels.some(m => m.provider === "openai" && m.id === "gpt-5.1-codex-max")) {
846
+ allModels.push({
847
+ id: "gpt-5.1-codex-max",
848
+ name: "GPT-5.1 Codex Max",
849
+ api: "openai-responses",
850
+ baseUrl: "https://api.openai.com/v1",
851
+ provider: "openai",
852
+ reasoning: true,
853
+ input: ["text", "image"],
854
+ cost: {
855
+ input: 1.25,
856
+ output: 10,
857
+ cacheRead: 0.125,
858
+ cacheWrite: 0,
859
+ },
860
+ contextWindow: 400000,
861
+ maxTokens: 128000,
862
+ });
863
+ }
864
+
865
+ if (!allModels.some(m => m.provider === "openai" && m.id === "gpt-5.3-codex-spark")) {
866
+ allModels.push({
867
+ id: "gpt-5.3-codex-spark",
868
+ name: "GPT-5.3 Codex Spark",
869
+ api: "openai-responses",
870
+ baseUrl: "https://api.openai.com/v1",
871
+ provider: "openai",
872
+ reasoning: true,
873
+ input: ["text"],
874
+ cost: {
875
+ input: 0,
876
+ output: 0,
877
+ cacheRead: 0,
878
+ cacheWrite: 0,
879
+ },
880
+ contextWindow: 128000,
881
+ maxTokens: 16384,
882
+ });
883
+ }
884
+
885
+ // Add missing GitHub Copilot GPT-5.3 models until models.dev includes them.
886
+ const copilotBaseModel = allModels.find(
887
+ (m) => m.provider === "github-copilot" && m.id === "gpt-5.2-codex",
888
+ );
889
+ if (copilotBaseModel) {
890
+ if (!allModels.some((m) => m.provider === "github-copilot" && m.id === "gpt-5.3-codex")) {
891
+ allModels.push({
892
+ ...copilotBaseModel,
893
+ id: "gpt-5.3-codex",
894
+ name: "GPT-5.3 Codex",
895
+ });
896
+ }
897
+ }
898
+
899
+ if (!allModels.some((m) => m.provider === "openai" && m.id === "gpt-5.4")) {
900
+ allModels.push({
901
+ id: "gpt-5.4",
902
+ name: "GPT-5.4",
903
+ api: "openai-responses",
904
+ baseUrl: "https://api.openai.com/v1",
905
+ provider: "openai",
906
+ reasoning: true,
907
+ input: ["text", "image"],
908
+ cost: {
909
+ input: 2.5,
910
+ output: 15,
911
+ cacheRead: 0.25,
912
+ cacheWrite: 0,
913
+ },
914
+ contextWindow: 272000,
915
+ maxTokens: 128000,
916
+ });
917
+ }
918
+
919
+ // OpenAI Codex (ChatGPT OAuth) models
920
+ // NOTE: These are not fetched from models.dev; we keep a small, explicit list to avoid aliases.
921
+ // Context window is based on observed server limits (400s above ~272k), not marketing numbers.
922
+ const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
923
+ const CODEX_CONTEXT = 272000;
924
+ const CODEX_MAX_TOKENS = 128000;
925
+ const codexModels: Model<"openai-codex-responses">[] = [
926
+ {
927
+ id: "gpt-5.1",
928
+ name: "GPT-5.1",
929
+ api: "openai-codex-responses",
930
+ provider: "openai-codex",
931
+ baseUrl: CODEX_BASE_URL,
932
+ reasoning: true,
933
+ input: ["text", "image"],
934
+ cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
935
+ contextWindow: CODEX_CONTEXT,
936
+ maxTokens: CODEX_MAX_TOKENS,
937
+ },
938
+ {
939
+ id: "gpt-5.1-codex-max",
940
+ name: "GPT-5.1 Codex Max",
941
+ api: "openai-codex-responses",
942
+ provider: "openai-codex",
943
+ baseUrl: CODEX_BASE_URL,
944
+ reasoning: true,
945
+ input: ["text", "image"],
946
+ cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
947
+ contextWindow: CODEX_CONTEXT,
948
+ maxTokens: CODEX_MAX_TOKENS,
949
+ },
950
+ {
951
+ id: "gpt-5.1-codex-mini",
952
+ name: "GPT-5.1 Codex Mini",
953
+ api: "openai-codex-responses",
954
+ provider: "openai-codex",
955
+ baseUrl: CODEX_BASE_URL,
956
+ reasoning: true,
957
+ input: ["text", "image"],
958
+ cost: { input: 0.25, output: 2, cacheRead: 0.025, cacheWrite: 0 },
959
+ contextWindow: CODEX_CONTEXT,
960
+ maxTokens: CODEX_MAX_TOKENS,
961
+ },
962
+ {
963
+ id: "gpt-5.2",
964
+ name: "GPT-5.2",
965
+ api: "openai-codex-responses",
966
+ provider: "openai-codex",
967
+ baseUrl: CODEX_BASE_URL,
968
+ reasoning: true,
969
+ input: ["text", "image"],
970
+ cost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
971
+ contextWindow: CODEX_CONTEXT,
972
+ maxTokens: CODEX_MAX_TOKENS,
973
+ },
974
+ {
975
+ id: "gpt-5.2-codex",
976
+ name: "GPT-5.2 Codex",
977
+ api: "openai-codex-responses",
978
+ provider: "openai-codex",
979
+ baseUrl: CODEX_BASE_URL,
980
+ reasoning: true,
981
+ input: ["text", "image"],
982
+ cost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
983
+ contextWindow: CODEX_CONTEXT,
984
+ maxTokens: CODEX_MAX_TOKENS,
985
+ },
986
+ {
987
+ id: "gpt-5.3-codex",
988
+ name: "GPT-5.3 Codex",
989
+ api: "openai-codex-responses",
990
+ provider: "openai-codex",
991
+ baseUrl: CODEX_BASE_URL,
992
+ reasoning: true,
993
+ input: ["text", "image"],
994
+ cost: { input: 1.75, output: 14, cacheRead: 0.175, cacheWrite: 0 },
995
+ contextWindow: CODEX_CONTEXT,
996
+ maxTokens: CODEX_MAX_TOKENS,
997
+ },
998
+ {
999
+ id: "gpt-5.4",
1000
+ name: "GPT-5.4",
1001
+ api: "openai-codex-responses",
1002
+ provider: "openai-codex",
1003
+ baseUrl: CODEX_BASE_URL,
1004
+ reasoning: true,
1005
+ input: ["text", "image"],
1006
+ cost: { input: 2.5, output: 15, cacheRead: 0.25, cacheWrite: 0 },
1007
+ contextWindow: CODEX_CONTEXT,
1008
+ maxTokens: CODEX_MAX_TOKENS,
1009
+ },
1010
+ {
1011
+ id: "gpt-5.3-codex-spark",
1012
+ name: "GPT-5.3 Codex Spark",
1013
+ api: "openai-codex-responses",
1014
+ provider: "openai-codex",
1015
+ baseUrl: CODEX_BASE_URL,
1016
+ reasoning: true,
1017
+ input: ["text"],
1018
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1019
+ contextWindow: 128000,
1020
+ maxTokens: CODEX_MAX_TOKENS,
1021
+ },
1022
+ ];
1023
+ allModels.push(...codexModels);
1024
+
1025
+ // Add missing Grok models
1026
+ if (!allModels.some(m => m.provider === "xai" && m.id === "grok-code-fast-1")) {
1027
+ allModels.push({
1028
+ id: "grok-code-fast-1",
1029
+ name: "Grok Code Fast 1",
1030
+ api: "openai-completions",
1031
+ baseUrl: "https://api.x.ai/v1",
1032
+ provider: "xai",
1033
+ reasoning: false,
1034
+ input: ["text"],
1035
+ cost: {
1036
+ input: 0.2,
1037
+ output: 1.5,
1038
+ cacheRead: 0.02,
1039
+ cacheWrite: 0,
1040
+ },
1041
+ contextWindow: 32768,
1042
+ maxTokens: 8192,
1043
+ });
1044
+ }
1045
+
1046
+ // Add "auto" alias for openrouter/auto
1047
+ if (!allModels.some(m => m.provider === "openrouter" && m.id === "auto")) {
1048
+ allModels.push({
1049
+ id: "auto",
1050
+ name: "Auto",
1051
+ api: "openai-completions",
1052
+ provider: "openrouter",
1053
+ baseUrl: "https://openrouter.ai/api/v1",
1054
+ reasoning: true,
1055
+ input: ["text", "image"],
1056
+ cost: {
1057
+ // we dont know about the costs because OpenRouter auto routes to different models
1058
+ // and then charges you for the underlying used model
1059
+ input:0,
1060
+ output:0,
1061
+ cacheRead:0,
1062
+ cacheWrite:0,
1063
+ },
1064
+ contextWindow: 2000000,
1065
+ maxTokens: 30000,
1066
+ });
1067
+ }
1068
+
1069
+ // Google Cloud Code Assist models (Gemini CLI)
1070
+ // Uses production endpoint, standard Gemini models only
1071
+ const CLOUD_CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
1072
+ const cloudCodeAssistModels: Model<"google-gemini-cli">[] = [
1073
+ {
1074
+ id: "gemini-2.5-pro",
1075
+ name: "Gemini 2.5 Pro (Cloud Code Assist)",
1076
+ api: "google-gemini-cli",
1077
+ provider: "google-gemini-cli",
1078
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1079
+ reasoning: true,
1080
+ input: ["text", "image"],
1081
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1082
+ contextWindow: 1048576,
1083
+ maxTokens: 65535,
1084
+ },
1085
+ {
1086
+ id: "gemini-2.5-flash",
1087
+ name: "Gemini 2.5 Flash (Cloud Code Assist)",
1088
+ api: "google-gemini-cli",
1089
+ provider: "google-gemini-cli",
1090
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1091
+ reasoning: true,
1092
+ input: ["text", "image"],
1093
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1094
+ contextWindow: 1048576,
1095
+ maxTokens: 65535,
1096
+ },
1097
+ {
1098
+ id: "gemini-2.0-flash",
1099
+ name: "Gemini 2.0 Flash (Cloud Code Assist)",
1100
+ api: "google-gemini-cli",
1101
+ provider: "google-gemini-cli",
1102
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1103
+ reasoning: false,
1104
+ input: ["text", "image"],
1105
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1106
+ contextWindow: 1048576,
1107
+ maxTokens: 8192,
1108
+ },
1109
+ {
1110
+ id: "gemini-3-pro-preview",
1111
+ name: "Gemini 3 Pro Preview (Cloud Code Assist)",
1112
+ api: "google-gemini-cli",
1113
+ provider: "google-gemini-cli",
1114
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1115
+ reasoning: true,
1116
+ input: ["text", "image"],
1117
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1118
+ contextWindow: 1048576,
1119
+ maxTokens: 65535,
1120
+ },
1121
+ {
1122
+ id: "gemini-3-flash-preview",
1123
+ name: "Gemini 3 Flash Preview (Cloud Code Assist)",
1124
+ api: "google-gemini-cli",
1125
+ provider: "google-gemini-cli",
1126
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1127
+ reasoning: true,
1128
+ input: ["text", "image"],
1129
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1130
+ contextWindow: 1048576,
1131
+ maxTokens: 65535,
1132
+ },
1133
+ {
1134
+ id: "gemini-3.1-pro-preview",
1135
+ name: "Gemini 3.1 Pro Preview (Cloud Code Assist)",
1136
+ api: "google-gemini-cli",
1137
+ provider: "google-gemini-cli",
1138
+ baseUrl: CLOUD_CODE_ASSIST_ENDPOINT,
1139
+ reasoning: true,
1140
+ input: ["text", "image"],
1141
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1142
+ contextWindow: 1048576,
1143
+ maxTokens: 65535,
1144
+ },
1145
+ ];
1146
+ allModels.push(...cloudCodeAssistModels);
1147
+
1148
+ // Antigravity models (Gemini 3, Claude, GPT-OSS via Google Cloud)
1149
+ // Uses sandbox endpoint and different OAuth credentials for access to additional models
1150
+ const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
1151
+ const antigravityModels: Model<"google-gemini-cli">[] = [
1152
+ {
1153
+ id: "gemini-3.1-pro-high",
1154
+ name: "Gemini 3.1 Pro High (Antigravity)",
1155
+ api: "google-gemini-cli",
1156
+ provider: "google-antigravity",
1157
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1158
+ reasoning: true,
1159
+ input: ["text", "image"],
1160
+ // the Model type doesn't seem to support having extended-context costs, so I'm just using the pricing for <200k input
1161
+ cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.375 },
1162
+ contextWindow: 1048576,
1163
+ maxTokens: 65535,
1164
+ },
1165
+ {
1166
+ id: "gemini-3.1-pro-low",
1167
+ name: "Gemini 3.1 Pro Low (Antigravity)",
1168
+ api: "google-gemini-cli",
1169
+ provider: "google-antigravity",
1170
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1171
+ reasoning: true,
1172
+ input: ["text", "image"],
1173
+ // the Model type doesn't seem to support having extended-context costs, so I'm just using the pricing for <200k input
1174
+ cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.375 },
1175
+ contextWindow: 1048576,
1176
+ maxTokens: 65535,
1177
+ },
1178
+ {
1179
+ id: "gemini-3-flash",
1180
+ name: "Gemini 3 Flash (Antigravity)",
1181
+ api: "google-gemini-cli",
1182
+ provider: "google-antigravity",
1183
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1184
+ reasoning: true,
1185
+ input: ["text", "image"],
1186
+ cost: { input: 0.5, output: 3, cacheRead: 0.5, cacheWrite: 0 },
1187
+ contextWindow: 1048576,
1188
+ maxTokens: 65535,
1189
+ },
1190
+ {
1191
+ id: "claude-sonnet-4-5",
1192
+ name: "Claude Sonnet 4.5 (Antigravity)",
1193
+ api: "google-gemini-cli",
1194
+ provider: "google-antigravity",
1195
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1196
+ reasoning: false,
1197
+ input: ["text", "image"],
1198
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1199
+ contextWindow: 200000,
1200
+ maxTokens: 64000,
1201
+ },
1202
+ {
1203
+ id: "claude-sonnet-4-5-thinking",
1204
+ name: "Claude Sonnet 4.5 Thinking (Antigravity)",
1205
+ api: "google-gemini-cli",
1206
+ provider: "google-antigravity",
1207
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1208
+ reasoning: true,
1209
+ input: ["text", "image"],
1210
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1211
+ contextWindow: 200000,
1212
+ maxTokens: 64000,
1213
+ },
1214
+ {
1215
+ id: "claude-opus-4-5-thinking",
1216
+ name: "Claude Opus 4.5 Thinking (Antigravity)",
1217
+ api: "google-gemini-cli",
1218
+ provider: "google-antigravity",
1219
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1220
+ reasoning: true,
1221
+ input: ["text", "image"],
1222
+ cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
1223
+ contextWindow: 200000,
1224
+ maxTokens: 64000,
1225
+ },
1226
+ {
1227
+ id: "claude-opus-4-6-thinking",
1228
+ name: "Claude Opus 4.6 Thinking (Antigravity)",
1229
+ api: "google-gemini-cli",
1230
+ provider: "google-antigravity",
1231
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1232
+ reasoning: true,
1233
+ input: ["text", "image"],
1234
+ cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
1235
+ contextWindow: 200000,
1236
+ maxTokens: 128000,
1237
+ },
1238
+ {
1239
+ id: "claude-sonnet-4-6",
1240
+ name: "Claude Sonnet 4.6 (Antigravity)",
1241
+ api: "google-gemini-cli",
1242
+ provider: "google-antigravity",
1243
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1244
+ reasoning: true,
1245
+ input: ["text", "image"],
1246
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
1247
+ contextWindow: 200000,
1248
+ maxTokens: 64000,
1249
+ },
1250
+ {
1251
+ id: "gpt-oss-120b-medium",
1252
+ name: "GPT-OSS 120B Medium (Antigravity)",
1253
+ api: "google-gemini-cli",
1254
+ provider: "google-antigravity",
1255
+ baseUrl: ANTIGRAVITY_ENDPOINT,
1256
+ reasoning: false,
1257
+ input: ["text"],
1258
+ cost: { input: 0.09, output: 0.36, cacheRead: 0, cacheWrite: 0 },
1259
+ contextWindow: 131072,
1260
+ maxTokens: 32768,
1261
+ },
1262
+ ];
1263
+ allModels.push(...antigravityModels);
1264
+
1265
+ const VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com";
1266
+ const vertexModels: Model<"google-vertex">[] = [
1267
+ {
1268
+ id: "gemini-3-pro-preview",
1269
+ name: "Gemini 3 Pro Preview (Vertex)",
1270
+ api: "google-vertex",
1271
+ provider: "google-vertex",
1272
+ baseUrl: VERTEX_BASE_URL,
1273
+ reasoning: true,
1274
+ input: ["text", "image"],
1275
+ cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
1276
+ contextWindow: 1000000,
1277
+ maxTokens: 64000,
1278
+ },
1279
+ {
1280
+ id: "gemini-3.1-pro-preview",
1281
+ name: "Gemini 3.1 Pro Preview (Vertex)",
1282
+ api: "google-vertex",
1283
+ provider: "google-vertex",
1284
+ baseUrl: VERTEX_BASE_URL,
1285
+ reasoning: true,
1286
+ input: ["text", "image"],
1287
+ cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
1288
+ contextWindow: 1048576,
1289
+ maxTokens: 65536,
1290
+ },
1291
+ {
1292
+ id: "gemini-3-flash-preview",
1293
+ name: "Gemini 3 Flash Preview (Vertex)",
1294
+ api: "google-vertex",
1295
+ provider: "google-vertex",
1296
+ baseUrl: VERTEX_BASE_URL,
1297
+ reasoning: true,
1298
+ input: ["text", "image"],
1299
+ cost: { input: 0.5, output: 3, cacheRead: 0.05, cacheWrite: 0 },
1300
+ contextWindow: 1048576,
1301
+ maxTokens: 65536,
1302
+ },
1303
+ {
1304
+ id: "gemini-2.0-flash",
1305
+ name: "Gemini 2.0 Flash (Vertex)",
1306
+ api: "google-vertex",
1307
+ provider: "google-vertex",
1308
+ baseUrl: VERTEX_BASE_URL,
1309
+ reasoning: false,
1310
+ input: ["text", "image"],
1311
+ cost: { input: 0.15, output: 0.6, cacheRead: 0.0375, cacheWrite: 0 },
1312
+ contextWindow: 1048576,
1313
+ maxTokens: 8192,
1314
+ },
1315
+ {
1316
+ id: "gemini-2.0-flash-lite",
1317
+ name: "Gemini 2.0 Flash Lite (Vertex)",
1318
+ api: "google-vertex",
1319
+ provider: "google-vertex",
1320
+ baseUrl: VERTEX_BASE_URL,
1321
+ reasoning: true,
1322
+ input: ["text", "image"],
1323
+ cost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },
1324
+ contextWindow: 1048576,
1325
+ maxTokens: 65536,
1326
+ },
1327
+ {
1328
+ id: "gemini-2.5-pro",
1329
+ name: "Gemini 2.5 Pro (Vertex)",
1330
+ api: "google-vertex",
1331
+ provider: "google-vertex",
1332
+ baseUrl: VERTEX_BASE_URL,
1333
+ reasoning: true,
1334
+ input: ["text", "image"],
1335
+ cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0 },
1336
+ contextWindow: 1048576,
1337
+ maxTokens: 65536,
1338
+ },
1339
+ {
1340
+ id: "gemini-2.5-flash",
1341
+ name: "Gemini 2.5 Flash (Vertex)",
1342
+ api: "google-vertex",
1343
+ provider: "google-vertex",
1344
+ baseUrl: VERTEX_BASE_URL,
1345
+ reasoning: true,
1346
+ input: ["text", "image"],
1347
+ cost: { input: 0.3, output: 2.5, cacheRead: 0.03, cacheWrite: 0 },
1348
+ contextWindow: 1048576,
1349
+ maxTokens: 65536,
1350
+ },
1351
+ {
1352
+ id: "gemini-2.5-flash-lite-preview-09-2025",
1353
+ name: "Gemini 2.5 Flash Lite Preview 09-25 (Vertex)",
1354
+ api: "google-vertex",
1355
+ provider: "google-vertex",
1356
+ baseUrl: VERTEX_BASE_URL,
1357
+ reasoning: true,
1358
+ input: ["text", "image"],
1359
+ cost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
1360
+ contextWindow: 1048576,
1361
+ maxTokens: 65536,
1362
+ },
1363
+ {
1364
+ id: "gemini-2.5-flash-lite",
1365
+ name: "Gemini 2.5 Flash Lite (Vertex)",
1366
+ api: "google-vertex",
1367
+ provider: "google-vertex",
1368
+ baseUrl: VERTEX_BASE_URL,
1369
+ reasoning: true,
1370
+ input: ["text", "image"],
1371
+ cost: { input: 0.1, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
1372
+ contextWindow: 1048576,
1373
+ maxTokens: 65536,
1374
+ },
1375
+ {
1376
+ id: "gemini-1.5-pro",
1377
+ name: "Gemini 1.5 Pro (Vertex)",
1378
+ api: "google-vertex",
1379
+ provider: "google-vertex",
1380
+ baseUrl: VERTEX_BASE_URL,
1381
+ reasoning: false,
1382
+ input: ["text", "image"],
1383
+ cost: { input: 1.25, output: 5, cacheRead: 0.3125, cacheWrite: 0 },
1384
+ contextWindow: 1000000,
1385
+ maxTokens: 8192,
1386
+ },
1387
+ {
1388
+ id: "gemini-1.5-flash",
1389
+ name: "Gemini 1.5 Flash (Vertex)",
1390
+ api: "google-vertex",
1391
+ provider: "google-vertex",
1392
+ baseUrl: VERTEX_BASE_URL,
1393
+ reasoning: false,
1394
+ input: ["text", "image"],
1395
+ cost: { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0 },
1396
+ contextWindow: 1000000,
1397
+ maxTokens: 8192,
1398
+ },
1399
+ {
1400
+ id: "gemini-1.5-flash-8b",
1401
+ name: "Gemini 1.5 Flash-8B (Vertex)",
1402
+ api: "google-vertex",
1403
+ provider: "google-vertex",
1404
+ baseUrl: VERTEX_BASE_URL,
1405
+ reasoning: false,
1406
+ input: ["text", "image"],
1407
+ cost: { input: 0.0375, output: 0.15, cacheRead: 0.01, cacheWrite: 0 },
1408
+ contextWindow: 1000000,
1409
+ maxTokens: 8192,
1410
+ },
1411
+ ];
1412
+ allModels.push(...vertexModels);
1413
+
1414
+ // Kimi For Coding models (Moonshot AI's Anthropic-compatible coding API)
1415
+ // Static fallback in case models.dev doesn't have them yet
1416
+ const KIMI_CODING_BASE_URL = "https://api.kimi.com/coding";
1417
+ const kimiCodingModels: Model<"anthropic-messages">[] = [
1418
+ {
1419
+ id: "kimi-k2-thinking",
1420
+ name: "Kimi K2 Thinking",
1421
+ api: "anthropic-messages",
1422
+ provider: "kimi-coding",
1423
+ baseUrl: KIMI_CODING_BASE_URL,
1424
+ reasoning: true,
1425
+ input: ["text"],
1426
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1427
+ contextWindow: 262144,
1428
+ maxTokens: 32768,
1429
+ },
1430
+ {
1431
+ id: "k2p5",
1432
+ name: "Kimi K2.5",
1433
+ api: "anthropic-messages",
1434
+ provider: "kimi-coding",
1435
+ baseUrl: KIMI_CODING_BASE_URL,
1436
+ reasoning: true,
1437
+ input: ["text"],
1438
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1439
+ contextWindow: 262144,
1440
+ maxTokens: 32768,
1441
+ },
1442
+ ];
1443
+ // Only add if not already present from models.dev
1444
+ for (const model of kimiCodingModels) {
1445
+ if (!allModels.some(m => m.provider === "kimi-coding" && m.id === model.id)) {
1446
+ allModels.push(model);
1447
+ }
1448
+ }
1449
+
1450
+ const azureOpenAiModels: Model<Api>[] = allModels
1451
+ .filter((model) => model.provider === "openai" && model.api === "openai-responses")
1452
+ .map((model) => ({
1453
+ ...model,
1454
+ api: "azure-openai-responses",
1455
+ provider: "azure-openai-responses",
1456
+ baseUrl: "",
1457
+ }));
1458
+ allModels.push(...azureOpenAiModels);
1459
+
1460
+ // Group by provider and deduplicate by model ID
1461
+ const providers: Record<string, Record<string, Model<any>>> = {};
1462
+ for (const model of allModels) {
1463
+ if (!providers[model.provider]) {
1464
+ providers[model.provider] = {};
1465
+ }
1466
+ // Use model ID as key to automatically deduplicate
1467
+ // Only add if not already present (models.dev takes priority over OpenRouter)
1468
+ if (!providers[model.provider][model.id]) {
1469
+ providers[model.provider][model.id] = model;
1470
+ }
1471
+ }
1472
+
1473
+ // Generate TypeScript file
1474
+ let output = `// This file is auto-generated by scripts/generate-models.ts
1475
+ // Do not edit manually - run 'npm run generate-models' to update
1476
+
1477
+ import type { Model } from "./types.js";
1478
+
1479
+ export const MODELS = {
1480
+ `;
1481
+
1482
+ // Generate provider sections (sorted for deterministic output)
1483
+ const sortedProviderIds = Object.keys(providers).sort();
1484
+ for (const providerId of sortedProviderIds) {
1485
+ const models = providers[providerId];
1486
+ output += `\t${JSON.stringify(providerId)}: {\n`;
1487
+
1488
+ const sortedModelIds = Object.keys(models).sort();
1489
+ for (const modelId of sortedModelIds) {
1490
+ const model = models[modelId];
1491
+ output += `\t\t"${model.id}": {\n`;
1492
+ output += `\t\t\tid: "${model.id}",\n`;
1493
+ output += `\t\t\tname: "${model.name}",\n`;
1494
+ output += `\t\t\tapi: "${model.api}",\n`;
1495
+ output += `\t\t\tprovider: "${model.provider}",\n`;
1496
+ if (model.baseUrl !== undefined) {
1497
+ output += `\t\t\tbaseUrl: "${model.baseUrl}",\n`;
1498
+ }
1499
+ if (model.headers) {
1500
+ output += `\t\t\theaders: ${JSON.stringify(model.headers)},\n`;
1501
+ }
1502
+ if (model.compat) {
1503
+ output += ` compat: ${JSON.stringify(model.compat)},
1504
+ `;
1505
+ }
1506
+ output += `\t\t\treasoning: ${model.reasoning},\n`;
1507
+ output += `\t\t\tinput: [${model.input.map(i => `"${i}"`).join(", ")}],\n`;
1508
+ output += `\t\t\tcost: {\n`;
1509
+ output += `\t\t\t\tinput: ${model.cost.input},\n`;
1510
+ output += `\t\t\t\toutput: ${model.cost.output},\n`;
1511
+ output += `\t\t\t\tcacheRead: ${model.cost.cacheRead},\n`;
1512
+ output += `\t\t\t\tcacheWrite: ${model.cost.cacheWrite},\n`;
1513
+ output += `\t\t\t},\n`;
1514
+ output += `\t\t\tcontextWindow: ${model.contextWindow},\n`;
1515
+ output += `\t\t\tmaxTokens: ${model.maxTokens},\n`;
1516
+ output += `\t\t} satisfies Model<"${model.api}">,\n`;
1517
+ }
1518
+
1519
+ output += `\t},\n`;
1520
+ }
1521
+
1522
+ output += `} as const;
1523
+ `;
1524
+
1525
+ // Write file
1526
+ writeFileSync(join(packageRoot, "src/models.generated.ts"), output);
1527
+ console.log("Generated src/models.generated.ts");
1528
+
1529
+ // Print statistics
1530
+ const totalModels = allModels.length;
1531
+ const reasoningModels = allModels.filter(m => m.reasoning).length;
1532
+
1533
+ console.log(`\nModel Statistics:`);
1534
+ console.log(` Total tool-capable models: ${totalModels}`);
1535
+ console.log(` Reasoning-capable models: ${reasoningModels}`);
1536
+
1537
+ for (const [provider, models] of Object.entries(providers)) {
1538
+ console.log(` ${provider}: ${Object.keys(models).length} models`);
1539
+ }
1540
+ }
1541
+
1542
+ // Run the generator
1543
+ generateModels().catch(console.error);