cascade-ai 0.48.1 → 0.50.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.
- package/README.md +23 -2
- package/dist/cli.cjs +2113 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2113 -51
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +2050 -47
- package/dist/index.cjs +2050 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +2050 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -112,11 +112,1688 @@ var __export = (target, all) => {
|
|
|
112
112
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
// src/core/router/pricing-data.json
|
|
116
|
+
var pricing_data_default;
|
|
117
|
+
var init_pricing_data = __esm({
|
|
118
|
+
"src/core/router/pricing-data.json"() {
|
|
119
|
+
pricing_data_default = {
|
|
120
|
+
schemaVersion: 1,
|
|
121
|
+
generatedAt: "2026-07-27",
|
|
122
|
+
defaultCurrency: "USD",
|
|
123
|
+
note: 'Authoritative offline baseline for model pricing, keyed by model x provider (x region). All token rates are per 1,000,000 tokens in the entry currency. A live source (see live-data.ts) may override an entry when it is fresher; disagreements are recorded rather than silently accepted. Every entry must carry currency, asOf, source and provenance \u2014 a model with NO entry is priced "unknown", never $0. Regenerate with scripts/refresh-pricing.mjs.',
|
|
124
|
+
provenanceLegend: {
|
|
125
|
+
"vendor-pricing-page": "Read directly from the vendor's own published pricing page (see `source`).",
|
|
126
|
+
"litellm-catalog": "From BerriAI/litellm model_prices_and_context_window.json, which cites the vendor page per entry. Used where the vendor page refused automated access.",
|
|
127
|
+
"seed-approximation": "NOT sourced \u2014 an approximation. No entry in this file currently uses this value; it exists so an approximation can never masquerade as a measured price."
|
|
128
|
+
},
|
|
129
|
+
entries: [
|
|
130
|
+
{
|
|
131
|
+
model: "claude-opus-5",
|
|
132
|
+
provider: "anthropic",
|
|
133
|
+
modality: "text",
|
|
134
|
+
currency: "USD",
|
|
135
|
+
asOf: "2026-07-27",
|
|
136
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
137
|
+
provenance: "vendor-pricing-page",
|
|
138
|
+
tiers: [
|
|
139
|
+
{
|
|
140
|
+
inputPer1m: 5,
|
|
141
|
+
outputPer1m: 25,
|
|
142
|
+
cachedInputPer1m: 0.5,
|
|
143
|
+
cacheWritePer1m: 6.25,
|
|
144
|
+
reasoningOutputPer1m: 25
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
batchMultiplier: 0.5,
|
|
148
|
+
contextWindow: 1e6
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
model: "claude-opus-4-8",
|
|
152
|
+
provider: "anthropic",
|
|
153
|
+
modality: "text",
|
|
154
|
+
currency: "USD",
|
|
155
|
+
asOf: "2026-07-27",
|
|
156
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
157
|
+
provenance: "vendor-pricing-page",
|
|
158
|
+
tiers: [
|
|
159
|
+
{
|
|
160
|
+
inputPer1m: 5,
|
|
161
|
+
outputPer1m: 25,
|
|
162
|
+
cachedInputPer1m: 0.5,
|
|
163
|
+
cacheWritePer1m: 6.25,
|
|
164
|
+
reasoningOutputPer1m: 25
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
batchMultiplier: 0.5,
|
|
168
|
+
contextWindow: 1e6
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
model: "claude-opus-4-7",
|
|
172
|
+
provider: "anthropic",
|
|
173
|
+
modality: "text",
|
|
174
|
+
currency: "USD",
|
|
175
|
+
asOf: "2026-07-27",
|
|
176
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
177
|
+
provenance: "vendor-pricing-page",
|
|
178
|
+
tiers: [
|
|
179
|
+
{
|
|
180
|
+
inputPer1m: 5,
|
|
181
|
+
outputPer1m: 25,
|
|
182
|
+
cachedInputPer1m: 0.5,
|
|
183
|
+
cacheWritePer1m: 6.25,
|
|
184
|
+
reasoningOutputPer1m: 25
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
batchMultiplier: 0.5,
|
|
188
|
+
contextWindow: 1e6
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
model: "claude-opus-4-6",
|
|
192
|
+
provider: "anthropic",
|
|
193
|
+
modality: "text",
|
|
194
|
+
currency: "USD",
|
|
195
|
+
asOf: "2026-07-27",
|
|
196
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
197
|
+
provenance: "vendor-pricing-page",
|
|
198
|
+
tiers: [
|
|
199
|
+
{
|
|
200
|
+
inputPer1m: 5,
|
|
201
|
+
outputPer1m: 25,
|
|
202
|
+
cachedInputPer1m: 0.5,
|
|
203
|
+
cacheWritePer1m: 6.25,
|
|
204
|
+
reasoningOutputPer1m: 25
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
batchMultiplier: 0.5,
|
|
208
|
+
contextWindow: 1e6
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
model: "claude-opus-4-5",
|
|
212
|
+
provider: "anthropic",
|
|
213
|
+
modality: "text",
|
|
214
|
+
currency: "USD",
|
|
215
|
+
asOf: "2026-07-27",
|
|
216
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
217
|
+
provenance: "vendor-pricing-page",
|
|
218
|
+
tiers: [
|
|
219
|
+
{
|
|
220
|
+
inputPer1m: 5,
|
|
221
|
+
outputPer1m: 25,
|
|
222
|
+
cachedInputPer1m: 0.5,
|
|
223
|
+
cacheWritePer1m: 6.25,
|
|
224
|
+
reasoningOutputPer1m: 25
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
batchMultiplier: 0.5,
|
|
228
|
+
contextWindow: 2e5
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
model: "claude-opus-4-1",
|
|
232
|
+
provider: "anthropic",
|
|
233
|
+
modality: "text",
|
|
234
|
+
currency: "USD",
|
|
235
|
+
asOf: "2026-07-27",
|
|
236
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
237
|
+
provenance: "vendor-pricing-page",
|
|
238
|
+
tiers: [
|
|
239
|
+
{
|
|
240
|
+
inputPer1m: 15,
|
|
241
|
+
outputPer1m: 75,
|
|
242
|
+
cachedInputPer1m: 1.5,
|
|
243
|
+
cacheWritePer1m: 18.75,
|
|
244
|
+
reasoningOutputPer1m: 75
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
batchMultiplier: 0.5,
|
|
248
|
+
contextWindow: 2e5,
|
|
249
|
+
notes: "Deprecated by Anthropic; priced for historical/cost-reporting accuracy."
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
model: "claude-opus-4",
|
|
253
|
+
provider: "anthropic",
|
|
254
|
+
modality: "text",
|
|
255
|
+
currency: "USD",
|
|
256
|
+
asOf: "2026-07-27",
|
|
257
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
258
|
+
provenance: "vendor-pricing-page",
|
|
259
|
+
tiers: [
|
|
260
|
+
{
|
|
261
|
+
inputPer1m: 15,
|
|
262
|
+
outputPer1m: 75,
|
|
263
|
+
cachedInputPer1m: 1.5,
|
|
264
|
+
cacheWritePer1m: 18.75,
|
|
265
|
+
reasoningOutputPer1m: 75
|
|
266
|
+
}
|
|
267
|
+
],
|
|
268
|
+
batchMultiplier: 0.5,
|
|
269
|
+
contextWindow: 2e5,
|
|
270
|
+
notes: "Retired except on Google Cloud; priced for historical/cost-reporting accuracy."
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
model: "claude-sonnet-5",
|
|
274
|
+
provider: "anthropic",
|
|
275
|
+
modality: "text",
|
|
276
|
+
currency: "USD",
|
|
277
|
+
asOf: "2026-07-27",
|
|
278
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
279
|
+
provenance: "vendor-pricing-page",
|
|
280
|
+
tiers: [
|
|
281
|
+
{
|
|
282
|
+
inputPer1m: 2,
|
|
283
|
+
outputPer1m: 10,
|
|
284
|
+
cachedInputPer1m: 0.2,
|
|
285
|
+
cacheWritePer1m: 2.5,
|
|
286
|
+
reasoningOutputPer1m: 10
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
batchMultiplier: 0.5,
|
|
290
|
+
contextWindow: 1e6,
|
|
291
|
+
notes: "Introductory pricing through 2026-08-31. Standard pricing of $3/$15 per 1M takes effect 2026-09-01 \u2014 re-check this entry then."
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
model: "claude-sonnet-4-6",
|
|
295
|
+
provider: "anthropic",
|
|
296
|
+
modality: "text",
|
|
297
|
+
currency: "USD",
|
|
298
|
+
asOf: "2026-07-27",
|
|
299
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
300
|
+
provenance: "vendor-pricing-page",
|
|
301
|
+
tiers: [
|
|
302
|
+
{
|
|
303
|
+
inputPer1m: 3,
|
|
304
|
+
outputPer1m: 15,
|
|
305
|
+
cachedInputPer1m: 0.3,
|
|
306
|
+
cacheWritePer1m: 3.75,
|
|
307
|
+
reasoningOutputPer1m: 15
|
|
308
|
+
}
|
|
309
|
+
],
|
|
310
|
+
batchMultiplier: 0.5,
|
|
311
|
+
contextWindow: 1e6
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
model: "claude-sonnet-4-5",
|
|
315
|
+
provider: "anthropic",
|
|
316
|
+
modality: "text",
|
|
317
|
+
currency: "USD",
|
|
318
|
+
asOf: "2026-07-27",
|
|
319
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
320
|
+
provenance: "vendor-pricing-page",
|
|
321
|
+
tiers: [
|
|
322
|
+
{
|
|
323
|
+
maxInputTokens: 2e5,
|
|
324
|
+
inputPer1m: 3,
|
|
325
|
+
outputPer1m: 15,
|
|
326
|
+
cachedInputPer1m: 0.3,
|
|
327
|
+
cacheWritePer1m: 3.75,
|
|
328
|
+
reasoningOutputPer1m: 15
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
inputPer1m: 6,
|
|
332
|
+
outputPer1m: 22.5,
|
|
333
|
+
cachedInputPer1m: 0.6,
|
|
334
|
+
cacheWritePer1m: 7.5,
|
|
335
|
+
reasoningOutputPer1m: 22.5
|
|
336
|
+
}
|
|
337
|
+
],
|
|
338
|
+
batchMultiplier: 0.5,
|
|
339
|
+
contextWindow: 2e5,
|
|
340
|
+
notes: "Long-context (>200K) premium applies to the 1M-context beta. Base rates from the Anthropic pricing page; the >200K tier from the litellm catalog (Anthropic does not tabulate it on the pricing page)."
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
model: "claude-sonnet-4",
|
|
344
|
+
provider: "anthropic",
|
|
345
|
+
modality: "text",
|
|
346
|
+
currency: "USD",
|
|
347
|
+
asOf: "2026-07-27",
|
|
348
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
349
|
+
provenance: "vendor-pricing-page",
|
|
350
|
+
tiers: [
|
|
351
|
+
{
|
|
352
|
+
inputPer1m: 3,
|
|
353
|
+
outputPer1m: 15,
|
|
354
|
+
cachedInputPer1m: 0.3,
|
|
355
|
+
cacheWritePer1m: 3.75,
|
|
356
|
+
reasoningOutputPer1m: 15
|
|
357
|
+
}
|
|
358
|
+
],
|
|
359
|
+
batchMultiplier: 0.5,
|
|
360
|
+
contextWindow: 2e5
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
model: "claude-haiku-4-5",
|
|
364
|
+
provider: "anthropic",
|
|
365
|
+
modality: "text",
|
|
366
|
+
currency: "USD",
|
|
367
|
+
asOf: "2026-07-27",
|
|
368
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
369
|
+
provenance: "vendor-pricing-page",
|
|
370
|
+
tiers: [
|
|
371
|
+
{
|
|
372
|
+
inputPer1m: 1,
|
|
373
|
+
outputPer1m: 5,
|
|
374
|
+
cachedInputPer1m: 0.1,
|
|
375
|
+
cacheWritePer1m: 1.25,
|
|
376
|
+
reasoningOutputPer1m: 5
|
|
377
|
+
}
|
|
378
|
+
],
|
|
379
|
+
batchMultiplier: 0.5,
|
|
380
|
+
contextWindow: 2e5
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
model: "claude-haiku-3-5",
|
|
384
|
+
provider: "anthropic",
|
|
385
|
+
modality: "text",
|
|
386
|
+
currency: "USD",
|
|
387
|
+
asOf: "2026-07-27",
|
|
388
|
+
source: "https://platform.claude.com/docs/en/about-claude/pricing",
|
|
389
|
+
provenance: "vendor-pricing-page",
|
|
390
|
+
tiers: [
|
|
391
|
+
{
|
|
392
|
+
inputPer1m: 0.8,
|
|
393
|
+
outputPer1m: 4,
|
|
394
|
+
cachedInputPer1m: 0.08,
|
|
395
|
+
cacheWritePer1m: 1,
|
|
396
|
+
reasoningOutputPer1m: 4
|
|
397
|
+
}
|
|
398
|
+
],
|
|
399
|
+
batchMultiplier: 0.5,
|
|
400
|
+
contextWindow: 2e5
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
model: "gpt-5.6-sol",
|
|
404
|
+
provider: "openai",
|
|
405
|
+
aliases: [
|
|
406
|
+
"gpt-5.6"
|
|
407
|
+
],
|
|
408
|
+
modality: "text",
|
|
409
|
+
currency: "USD",
|
|
410
|
+
asOf: "2026-07-27",
|
|
411
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
412
|
+
provenance: "litellm-catalog",
|
|
413
|
+
tiers: [
|
|
414
|
+
{
|
|
415
|
+
maxInputTokens: 272e3,
|
|
416
|
+
inputPer1m: 5,
|
|
417
|
+
outputPer1m: 30,
|
|
418
|
+
cachedInputPer1m: 0.5,
|
|
419
|
+
cacheWritePer1m: 6.25
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
inputPer1m: 10,
|
|
423
|
+
outputPer1m: 45,
|
|
424
|
+
cachedInputPer1m: 1,
|
|
425
|
+
cacheWritePer1m: 12.5
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
batchMultiplier: 0.5,
|
|
429
|
+
contextWindow: 105e4
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
model: "gpt-5.6-terra",
|
|
433
|
+
provider: "openai",
|
|
434
|
+
modality: "text",
|
|
435
|
+
currency: "USD",
|
|
436
|
+
asOf: "2026-07-27",
|
|
437
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
438
|
+
provenance: "litellm-catalog",
|
|
439
|
+
tiers: [
|
|
440
|
+
{
|
|
441
|
+
maxInputTokens: 272e3,
|
|
442
|
+
inputPer1m: 2.5,
|
|
443
|
+
outputPer1m: 15,
|
|
444
|
+
cachedInputPer1m: 0.25,
|
|
445
|
+
cacheWritePer1m: 3.125
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
inputPer1m: 5,
|
|
449
|
+
outputPer1m: 22.5,
|
|
450
|
+
cachedInputPer1m: 0.5,
|
|
451
|
+
cacheWritePer1m: 6.25
|
|
452
|
+
}
|
|
453
|
+
],
|
|
454
|
+
batchMultiplier: 0.5,
|
|
455
|
+
contextWindow: 105e4
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
model: "gpt-5.6-luna",
|
|
459
|
+
provider: "openai",
|
|
460
|
+
modality: "text",
|
|
461
|
+
currency: "USD",
|
|
462
|
+
asOf: "2026-07-27",
|
|
463
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
464
|
+
provenance: "litellm-catalog",
|
|
465
|
+
tiers: [
|
|
466
|
+
{
|
|
467
|
+
maxInputTokens: 272e3,
|
|
468
|
+
inputPer1m: 1,
|
|
469
|
+
outputPer1m: 6,
|
|
470
|
+
cachedInputPer1m: 0.1,
|
|
471
|
+
cacheWritePer1m: 1.25
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
inputPer1m: 2,
|
|
475
|
+
outputPer1m: 9,
|
|
476
|
+
cachedInputPer1m: 0.2,
|
|
477
|
+
cacheWritePer1m: 2.5
|
|
478
|
+
}
|
|
479
|
+
],
|
|
480
|
+
batchMultiplier: 0.5,
|
|
481
|
+
contextWindow: 105e4
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
model: "gpt-5.5",
|
|
485
|
+
provider: "openai",
|
|
486
|
+
modality: "text",
|
|
487
|
+
currency: "USD",
|
|
488
|
+
asOf: "2026-07-27",
|
|
489
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
490
|
+
provenance: "litellm-catalog",
|
|
491
|
+
tiers: [
|
|
492
|
+
{
|
|
493
|
+
maxInputTokens: 272e3,
|
|
494
|
+
inputPer1m: 5,
|
|
495
|
+
outputPer1m: 30,
|
|
496
|
+
cachedInputPer1m: 0.5
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
inputPer1m: 10,
|
|
500
|
+
outputPer1m: 45,
|
|
501
|
+
cachedInputPer1m: 1
|
|
502
|
+
}
|
|
503
|
+
],
|
|
504
|
+
batchMultiplier: 0.5,
|
|
505
|
+
contextWindow: 105e4
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
model: "gpt-5.4",
|
|
509
|
+
provider: "openai",
|
|
510
|
+
modality: "text",
|
|
511
|
+
currency: "USD",
|
|
512
|
+
asOf: "2026-07-27",
|
|
513
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
514
|
+
provenance: "litellm-catalog",
|
|
515
|
+
tiers: [
|
|
516
|
+
{
|
|
517
|
+
maxInputTokens: 272e3,
|
|
518
|
+
inputPer1m: 2.5,
|
|
519
|
+
outputPer1m: 15,
|
|
520
|
+
cachedInputPer1m: 0.25
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
inputPer1m: 5,
|
|
524
|
+
outputPer1m: 22.5,
|
|
525
|
+
cachedInputPer1m: 0.5
|
|
526
|
+
}
|
|
527
|
+
],
|
|
528
|
+
batchMultiplier: 0.5,
|
|
529
|
+
contextWindow: 105e4
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
model: "gpt-5.4-mini",
|
|
533
|
+
provider: "openai",
|
|
534
|
+
modality: "text",
|
|
535
|
+
currency: "USD",
|
|
536
|
+
asOf: "2026-07-27",
|
|
537
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
538
|
+
provenance: "litellm-catalog",
|
|
539
|
+
tiers: [
|
|
540
|
+
{
|
|
541
|
+
inputPer1m: 0.75,
|
|
542
|
+
outputPer1m: 4.5,
|
|
543
|
+
cachedInputPer1m: 0.075
|
|
544
|
+
}
|
|
545
|
+
],
|
|
546
|
+
batchMultiplier: 0.5,
|
|
547
|
+
contextWindow: 105e4
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
model: "gpt-5",
|
|
551
|
+
provider: "openai",
|
|
552
|
+
modality: "text",
|
|
553
|
+
currency: "USD",
|
|
554
|
+
asOf: "2026-07-27",
|
|
555
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
556
|
+
provenance: "litellm-catalog",
|
|
557
|
+
tiers: [
|
|
558
|
+
{
|
|
559
|
+
inputPer1m: 1.25,
|
|
560
|
+
outputPer1m: 10,
|
|
561
|
+
cachedInputPer1m: 0.125
|
|
562
|
+
}
|
|
563
|
+
],
|
|
564
|
+
contextWindow: 272e3
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
model: "gpt-5-mini",
|
|
568
|
+
provider: "openai",
|
|
569
|
+
modality: "text",
|
|
570
|
+
currency: "USD",
|
|
571
|
+
asOf: "2026-07-27",
|
|
572
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
573
|
+
provenance: "litellm-catalog",
|
|
574
|
+
tiers: [
|
|
575
|
+
{
|
|
576
|
+
inputPer1m: 0.25,
|
|
577
|
+
outputPer1m: 2,
|
|
578
|
+
cachedInputPer1m: 0.025
|
|
579
|
+
}
|
|
580
|
+
],
|
|
581
|
+
contextWindow: 272e3
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
model: "gpt-5-nano",
|
|
585
|
+
provider: "openai",
|
|
586
|
+
modality: "text",
|
|
587
|
+
currency: "USD",
|
|
588
|
+
asOf: "2026-07-27",
|
|
589
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
590
|
+
provenance: "litellm-catalog",
|
|
591
|
+
tiers: [
|
|
592
|
+
{
|
|
593
|
+
inputPer1m: 0.05,
|
|
594
|
+
outputPer1m: 0.4,
|
|
595
|
+
cachedInputPer1m: 5e-3
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
contextWindow: 272e3
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
model: "gpt-4.1",
|
|
602
|
+
provider: "openai",
|
|
603
|
+
modality: "text",
|
|
604
|
+
currency: "USD",
|
|
605
|
+
asOf: "2026-07-27",
|
|
606
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
607
|
+
provenance: "litellm-catalog",
|
|
608
|
+
tiers: [
|
|
609
|
+
{
|
|
610
|
+
inputPer1m: 2,
|
|
611
|
+
outputPer1m: 8,
|
|
612
|
+
cachedInputPer1m: 0.5
|
|
613
|
+
}
|
|
614
|
+
],
|
|
615
|
+
batchMultiplier: 0.5,
|
|
616
|
+
contextWindow: 1047576
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
model: "gpt-4.1-mini",
|
|
620
|
+
provider: "openai",
|
|
621
|
+
modality: "text",
|
|
622
|
+
currency: "USD",
|
|
623
|
+
asOf: "2026-07-27",
|
|
624
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
625
|
+
provenance: "litellm-catalog",
|
|
626
|
+
tiers: [
|
|
627
|
+
{
|
|
628
|
+
inputPer1m: 0.4,
|
|
629
|
+
outputPer1m: 1.6,
|
|
630
|
+
cachedInputPer1m: 0.1
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
batchMultiplier: 0.5,
|
|
634
|
+
contextWindow: 1047576
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
model: "gpt-4.1-nano",
|
|
638
|
+
provider: "openai",
|
|
639
|
+
modality: "text",
|
|
640
|
+
currency: "USD",
|
|
641
|
+
asOf: "2026-07-27",
|
|
642
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
643
|
+
provenance: "litellm-catalog",
|
|
644
|
+
tiers: [
|
|
645
|
+
{
|
|
646
|
+
inputPer1m: 0.1,
|
|
647
|
+
outputPer1m: 0.4,
|
|
648
|
+
cachedInputPer1m: 0.025
|
|
649
|
+
}
|
|
650
|
+
],
|
|
651
|
+
batchMultiplier: 0.5,
|
|
652
|
+
contextWindow: 1047576
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
model: "gpt-4o",
|
|
656
|
+
provider: "openai",
|
|
657
|
+
modality: "text",
|
|
658
|
+
currency: "USD",
|
|
659
|
+
asOf: "2026-07-27",
|
|
660
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
661
|
+
provenance: "litellm-catalog",
|
|
662
|
+
tiers: [
|
|
663
|
+
{
|
|
664
|
+
inputPer1m: 2.5,
|
|
665
|
+
outputPer1m: 10,
|
|
666
|
+
cachedInputPer1m: 1.25
|
|
667
|
+
}
|
|
668
|
+
],
|
|
669
|
+
batchMultiplier: 0.5,
|
|
670
|
+
contextWindow: 128e3
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
model: "gpt-4o-mini",
|
|
674
|
+
provider: "openai",
|
|
675
|
+
modality: "text",
|
|
676
|
+
currency: "USD",
|
|
677
|
+
asOf: "2026-07-27",
|
|
678
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
679
|
+
provenance: "litellm-catalog",
|
|
680
|
+
tiers: [
|
|
681
|
+
{
|
|
682
|
+
inputPer1m: 0.15,
|
|
683
|
+
outputPer1m: 0.6,
|
|
684
|
+
cachedInputPer1m: 0.075
|
|
685
|
+
}
|
|
686
|
+
],
|
|
687
|
+
batchMultiplier: 0.5,
|
|
688
|
+
contextWindow: 128e3
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
model: "gpt-5.5",
|
|
692
|
+
provider: "azure",
|
|
693
|
+
region: "global",
|
|
694
|
+
modality: "text",
|
|
695
|
+
currency: "USD",
|
|
696
|
+
asOf: "2026-07-27",
|
|
697
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
698
|
+
provenance: "litellm-catalog",
|
|
699
|
+
tiers: [
|
|
700
|
+
{
|
|
701
|
+
maxInputTokens: 272e3,
|
|
702
|
+
inputPer1m: 5,
|
|
703
|
+
outputPer1m: 30,
|
|
704
|
+
cachedInputPer1m: 0.5
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
inputPer1m: 10,
|
|
708
|
+
outputPer1m: 45,
|
|
709
|
+
cachedInputPer1m: 1
|
|
710
|
+
}
|
|
711
|
+
],
|
|
712
|
+
contextWindow: 105e4
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
model: "gpt-5.5",
|
|
716
|
+
provider: "azure",
|
|
717
|
+
region: "us",
|
|
718
|
+
modality: "text",
|
|
719
|
+
currency: "USD",
|
|
720
|
+
asOf: "2026-07-27",
|
|
721
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
722
|
+
provenance: "litellm-catalog",
|
|
723
|
+
tiers: [
|
|
724
|
+
{
|
|
725
|
+
maxInputTokens: 272e3,
|
|
726
|
+
inputPer1m: 5.5,
|
|
727
|
+
outputPer1m: 33,
|
|
728
|
+
cachedInputPer1m: 0.55
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
inputPer1m: 11,
|
|
732
|
+
outputPer1m: 49.5,
|
|
733
|
+
cachedInputPer1m: 1.1
|
|
734
|
+
}
|
|
735
|
+
],
|
|
736
|
+
contextWindow: 105e4
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
model: "gpt-5.5",
|
|
740
|
+
provider: "azure",
|
|
741
|
+
region: "eu",
|
|
742
|
+
modality: "text",
|
|
743
|
+
currency: "USD",
|
|
744
|
+
asOf: "2026-07-27",
|
|
745
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
746
|
+
provenance: "litellm-catalog",
|
|
747
|
+
tiers: [
|
|
748
|
+
{
|
|
749
|
+
maxInputTokens: 272e3,
|
|
750
|
+
inputPer1m: 5.5,
|
|
751
|
+
outputPer1m: 33,
|
|
752
|
+
cachedInputPer1m: 0.55
|
|
753
|
+
},
|
|
754
|
+
{
|
|
755
|
+
inputPer1m: 11,
|
|
756
|
+
outputPer1m: 49.5,
|
|
757
|
+
cachedInputPer1m: 1.1
|
|
758
|
+
}
|
|
759
|
+
],
|
|
760
|
+
contextWindow: 105e4
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
model: "gpt-5.4",
|
|
764
|
+
provider: "azure",
|
|
765
|
+
region: "global",
|
|
766
|
+
modality: "text",
|
|
767
|
+
currency: "USD",
|
|
768
|
+
asOf: "2026-07-27",
|
|
769
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
770
|
+
provenance: "litellm-catalog",
|
|
771
|
+
tiers: [
|
|
772
|
+
{
|
|
773
|
+
maxInputTokens: 272e3,
|
|
774
|
+
inputPer1m: 2.5,
|
|
775
|
+
outputPer1m: 15,
|
|
776
|
+
cachedInputPer1m: 0.25
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
inputPer1m: 5,
|
|
780
|
+
outputPer1m: 22.5,
|
|
781
|
+
cachedInputPer1m: 0.5
|
|
782
|
+
}
|
|
783
|
+
],
|
|
784
|
+
contextWindow: 105e4
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
model: "gpt-5.4",
|
|
788
|
+
provider: "azure",
|
|
789
|
+
region: "us",
|
|
790
|
+
modality: "text",
|
|
791
|
+
currency: "USD",
|
|
792
|
+
asOf: "2026-07-27",
|
|
793
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
794
|
+
provenance: "litellm-catalog",
|
|
795
|
+
tiers: [
|
|
796
|
+
{
|
|
797
|
+
inputPer1m: 2.75,
|
|
798
|
+
outputPer1m: 16.5,
|
|
799
|
+
cachedInputPer1m: 0.28
|
|
800
|
+
}
|
|
801
|
+
],
|
|
802
|
+
contextWindow: 105e4
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
model: "gpt-5.4",
|
|
806
|
+
provider: "azure",
|
|
807
|
+
region: "eu",
|
|
808
|
+
modality: "text",
|
|
809
|
+
currency: "USD",
|
|
810
|
+
asOf: "2026-07-27",
|
|
811
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
812
|
+
provenance: "litellm-catalog",
|
|
813
|
+
tiers: [
|
|
814
|
+
{
|
|
815
|
+
inputPer1m: 2.75,
|
|
816
|
+
outputPer1m: 16.5,
|
|
817
|
+
cachedInputPer1m: 0.28
|
|
818
|
+
}
|
|
819
|
+
],
|
|
820
|
+
contextWindow: 105e4
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
model: "gpt-5.6-terra",
|
|
824
|
+
provider: "azure",
|
|
825
|
+
region: "global",
|
|
826
|
+
modality: "text",
|
|
827
|
+
currency: "USD",
|
|
828
|
+
asOf: "2026-07-27",
|
|
829
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
830
|
+
provenance: "litellm-catalog",
|
|
831
|
+
tiers: [
|
|
832
|
+
{
|
|
833
|
+
maxInputTokens: 272e3,
|
|
834
|
+
inputPer1m: 2.5,
|
|
835
|
+
outputPer1m: 15,
|
|
836
|
+
cachedInputPer1m: 0.25
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
inputPer1m: 5,
|
|
840
|
+
outputPer1m: 22.5,
|
|
841
|
+
cachedInputPer1m: 0.5
|
|
842
|
+
}
|
|
843
|
+
],
|
|
844
|
+
contextWindow: 105e4
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
model: "gpt-5.6-terra",
|
|
848
|
+
provider: "azure",
|
|
849
|
+
region: "eu",
|
|
850
|
+
modality: "text",
|
|
851
|
+
currency: "USD",
|
|
852
|
+
asOf: "2026-07-27",
|
|
853
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
854
|
+
provenance: "litellm-catalog",
|
|
855
|
+
tiers: [
|
|
856
|
+
{
|
|
857
|
+
maxInputTokens: 272e3,
|
|
858
|
+
inputPer1m: 2.75,
|
|
859
|
+
outputPer1m: 16.5,
|
|
860
|
+
cachedInputPer1m: 0.275
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
inputPer1m: 5.5,
|
|
864
|
+
outputPer1m: 24.75,
|
|
865
|
+
cachedInputPer1m: 0.55
|
|
866
|
+
}
|
|
867
|
+
],
|
|
868
|
+
contextWindow: 105e4
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
model: "gpt-5",
|
|
872
|
+
provider: "azure",
|
|
873
|
+
region: "global",
|
|
874
|
+
modality: "text",
|
|
875
|
+
currency: "USD",
|
|
876
|
+
asOf: "2026-07-27",
|
|
877
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
878
|
+
provenance: "litellm-catalog",
|
|
879
|
+
tiers: [
|
|
880
|
+
{
|
|
881
|
+
inputPer1m: 1.25,
|
|
882
|
+
outputPer1m: 10,
|
|
883
|
+
cachedInputPer1m: 0.125
|
|
884
|
+
}
|
|
885
|
+
],
|
|
886
|
+
contextWindow: 272e3
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
model: "gpt-5-mini",
|
|
890
|
+
provider: "azure",
|
|
891
|
+
region: "global",
|
|
892
|
+
modality: "text",
|
|
893
|
+
currency: "USD",
|
|
894
|
+
asOf: "2026-07-27",
|
|
895
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
896
|
+
provenance: "litellm-catalog",
|
|
897
|
+
tiers: [
|
|
898
|
+
{
|
|
899
|
+
inputPer1m: 0.25,
|
|
900
|
+
outputPer1m: 2,
|
|
901
|
+
cachedInputPer1m: 0.025
|
|
902
|
+
}
|
|
903
|
+
],
|
|
904
|
+
contextWindow: 272e3
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
model: "gpt-5-nano",
|
|
908
|
+
provider: "azure",
|
|
909
|
+
region: "global",
|
|
910
|
+
modality: "text",
|
|
911
|
+
currency: "USD",
|
|
912
|
+
asOf: "2026-07-27",
|
|
913
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
914
|
+
provenance: "litellm-catalog",
|
|
915
|
+
tiers: [
|
|
916
|
+
{
|
|
917
|
+
inputPer1m: 0.05,
|
|
918
|
+
outputPer1m: 0.4,
|
|
919
|
+
cachedInputPer1m: 5e-3
|
|
920
|
+
}
|
|
921
|
+
],
|
|
922
|
+
contextWindow: 272e3
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
model: "gpt-4.1",
|
|
926
|
+
provider: "azure",
|
|
927
|
+
region: "global",
|
|
928
|
+
modality: "text",
|
|
929
|
+
currency: "USD",
|
|
930
|
+
asOf: "2026-07-27",
|
|
931
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
932
|
+
provenance: "litellm-catalog",
|
|
933
|
+
tiers: [
|
|
934
|
+
{
|
|
935
|
+
inputPer1m: 2,
|
|
936
|
+
outputPer1m: 8,
|
|
937
|
+
cachedInputPer1m: 0.5
|
|
938
|
+
}
|
|
939
|
+
],
|
|
940
|
+
batchMultiplier: 0.5,
|
|
941
|
+
contextWindow: 1047576
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
model: "gpt-4o",
|
|
945
|
+
provider: "azure",
|
|
946
|
+
region: "global",
|
|
947
|
+
modality: "text",
|
|
948
|
+
currency: "USD",
|
|
949
|
+
asOf: "2026-07-27",
|
|
950
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
951
|
+
provenance: "litellm-catalog",
|
|
952
|
+
tiers: [
|
|
953
|
+
{
|
|
954
|
+
inputPer1m: 2.5,
|
|
955
|
+
outputPer1m: 10,
|
|
956
|
+
cachedInputPer1m: 1.25
|
|
957
|
+
}
|
|
958
|
+
],
|
|
959
|
+
contextWindow: 128e3
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
model: "gpt-4o-mini",
|
|
963
|
+
provider: "azure",
|
|
964
|
+
region: "global",
|
|
965
|
+
modality: "text",
|
|
966
|
+
currency: "USD",
|
|
967
|
+
asOf: "2026-07-27",
|
|
968
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
969
|
+
provenance: "litellm-catalog",
|
|
970
|
+
tiers: [
|
|
971
|
+
{
|
|
972
|
+
inputPer1m: 0.165,
|
|
973
|
+
outputPer1m: 0.66,
|
|
974
|
+
cachedInputPer1m: 0.075
|
|
975
|
+
}
|
|
976
|
+
],
|
|
977
|
+
contextWindow: 128e3,
|
|
978
|
+
notes: "Azure charges $0.165/$0.66 per 1M for gpt-4o-mini vs $0.15/$0.60 on OpenAI \u2014 same model, different price."
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
model: "gemini-3.1-pro",
|
|
982
|
+
provider: "gemini",
|
|
983
|
+
aliases: [
|
|
984
|
+
"gemini-3.1-pro-preview"
|
|
985
|
+
],
|
|
986
|
+
modality: "text",
|
|
987
|
+
currency: "USD",
|
|
988
|
+
asOf: "2026-07-27",
|
|
989
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
990
|
+
provenance: "vendor-pricing-page",
|
|
991
|
+
tiers: [
|
|
992
|
+
{
|
|
993
|
+
maxInputTokens: 2e5,
|
|
994
|
+
inputPer1m: 2,
|
|
995
|
+
outputPer1m: 12,
|
|
996
|
+
cachedInputPer1m: 0.2,
|
|
997
|
+
reasoningOutputPer1m: 12
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
inputPer1m: 4,
|
|
1001
|
+
outputPer1m: 18,
|
|
1002
|
+
cachedInputPer1m: 0.4,
|
|
1003
|
+
reasoningOutputPer1m: 18
|
|
1004
|
+
}
|
|
1005
|
+
],
|
|
1006
|
+
batchMultiplier: 0.5,
|
|
1007
|
+
contextWindow: 1048576,
|
|
1008
|
+
notes: "Long-context tier: $2/$12 per 1M up to 200K prompt tokens, $4/$18 above it."
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
model: "gemini-3-pro",
|
|
1012
|
+
provider: "gemini",
|
|
1013
|
+
aliases: [
|
|
1014
|
+
"gemini-3-pro-preview"
|
|
1015
|
+
],
|
|
1016
|
+
modality: "text",
|
|
1017
|
+
currency: "USD",
|
|
1018
|
+
asOf: "2026-07-27",
|
|
1019
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1020
|
+
provenance: "vendor-pricing-page",
|
|
1021
|
+
tiers: [
|
|
1022
|
+
{
|
|
1023
|
+
maxInputTokens: 2e5,
|
|
1024
|
+
inputPer1m: 2,
|
|
1025
|
+
outputPer1m: 12,
|
|
1026
|
+
cachedInputPer1m: 0.2,
|
|
1027
|
+
reasoningOutputPer1m: 12
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
inputPer1m: 4,
|
|
1031
|
+
outputPer1m: 18,
|
|
1032
|
+
cachedInputPer1m: 0.4,
|
|
1033
|
+
reasoningOutputPer1m: 18
|
|
1034
|
+
}
|
|
1035
|
+
],
|
|
1036
|
+
batchMultiplier: 0.5,
|
|
1037
|
+
contextWindow: 1048576
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
model: "gemini-3.6-flash",
|
|
1041
|
+
provider: "gemini",
|
|
1042
|
+
modality: "text",
|
|
1043
|
+
currency: "USD",
|
|
1044
|
+
asOf: "2026-07-27",
|
|
1045
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1046
|
+
provenance: "vendor-pricing-page",
|
|
1047
|
+
tiers: [
|
|
1048
|
+
{
|
|
1049
|
+
inputPer1m: 1.5,
|
|
1050
|
+
outputPer1m: 7.5,
|
|
1051
|
+
cachedInputPer1m: 0.15,
|
|
1052
|
+
reasoningOutputPer1m: 7.5
|
|
1053
|
+
}
|
|
1054
|
+
],
|
|
1055
|
+
batchMultiplier: 0.5,
|
|
1056
|
+
contextWindow: 1048576,
|
|
1057
|
+
notes: "Released 2026-07-21. Output pricing dropped ~17% from Gemini 3.5 Flash ($9.00 -> $7.50 per 1M); input unchanged."
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
model: "gemini-3.5-flash",
|
|
1061
|
+
provider: "gemini",
|
|
1062
|
+
modality: "text",
|
|
1063
|
+
currency: "USD",
|
|
1064
|
+
asOf: "2026-07-27",
|
|
1065
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1066
|
+
provenance: "vendor-pricing-page",
|
|
1067
|
+
tiers: [
|
|
1068
|
+
{
|
|
1069
|
+
inputPer1m: 1.5,
|
|
1070
|
+
outputPer1m: 9,
|
|
1071
|
+
cachedInputPer1m: 0.15,
|
|
1072
|
+
reasoningOutputPer1m: 9
|
|
1073
|
+
}
|
|
1074
|
+
],
|
|
1075
|
+
batchMultiplier: 0.5,
|
|
1076
|
+
contextWindow: 1048576
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
model: "gemini-3.5-flash-lite",
|
|
1080
|
+
provider: "gemini",
|
|
1081
|
+
modality: "text",
|
|
1082
|
+
currency: "USD",
|
|
1083
|
+
asOf: "2026-07-27",
|
|
1084
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing",
|
|
1085
|
+
provenance: "litellm-catalog",
|
|
1086
|
+
tiers: [
|
|
1087
|
+
{
|
|
1088
|
+
inputPer1m: 0.3,
|
|
1089
|
+
outputPer1m: 2.5,
|
|
1090
|
+
cachedInputPer1m: 0.03,
|
|
1091
|
+
reasoningOutputPer1m: 2.5
|
|
1092
|
+
}
|
|
1093
|
+
],
|
|
1094
|
+
batchMultiplier: 0.5,
|
|
1095
|
+
contextWindow: 1048576
|
|
1096
|
+
},
|
|
1097
|
+
{
|
|
1098
|
+
model: "gemini-3.1-flash-lite",
|
|
1099
|
+
provider: "gemini",
|
|
1100
|
+
modality: "text",
|
|
1101
|
+
currency: "USD",
|
|
1102
|
+
asOf: "2026-07-27",
|
|
1103
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite",
|
|
1104
|
+
provenance: "litellm-catalog",
|
|
1105
|
+
tiers: [
|
|
1106
|
+
{
|
|
1107
|
+
inputPer1m: 0.25,
|
|
1108
|
+
outputPer1m: 1.5,
|
|
1109
|
+
cachedInputPer1m: 0.025,
|
|
1110
|
+
reasoningOutputPer1m: 1.5
|
|
1111
|
+
}
|
|
1112
|
+
],
|
|
1113
|
+
batchMultiplier: 0.5,
|
|
1114
|
+
contextWindow: 1048576
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
model: "gemini-3-flash",
|
|
1118
|
+
provider: "gemini",
|
|
1119
|
+
aliases: [
|
|
1120
|
+
"gemini-3-flash-preview"
|
|
1121
|
+
],
|
|
1122
|
+
modality: "text",
|
|
1123
|
+
currency: "USD",
|
|
1124
|
+
asOf: "2026-07-27",
|
|
1125
|
+
source: "https://ai.google.dev/pricing/gemini-3",
|
|
1126
|
+
provenance: "litellm-catalog",
|
|
1127
|
+
tiers: [
|
|
1128
|
+
{
|
|
1129
|
+
inputPer1m: 0.5,
|
|
1130
|
+
outputPer1m: 3,
|
|
1131
|
+
cachedInputPer1m: 0.05,
|
|
1132
|
+
reasoningOutputPer1m: 3
|
|
1133
|
+
}
|
|
1134
|
+
],
|
|
1135
|
+
contextWindow: 1048576
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
model: "gemini-2.5-pro",
|
|
1139
|
+
provider: "gemini",
|
|
1140
|
+
modality: "text",
|
|
1141
|
+
currency: "USD",
|
|
1142
|
+
asOf: "2026-07-27",
|
|
1143
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1144
|
+
provenance: "litellm-catalog",
|
|
1145
|
+
tiers: [
|
|
1146
|
+
{
|
|
1147
|
+
maxInputTokens: 2e5,
|
|
1148
|
+
inputPer1m: 1.25,
|
|
1149
|
+
outputPer1m: 10,
|
|
1150
|
+
cachedInputPer1m: 0.125
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
inputPer1m: 2.5,
|
|
1154
|
+
outputPer1m: 15,
|
|
1155
|
+
cachedInputPer1m: 0.25
|
|
1156
|
+
}
|
|
1157
|
+
],
|
|
1158
|
+
contextWindow: 1048576
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
model: "gemini-2.5-flash",
|
|
1162
|
+
provider: "gemini",
|
|
1163
|
+
modality: "text",
|
|
1164
|
+
currency: "USD",
|
|
1165
|
+
asOf: "2026-07-27",
|
|
1166
|
+
source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
|
|
1167
|
+
provenance: "litellm-catalog",
|
|
1168
|
+
tiers: [
|
|
1169
|
+
{
|
|
1170
|
+
inputPer1m: 0.3,
|
|
1171
|
+
outputPer1m: 2.5,
|
|
1172
|
+
cachedInputPer1m: 0.03,
|
|
1173
|
+
reasoningOutputPer1m: 2.5
|
|
1174
|
+
}
|
|
1175
|
+
],
|
|
1176
|
+
contextWindow: 1048576
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
model: "gemini-2.5-flash-lite",
|
|
1180
|
+
provider: "gemini",
|
|
1181
|
+
modality: "text",
|
|
1182
|
+
currency: "USD",
|
|
1183
|
+
asOf: "2026-07-27",
|
|
1184
|
+
source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
|
|
1185
|
+
provenance: "litellm-catalog",
|
|
1186
|
+
tiers: [
|
|
1187
|
+
{
|
|
1188
|
+
inputPer1m: 0.1,
|
|
1189
|
+
outputPer1m: 0.4,
|
|
1190
|
+
cachedInputPer1m: 0.01,
|
|
1191
|
+
reasoningOutputPer1m: 0.4
|
|
1192
|
+
}
|
|
1193
|
+
],
|
|
1194
|
+
contextWindow: 1048576
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
model: "gemini-2.0-flash",
|
|
1198
|
+
provider: "gemini",
|
|
1199
|
+
modality: "text",
|
|
1200
|
+
currency: "USD",
|
|
1201
|
+
asOf: "2026-07-27",
|
|
1202
|
+
source: "https://ai.google.dev/pricing#2_0flash",
|
|
1203
|
+
provenance: "litellm-catalog",
|
|
1204
|
+
tiers: [
|
|
1205
|
+
{
|
|
1206
|
+
inputPer1m: 0.1,
|
|
1207
|
+
outputPer1m: 0.4,
|
|
1208
|
+
cachedInputPer1m: 0.025
|
|
1209
|
+
}
|
|
1210
|
+
],
|
|
1211
|
+
contextWindow: 1048576,
|
|
1212
|
+
notes: "Gemini Developer API price. Vertex AI lists $0.15/$0.60 per 1M for the same model \u2014 see the vertex entry."
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
model: "gemini-2.0-flash-lite",
|
|
1216
|
+
provider: "gemini",
|
|
1217
|
+
modality: "text",
|
|
1218
|
+
currency: "USD",
|
|
1219
|
+
asOf: "2026-07-27",
|
|
1220
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite",
|
|
1221
|
+
provenance: "litellm-catalog",
|
|
1222
|
+
tiers: [
|
|
1223
|
+
{
|
|
1224
|
+
inputPer1m: 0.075,
|
|
1225
|
+
outputPer1m: 0.3,
|
|
1226
|
+
cachedInputPer1m: 0.01875
|
|
1227
|
+
}
|
|
1228
|
+
],
|
|
1229
|
+
contextWindow: 1048576
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
model: "gemini-2.0-flash",
|
|
1233
|
+
provider: "vertex",
|
|
1234
|
+
modality: "text",
|
|
1235
|
+
currency: "USD",
|
|
1236
|
+
asOf: "2026-07-27",
|
|
1237
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1238
|
+
provenance: "vendor-pricing-page",
|
|
1239
|
+
tiers: [
|
|
1240
|
+
{
|
|
1241
|
+
inputPer1m: 0.15,
|
|
1242
|
+
outputPer1m: 0.6
|
|
1243
|
+
}
|
|
1244
|
+
],
|
|
1245
|
+
batchMultiplier: 0.5,
|
|
1246
|
+
notes: "Vertex AI lists $0.15/$0.60 per 1M; the Gemini Developer API lists $0.10/$0.40 for the same model."
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
model: "gemini-3.1-pro",
|
|
1250
|
+
provider: "vertex",
|
|
1251
|
+
aliases: [
|
|
1252
|
+
"gemini-3.1-pro-preview"
|
|
1253
|
+
],
|
|
1254
|
+
modality: "text",
|
|
1255
|
+
currency: "USD",
|
|
1256
|
+
asOf: "2026-07-27",
|
|
1257
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1258
|
+
provenance: "vendor-pricing-page",
|
|
1259
|
+
tiers: [
|
|
1260
|
+
{
|
|
1261
|
+
maxInputTokens: 2e5,
|
|
1262
|
+
inputPer1m: 2,
|
|
1263
|
+
outputPer1m: 12,
|
|
1264
|
+
cachedInputPer1m: 0.2,
|
|
1265
|
+
reasoningOutputPer1m: 12
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
inputPer1m: 4,
|
|
1269
|
+
outputPer1m: 18,
|
|
1270
|
+
cachedInputPer1m: 0.4,
|
|
1271
|
+
reasoningOutputPer1m: 18
|
|
1272
|
+
}
|
|
1273
|
+
],
|
|
1274
|
+
batchMultiplier: 0.5,
|
|
1275
|
+
contextWindow: 1048576
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
model: "gemini-3.6-flash",
|
|
1279
|
+
provider: "vertex",
|
|
1280
|
+
modality: "text",
|
|
1281
|
+
currency: "USD",
|
|
1282
|
+
asOf: "2026-07-27",
|
|
1283
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1284
|
+
provenance: "vendor-pricing-page",
|
|
1285
|
+
tiers: [
|
|
1286
|
+
{
|
|
1287
|
+
inputPer1m: 1.5,
|
|
1288
|
+
outputPer1m: 7.5,
|
|
1289
|
+
cachedInputPer1m: 0.15,
|
|
1290
|
+
reasoningOutputPer1m: 7.5
|
|
1291
|
+
}
|
|
1292
|
+
],
|
|
1293
|
+
batchMultiplier: 0.5,
|
|
1294
|
+
contextWindow: 1048576
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
model: "gemini-2.5-pro",
|
|
1298
|
+
provider: "vertex",
|
|
1299
|
+
modality: "text",
|
|
1300
|
+
currency: "USD",
|
|
1301
|
+
asOf: "2026-07-27",
|
|
1302
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1303
|
+
provenance: "vendor-pricing-page",
|
|
1304
|
+
tiers: [
|
|
1305
|
+
{
|
|
1306
|
+
maxInputTokens: 2e5,
|
|
1307
|
+
inputPer1m: 1.25,
|
|
1308
|
+
outputPer1m: 10,
|
|
1309
|
+
cachedInputPer1m: 0.13
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
inputPer1m: 2.5,
|
|
1313
|
+
outputPer1m: 15,
|
|
1314
|
+
cachedInputPer1m: 0.25
|
|
1315
|
+
}
|
|
1316
|
+
],
|
|
1317
|
+
batchMultiplier: 0.5,
|
|
1318
|
+
contextWindow: 1048576
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
model: "gemini-2.5-flash",
|
|
1322
|
+
provider: "vertex",
|
|
1323
|
+
modality: "text",
|
|
1324
|
+
currency: "USD",
|
|
1325
|
+
asOf: "2026-07-27",
|
|
1326
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1327
|
+
provenance: "vendor-pricing-page",
|
|
1328
|
+
tiers: [
|
|
1329
|
+
{
|
|
1330
|
+
inputPer1m: 0.3,
|
|
1331
|
+
outputPer1m: 2.5,
|
|
1332
|
+
cachedInputPer1m: 0.03,
|
|
1333
|
+
reasoningOutputPer1m: 2.5
|
|
1334
|
+
}
|
|
1335
|
+
],
|
|
1336
|
+
batchMultiplier: 0.5,
|
|
1337
|
+
contextWindow: 1048576
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
model: "claude-sonnet-4-6",
|
|
1341
|
+
provider: "openrouter",
|
|
1342
|
+
modality: "text",
|
|
1343
|
+
currency: "USD",
|
|
1344
|
+
asOf: "2026-07-27",
|
|
1345
|
+
source: "https://openrouter.ai/anthropic/claude-sonnet-4.6",
|
|
1346
|
+
provenance: "litellm-catalog",
|
|
1347
|
+
tiers: [
|
|
1348
|
+
{
|
|
1349
|
+
maxInputTokens: 2e5,
|
|
1350
|
+
inputPer1m: 3,
|
|
1351
|
+
outputPer1m: 15,
|
|
1352
|
+
cachedInputPer1m: 0.3,
|
|
1353
|
+
cacheWritePer1m: 3.75
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
inputPer1m: 6,
|
|
1357
|
+
outputPer1m: 22.5,
|
|
1358
|
+
cachedInputPer1m: 0.6,
|
|
1359
|
+
cacheWritePer1m: 7.5
|
|
1360
|
+
}
|
|
1361
|
+
],
|
|
1362
|
+
contextWindow: 1e6
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
model: "claude-haiku-4-5",
|
|
1366
|
+
provider: "openrouter",
|
|
1367
|
+
modality: "text",
|
|
1368
|
+
currency: "USD",
|
|
1369
|
+
asOf: "2026-07-27",
|
|
1370
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1371
|
+
provenance: "litellm-catalog",
|
|
1372
|
+
tiers: [
|
|
1373
|
+
{
|
|
1374
|
+
inputPer1m: 1,
|
|
1375
|
+
outputPer1m: 5,
|
|
1376
|
+
cachedInputPer1m: 0.1,
|
|
1377
|
+
cacheWritePer1m: 1.25
|
|
1378
|
+
}
|
|
1379
|
+
],
|
|
1380
|
+
contextWindow: 2e5
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
model: "gpt-5",
|
|
1384
|
+
provider: "openrouter",
|
|
1385
|
+
modality: "text",
|
|
1386
|
+
currency: "USD",
|
|
1387
|
+
asOf: "2026-07-27",
|
|
1388
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1389
|
+
provenance: "litellm-catalog",
|
|
1390
|
+
tiers: [
|
|
1391
|
+
{
|
|
1392
|
+
inputPer1m: 1.25,
|
|
1393
|
+
outputPer1m: 10,
|
|
1394
|
+
cachedInputPer1m: 0.125
|
|
1395
|
+
}
|
|
1396
|
+
],
|
|
1397
|
+
contextWindow: 272e3
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
model: "gpt-5-mini",
|
|
1401
|
+
provider: "openrouter",
|
|
1402
|
+
modality: "text",
|
|
1403
|
+
currency: "USD",
|
|
1404
|
+
asOf: "2026-07-27",
|
|
1405
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1406
|
+
provenance: "litellm-catalog",
|
|
1407
|
+
tiers: [
|
|
1408
|
+
{
|
|
1409
|
+
inputPer1m: 0.25,
|
|
1410
|
+
outputPer1m: 2,
|
|
1411
|
+
cachedInputPer1m: 0.025
|
|
1412
|
+
}
|
|
1413
|
+
],
|
|
1414
|
+
contextWindow: 272e3
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
model: "gemini-3.1-pro",
|
|
1418
|
+
provider: "openrouter",
|
|
1419
|
+
aliases: [
|
|
1420
|
+
"gemini-3.1-pro-preview"
|
|
1421
|
+
],
|
|
1422
|
+
modality: "text",
|
|
1423
|
+
currency: "USD",
|
|
1424
|
+
asOf: "2026-07-27",
|
|
1425
|
+
source: "https://openrouter.ai/google/gemini-3.1-pro-preview",
|
|
1426
|
+
provenance: "litellm-catalog",
|
|
1427
|
+
tiers: [
|
|
1428
|
+
{
|
|
1429
|
+
maxInputTokens: 2e5,
|
|
1430
|
+
inputPer1m: 2,
|
|
1431
|
+
outputPer1m: 12,
|
|
1432
|
+
cachedInputPer1m: 0.2
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
inputPer1m: 4,
|
|
1436
|
+
outputPer1m: 18,
|
|
1437
|
+
cachedInputPer1m: 0.4,
|
|
1438
|
+
cacheWritePer1m: 0.25
|
|
1439
|
+
}
|
|
1440
|
+
],
|
|
1441
|
+
contextWindow: 1048576
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
model: "text-embedding-3-small",
|
|
1445
|
+
provider: "openai",
|
|
1446
|
+
modality: "embedding",
|
|
1447
|
+
currency: "USD",
|
|
1448
|
+
asOf: "2026-07-27",
|
|
1449
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1450
|
+
provenance: "litellm-catalog",
|
|
1451
|
+
tiers: [
|
|
1452
|
+
{
|
|
1453
|
+
inputPer1m: 0.02,
|
|
1454
|
+
outputPer1m: 0
|
|
1455
|
+
}
|
|
1456
|
+
],
|
|
1457
|
+
batchMultiplier: 0.5,
|
|
1458
|
+
contextWindow: 8191
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
model: "text-embedding-3-large",
|
|
1462
|
+
provider: "openai",
|
|
1463
|
+
modality: "embedding",
|
|
1464
|
+
currency: "USD",
|
|
1465
|
+
asOf: "2026-07-27",
|
|
1466
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1467
|
+
provenance: "litellm-catalog",
|
|
1468
|
+
tiers: [
|
|
1469
|
+
{
|
|
1470
|
+
inputPer1m: 0.13,
|
|
1471
|
+
outputPer1m: 0
|
|
1472
|
+
}
|
|
1473
|
+
],
|
|
1474
|
+
batchMultiplier: 0.5,
|
|
1475
|
+
contextWindow: 8191
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
model: "gemini-embedding-001",
|
|
1479
|
+
provider: "gemini",
|
|
1480
|
+
modality: "embedding",
|
|
1481
|
+
currency: "USD",
|
|
1482
|
+
asOf: "2026-07-27",
|
|
1483
|
+
source: "https://ai.google.dev/gemini-api/docs/embeddings#model-versions",
|
|
1484
|
+
provenance: "litellm-catalog",
|
|
1485
|
+
tiers: [
|
|
1486
|
+
{
|
|
1487
|
+
inputPer1m: 0.15,
|
|
1488
|
+
outputPer1m: 0
|
|
1489
|
+
}
|
|
1490
|
+
],
|
|
1491
|
+
contextWindow: 2048
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
model: "dall-e-3",
|
|
1495
|
+
provider: "openai",
|
|
1496
|
+
modality: "image",
|
|
1497
|
+
currency: "USD",
|
|
1498
|
+
asOf: "2026-07-27",
|
|
1499
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1500
|
+
provenance: "litellm-catalog",
|
|
1501
|
+
rates: [
|
|
1502
|
+
{
|
|
1503
|
+
unit: "per_image",
|
|
1504
|
+
amount: 0.04,
|
|
1505
|
+
variant: "1024x1024 standard"
|
|
1506
|
+
}
|
|
1507
|
+
]
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
model: "gemini-3-pro-image",
|
|
1511
|
+
provider: "gemini",
|
|
1512
|
+
modality: "image",
|
|
1513
|
+
currency: "USD",
|
|
1514
|
+
asOf: "2026-07-27",
|
|
1515
|
+
source: "https://ai.google.dev/gemini-api/docs/pricing",
|
|
1516
|
+
provenance: "litellm-catalog",
|
|
1517
|
+
rates: [
|
|
1518
|
+
{
|
|
1519
|
+
unit: "per_image",
|
|
1520
|
+
amount: 0.134,
|
|
1521
|
+
variant: "default output resolution"
|
|
1522
|
+
}
|
|
1523
|
+
],
|
|
1524
|
+
notes: "Google bills image output per output-token ($120 per 1M image output tokens); $0.134/image is that rate applied to one default-resolution image. Cost per image therefore varies with resolution."
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
model: "imagen-4.0-generate-001",
|
|
1528
|
+
provider: "vertex",
|
|
1529
|
+
modality: "image",
|
|
1530
|
+
currency: "USD",
|
|
1531
|
+
asOf: "2026-07-27",
|
|
1532
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
|
|
1533
|
+
provenance: "litellm-catalog",
|
|
1534
|
+
rates: [
|
|
1535
|
+
{
|
|
1536
|
+
unit: "per_image",
|
|
1537
|
+
amount: 0.04
|
|
1538
|
+
}
|
|
1539
|
+
]
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
model: "veo-3.1-generate-001",
|
|
1543
|
+
provider: "gemini",
|
|
1544
|
+
modality: "video",
|
|
1545
|
+
currency: "USD",
|
|
1546
|
+
asOf: "2026-07-27",
|
|
1547
|
+
source: "https://ai.google.dev/gemini-api/docs/video",
|
|
1548
|
+
provenance: "litellm-catalog",
|
|
1549
|
+
rates: [
|
|
1550
|
+
{
|
|
1551
|
+
unit: "per_second",
|
|
1552
|
+
amount: 0.4
|
|
1553
|
+
}
|
|
1554
|
+
]
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
model: "veo-3.1-generate-001",
|
|
1558
|
+
provider: "vertex",
|
|
1559
|
+
modality: "video",
|
|
1560
|
+
currency: "USD",
|
|
1561
|
+
asOf: "2026-07-27",
|
|
1562
|
+
source: "https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo",
|
|
1563
|
+
provenance: "litellm-catalog",
|
|
1564
|
+
rates: [
|
|
1565
|
+
{
|
|
1566
|
+
unit: "per_second",
|
|
1567
|
+
amount: 0.4
|
|
1568
|
+
}
|
|
1569
|
+
]
|
|
1570
|
+
},
|
|
1571
|
+
{
|
|
1572
|
+
model: "tts-1",
|
|
1573
|
+
provider: "openai",
|
|
1574
|
+
modality: "speech",
|
|
1575
|
+
currency: "USD",
|
|
1576
|
+
asOf: "2026-07-27",
|
|
1577
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1578
|
+
provenance: "litellm-catalog",
|
|
1579
|
+
rates: [
|
|
1580
|
+
{
|
|
1581
|
+
unit: "per_character",
|
|
1582
|
+
amount: 15e-6
|
|
1583
|
+
}
|
|
1584
|
+
],
|
|
1585
|
+
notes: "$15 per 1M characters."
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
model: "tts-1-hd",
|
|
1589
|
+
provider: "openai",
|
|
1590
|
+
modality: "speech",
|
|
1591
|
+
currency: "USD",
|
|
1592
|
+
asOf: "2026-07-27",
|
|
1593
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1594
|
+
provenance: "litellm-catalog",
|
|
1595
|
+
rates: [
|
|
1596
|
+
{
|
|
1597
|
+
unit: "per_character",
|
|
1598
|
+
amount: 3e-5
|
|
1599
|
+
}
|
|
1600
|
+
],
|
|
1601
|
+
notes: "$30 per 1M characters."
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
model: "whisper-1",
|
|
1605
|
+
provider: "openai",
|
|
1606
|
+
modality: "transcription",
|
|
1607
|
+
currency: "USD",
|
|
1608
|
+
asOf: "2026-07-27",
|
|
1609
|
+
source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
|
|
1610
|
+
provenance: "litellm-catalog",
|
|
1611
|
+
rates: [
|
|
1612
|
+
{
|
|
1613
|
+
unit: "per_audio_minute",
|
|
1614
|
+
amount: 6e-3
|
|
1615
|
+
}
|
|
1616
|
+
],
|
|
1617
|
+
notes: "litellm records $0.0001 per audio second; $0.006/minute is that rate x 60."
|
|
1618
|
+
}
|
|
1619
|
+
]
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
// src/core/router/pricing.ts
|
|
1625
|
+
function normalizeForPricing(id) {
|
|
1626
|
+
let s = id.trim().toLowerCase();
|
|
1627
|
+
s = s.replace(/^models\//, "");
|
|
1628
|
+
const slash = s.lastIndexOf("/");
|
|
1629
|
+
if (slash !== -1) s = s.slice(slash + 1);
|
|
1630
|
+
const tag = s.search(/[:@]/);
|
|
1631
|
+
if (tag !== -1) s = s.slice(0, tag);
|
|
1632
|
+
s = s.replace(/-latest$/, "");
|
|
1633
|
+
s = s.replace(/-preview(?:-\d{2}-\d{2})?$/, "");
|
|
1634
|
+
s = s.replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
1635
|
+
s = s.replace(/-\d{8}$/, "");
|
|
1636
|
+
s = s.replace(/-preview$/, "");
|
|
1637
|
+
return s;
|
|
1638
|
+
}
|
|
1639
|
+
function indexKey(model, provider, region) {
|
|
1640
|
+
return `${provider}|${normalizeForPricing(model)}|${region ?? ""}`;
|
|
1641
|
+
}
|
|
1642
|
+
function findPricing(modelId, provider, opts = {}) {
|
|
1643
|
+
const ids = [modelId, opts.baseModelId].filter((x) => !!x);
|
|
1644
|
+
const providers = [provider, ...opts.fallbackProviders ?? []];
|
|
1645
|
+
for (const p of providers) {
|
|
1646
|
+
for (const id of ids) {
|
|
1647
|
+
const hit = (opts.region ? INDEX.get(indexKey(id, p, opts.region)) : void 0) ?? INDEX.get(indexKey(id, p));
|
|
1648
|
+
if (hit) return { entry: hit, matchedProvider: p, viaFallback: p !== provider };
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return null;
|
|
1652
|
+
}
|
|
1653
|
+
function pricingDatasetMeta() {
|
|
1654
|
+
const { schemaVersion, generatedAt, defaultCurrency } = DATASET;
|
|
1655
|
+
return { schemaVersion, generatedAt, defaultCurrency };
|
|
1656
|
+
}
|
|
1657
|
+
function tierFor(entry, inputTokens = 0) {
|
|
1658
|
+
const tiers = entry.tiers;
|
|
1659
|
+
if (!tiers?.length) return null;
|
|
1660
|
+
for (const t of tiers) {
|
|
1661
|
+
if (t.maxInputTokens === void 0 || inputTokens <= t.maxInputTokens) return t;
|
|
1662
|
+
}
|
|
1663
|
+
return tiers[tiers.length - 1] ?? null;
|
|
1664
|
+
}
|
|
1665
|
+
function tokenRatesPer1k(entry, opts = {}) {
|
|
1666
|
+
const tier = tierFor(entry, opts.inputTokens ?? 0);
|
|
1667
|
+
if (!tier) return null;
|
|
1668
|
+
const mult = (opts.batch ? entry.batchMultiplier ?? 1 : 1) * PER_1M_TO_PER_1K;
|
|
1669
|
+
const out = {
|
|
1670
|
+
input: tier.inputPer1m * mult,
|
|
1671
|
+
output: tier.outputPer1m * mult
|
|
1672
|
+
};
|
|
1673
|
+
if (tier.cachedInputPer1m !== void 0) out.cachedInput = tier.cachedInputPer1m * mult;
|
|
1674
|
+
if (tier.cacheWritePer1m !== void 0) out.cacheWrite = tier.cacheWritePer1m * mult;
|
|
1675
|
+
if (tier.reasoningOutputPer1m !== void 0) out.reasoningOutput = tier.reasoningOutputPer1m * mult;
|
|
1676
|
+
return out;
|
|
1677
|
+
}
|
|
1678
|
+
function isLoopbackOrPrivateHost(url) {
|
|
1679
|
+
if (!url) return false;
|
|
1680
|
+
try {
|
|
1681
|
+
const host = new URL(url).hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
1682
|
+
if (LOOPBACK_HOSTS.has(host) || LOOPBACK_HOSTS.has(`[${host}]`)) return true;
|
|
1683
|
+
if (host.endsWith(".local")) return true;
|
|
1684
|
+
if (/^127\./.test(host)) return true;
|
|
1685
|
+
if (/^10\./.test(host)) return true;
|
|
1686
|
+
if (/^192\.168\./.test(host)) return true;
|
|
1687
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
|
|
1688
|
+
return false;
|
|
1689
|
+
} catch {
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
function isLocalEndpoint(cfg) {
|
|
1694
|
+
if (typeof cfg.local === "boolean") return cfg.local;
|
|
1695
|
+
if (cfg.type === "ollama") return true;
|
|
1696
|
+
if (cfg.type === "openai-compatible") return isLoopbackOrPrivateHost(cfg.baseUrl);
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
function resolvePricing(model, opts = {}) {
|
|
1700
|
+
if (model.isLocal) return FREE;
|
|
1701
|
+
const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
|
|
1702
|
+
const match = findPricing(model.id, model.provider, {
|
|
1703
|
+
region: opts.region,
|
|
1704
|
+
baseModelId: model.baseModelId,
|
|
1705
|
+
fallbackProviders
|
|
1706
|
+
});
|
|
1707
|
+
if (!match) return UNKNOWN;
|
|
1708
|
+
const rates = tokenRatesPer1k(match.entry, { inputTokens: opts.inputTokens, batch: opts.batch });
|
|
1709
|
+
if (!rates) return UNKNOWN;
|
|
1710
|
+
return {
|
|
1711
|
+
input: rates.input,
|
|
1712
|
+
output: rates.output,
|
|
1713
|
+
unknown: false,
|
|
1714
|
+
free: false,
|
|
1715
|
+
entry: match.entry,
|
|
1716
|
+
...match.viaFallback ? { estimatedFromProvider: match.matchedProvider } : {}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
function withResolvedPricing(model, opts = {}) {
|
|
1720
|
+
const p = resolvePricing(model, opts);
|
|
1721
|
+
return {
|
|
1722
|
+
...model,
|
|
1723
|
+
inputCostPer1kTokens: p.input,
|
|
1724
|
+
outputCostPer1kTokens: p.output,
|
|
1725
|
+
...p.unknown ? { pricingUnknown: true } : { pricingUnknown: false }
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
function isPricingUnknown(model) {
|
|
1729
|
+
return model.pricingUnknown === true && !model.isLocal;
|
|
1730
|
+
}
|
|
1731
|
+
function blendedCostPer1k(model) {
|
|
1732
|
+
if (isPricingUnknown(model)) return BLENDED_COST_CEILING;
|
|
1733
|
+
return model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
|
|
1734
|
+
}
|
|
1735
|
+
function reconcilePrice(model, live) {
|
|
1736
|
+
const baseline = resolvePricing(model);
|
|
1737
|
+
const liveIsZero = !(live.input > 0) && !(live.output > 0);
|
|
1738
|
+
if (liveIsZero && !model.isLocal) {
|
|
1739
|
+
return { input: baseline.input, output: baseline.output, accepted: false };
|
|
1740
|
+
}
|
|
1741
|
+
if (baseline.unknown || !baseline.entry) {
|
|
1742
|
+
return { input: live.input, output: live.output, accepted: true };
|
|
1743
|
+
}
|
|
1744
|
+
const dsBlend = baseline.input + baseline.output * 2;
|
|
1745
|
+
const liveBlend = live.input + live.output * 2;
|
|
1746
|
+
const ratio = dsBlend > 0 ? liveBlend / dsBlend : Number.POSITIVE_INFINITY;
|
|
1747
|
+
const material = dsBlend > 0 && Math.abs(ratio - 1) > DISAGREEMENT_THRESHOLD;
|
|
1748
|
+
return {
|
|
1749
|
+
input: live.input,
|
|
1750
|
+
output: live.output,
|
|
1751
|
+
accepted: true,
|
|
1752
|
+
...material ? {
|
|
1753
|
+
disagreement: {
|
|
1754
|
+
modelId: model.id,
|
|
1755
|
+
provider: model.provider,
|
|
1756
|
+
dataset: { input: baseline.input, output: baseline.output },
|
|
1757
|
+
live: { input: live.input, output: live.output },
|
|
1758
|
+
ratio: Math.round(ratio * 1e3) / 1e3,
|
|
1759
|
+
datasetAsOf: baseline.entry.asOf,
|
|
1760
|
+
datasetSource: baseline.entry.source
|
|
1761
|
+
}
|
|
1762
|
+
} : {}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
var DATASET, INDEX, PER_1M_TO_PER_1K, LOOPBACK_HOSTS, UNKNOWN, FREE, BLENDED_COST_CEILING, DISAGREEMENT_THRESHOLD;
|
|
1766
|
+
var init_pricing = __esm({
|
|
1767
|
+
"src/core/router/pricing.ts"() {
|
|
1768
|
+
init_pricing_data();
|
|
1769
|
+
DATASET = pricing_data_default;
|
|
1770
|
+
INDEX = /* @__PURE__ */ new Map();
|
|
1771
|
+
for (const entry of DATASET.entries) {
|
|
1772
|
+
for (const name of [entry.model, ...entry.aliases ?? []]) {
|
|
1773
|
+
const withRegion = indexKey(name, entry.provider, entry.region);
|
|
1774
|
+
if (!INDEX.has(withRegion)) INDEX.set(withRegion, entry);
|
|
1775
|
+
const bare = indexKey(name, entry.provider);
|
|
1776
|
+
const existing = INDEX.get(bare);
|
|
1777
|
+
if (!existing || existing.region && existing.region !== "global" && entry.region === "global") {
|
|
1778
|
+
INDEX.set(bare, entry);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
PER_1M_TO_PER_1K = 1 / 1e3;
|
|
1783
|
+
LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
|
|
1784
|
+
UNKNOWN = { input: 0, output: 0, unknown: true, free: false };
|
|
1785
|
+
FREE = { input: 0, output: 0, unknown: false, free: true };
|
|
1786
|
+
BLENDED_COST_CEILING = 0.05;
|
|
1787
|
+
DISAGREEMENT_THRESHOLD = 0.02;
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
|
|
115
1791
|
// src/constants.ts
|
|
116
1792
|
var CASCADE_VERSION, CASCADE_CONFIG_FILE, CASCADE_DB_FILE, CASCADE_DASHBOARD_SECRET_FILE, GLOBAL_CONFIG_DIR, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_CREDENTIALS_FILE, GLOBAL_RUNTIME_DB_FILE, DEFAULT_DASHBOARD_PORT, DEFAULT_CONTEXT_LIMIT, DEFAULT_AUTO_SUMMARIZE_AT, MODELS, T1_MODEL_PRIORITY, T2_MODEL_PRIORITY, T3_MODEL_PRIORITY, VISION_MODEL_PRIORITY, COMPLEXITY_T2_COUNT, THEME_NAMES, DEFAULT_THEME, OLLAMA_BASE_URL, LM_STUDIO_BASE_URL, AZURE_BASE_URL_TEMPLATE, TOOL_NAMES, DEFAULT_APPROVAL_REQUIRED;
|
|
117
1793
|
var init_constants = __esm({
|
|
118
1794
|
"src/constants.ts"() {
|
|
119
|
-
|
|
1795
|
+
init_pricing();
|
|
1796
|
+
CASCADE_VERSION = "0.50.0";
|
|
120
1797
|
CASCADE_CONFIG_FILE = ".cascade/config.json";
|
|
121
1798
|
CASCADE_DB_FILE = ".cascade/memory.db";
|
|
122
1799
|
CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
|
|
@@ -457,6 +2134,13 @@ var init_constants = __esm({
|
|
|
457
2134
|
for (const _m of Object.values(MODELS)) {
|
|
458
2135
|
if (_m.supportsToolUse === void 0) _m.supportsToolUse = !_m.isLocal;
|
|
459
2136
|
}
|
|
2137
|
+
for (const _m of Object.values(MODELS)) {
|
|
2138
|
+
const priced = resolvePricing(_m);
|
|
2139
|
+
if (priced.unknown || priced.free || priced.estimatedFromProvider) continue;
|
|
2140
|
+
_m.inputCostPer1kTokens = priced.input;
|
|
2141
|
+
_m.outputCostPer1kTokens = priced.output;
|
|
2142
|
+
_m.pricingUnknown = false;
|
|
2143
|
+
}
|
|
460
2144
|
T1_MODEL_PRIORITY = [
|
|
461
2145
|
"gpt-5.5",
|
|
462
2146
|
"gpt-5.4",
|
|
@@ -541,24 +2225,53 @@ var init_constants = __esm({
|
|
|
541
2225
|
|
|
542
2226
|
// src/utils/cost.ts
|
|
543
2227
|
function resolveModelPricing(model) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
2228
|
+
if (model.isLocal) return { input: 0, output: 0, unknown: false };
|
|
2229
|
+
if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
|
|
2230
|
+
return {
|
|
2231
|
+
input: model.inputCostPer1kTokens,
|
|
2232
|
+
output: model.outputCostPer1kTokens,
|
|
2233
|
+
unknown: false
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
const fromDataset = resolvePricing(model);
|
|
2237
|
+
if (!fromDataset.unknown) {
|
|
2238
|
+
return {
|
|
2239
|
+
input: fromDataset.input,
|
|
2240
|
+
output: fromDataset.output,
|
|
2241
|
+
unknown: false,
|
|
2242
|
+
...fromDataset.estimatedFromProvider ? { estimatedFromProvider: fromDataset.estimatedFromProvider } : {}
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
if (model.id) {
|
|
547
2246
|
const known = Object.values(MODELS).find((m) => m.id === model.id && !m.isLocal);
|
|
548
|
-
if (known) {
|
|
549
|
-
|
|
550
|
-
|
|
2247
|
+
if (known && (known.inputCostPer1kTokens > 0 || known.outputCostPer1kTokens > 0)) {
|
|
2248
|
+
return {
|
|
2249
|
+
input: known.inputCostPer1kTokens,
|
|
2250
|
+
output: known.outputCostPer1kTokens,
|
|
2251
|
+
unknown: false
|
|
2252
|
+
};
|
|
551
2253
|
}
|
|
552
2254
|
}
|
|
553
|
-
return { input, output };
|
|
2255
|
+
return { input: 0, output: 0, unknown: true };
|
|
554
2256
|
}
|
|
555
2257
|
function calculateCost(inputTokens, outputTokens, model) {
|
|
556
2258
|
const { input, output } = resolveModelPricing(model);
|
|
557
2259
|
return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
|
|
558
2260
|
}
|
|
2261
|
+
function buildTokenUsage(inputTokens, outputTokens, model) {
|
|
2262
|
+
const { input, output, unknown } = resolveModelPricing(model);
|
|
2263
|
+
return {
|
|
2264
|
+
inputTokens,
|
|
2265
|
+
outputTokens,
|
|
2266
|
+
totalTokens: inputTokens + outputTokens,
|
|
2267
|
+
estimatedCostUsd: inputTokens / 1e3 * input + outputTokens / 1e3 * output,
|
|
2268
|
+
...unknown ? { costUnknown: true } : {}
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
559
2271
|
var init_cost = __esm({
|
|
560
2272
|
"src/utils/cost.ts"() {
|
|
561
2273
|
init_constants();
|
|
2274
|
+
init_pricing();
|
|
562
2275
|
}
|
|
563
2276
|
});
|
|
564
2277
|
|
|
@@ -584,12 +2297,7 @@ var init_base = __esm({
|
|
|
584
2297
|
return calculateCost(inputTokens, outputTokens, this.model);
|
|
585
2298
|
}
|
|
586
2299
|
makeUsage(inputTokens, outputTokens) {
|
|
587
|
-
return
|
|
588
|
-
inputTokens,
|
|
589
|
-
outputTokens,
|
|
590
|
-
totalTokens: inputTokens + outputTokens,
|
|
591
|
-
estimatedCostUsd: this.estimateCost(inputTokens, outputTokens)
|
|
592
|
-
};
|
|
2300
|
+
return buildTokenUsage(inputTokens, outputTokens, this.model);
|
|
593
2301
|
}
|
|
594
2302
|
};
|
|
595
2303
|
}
|
|
@@ -665,6 +2373,7 @@ var init_anthropic = __esm({
|
|
|
665
2373
|
"src/providers/anthropic.ts"() {
|
|
666
2374
|
init_constants();
|
|
667
2375
|
init_base();
|
|
2376
|
+
init_pricing();
|
|
668
2377
|
init_model_filter();
|
|
669
2378
|
AnthropicProvider = class extends BaseProvider {
|
|
670
2379
|
client;
|
|
@@ -775,7 +2484,7 @@ var init_anthropic = __esm({
|
|
|
775
2484
|
return data.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
776
2485
|
const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
|
|
777
2486
|
if (known) return known;
|
|
778
|
-
return {
|
|
2487
|
+
return withResolvedPricing({
|
|
779
2488
|
id: m.id,
|
|
780
2489
|
name: m.display_name || m.id,
|
|
781
2490
|
provider: "anthropic",
|
|
@@ -786,7 +2495,7 @@ var init_anthropic = __esm({
|
|
|
786
2495
|
maxOutputTokens: 8e3,
|
|
787
2496
|
supportsStreaming: true,
|
|
788
2497
|
isLocal: false
|
|
789
|
-
};
|
|
2498
|
+
});
|
|
790
2499
|
});
|
|
791
2500
|
} catch {
|
|
792
2501
|
return Object.values(MODELS).filter((m) => m.provider === "anthropic");
|
|
@@ -881,6 +2590,7 @@ var init_openai = __esm({
|
|
|
881
2590
|
"src/providers/openai.ts"() {
|
|
882
2591
|
init_constants();
|
|
883
2592
|
init_base();
|
|
2593
|
+
init_pricing();
|
|
884
2594
|
init_model_filter();
|
|
885
2595
|
OpenAIProvider = class extends BaseProvider {
|
|
886
2596
|
client;
|
|
@@ -1006,7 +2716,7 @@ var init_openai = __esm({
|
|
|
1006
2716
|
return response.data.filter((m) => isChatModel(m.id)).map((m) => {
|
|
1007
2717
|
const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "openai");
|
|
1008
2718
|
if (known) return known;
|
|
1009
|
-
return {
|
|
2719
|
+
return withResolvedPricing({
|
|
1010
2720
|
id: m.id,
|
|
1011
2721
|
name: m.id,
|
|
1012
2722
|
provider: "openai",
|
|
@@ -1017,7 +2727,7 @@ var init_openai = __esm({
|
|
|
1017
2727
|
maxOutputTokens: 4e3,
|
|
1018
2728
|
supportsStreaming: true,
|
|
1019
2729
|
isLocal: false
|
|
1020
|
-
};
|
|
2730
|
+
});
|
|
1021
2731
|
});
|
|
1022
2732
|
} catch {
|
|
1023
2733
|
return Object.values(MODELS).filter((m) => m.provider === "openai");
|
|
@@ -1120,6 +2830,10 @@ function azureModelForDeployment(cfg) {
|
|
|
1120
2830
|
const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
|
|
1121
2831
|
const base = baseModelId ? MODELS[baseModelId] : void 0;
|
|
1122
2832
|
if (base) {
|
|
2833
|
+
const azurePrice = resolvePricing(
|
|
2834
|
+
{ id, provider: "azure", isLocal: false, baseModelId },
|
|
2835
|
+
{ region: cfg.region }
|
|
2836
|
+
);
|
|
1123
2837
|
return {
|
|
1124
2838
|
...base,
|
|
1125
2839
|
id,
|
|
@@ -1128,7 +2842,12 @@ function azureModelForDeployment(cfg) {
|
|
|
1128
2842
|
provider: "azure",
|
|
1129
2843
|
baseModelId,
|
|
1130
2844
|
// real identity for benchmark + live pricing
|
|
1131
|
-
supportsToolUse: base.supportsToolUse ?? true
|
|
2845
|
+
supportsToolUse: base.supportsToolUse ?? true,
|
|
2846
|
+
...azurePrice.unknown ? {} : {
|
|
2847
|
+
inputCostPer1kTokens: azurePrice.input,
|
|
2848
|
+
outputCostPer1kTokens: azurePrice.output,
|
|
2849
|
+
pricingUnknown: false
|
|
2850
|
+
}
|
|
1132
2851
|
};
|
|
1133
2852
|
}
|
|
1134
2853
|
return {
|
|
@@ -1151,6 +2870,7 @@ var init_azure = __esm({
|
|
|
1151
2870
|
"src/providers/azure.ts"() {
|
|
1152
2871
|
init_constants();
|
|
1153
2872
|
init_openai();
|
|
2873
|
+
init_pricing();
|
|
1154
2874
|
DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
|
|
1155
2875
|
AzureOpenAIProvider = class extends OpenAIProvider {
|
|
1156
2876
|
constructor(config, model) {
|
|
@@ -1211,6 +2931,7 @@ var init_gemini = __esm({
|
|
|
1211
2931
|
"src/providers/gemini.ts"() {
|
|
1212
2932
|
init_constants();
|
|
1213
2933
|
init_base();
|
|
2934
|
+
init_pricing();
|
|
1214
2935
|
init_model_filter();
|
|
1215
2936
|
GeminiProvider = class extends BaseProvider {
|
|
1216
2937
|
client;
|
|
@@ -1311,7 +3032,7 @@ var init_gemini = __esm({
|
|
|
1311
3032
|
(km) => km.id === id && km.provider === "gemini"
|
|
1312
3033
|
);
|
|
1313
3034
|
if (known) return known;
|
|
1314
|
-
return {
|
|
3035
|
+
return withResolvedPricing({
|
|
1315
3036
|
id,
|
|
1316
3037
|
name: m.displayName || id,
|
|
1317
3038
|
provider: "gemini",
|
|
@@ -1322,7 +3043,7 @@ var init_gemini = __esm({
|
|
|
1322
3043
|
maxOutputTokens: m.outputTokenLimit || 8e3,
|
|
1323
3044
|
supportsStreaming: true,
|
|
1324
3045
|
isLocal: false
|
|
1325
|
-
};
|
|
3046
|
+
});
|
|
1326
3047
|
});
|
|
1327
3048
|
} catch {
|
|
1328
3049
|
return Object.values(MODELS).filter((m) => m.provider === "gemini");
|
|
@@ -1528,6 +3249,7 @@ var init_ollama = __esm({
|
|
|
1528
3249
|
init_constants();
|
|
1529
3250
|
init_net();
|
|
1530
3251
|
init_base();
|
|
3252
|
+
init_pricing();
|
|
1531
3253
|
init_model_filter();
|
|
1532
3254
|
TOOL_CAPABLE_FAMILIES = [
|
|
1533
3255
|
"llama3.1",
|
|
@@ -1687,9 +3409,10 @@ var init_ollama = __esm({
|
|
|
1687
3409
|
return supportedKeywords.some((k) => name.includes(k)) && isChatModel(name);
|
|
1688
3410
|
});
|
|
1689
3411
|
const shows = await Promise.all(entries.map((m) => this.showModel(m.name)));
|
|
3412
|
+
const local = isLocalEndpoint({ ...this.config, type: "ollama" });
|
|
1690
3413
|
return entries.map((m, i) => {
|
|
1691
3414
|
const show = shows[i];
|
|
1692
|
-
return {
|
|
3415
|
+
return withResolvedPricing({
|
|
1693
3416
|
id: m.name,
|
|
1694
3417
|
name: m.name,
|
|
1695
3418
|
provider: "ollama",
|
|
@@ -1699,10 +3422,10 @@ var init_ollama = __esm({
|
|
|
1699
3422
|
outputCostPer1kTokens: 0,
|
|
1700
3423
|
maxOutputTokens: 4e3,
|
|
1701
3424
|
supportsStreaming: true,
|
|
1702
|
-
isLocal:
|
|
3425
|
+
isLocal: local,
|
|
1703
3426
|
supportsToolUse: show?.tools ?? isToolCapable(m.name),
|
|
1704
3427
|
minSizeB: this.parseSizeB(m.details?.parameter_size)
|
|
1705
|
-
};
|
|
3428
|
+
});
|
|
1706
3429
|
});
|
|
1707
3430
|
} catch {
|
|
1708
3431
|
return [];
|
|
@@ -1786,6 +3509,7 @@ var init_openai_compatible = __esm({
|
|
|
1786
3509
|
init_openai();
|
|
1787
3510
|
init_model_filter();
|
|
1788
3511
|
init_net();
|
|
3512
|
+
init_pricing();
|
|
1789
3513
|
OpenAICompatibleProvider = class extends OpenAIProvider {
|
|
1790
3514
|
constructor(config, model) {
|
|
1791
3515
|
super({ ...config, apiKey: config.apiKey ?? "not-required" }, model);
|
|
@@ -1819,7 +3543,8 @@ var init_openai_compatible = __esm({
|
|
|
1819
3543
|
}).filter((x) => typeof x === "string" && x.length > 0);
|
|
1820
3544
|
if (ids.length === 0) return [this.model];
|
|
1821
3545
|
const chatIds = ids.filter((id) => isChatModel(id));
|
|
1822
|
-
|
|
3546
|
+
const local = isLocalEndpoint(this.config);
|
|
3547
|
+
return (chatIds.length ? chatIds : ids).map((id) => withResolvedPricing({
|
|
1823
3548
|
id,
|
|
1824
3549
|
name: id,
|
|
1825
3550
|
provider: "openai-compatible",
|
|
@@ -1829,7 +3554,7 @@ var init_openai_compatible = __esm({
|
|
|
1829
3554
|
outputCostPer1kTokens: 0,
|
|
1830
3555
|
maxOutputTokens: 4e3,
|
|
1831
3556
|
supportsStreaming: true,
|
|
1832
|
-
isLocal:
|
|
3557
|
+
isLocal: local
|
|
1833
3558
|
}));
|
|
1834
3559
|
}
|
|
1835
3560
|
async isAvailable() {
|
|
@@ -3151,7 +4876,18 @@ var ProviderConfigSchema = zod.z.object({
|
|
|
3151
4876
|
apiVersion: zod.z.string().optional(),
|
|
3152
4877
|
model: zod.z.string().optional(),
|
|
3153
4878
|
authToken: zod.z.string().optional(),
|
|
3154
|
-
credentialSource: zod.z.string().optional()
|
|
4879
|
+
credentialSource: zod.z.string().optional(),
|
|
4880
|
+
/**
|
|
4881
|
+
* "This endpoint is self-hosted, so inference genuinely costs $0."
|
|
4882
|
+
* Only meaningful for `ollama` and `openai-compatible`. Unset ⇒ inferred:
|
|
4883
|
+
* ollama is local, openai-compatible is local when its baseUrl is
|
|
4884
|
+
* loopback/LAN. Set it explicitly to override — e.g. `false` for an Ollama
|
|
4885
|
+
* instance you rent, `true` for a llama.cpp box on your own LAN reached by
|
|
4886
|
+
* hostname.
|
|
4887
|
+
*/
|
|
4888
|
+
local: zod.z.boolean().optional(),
|
|
4889
|
+
/** Region for providers whose price varies by region (Azure: global | us | eu | …). */
|
|
4890
|
+
region: zod.z.string().optional()
|
|
3155
4891
|
});
|
|
3156
4892
|
var ModelOverridesSchema = zod.z.object({
|
|
3157
4893
|
t1: zod.z.string().optional(),
|
|
@@ -4000,6 +5736,7 @@ init_openai();
|
|
|
4000
5736
|
|
|
4001
5737
|
// src/core/router/selector.ts
|
|
4002
5738
|
init_constants();
|
|
5739
|
+
init_pricing();
|
|
4003
5740
|
function normalizeModelId(id) {
|
|
4004
5741
|
return id.replace(/^models\//, "").toLowerCase();
|
|
4005
5742
|
}
|
|
@@ -4201,7 +5938,7 @@ var ModelSelector = class {
|
|
|
4201
5938
|
else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
|
|
4202
5939
|
}
|
|
4203
5940
|
if (providerStr && this.availableProviders.has(providerStr)) {
|
|
4204
|
-
const dynamicModel = {
|
|
5941
|
+
const dynamicModel = withResolvedPricing({
|
|
4205
5942
|
id: actualId,
|
|
4206
5943
|
name: actualId,
|
|
4207
5944
|
provider: providerStr,
|
|
@@ -4212,7 +5949,7 @@ var ModelSelector = class {
|
|
|
4212
5949
|
maxOutputTokens: 8e3,
|
|
4213
5950
|
supportsStreaming: true,
|
|
4214
5951
|
isLocal: providerStr === "ollama"
|
|
4215
|
-
};
|
|
5952
|
+
});
|
|
4216
5953
|
this.addDynamicModel(dynamicModel);
|
|
4217
5954
|
return dynamicModel;
|
|
4218
5955
|
}
|
|
@@ -4582,6 +6319,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4582
6319
|
|
|
4583
6320
|
// src/core/router/live-data.ts
|
|
4584
6321
|
init_constants();
|
|
6322
|
+
init_pricing();
|
|
4585
6323
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4586
6324
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4587
6325
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
@@ -4599,6 +6337,8 @@ var LiveDataProvider = class {
|
|
|
4599
6337
|
snapshot = null;
|
|
4600
6338
|
prices = /* @__PURE__ */ new Map();
|
|
4601
6339
|
capabilities = /* @__PURE__ */ new Map();
|
|
6340
|
+
/** Dataset-vs-live price mismatches, deduped by `provider:modelId`. */
|
|
6341
|
+
disagreements = /* @__PURE__ */ new Map();
|
|
4602
6342
|
source = "bundled";
|
|
4603
6343
|
fetchedAt = 0;
|
|
4604
6344
|
loaded = false;
|
|
@@ -4752,14 +6492,45 @@ var LiveDataProvider = class {
|
|
|
4752
6492
|
/**
|
|
4753
6493
|
* Returns a price-corrected copy of each model when live pricing is known,
|
|
4754
6494
|
* leaving the original untouched (so the shared catalog is never mutated).
|
|
6495
|
+
*
|
|
6496
|
+
* Reconciliation policy (see reconcilePrice in pricing.ts): the bundled
|
|
6497
|
+
* dataset is the authoritative baseline, and a live quote overrides it
|
|
6498
|
+
* because it is fresher by construction — but the disagreement is RECORDED,
|
|
6499
|
+
* not swallowed. A dataset/live mismatch is the best signal available that
|
|
6500
|
+
* the committed prices have gone stale, so it must be visible rather than
|
|
6501
|
+
* silently resolved in either direction. A live quote of $0 for a non-local
|
|
6502
|
+
* model is rejected outright: that is the very failure mode this change
|
|
6503
|
+
* exists to prevent, and a marketplace's free-tier alias must never zero out
|
|
6504
|
+
* the price of the paid model Cascade is actually calling.
|
|
4755
6505
|
*/
|
|
4756
6506
|
applyLivePricing(models) {
|
|
4757
6507
|
return models.map((m) => {
|
|
4758
6508
|
const p = this.getLivePrice(m.baseModelId ?? m.id);
|
|
4759
6509
|
if (!p) return m;
|
|
4760
|
-
|
|
6510
|
+
const reconciled = reconcilePrice(m, p);
|
|
6511
|
+
if (reconciled.disagreement) this.recordDisagreement(reconciled.disagreement);
|
|
6512
|
+
if (!reconciled.accepted) return m;
|
|
6513
|
+
return {
|
|
6514
|
+
...m,
|
|
6515
|
+
inputCostPer1kTokens: reconciled.input,
|
|
6516
|
+
outputCostPer1kTokens: reconciled.output,
|
|
6517
|
+
pricingUnknown: false
|
|
6518
|
+
};
|
|
4761
6519
|
});
|
|
4762
6520
|
}
|
|
6521
|
+
recordDisagreement(d) {
|
|
6522
|
+
const key = `${d.provider}:${d.modelId}`;
|
|
6523
|
+
if (this.disagreements.has(key)) return;
|
|
6524
|
+
this.disagreements.set(key, d);
|
|
6525
|
+
}
|
|
6526
|
+
/**
|
|
6527
|
+
* Dataset-vs-live price mismatches seen this session, newest-model-first
|
|
6528
|
+
* insertion order. Surfaced by `cascade models --verbose`; a non-empty list
|
|
6529
|
+
* means src/core/router/pricing-data.json is probably due a refresh.
|
|
6530
|
+
*/
|
|
6531
|
+
getPriceDisagreements() {
|
|
6532
|
+
return [...this.disagreements.values()];
|
|
6533
|
+
}
|
|
4763
6534
|
/** Current capability facts for a model id, or null when unknown. */
|
|
4764
6535
|
getCapability(modelId) {
|
|
4765
6536
|
return this.capabilities.get(normalizeModelId2(modelId)) ?? null;
|
|
@@ -4956,7 +6727,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
4956
6727
|
tokensByTier: {},
|
|
4957
6728
|
inputTokensByTier: {},
|
|
4958
6729
|
outputTokensByTier: {},
|
|
4959
|
-
costByFeature: {}
|
|
6730
|
+
costByFeature: {},
|
|
6731
|
+
untrackedCostCalls: 0,
|
|
6732
|
+
untrackedCostModels: []
|
|
4960
6733
|
};
|
|
4961
6734
|
tierModels = /* @__PURE__ */ new Map();
|
|
4962
6735
|
/**
|
|
@@ -5306,7 +7079,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5306
7079
|
`Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`
|
|
5307
7080
|
);
|
|
5308
7081
|
}
|
|
5309
|
-
const
|
|
7082
|
+
const corrected = buildTokenUsage(
|
|
5310
7083
|
result.usage.inputTokens,
|
|
5311
7084
|
result.usage.outputTokens,
|
|
5312
7085
|
model
|
|
@@ -5315,7 +7088,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5315
7088
|
...result,
|
|
5316
7089
|
usage: {
|
|
5317
7090
|
...result.usage,
|
|
5318
|
-
estimatedCostUsd:
|
|
7091
|
+
estimatedCostUsd: corrected.estimatedCostUsd,
|
|
7092
|
+
...corrected.costUnknown ? { costUnknown: true } : { costUnknown: void 0 }
|
|
5319
7093
|
}
|
|
5320
7094
|
};
|
|
5321
7095
|
if (!result || typeof result.content !== "string" || !result.usage) {
|
|
@@ -5502,7 +7276,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5502
7276
|
tokensByTier: { ...this.stats.tokensByTier },
|
|
5503
7277
|
inputTokensByTier: { ...this.stats.inputTokensByTier },
|
|
5504
7278
|
outputTokensByTier: { ...this.stats.outputTokensByTier },
|
|
5505
|
-
costByFeature: { ...this.stats.costByFeature }
|
|
7279
|
+
costByFeature: { ...this.stats.costByFeature },
|
|
7280
|
+
untrackedCostCalls: this.stats.untrackedCostCalls,
|
|
7281
|
+
untrackedCostModels: [...this.stats.untrackedCostModels]
|
|
5506
7282
|
};
|
|
5507
7283
|
}
|
|
5508
7284
|
/**
|
|
@@ -5553,7 +7329,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5553
7329
|
tokensByTier: {},
|
|
5554
7330
|
inputTokensByTier: {},
|
|
5555
7331
|
outputTokensByTier: {},
|
|
5556
|
-
costByFeature: {}
|
|
7332
|
+
costByFeature: {},
|
|
7333
|
+
untrackedCostCalls: 0,
|
|
7334
|
+
untrackedCostModels: []
|
|
5557
7335
|
};
|
|
5558
7336
|
this.sessionCostUsd = 0;
|
|
5559
7337
|
this.budgetState = "ok";
|
|
@@ -5738,6 +7516,18 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
|
|
|
5738
7516
|
if (featureTag) {
|
|
5739
7517
|
this.stats.costByFeature[featureTag] = (this.stats.costByFeature[featureTag] ?? 0) + usage.estimatedCostUsd;
|
|
5740
7518
|
}
|
|
7519
|
+
if (usage.costUnknown) {
|
|
7520
|
+
this.stats.untrackedCostCalls += 1;
|
|
7521
|
+
if (!this.stats.untrackedCostModels.includes(model.id)) {
|
|
7522
|
+
this.stats.untrackedCostModels.push(model.id);
|
|
7523
|
+
const capped = this.config?.budget?.maxCostPerRunUsd != null || this.config?.budget?.sessionBudgetUsd != null;
|
|
7524
|
+
this.emit("cost:untracked", {
|
|
7525
|
+
modelId: model.id,
|
|
7526
|
+
provider: model.provider,
|
|
7527
|
+
reason: capped ? `No published price for ${model.provider}:${model.id}. Its spend is not counted toward your cost budget \u2014 the token cap still applies. Add it to the pricing dataset, or set the provider's \`local: true\` if this endpoint really is free.` : `No published price for ${model.provider}:${model.id}. Cost for this model is reported as "not tracked" rather than $0.00.`
|
|
7528
|
+
});
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
5741
7531
|
this.runTokens += usage.totalTokens;
|
|
5742
7532
|
this.runCostUsd += usage.estimatedCostUsd;
|
|
5743
7533
|
this.updateBudgetState();
|
|
@@ -5878,6 +7668,14 @@ var BaseTier = class extends EventEmitter__default.default {
|
|
|
5878
7668
|
this.emit("tier:status", event);
|
|
5879
7669
|
}
|
|
5880
7670
|
/** Record the model serving this tier; future status events carry it. */
|
|
7671
|
+
/**
|
|
7672
|
+
* The model actually serving this tier ("provider:id"), once one has been
|
|
7673
|
+
* chosen. The run breaker reads it to attribute a failure to a model rather
|
|
7674
|
+
* than to the subtask that happened to hit it.
|
|
7675
|
+
*/
|
|
7676
|
+
getServingModel() {
|
|
7677
|
+
return this.servingModel;
|
|
7678
|
+
}
|
|
5881
7679
|
setServingModel(model) {
|
|
5882
7680
|
this.servingModel = model || void 0;
|
|
5883
7681
|
}
|
|
@@ -7614,6 +9412,7 @@ var T2Manager = class extends BaseTier {
|
|
|
7614
9412
|
escalations = [];
|
|
7615
9413
|
peerSyncBuffer = [];
|
|
7616
9414
|
store;
|
|
9415
|
+
runBreaker;
|
|
7617
9416
|
t3PeerBus = new PeerBus();
|
|
7618
9417
|
// ← T3↔T3 bus (local to this T2)
|
|
7619
9418
|
t2PeerBus;
|
|
@@ -7644,6 +9443,15 @@ var T2Manager = class extends BaseTier {
|
|
|
7644
9443
|
this.router = router;
|
|
7645
9444
|
this.toolRegistry = toolRegistry;
|
|
7646
9445
|
}
|
|
9446
|
+
/**
|
|
9447
|
+
* Share the run-wide circuit breaker. Every T2 in a run gets the SAME
|
|
9448
|
+
* instance: three sections each failing once against a dead model is the
|
|
9449
|
+
* same fact as one section failing three times, and a per-section breaker
|
|
9450
|
+
* would never see it.
|
|
9451
|
+
*/
|
|
9452
|
+
setRunBreaker(breaker) {
|
|
9453
|
+
this.runBreaker = breaker;
|
|
9454
|
+
}
|
|
7647
9455
|
setStore(store) {
|
|
7648
9456
|
this.store = store;
|
|
7649
9457
|
}
|
|
@@ -7767,7 +9575,8 @@ Guidance (must be followed): ${decision.note}`
|
|
|
7767
9575
|
const overallStatus = this.determineStatus(t3Results);
|
|
7768
9576
|
const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
|
|
7769
9577
|
this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
|
|
7770
|
-
|
|
9578
|
+
const sectionAction = overallStatus === "COMPLETED" ? "Section complete" : overallStatus === "PARTIAL" ? "Section partially complete \u2014 some subtasks failed" : overallStatus === "ESCALATED" ? "Section escalated \u2014 needs a decision" : "Section failed \u2014 no subtask completed";
|
|
9579
|
+
this.sendStatusUpdate({ progressPct: 100, currentAction: sectionAction, status: "IN_PROGRESS", output: summary });
|
|
7771
9580
|
const result = {
|
|
7772
9581
|
sectionId: assignment.sectionId,
|
|
7773
9582
|
sectionTitle: assignment.sectionTitle,
|
|
@@ -7937,6 +9746,24 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
7937
9746
|
const reinforceCfg = this.router.getReinforcementsConfig?.() ?? { enabled: false, maxPerSection: 4 };
|
|
7938
9747
|
let reinforcementsAdded = 0;
|
|
7939
9748
|
while (remaining.size > 0) {
|
|
9749
|
+
if (this.runBreaker?.isOpen()) {
|
|
9750
|
+
const why = this.runBreaker.skipMessage();
|
|
9751
|
+
this.log(`Run breaker open \u2014 skipping ${remaining.size} remaining subtask(s)`);
|
|
9752
|
+
for (const id of remaining) {
|
|
9753
|
+
this.t3PeerBus.publish(this.id, id, why, "FAILED");
|
|
9754
|
+
resultMap.set(id, {
|
|
9755
|
+
subtaskId: id,
|
|
9756
|
+
status: "FAILED",
|
|
9757
|
+
output: why,
|
|
9758
|
+
testResults: { checksRun: [], passed: [], failed: [] },
|
|
9759
|
+
issues: [why],
|
|
9760
|
+
peerSyncsUsed: [],
|
|
9761
|
+
correctionAttempts: 0
|
|
9762
|
+
});
|
|
9763
|
+
}
|
|
9764
|
+
remaining.clear();
|
|
9765
|
+
break;
|
|
9766
|
+
}
|
|
7940
9767
|
const runnableIds = [...remaining].filter((id) => (inDegree.get(id) ?? 0) === 0);
|
|
7941
9768
|
if (runnableIds.length === 0) {
|
|
7942
9769
|
const fallbackId = [...remaining].sort(
|
|
@@ -8039,8 +9866,32 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
|
8039
9866
|
remaining.delete(id);
|
|
8040
9867
|
const r = waveResults[i];
|
|
8041
9868
|
if (r.status === "rejected") {
|
|
8042
|
-
this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
|
|
8043
9869
|
const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
|
|
9870
|
+
const failedModel = workerMap.get(id)?.getServingModel();
|
|
9871
|
+
const tripped = this.runBreaker?.record(r.reason, failedModel);
|
|
9872
|
+
if (tripped) {
|
|
9873
|
+
this.log(`Run breaker opened: ${tripped.failures} consecutive ${tripped.kind} failures on ${tripped.modelId} \u2014 stopping`);
|
|
9874
|
+
this.emit("run:breaker", tripped);
|
|
9875
|
+
}
|
|
9876
|
+
if (this.runBreaker?.isOpen()) {
|
|
9877
|
+
const why = this.runBreaker.skipMessage();
|
|
9878
|
+
this.log(`T3 worker ${id} failed and the run breaker is open \u2014 not retrying`);
|
|
9879
|
+
this.t3PeerBus.publish(this.id, id, why, "FAILED");
|
|
9880
|
+
resultMap.set(id, {
|
|
9881
|
+
subtaskId: id,
|
|
9882
|
+
status: "FAILED",
|
|
9883
|
+
output: why,
|
|
9884
|
+
testResults: { checksRun: [], passed: [], failed: [] },
|
|
9885
|
+
issues: [why],
|
|
9886
|
+
peerSyncsUsed: [],
|
|
9887
|
+
correctionAttempts: 0
|
|
9888
|
+
});
|
|
9889
|
+
for (const dependent of adj.get(id) ?? []) {
|
|
9890
|
+
inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
|
|
9891
|
+
}
|
|
9892
|
+
continue;
|
|
9893
|
+
}
|
|
9894
|
+
this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
|
|
8044
9895
|
try {
|
|
8045
9896
|
const retried = await this.retryT3(assignment, taskId);
|
|
8046
9897
|
resultMap.set(id, retried);
|
|
@@ -8375,6 +10226,7 @@ var T1Administrator = class extends BaseTier {
|
|
|
8375
10226
|
t2Managers = /* @__PURE__ */ new Map();
|
|
8376
10227
|
escalations = [];
|
|
8377
10228
|
store;
|
|
10229
|
+
runBreaker;
|
|
8378
10230
|
t2PeerBus = new PeerBus();
|
|
8379
10231
|
permissionEscalator;
|
|
8380
10232
|
toolCreator;
|
|
@@ -8389,6 +10241,10 @@ var T1Administrator = class extends BaseTier {
|
|
|
8389
10241
|
this.toolRegistry = toolRegistry;
|
|
8390
10242
|
this.config = config;
|
|
8391
10243
|
}
|
|
10244
|
+
/** Share the run-wide circuit breaker with every section manager. */
|
|
10245
|
+
setRunBreaker(breaker) {
|
|
10246
|
+
this.runBreaker = breaker;
|
|
10247
|
+
}
|
|
8392
10248
|
setStore(store) {
|
|
8393
10249
|
this.store = store;
|
|
8394
10250
|
}
|
|
@@ -8751,6 +10607,7 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
|
|
|
8751
10607
|
};
|
|
8752
10608
|
const managers = sections.map((section) => {
|
|
8753
10609
|
const manager = new T2Manager(this.router, this.toolRegistry, this.id);
|
|
10610
|
+
if (this.runBreaker) manager.setRunBreaker(this.runBreaker);
|
|
8754
10611
|
manager.setHierarchyContext(`You are a T2 Manager for the section "${section.sectionTitle}". You are part of a COMPLEX task overseen by T1 Administrator.`);
|
|
8755
10612
|
if (this.store) {
|
|
8756
10613
|
manager.setStore(this.store);
|
|
@@ -9039,6 +10896,158 @@ Reply with exactly one word: YES, NO, or UNSURE.
|
|
|
9039
10896
|
// src/core/cascade.ts
|
|
9040
10897
|
init_cost();
|
|
9041
10898
|
|
|
10899
|
+
// src/core/router/provider-errors.ts
|
|
10900
|
+
var SYSTEMIC = /* @__PURE__ */ new Set([
|
|
10901
|
+
"rate_limit",
|
|
10902
|
+
"quota_exhausted",
|
|
10903
|
+
"auth",
|
|
10904
|
+
"model_unavailable"
|
|
10905
|
+
]);
|
|
10906
|
+
function isSystemicKind(kind) {
|
|
10907
|
+
return SYSTEMIC.has(kind);
|
|
10908
|
+
}
|
|
10909
|
+
function statusOf(err) {
|
|
10910
|
+
if (!err || typeof err !== "object") return void 0;
|
|
10911
|
+
const e = err;
|
|
10912
|
+
for (const key of ["status", "statusCode", "code"]) {
|
|
10913
|
+
const v = e[key];
|
|
10914
|
+
if (typeof v === "number" && v >= 100 && v < 600) return v;
|
|
10915
|
+
}
|
|
10916
|
+
const resp = e["response"];
|
|
10917
|
+
if (resp && typeof resp["status"] === "number") return resp["status"];
|
|
10918
|
+
const inner = e["error"];
|
|
10919
|
+
if (inner && typeof inner["code"] === "number") return inner["code"];
|
|
10920
|
+
return void 0;
|
|
10921
|
+
}
|
|
10922
|
+
function messageOf(err) {
|
|
10923
|
+
if (err instanceof Error) return err.message;
|
|
10924
|
+
if (typeof err === "string") return err;
|
|
10925
|
+
if (err && typeof err === "object") {
|
|
10926
|
+
const e = err;
|
|
10927
|
+
if (typeof e["message"] === "string") return e["message"];
|
|
10928
|
+
const inner = e["error"];
|
|
10929
|
+
if (inner && typeof inner["message"] === "string") return inner["message"];
|
|
10930
|
+
try {
|
|
10931
|
+
return JSON.stringify(err);
|
|
10932
|
+
} catch {
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
10935
|
+
return String(err);
|
|
10936
|
+
}
|
|
10937
|
+
function classifyProviderError(err) {
|
|
10938
|
+
const raw = messageOf(err).trim();
|
|
10939
|
+
const status = statusOf(err);
|
|
10940
|
+
const m = raw.toLowerCase();
|
|
10941
|
+
const kind = (() => {
|
|
10942
|
+
if (status === 429) {
|
|
10943
|
+
return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
10944
|
+
}
|
|
10945
|
+
if (status === 401 || status === 403) return "auth";
|
|
10946
|
+
if (status === 404) return "model_unavailable";
|
|
10947
|
+
if (/rate limit|too many requests|resource_exhausted/.test(m)) {
|
|
10948
|
+
return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
|
|
10949
|
+
}
|
|
10950
|
+
if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
|
|
10951
|
+
if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
|
|
10952
|
+
if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
|
|
10953
|
+
return "model_unavailable";
|
|
10954
|
+
}
|
|
10955
|
+
if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
|
|
10956
|
+
if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
|
|
10957
|
+
if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
|
|
10958
|
+
return "unknown";
|
|
10959
|
+
})();
|
|
10960
|
+
return { kind, systemic: isSystemicKind(kind), status, raw };
|
|
10961
|
+
}
|
|
10962
|
+
function describeProviderError(c, modelId) {
|
|
10963
|
+
const on = ` on ${modelId}` ;
|
|
10964
|
+
const detail = c.raw ? ` Provider said: ${c.raw}` : "";
|
|
10965
|
+
switch (c.kind) {
|
|
10966
|
+
case "rate_limit":
|
|
10967
|
+
return `Rate limit hit${on}. The provider is throttling requests \u2014 lower the number of parallel workers, or use a model with a higher requests-per-minute allowance.${detail}`;
|
|
10968
|
+
case "quota_exhausted":
|
|
10969
|
+
return `Quota or billing limit reached${on}. This will not recover on its own \u2014 check the provider's billing page, or switch this tier to a different provider.${detail}`;
|
|
10970
|
+
case "auth":
|
|
10971
|
+
return `Authentication failed${on}. The API key is missing, expired, or lacks access to this model \u2014 re-check the key in Settings > Providers.${detail}`;
|
|
10972
|
+
case "model_unavailable":
|
|
10973
|
+
return `Model unavailable${on}. The id may be wrong, retired, or not enabled for this API key \u2014 pick a different model for this tier.${detail}`;
|
|
10974
|
+
case "context_length":
|
|
10975
|
+
return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
|
|
10976
|
+
case "content_filter":
|
|
10977
|
+
return `The provider's safety filter refused this request${on}.${detail}`;
|
|
10978
|
+
case "network":
|
|
10979
|
+
return `Network error reaching the provider${on}. This is usually transient.${detail}`;
|
|
10980
|
+
default:
|
|
10981
|
+
return `The model call failed${on}.${detail}`;
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
|
|
10985
|
+
// src/core/run-breaker.ts
|
|
10986
|
+
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
10987
|
+
var RunBreaker = class {
|
|
10988
|
+
threshold;
|
|
10989
|
+
/** modelId → consecutive systemic failures of the SAME kind. */
|
|
10990
|
+
counts = /* @__PURE__ */ new Map();
|
|
10991
|
+
trip = null;
|
|
10992
|
+
constructor(threshold = DEFAULT_FAILURE_THRESHOLD) {
|
|
10993
|
+
this.threshold = Math.max(1, threshold);
|
|
10994
|
+
}
|
|
10995
|
+
/**
|
|
10996
|
+
* Record a failed model call. Returns the trip if this failure is the one
|
|
10997
|
+
* that opened the breaker, otherwise null.
|
|
10998
|
+
*
|
|
10999
|
+
* A non-systemic failure RESETS the model's counter: it is evidence the model
|
|
11000
|
+
* is reachable and answering, which is exactly what a systemic streak claims
|
|
11001
|
+
* it is not.
|
|
11002
|
+
*/
|
|
11003
|
+
record(err, modelId) {
|
|
11004
|
+
if (this.trip) return null;
|
|
11005
|
+
const id = modelId?.trim() || "unknown-model";
|
|
11006
|
+
const c = classifyProviderError(err);
|
|
11007
|
+
if (!c.systemic) {
|
|
11008
|
+
this.counts.delete(id);
|
|
11009
|
+
return null;
|
|
11010
|
+
}
|
|
11011
|
+
const prev = this.counts.get(id);
|
|
11012
|
+
const n = prev && prev.kind === c.kind ? prev.n + 1 : 1;
|
|
11013
|
+
this.counts.set(id, { kind: c.kind, n, last: c });
|
|
11014
|
+
if (n >= this.threshold) {
|
|
11015
|
+
this.trip = {
|
|
11016
|
+
modelId: id,
|
|
11017
|
+
kind: c.kind,
|
|
11018
|
+
failures: n,
|
|
11019
|
+
message: describeProviderError(c, id)
|
|
11020
|
+
};
|
|
11021
|
+
return this.trip;
|
|
11022
|
+
}
|
|
11023
|
+
return null;
|
|
11024
|
+
}
|
|
11025
|
+
/** True once the run should stop launching work that would fail identically. */
|
|
11026
|
+
isOpen() {
|
|
11027
|
+
return this.trip !== null;
|
|
11028
|
+
}
|
|
11029
|
+
/** Why the breaker opened, or null while it is closed. */
|
|
11030
|
+
reason() {
|
|
11031
|
+
return this.trip;
|
|
11032
|
+
}
|
|
11033
|
+
/**
|
|
11034
|
+
* One line for a worker/section that never ran because the breaker was
|
|
11035
|
+
* already open. Says the work was skipped deliberately — not that it failed
|
|
11036
|
+
* on its own merits, which is what an empty FAILED status would imply.
|
|
11037
|
+
*/
|
|
11038
|
+
skipMessage() {
|
|
11039
|
+
const t = this.trip;
|
|
11040
|
+
if (!t) return "Skipped.";
|
|
11041
|
+
return `Skipped \u2014 the run stopped after ${t.failures} consecutive failures on ${t.modelId}. ${t.message}`;
|
|
11042
|
+
}
|
|
11043
|
+
/** Test/diagnostic view of the per-model streaks. */
|
|
11044
|
+
streaks() {
|
|
11045
|
+
const out = {};
|
|
11046
|
+
for (const [k, v] of this.counts) out[k] = { kind: v.kind, n: v.n };
|
|
11047
|
+
return out;
|
|
11048
|
+
}
|
|
11049
|
+
};
|
|
11050
|
+
|
|
9042
11051
|
// src/tools/registry.ts
|
|
9043
11052
|
init_constants();
|
|
9044
11053
|
|
|
@@ -11268,6 +13277,7 @@ var noopTelemetry = {
|
|
|
11268
13277
|
};
|
|
11269
13278
|
|
|
11270
13279
|
// src/core/router/task-analyzer.ts
|
|
13280
|
+
init_pricing();
|
|
11271
13281
|
var CODE_SIGNALS = [
|
|
11272
13282
|
/\b(?:function|class|interface|async|await|import|export|const|let|var|def|return|if|else|for|while|try|catch)\b/,
|
|
11273
13283
|
/\b(?:typescript|javascript|python|rust|go|java|c\+\+|sql|bash|shell|dockerfile|kubernetes|terraform)\b/i,
|
|
@@ -11444,8 +13454,8 @@ var TaskAnalyzer = class {
|
|
|
11444
13454
|
}
|
|
11445
13455
|
costEfficiency(model, complexity) {
|
|
11446
13456
|
if (this.tracker) return this.tracker.costEfficiencyScore(model, complexity);
|
|
11447
|
-
const blended = model
|
|
11448
|
-
const normalised = Math.min(1, blended /
|
|
13457
|
+
const blended = blendedCostPer1k(model);
|
|
13458
|
+
const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
|
|
11449
13459
|
const complexityWeight = (6 - complexity) / 5;
|
|
11450
13460
|
return Math.max(0.1, 1 - normalised * complexityWeight);
|
|
11451
13461
|
}
|
|
@@ -11460,6 +13470,9 @@ var TaskAnalyzer = class {
|
|
|
11460
13470
|
analysisCache.clear();
|
|
11461
13471
|
}
|
|
11462
13472
|
};
|
|
13473
|
+
|
|
13474
|
+
// src/core/router/model-performance-tracker.ts
|
|
13475
|
+
init_pricing();
|
|
11463
13476
|
var DEFAULT_STATS_FILE = path32__default.default.join(os10__default.default.homedir(), ".cascade", "model-perf.json");
|
|
11464
13477
|
var ModelPerformanceTracker = class {
|
|
11465
13478
|
stats = /* @__PURE__ */ new Map();
|
|
@@ -11580,8 +13593,8 @@ var ModelPerformanceTracker = class {
|
|
|
11580
13593
|
* normalised over $0.05 blended as the "expensive" ceiling.
|
|
11581
13594
|
*/
|
|
11582
13595
|
costEfficiencyScore(model, complexity) {
|
|
11583
|
-
const blended = model
|
|
11584
|
-
const normalised = Math.min(1, blended /
|
|
13596
|
+
const blended = blendedCostPer1k(model);
|
|
13597
|
+
const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
|
|
11585
13598
|
const complexityWeight = (6 - complexity) / 5;
|
|
11586
13599
|
return Math.max(0.1, 1 - normalised * complexityWeight);
|
|
11587
13600
|
}
|
|
@@ -14167,6 +16180,7 @@ ${prompt}` : prompt;
|
|
|
14167
16180
|
if (toolCreator) toolCreator.setPermissionEscalator(escalator);
|
|
14168
16181
|
const rootPrompt = buildContextualPrompt(options.prompt, options.conversationHistory);
|
|
14169
16182
|
let finalOutput = "";
|
|
16183
|
+
const runBreaker = new RunBreaker();
|
|
14170
16184
|
let t2Results = [];
|
|
14171
16185
|
let runError = null;
|
|
14172
16186
|
let identityPrompt = "";
|
|
@@ -14235,6 +16249,7 @@ ${prompt}` : prompt;
|
|
|
14235
16249
|
this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
|
|
14236
16250
|
} else if (complexity === "Moderate") {
|
|
14237
16251
|
const t2 = new T2Manager(this.router, this.toolRegistry, "root");
|
|
16252
|
+
t2.setRunBreaker(runBreaker);
|
|
14238
16253
|
t2.setPresenter(true);
|
|
14239
16254
|
t2.setHierarchyContext("You are the ROOT Manager for this task. There is no T1 Administrator involved in this run. You are responsible for decomposing the task and managing T3 workers directly.");
|
|
14240
16255
|
if (identityPrompt) {
|
|
@@ -14295,6 +16310,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
14295
16310
|
}
|
|
14296
16311
|
} else {
|
|
14297
16312
|
const t1 = new T1Administrator(this.router, this.toolRegistry, this.config);
|
|
16313
|
+
t1.setRunBreaker(runBreaker);
|
|
14298
16314
|
t1.setPresenter(true);
|
|
14299
16315
|
t1.setHierarchyContext("You are the top-level Administrator. You are responsible for the overall plan and supervising multiple T2 Managers.");
|
|
14300
16316
|
if (identityPrompt) {
|
|
@@ -14319,6 +16335,20 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
14319
16335
|
finalOutput = result.output;
|
|
14320
16336
|
t2Results = result.t2Results;
|
|
14321
16337
|
}
|
|
16338
|
+
const trip = runBreaker.reason();
|
|
16339
|
+
if (trip) {
|
|
16340
|
+
finalOutput = [
|
|
16341
|
+
`\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
|
|
16342
|
+
"",
|
|
16343
|
+
trip.message,
|
|
16344
|
+
"",
|
|
16345
|
+
`Cascade stopped after ${trip.failures} consecutive failures rather than retrying every remaining subtask against the same model, which would have cost more and produced the same result.`,
|
|
16346
|
+
finalOutput ? `
|
|
16347
|
+
---
|
|
16348
|
+
|
|
16349
|
+
${finalOutput}` : ""
|
|
16350
|
+
].join("\n").trimEnd();
|
|
16351
|
+
}
|
|
14322
16352
|
} catch (err) {
|
|
14323
16353
|
if (err instanceof CascadeCancelledError || err instanceof Error && err.name === "AbortError" || options.signal?.aborted) {
|
|
14324
16354
|
this.emit("run:cancelled", {
|
|
@@ -14385,7 +16415,11 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
14385
16415
|
inputTokens: 0,
|
|
14386
16416
|
outputTokens: 0,
|
|
14387
16417
|
totalTokens: stats.totalTokens,
|
|
14388
|
-
estimatedCostUsd: stats.totalCostUsd
|
|
16418
|
+
estimatedCostUsd: stats.totalCostUsd,
|
|
16419
|
+
// At least one call ran on a model with no known price, so its spend is
|
|
16420
|
+
// missing from the total above. Say so rather than letting the caller
|
|
16421
|
+
// present an undercount as the final figure.
|
|
16422
|
+
...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
|
|
14389
16423
|
},
|
|
14390
16424
|
t2Results,
|
|
14391
16425
|
durationMs,
|
|
@@ -14455,7 +16489,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
14455
16489
|
inputTokens: 0,
|
|
14456
16490
|
outputTokens: 0,
|
|
14457
16491
|
totalTokens: stats2.totalTokens,
|
|
14458
|
-
estimatedCostUsd: stats2.totalCostUsd
|
|
16492
|
+
estimatedCostUsd: stats2.totalCostUsd,
|
|
16493
|
+
...stats2.untrackedCostCalls > 0 ? { costUnknown: true } : {}
|
|
14459
16494
|
},
|
|
14460
16495
|
t2Results: [],
|
|
14461
16496
|
durationMs: Date.now() - startMs,
|
|
@@ -14479,7 +16514,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
|
|
|
14479
16514
|
inputTokens: result.usage?.inputTokens ?? 0,
|
|
14480
16515
|
outputTokens: result.usage?.outputTokens ?? 0,
|
|
14481
16516
|
totalTokens: stats.totalTokens,
|
|
14482
|
-
estimatedCostUsd: stats.totalCostUsd
|
|
16517
|
+
estimatedCostUsd: stats.totalCostUsd,
|
|
16518
|
+
...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
|
|
14483
16519
|
},
|
|
14484
16520
|
t2Results: [],
|
|
14485
16521
|
durationMs: Date.now() - startMs,
|
|
@@ -19696,7 +21732,10 @@ var DashboardServer = class {
|
|
|
19696
21732
|
this.socket.broadcast("cost:update", {
|
|
19697
21733
|
sessionId,
|
|
19698
21734
|
totalTokens: result.usage.totalTokens,
|
|
19699
|
-
totalCostUsd: result.usage.estimatedCostUsd
|
|
21735
|
+
totalCostUsd: result.usage.estimatedCostUsd,
|
|
21736
|
+
// True when a model with no known price was used: the total above is
|
|
21737
|
+
// an undercount, and the UI must not present it as the full spend.
|
|
21738
|
+
costUnknown: result.usage.costUnknown === true
|
|
19700
21739
|
});
|
|
19701
21740
|
this.throttledBroadcast("workspace");
|
|
19702
21741
|
} catch (err) {
|
|
@@ -20009,7 +22048,8 @@ var DashboardServer = class {
|
|
|
20009
22048
|
this.socket.broadcast("cost:update", {
|
|
20010
22049
|
sessionId,
|
|
20011
22050
|
totalTokens: result.usage.totalTokens,
|
|
20012
|
-
totalCostUsd: result.usage.estimatedCostUsd
|
|
22051
|
+
totalCostUsd: result.usage.estimatedCostUsd,
|
|
22052
|
+
costUnknown: result.usage.costUnknown === true
|
|
20013
22053
|
});
|
|
20014
22054
|
this.throttledBroadcast("workspace");
|
|
20015
22055
|
} catch (err) {
|
|
@@ -20551,7 +22591,8 @@ ${prompt}`;
|
|
|
20551
22591
|
this.socket.broadcast("cost:update", {
|
|
20552
22592
|
sessionId,
|
|
20553
22593
|
totalTokens: result.usage.totalTokens,
|
|
20554
|
-
totalCostUsd: result.usage.estimatedCostUsd
|
|
22594
|
+
totalCostUsd: result.usage.estimatedCostUsd,
|
|
22595
|
+
costUnknown: result.usage.costUnknown === true
|
|
20555
22596
|
});
|
|
20556
22597
|
this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
|
|
20557
22598
|
this.throttledBroadcast("workspace");
|
|
@@ -20947,6 +22988,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
|
|
|
20947
22988
|
});
|
|
20948
22989
|
return identity;
|
|
20949
22990
|
}
|
|
22991
|
+
init_pricing();
|
|
20950
22992
|
async function modelsCommand(options = {}) {
|
|
20951
22993
|
console.log(chalk7__default.default.magenta("\n \u25C8 Cascade Models\n"));
|
|
20952
22994
|
const cm = new ConfigManager(process.cwd());
|
|
@@ -20968,17 +23010,20 @@ async function modelsCommand(options = {}) {
|
|
|
20968
23010
|
{ tier: "T3", label: "T3 Worker", color: chalk7__default.default.hex("#2DD4BF") }
|
|
20969
23011
|
];
|
|
20970
23012
|
let anyMissing = false;
|
|
23013
|
+
let anyUnpriced = false;
|
|
20971
23014
|
for (const { tier, label, color } of tiers) {
|
|
20972
23015
|
const model = router.getTierModel(tier);
|
|
20973
23016
|
if (model) {
|
|
20974
|
-
const
|
|
20975
|
-
|
|
23017
|
+
const unpriced = isPricingUnknown(model);
|
|
23018
|
+
if (unpriced) anyUnpriced = true;
|
|
23019
|
+
const costIn = unpriced ? "cost not tracked" : model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
|
|
23020
|
+
const costOut = unpriced ? "" : model.outputCostPer1kTokens === 0 ? "free" : `, $${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
|
|
20976
23021
|
const ctx = model.contextWindow >= 1e6 ? `${(model.contextWindow / 1e6).toFixed(1)}M ctx` : `${(model.contextWindow / 1e3).toFixed(0)}K ctx`;
|
|
20977
23022
|
const local = model.isLocal ? chalk7__default.default.gray(" [local]") : "";
|
|
20978
23023
|
const vision = model.isVisionCapable ? chalk7__default.default.gray(" \u{1F441}") : "";
|
|
20979
23024
|
const bench = Math.round(benchmarkScore01(model, "mixed") * 100);
|
|
20980
23025
|
console.log(
|
|
20981
|
-
` ${color.bold(tier)} ${chalk7__default.default.white(col(model.name, 24))}${chalk7__default.default.gray(col(model.provider, 16))}` + (options.verbose ? `${chalk7__default.default.gray(col(ctx, 12))}${chalk7__default.default.gray(col(`bench ${bench}/100`, 14))}${chalk7__default.default.gray(`${costIn}
|
|
23026
|
+
` ${color.bold(tier)} ${chalk7__default.default.white(col(model.name, 24))}${chalk7__default.default.gray(col(model.provider, 16))}` + (options.verbose ? `${chalk7__default.default.gray(col(ctx, 12))}${chalk7__default.default.gray(col(`bench ${bench}/100`, 14))}${chalk7__default.default.gray(`${costIn}${costOut}`)}` : `${chalk7__default.default.gray(col(ctx, 10))}${chalk7__default.default.gray(`bench ${bench}/100`)}`) + local + vision
|
|
20982
23027
|
);
|
|
20983
23028
|
} else {
|
|
20984
23029
|
console.log(` ${color.bold(tier)} ${chalk7__default.default.red("No model available")} ${chalk7__default.default.gray(`(check provider config for ${label})`)}`);
|
|
@@ -20992,8 +23037,25 @@ async function modelsCommand(options = {}) {
|
|
|
20992
23037
|
const src = liveData.getDataSource();
|
|
20993
23038
|
const gen = liveData.getGeneratedAt();
|
|
20994
23039
|
const srcLabel = src === "live" ? "live (just fetched)" : src === "cache" ? "cached" : "bundled";
|
|
23040
|
+
const pricingMeta = pricingDatasetMeta();
|
|
20995
23041
|
console.log(chalk7__default.default.gray(
|
|
20996
|
-
` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "
|
|
23042
|
+
` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "dataset"} (baseline ${pricingMeta.generatedAt})`
|
|
23043
|
+
));
|
|
23044
|
+
const drift = liveData.getPriceDisagreements();
|
|
23045
|
+
if (drift.length) {
|
|
23046
|
+
console.log(chalk7__default.default.yellow(`
|
|
23047
|
+
${drift.length} model${drift.length === 1 ? "" : "s"} priced differently by the live source than by the bundled dataset:`));
|
|
23048
|
+
for (const d of drift.slice(0, 10)) {
|
|
23049
|
+
console.log(chalk7__default.default.gray(
|
|
23050
|
+
` ${d.provider}:${d.modelId} dataset $${d.dataset.input.toFixed(5)}/$${d.dataset.output.toFixed(5)} per 1K (as of ${d.datasetAsOf}) \u2192 live $${d.live.input.toFixed(5)}/$${d.live.output.toFixed(5)} (${d.ratio}\xD7)`
|
|
23051
|
+
));
|
|
23052
|
+
}
|
|
23053
|
+
console.log(chalk7__default.default.gray(" Live pricing is used. Refresh src/core/router/pricing-data.json to clear this."));
|
|
23054
|
+
}
|
|
23055
|
+
}
|
|
23056
|
+
if (anyUnpriced) {
|
|
23057
|
+
console.log(chalk7__default.default.yellow(
|
|
23058
|
+
'\n Some tiers use a model with no published price \u2014 their spend is reported as "cost not tracked", never $0.00,\n and is not counted toward a cost budget. Add the model to the pricing dataset, or set `local: true` on the\n provider if the endpoint genuinely costs nothing to run.'
|
|
20997
23059
|
));
|
|
20998
23060
|
}
|
|
20999
23061
|
if (options.verbose) {
|