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/dist/cli.js CHANGED
@@ -59,11 +59,1688 @@ var __export = (target, all) => {
59
59
  __defProp(target, name, { get: all[name], enumerable: true });
60
60
  };
61
61
 
62
+ // src/core/router/pricing-data.json
63
+ var pricing_data_default;
64
+ var init_pricing_data = __esm({
65
+ "src/core/router/pricing-data.json"() {
66
+ pricing_data_default = {
67
+ schemaVersion: 1,
68
+ generatedAt: "2026-07-27",
69
+ defaultCurrency: "USD",
70
+ 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.',
71
+ provenanceLegend: {
72
+ "vendor-pricing-page": "Read directly from the vendor's own published pricing page (see `source`).",
73
+ "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.",
74
+ "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."
75
+ },
76
+ entries: [
77
+ {
78
+ model: "claude-opus-5",
79
+ provider: "anthropic",
80
+ modality: "text",
81
+ currency: "USD",
82
+ asOf: "2026-07-27",
83
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
84
+ provenance: "vendor-pricing-page",
85
+ tiers: [
86
+ {
87
+ inputPer1m: 5,
88
+ outputPer1m: 25,
89
+ cachedInputPer1m: 0.5,
90
+ cacheWritePer1m: 6.25,
91
+ reasoningOutputPer1m: 25
92
+ }
93
+ ],
94
+ batchMultiplier: 0.5,
95
+ contextWindow: 1e6
96
+ },
97
+ {
98
+ model: "claude-opus-4-8",
99
+ provider: "anthropic",
100
+ modality: "text",
101
+ currency: "USD",
102
+ asOf: "2026-07-27",
103
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
104
+ provenance: "vendor-pricing-page",
105
+ tiers: [
106
+ {
107
+ inputPer1m: 5,
108
+ outputPer1m: 25,
109
+ cachedInputPer1m: 0.5,
110
+ cacheWritePer1m: 6.25,
111
+ reasoningOutputPer1m: 25
112
+ }
113
+ ],
114
+ batchMultiplier: 0.5,
115
+ contextWindow: 1e6
116
+ },
117
+ {
118
+ model: "claude-opus-4-7",
119
+ provider: "anthropic",
120
+ modality: "text",
121
+ currency: "USD",
122
+ asOf: "2026-07-27",
123
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
124
+ provenance: "vendor-pricing-page",
125
+ tiers: [
126
+ {
127
+ inputPer1m: 5,
128
+ outputPer1m: 25,
129
+ cachedInputPer1m: 0.5,
130
+ cacheWritePer1m: 6.25,
131
+ reasoningOutputPer1m: 25
132
+ }
133
+ ],
134
+ batchMultiplier: 0.5,
135
+ contextWindow: 1e6
136
+ },
137
+ {
138
+ model: "claude-opus-4-6",
139
+ provider: "anthropic",
140
+ modality: "text",
141
+ currency: "USD",
142
+ asOf: "2026-07-27",
143
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
144
+ provenance: "vendor-pricing-page",
145
+ tiers: [
146
+ {
147
+ inputPer1m: 5,
148
+ outputPer1m: 25,
149
+ cachedInputPer1m: 0.5,
150
+ cacheWritePer1m: 6.25,
151
+ reasoningOutputPer1m: 25
152
+ }
153
+ ],
154
+ batchMultiplier: 0.5,
155
+ contextWindow: 1e6
156
+ },
157
+ {
158
+ model: "claude-opus-4-5",
159
+ provider: "anthropic",
160
+ modality: "text",
161
+ currency: "USD",
162
+ asOf: "2026-07-27",
163
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
164
+ provenance: "vendor-pricing-page",
165
+ tiers: [
166
+ {
167
+ inputPer1m: 5,
168
+ outputPer1m: 25,
169
+ cachedInputPer1m: 0.5,
170
+ cacheWritePer1m: 6.25,
171
+ reasoningOutputPer1m: 25
172
+ }
173
+ ],
174
+ batchMultiplier: 0.5,
175
+ contextWindow: 2e5
176
+ },
177
+ {
178
+ model: "claude-opus-4-1",
179
+ provider: "anthropic",
180
+ modality: "text",
181
+ currency: "USD",
182
+ asOf: "2026-07-27",
183
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
184
+ provenance: "vendor-pricing-page",
185
+ tiers: [
186
+ {
187
+ inputPer1m: 15,
188
+ outputPer1m: 75,
189
+ cachedInputPer1m: 1.5,
190
+ cacheWritePer1m: 18.75,
191
+ reasoningOutputPer1m: 75
192
+ }
193
+ ],
194
+ batchMultiplier: 0.5,
195
+ contextWindow: 2e5,
196
+ notes: "Deprecated by Anthropic; priced for historical/cost-reporting accuracy."
197
+ },
198
+ {
199
+ model: "claude-opus-4",
200
+ provider: "anthropic",
201
+ modality: "text",
202
+ currency: "USD",
203
+ asOf: "2026-07-27",
204
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
205
+ provenance: "vendor-pricing-page",
206
+ tiers: [
207
+ {
208
+ inputPer1m: 15,
209
+ outputPer1m: 75,
210
+ cachedInputPer1m: 1.5,
211
+ cacheWritePer1m: 18.75,
212
+ reasoningOutputPer1m: 75
213
+ }
214
+ ],
215
+ batchMultiplier: 0.5,
216
+ contextWindow: 2e5,
217
+ notes: "Retired except on Google Cloud; priced for historical/cost-reporting accuracy."
218
+ },
219
+ {
220
+ model: "claude-sonnet-5",
221
+ provider: "anthropic",
222
+ modality: "text",
223
+ currency: "USD",
224
+ asOf: "2026-07-27",
225
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
226
+ provenance: "vendor-pricing-page",
227
+ tiers: [
228
+ {
229
+ inputPer1m: 2,
230
+ outputPer1m: 10,
231
+ cachedInputPer1m: 0.2,
232
+ cacheWritePer1m: 2.5,
233
+ reasoningOutputPer1m: 10
234
+ }
235
+ ],
236
+ batchMultiplier: 0.5,
237
+ contextWindow: 1e6,
238
+ 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."
239
+ },
240
+ {
241
+ model: "claude-sonnet-4-6",
242
+ provider: "anthropic",
243
+ modality: "text",
244
+ currency: "USD",
245
+ asOf: "2026-07-27",
246
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
247
+ provenance: "vendor-pricing-page",
248
+ tiers: [
249
+ {
250
+ inputPer1m: 3,
251
+ outputPer1m: 15,
252
+ cachedInputPer1m: 0.3,
253
+ cacheWritePer1m: 3.75,
254
+ reasoningOutputPer1m: 15
255
+ }
256
+ ],
257
+ batchMultiplier: 0.5,
258
+ contextWindow: 1e6
259
+ },
260
+ {
261
+ model: "claude-sonnet-4-5",
262
+ provider: "anthropic",
263
+ modality: "text",
264
+ currency: "USD",
265
+ asOf: "2026-07-27",
266
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
267
+ provenance: "vendor-pricing-page",
268
+ tiers: [
269
+ {
270
+ maxInputTokens: 2e5,
271
+ inputPer1m: 3,
272
+ outputPer1m: 15,
273
+ cachedInputPer1m: 0.3,
274
+ cacheWritePer1m: 3.75,
275
+ reasoningOutputPer1m: 15
276
+ },
277
+ {
278
+ inputPer1m: 6,
279
+ outputPer1m: 22.5,
280
+ cachedInputPer1m: 0.6,
281
+ cacheWritePer1m: 7.5,
282
+ reasoningOutputPer1m: 22.5
283
+ }
284
+ ],
285
+ batchMultiplier: 0.5,
286
+ contextWindow: 2e5,
287
+ 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)."
288
+ },
289
+ {
290
+ model: "claude-sonnet-4",
291
+ provider: "anthropic",
292
+ modality: "text",
293
+ currency: "USD",
294
+ asOf: "2026-07-27",
295
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
296
+ provenance: "vendor-pricing-page",
297
+ tiers: [
298
+ {
299
+ inputPer1m: 3,
300
+ outputPer1m: 15,
301
+ cachedInputPer1m: 0.3,
302
+ cacheWritePer1m: 3.75,
303
+ reasoningOutputPer1m: 15
304
+ }
305
+ ],
306
+ batchMultiplier: 0.5,
307
+ contextWindow: 2e5
308
+ },
309
+ {
310
+ model: "claude-haiku-4-5",
311
+ provider: "anthropic",
312
+ modality: "text",
313
+ currency: "USD",
314
+ asOf: "2026-07-27",
315
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
316
+ provenance: "vendor-pricing-page",
317
+ tiers: [
318
+ {
319
+ inputPer1m: 1,
320
+ outputPer1m: 5,
321
+ cachedInputPer1m: 0.1,
322
+ cacheWritePer1m: 1.25,
323
+ reasoningOutputPer1m: 5
324
+ }
325
+ ],
326
+ batchMultiplier: 0.5,
327
+ contextWindow: 2e5
328
+ },
329
+ {
330
+ model: "claude-haiku-3-5",
331
+ provider: "anthropic",
332
+ modality: "text",
333
+ currency: "USD",
334
+ asOf: "2026-07-27",
335
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
336
+ provenance: "vendor-pricing-page",
337
+ tiers: [
338
+ {
339
+ inputPer1m: 0.8,
340
+ outputPer1m: 4,
341
+ cachedInputPer1m: 0.08,
342
+ cacheWritePer1m: 1,
343
+ reasoningOutputPer1m: 4
344
+ }
345
+ ],
346
+ batchMultiplier: 0.5,
347
+ contextWindow: 2e5
348
+ },
349
+ {
350
+ model: "gpt-5.6-sol",
351
+ provider: "openai",
352
+ aliases: [
353
+ "gpt-5.6"
354
+ ],
355
+ modality: "text",
356
+ currency: "USD",
357
+ asOf: "2026-07-27",
358
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
359
+ provenance: "litellm-catalog",
360
+ tiers: [
361
+ {
362
+ maxInputTokens: 272e3,
363
+ inputPer1m: 5,
364
+ outputPer1m: 30,
365
+ cachedInputPer1m: 0.5,
366
+ cacheWritePer1m: 6.25
367
+ },
368
+ {
369
+ inputPer1m: 10,
370
+ outputPer1m: 45,
371
+ cachedInputPer1m: 1,
372
+ cacheWritePer1m: 12.5
373
+ }
374
+ ],
375
+ batchMultiplier: 0.5,
376
+ contextWindow: 105e4
377
+ },
378
+ {
379
+ model: "gpt-5.6-terra",
380
+ provider: "openai",
381
+ modality: "text",
382
+ currency: "USD",
383
+ asOf: "2026-07-27",
384
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
385
+ provenance: "litellm-catalog",
386
+ tiers: [
387
+ {
388
+ maxInputTokens: 272e3,
389
+ inputPer1m: 2.5,
390
+ outputPer1m: 15,
391
+ cachedInputPer1m: 0.25,
392
+ cacheWritePer1m: 3.125
393
+ },
394
+ {
395
+ inputPer1m: 5,
396
+ outputPer1m: 22.5,
397
+ cachedInputPer1m: 0.5,
398
+ cacheWritePer1m: 6.25
399
+ }
400
+ ],
401
+ batchMultiplier: 0.5,
402
+ contextWindow: 105e4
403
+ },
404
+ {
405
+ model: "gpt-5.6-luna",
406
+ provider: "openai",
407
+ modality: "text",
408
+ currency: "USD",
409
+ asOf: "2026-07-27",
410
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
411
+ provenance: "litellm-catalog",
412
+ tiers: [
413
+ {
414
+ maxInputTokens: 272e3,
415
+ inputPer1m: 1,
416
+ outputPer1m: 6,
417
+ cachedInputPer1m: 0.1,
418
+ cacheWritePer1m: 1.25
419
+ },
420
+ {
421
+ inputPer1m: 2,
422
+ outputPer1m: 9,
423
+ cachedInputPer1m: 0.2,
424
+ cacheWritePer1m: 2.5
425
+ }
426
+ ],
427
+ batchMultiplier: 0.5,
428
+ contextWindow: 105e4
429
+ },
430
+ {
431
+ model: "gpt-5.5",
432
+ provider: "openai",
433
+ modality: "text",
434
+ currency: "USD",
435
+ asOf: "2026-07-27",
436
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
437
+ provenance: "litellm-catalog",
438
+ tiers: [
439
+ {
440
+ maxInputTokens: 272e3,
441
+ inputPer1m: 5,
442
+ outputPer1m: 30,
443
+ cachedInputPer1m: 0.5
444
+ },
445
+ {
446
+ inputPer1m: 10,
447
+ outputPer1m: 45,
448
+ cachedInputPer1m: 1
449
+ }
450
+ ],
451
+ batchMultiplier: 0.5,
452
+ contextWindow: 105e4
453
+ },
454
+ {
455
+ model: "gpt-5.4",
456
+ provider: "openai",
457
+ modality: "text",
458
+ currency: "USD",
459
+ asOf: "2026-07-27",
460
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
461
+ provenance: "litellm-catalog",
462
+ tiers: [
463
+ {
464
+ maxInputTokens: 272e3,
465
+ inputPer1m: 2.5,
466
+ outputPer1m: 15,
467
+ cachedInputPer1m: 0.25
468
+ },
469
+ {
470
+ inputPer1m: 5,
471
+ outputPer1m: 22.5,
472
+ cachedInputPer1m: 0.5
473
+ }
474
+ ],
475
+ batchMultiplier: 0.5,
476
+ contextWindow: 105e4
477
+ },
478
+ {
479
+ model: "gpt-5.4-mini",
480
+ provider: "openai",
481
+ modality: "text",
482
+ currency: "USD",
483
+ asOf: "2026-07-27",
484
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
485
+ provenance: "litellm-catalog",
486
+ tiers: [
487
+ {
488
+ inputPer1m: 0.75,
489
+ outputPer1m: 4.5,
490
+ cachedInputPer1m: 0.075
491
+ }
492
+ ],
493
+ batchMultiplier: 0.5,
494
+ contextWindow: 105e4
495
+ },
496
+ {
497
+ model: "gpt-5",
498
+ provider: "openai",
499
+ modality: "text",
500
+ currency: "USD",
501
+ asOf: "2026-07-27",
502
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
503
+ provenance: "litellm-catalog",
504
+ tiers: [
505
+ {
506
+ inputPer1m: 1.25,
507
+ outputPer1m: 10,
508
+ cachedInputPer1m: 0.125
509
+ }
510
+ ],
511
+ contextWindow: 272e3
512
+ },
513
+ {
514
+ model: "gpt-5-mini",
515
+ provider: "openai",
516
+ modality: "text",
517
+ currency: "USD",
518
+ asOf: "2026-07-27",
519
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
520
+ provenance: "litellm-catalog",
521
+ tiers: [
522
+ {
523
+ inputPer1m: 0.25,
524
+ outputPer1m: 2,
525
+ cachedInputPer1m: 0.025
526
+ }
527
+ ],
528
+ contextWindow: 272e3
529
+ },
530
+ {
531
+ model: "gpt-5-nano",
532
+ provider: "openai",
533
+ modality: "text",
534
+ currency: "USD",
535
+ asOf: "2026-07-27",
536
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
537
+ provenance: "litellm-catalog",
538
+ tiers: [
539
+ {
540
+ inputPer1m: 0.05,
541
+ outputPer1m: 0.4,
542
+ cachedInputPer1m: 5e-3
543
+ }
544
+ ],
545
+ contextWindow: 272e3
546
+ },
547
+ {
548
+ model: "gpt-4.1",
549
+ provider: "openai",
550
+ modality: "text",
551
+ currency: "USD",
552
+ asOf: "2026-07-27",
553
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
554
+ provenance: "litellm-catalog",
555
+ tiers: [
556
+ {
557
+ inputPer1m: 2,
558
+ outputPer1m: 8,
559
+ cachedInputPer1m: 0.5
560
+ }
561
+ ],
562
+ batchMultiplier: 0.5,
563
+ contextWindow: 1047576
564
+ },
565
+ {
566
+ model: "gpt-4.1-mini",
567
+ provider: "openai",
568
+ modality: "text",
569
+ currency: "USD",
570
+ asOf: "2026-07-27",
571
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
572
+ provenance: "litellm-catalog",
573
+ tiers: [
574
+ {
575
+ inputPer1m: 0.4,
576
+ outputPer1m: 1.6,
577
+ cachedInputPer1m: 0.1
578
+ }
579
+ ],
580
+ batchMultiplier: 0.5,
581
+ contextWindow: 1047576
582
+ },
583
+ {
584
+ model: "gpt-4.1-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.1,
594
+ outputPer1m: 0.4,
595
+ cachedInputPer1m: 0.025
596
+ }
597
+ ],
598
+ batchMultiplier: 0.5,
599
+ contextWindow: 1047576
600
+ },
601
+ {
602
+ model: "gpt-4o",
603
+ provider: "openai",
604
+ modality: "text",
605
+ currency: "USD",
606
+ asOf: "2026-07-27",
607
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
608
+ provenance: "litellm-catalog",
609
+ tiers: [
610
+ {
611
+ inputPer1m: 2.5,
612
+ outputPer1m: 10,
613
+ cachedInputPer1m: 1.25
614
+ }
615
+ ],
616
+ batchMultiplier: 0.5,
617
+ contextWindow: 128e3
618
+ },
619
+ {
620
+ model: "gpt-4o-mini",
621
+ provider: "openai",
622
+ modality: "text",
623
+ currency: "USD",
624
+ asOf: "2026-07-27",
625
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
626
+ provenance: "litellm-catalog",
627
+ tiers: [
628
+ {
629
+ inputPer1m: 0.15,
630
+ outputPer1m: 0.6,
631
+ cachedInputPer1m: 0.075
632
+ }
633
+ ],
634
+ batchMultiplier: 0.5,
635
+ contextWindow: 128e3
636
+ },
637
+ {
638
+ model: "gpt-5.5",
639
+ provider: "azure",
640
+ region: "global",
641
+ modality: "text",
642
+ currency: "USD",
643
+ asOf: "2026-07-27",
644
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
645
+ provenance: "litellm-catalog",
646
+ tiers: [
647
+ {
648
+ maxInputTokens: 272e3,
649
+ inputPer1m: 5,
650
+ outputPer1m: 30,
651
+ cachedInputPer1m: 0.5
652
+ },
653
+ {
654
+ inputPer1m: 10,
655
+ outputPer1m: 45,
656
+ cachedInputPer1m: 1
657
+ }
658
+ ],
659
+ contextWindow: 105e4
660
+ },
661
+ {
662
+ model: "gpt-5.5",
663
+ provider: "azure",
664
+ region: "us",
665
+ modality: "text",
666
+ currency: "USD",
667
+ asOf: "2026-07-27",
668
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
669
+ provenance: "litellm-catalog",
670
+ tiers: [
671
+ {
672
+ maxInputTokens: 272e3,
673
+ inputPer1m: 5.5,
674
+ outputPer1m: 33,
675
+ cachedInputPer1m: 0.55
676
+ },
677
+ {
678
+ inputPer1m: 11,
679
+ outputPer1m: 49.5,
680
+ cachedInputPer1m: 1.1
681
+ }
682
+ ],
683
+ contextWindow: 105e4
684
+ },
685
+ {
686
+ model: "gpt-5.5",
687
+ provider: "azure",
688
+ region: "eu",
689
+ modality: "text",
690
+ currency: "USD",
691
+ asOf: "2026-07-27",
692
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
693
+ provenance: "litellm-catalog",
694
+ tiers: [
695
+ {
696
+ maxInputTokens: 272e3,
697
+ inputPer1m: 5.5,
698
+ outputPer1m: 33,
699
+ cachedInputPer1m: 0.55
700
+ },
701
+ {
702
+ inputPer1m: 11,
703
+ outputPer1m: 49.5,
704
+ cachedInputPer1m: 1.1
705
+ }
706
+ ],
707
+ contextWindow: 105e4
708
+ },
709
+ {
710
+ model: "gpt-5.4",
711
+ provider: "azure",
712
+ region: "global",
713
+ modality: "text",
714
+ currency: "USD",
715
+ asOf: "2026-07-27",
716
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
717
+ provenance: "litellm-catalog",
718
+ tiers: [
719
+ {
720
+ maxInputTokens: 272e3,
721
+ inputPer1m: 2.5,
722
+ outputPer1m: 15,
723
+ cachedInputPer1m: 0.25
724
+ },
725
+ {
726
+ inputPer1m: 5,
727
+ outputPer1m: 22.5,
728
+ cachedInputPer1m: 0.5
729
+ }
730
+ ],
731
+ contextWindow: 105e4
732
+ },
733
+ {
734
+ model: "gpt-5.4",
735
+ provider: "azure",
736
+ region: "us",
737
+ modality: "text",
738
+ currency: "USD",
739
+ asOf: "2026-07-27",
740
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
741
+ provenance: "litellm-catalog",
742
+ tiers: [
743
+ {
744
+ inputPer1m: 2.75,
745
+ outputPer1m: 16.5,
746
+ cachedInputPer1m: 0.28
747
+ }
748
+ ],
749
+ contextWindow: 105e4
750
+ },
751
+ {
752
+ model: "gpt-5.4",
753
+ provider: "azure",
754
+ region: "eu",
755
+ modality: "text",
756
+ currency: "USD",
757
+ asOf: "2026-07-27",
758
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
759
+ provenance: "litellm-catalog",
760
+ tiers: [
761
+ {
762
+ inputPer1m: 2.75,
763
+ outputPer1m: 16.5,
764
+ cachedInputPer1m: 0.28
765
+ }
766
+ ],
767
+ contextWindow: 105e4
768
+ },
769
+ {
770
+ model: "gpt-5.6-terra",
771
+ provider: "azure",
772
+ region: "global",
773
+ modality: "text",
774
+ currency: "USD",
775
+ asOf: "2026-07-27",
776
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
777
+ provenance: "litellm-catalog",
778
+ tiers: [
779
+ {
780
+ maxInputTokens: 272e3,
781
+ inputPer1m: 2.5,
782
+ outputPer1m: 15,
783
+ cachedInputPer1m: 0.25
784
+ },
785
+ {
786
+ inputPer1m: 5,
787
+ outputPer1m: 22.5,
788
+ cachedInputPer1m: 0.5
789
+ }
790
+ ],
791
+ contextWindow: 105e4
792
+ },
793
+ {
794
+ model: "gpt-5.6-terra",
795
+ provider: "azure",
796
+ region: "eu",
797
+ modality: "text",
798
+ currency: "USD",
799
+ asOf: "2026-07-27",
800
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
801
+ provenance: "litellm-catalog",
802
+ tiers: [
803
+ {
804
+ maxInputTokens: 272e3,
805
+ inputPer1m: 2.75,
806
+ outputPer1m: 16.5,
807
+ cachedInputPer1m: 0.275
808
+ },
809
+ {
810
+ inputPer1m: 5.5,
811
+ outputPer1m: 24.75,
812
+ cachedInputPer1m: 0.55
813
+ }
814
+ ],
815
+ contextWindow: 105e4
816
+ },
817
+ {
818
+ model: "gpt-5",
819
+ provider: "azure",
820
+ region: "global",
821
+ modality: "text",
822
+ currency: "USD",
823
+ asOf: "2026-07-27",
824
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
825
+ provenance: "litellm-catalog",
826
+ tiers: [
827
+ {
828
+ inputPer1m: 1.25,
829
+ outputPer1m: 10,
830
+ cachedInputPer1m: 0.125
831
+ }
832
+ ],
833
+ contextWindow: 272e3
834
+ },
835
+ {
836
+ model: "gpt-5-mini",
837
+ provider: "azure",
838
+ region: "global",
839
+ modality: "text",
840
+ currency: "USD",
841
+ asOf: "2026-07-27",
842
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
843
+ provenance: "litellm-catalog",
844
+ tiers: [
845
+ {
846
+ inputPer1m: 0.25,
847
+ outputPer1m: 2,
848
+ cachedInputPer1m: 0.025
849
+ }
850
+ ],
851
+ contextWindow: 272e3
852
+ },
853
+ {
854
+ model: "gpt-5-nano",
855
+ provider: "azure",
856
+ region: "global",
857
+ modality: "text",
858
+ currency: "USD",
859
+ asOf: "2026-07-27",
860
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
861
+ provenance: "litellm-catalog",
862
+ tiers: [
863
+ {
864
+ inputPer1m: 0.05,
865
+ outputPer1m: 0.4,
866
+ cachedInputPer1m: 5e-3
867
+ }
868
+ ],
869
+ contextWindow: 272e3
870
+ },
871
+ {
872
+ model: "gpt-4.1",
873
+ provider: "azure",
874
+ region: "global",
875
+ modality: "text",
876
+ currency: "USD",
877
+ asOf: "2026-07-27",
878
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
879
+ provenance: "litellm-catalog",
880
+ tiers: [
881
+ {
882
+ inputPer1m: 2,
883
+ outputPer1m: 8,
884
+ cachedInputPer1m: 0.5
885
+ }
886
+ ],
887
+ batchMultiplier: 0.5,
888
+ contextWindow: 1047576
889
+ },
890
+ {
891
+ model: "gpt-4o",
892
+ provider: "azure",
893
+ region: "global",
894
+ modality: "text",
895
+ currency: "USD",
896
+ asOf: "2026-07-27",
897
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
898
+ provenance: "litellm-catalog",
899
+ tiers: [
900
+ {
901
+ inputPer1m: 2.5,
902
+ outputPer1m: 10,
903
+ cachedInputPer1m: 1.25
904
+ }
905
+ ],
906
+ contextWindow: 128e3
907
+ },
908
+ {
909
+ model: "gpt-4o-mini",
910
+ provider: "azure",
911
+ region: "global",
912
+ modality: "text",
913
+ currency: "USD",
914
+ asOf: "2026-07-27",
915
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
916
+ provenance: "litellm-catalog",
917
+ tiers: [
918
+ {
919
+ inputPer1m: 0.165,
920
+ outputPer1m: 0.66,
921
+ cachedInputPer1m: 0.075
922
+ }
923
+ ],
924
+ contextWindow: 128e3,
925
+ 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."
926
+ },
927
+ {
928
+ model: "gemini-3.1-pro",
929
+ provider: "gemini",
930
+ aliases: [
931
+ "gemini-3.1-pro-preview"
932
+ ],
933
+ modality: "text",
934
+ currency: "USD",
935
+ asOf: "2026-07-27",
936
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
937
+ provenance: "vendor-pricing-page",
938
+ tiers: [
939
+ {
940
+ maxInputTokens: 2e5,
941
+ inputPer1m: 2,
942
+ outputPer1m: 12,
943
+ cachedInputPer1m: 0.2,
944
+ reasoningOutputPer1m: 12
945
+ },
946
+ {
947
+ inputPer1m: 4,
948
+ outputPer1m: 18,
949
+ cachedInputPer1m: 0.4,
950
+ reasoningOutputPer1m: 18
951
+ }
952
+ ],
953
+ batchMultiplier: 0.5,
954
+ contextWindow: 1048576,
955
+ notes: "Long-context tier: $2/$12 per 1M up to 200K prompt tokens, $4/$18 above it."
956
+ },
957
+ {
958
+ model: "gemini-3-pro",
959
+ provider: "gemini",
960
+ aliases: [
961
+ "gemini-3-pro-preview"
962
+ ],
963
+ modality: "text",
964
+ currency: "USD",
965
+ asOf: "2026-07-27",
966
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
967
+ provenance: "vendor-pricing-page",
968
+ tiers: [
969
+ {
970
+ maxInputTokens: 2e5,
971
+ inputPer1m: 2,
972
+ outputPer1m: 12,
973
+ cachedInputPer1m: 0.2,
974
+ reasoningOutputPer1m: 12
975
+ },
976
+ {
977
+ inputPer1m: 4,
978
+ outputPer1m: 18,
979
+ cachedInputPer1m: 0.4,
980
+ reasoningOutputPer1m: 18
981
+ }
982
+ ],
983
+ batchMultiplier: 0.5,
984
+ contextWindow: 1048576
985
+ },
986
+ {
987
+ model: "gemini-3.6-flash",
988
+ provider: "gemini",
989
+ modality: "text",
990
+ currency: "USD",
991
+ asOf: "2026-07-27",
992
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
993
+ provenance: "vendor-pricing-page",
994
+ tiers: [
995
+ {
996
+ inputPer1m: 1.5,
997
+ outputPer1m: 7.5,
998
+ cachedInputPer1m: 0.15,
999
+ reasoningOutputPer1m: 7.5
1000
+ }
1001
+ ],
1002
+ batchMultiplier: 0.5,
1003
+ contextWindow: 1048576,
1004
+ notes: "Released 2026-07-21. Output pricing dropped ~17% from Gemini 3.5 Flash ($9.00 -> $7.50 per 1M); input unchanged."
1005
+ },
1006
+ {
1007
+ model: "gemini-3.5-flash",
1008
+ provider: "gemini",
1009
+ modality: "text",
1010
+ currency: "USD",
1011
+ asOf: "2026-07-27",
1012
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1013
+ provenance: "vendor-pricing-page",
1014
+ tiers: [
1015
+ {
1016
+ inputPer1m: 1.5,
1017
+ outputPer1m: 9,
1018
+ cachedInputPer1m: 0.15,
1019
+ reasoningOutputPer1m: 9
1020
+ }
1021
+ ],
1022
+ batchMultiplier: 0.5,
1023
+ contextWindow: 1048576
1024
+ },
1025
+ {
1026
+ model: "gemini-3.5-flash-lite",
1027
+ provider: "gemini",
1028
+ modality: "text",
1029
+ currency: "USD",
1030
+ asOf: "2026-07-27",
1031
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1032
+ provenance: "litellm-catalog",
1033
+ tiers: [
1034
+ {
1035
+ inputPer1m: 0.3,
1036
+ outputPer1m: 2.5,
1037
+ cachedInputPer1m: 0.03,
1038
+ reasoningOutputPer1m: 2.5
1039
+ }
1040
+ ],
1041
+ batchMultiplier: 0.5,
1042
+ contextWindow: 1048576
1043
+ },
1044
+ {
1045
+ model: "gemini-3.1-flash-lite",
1046
+ provider: "gemini",
1047
+ modality: "text",
1048
+ currency: "USD",
1049
+ asOf: "2026-07-27",
1050
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite",
1051
+ provenance: "litellm-catalog",
1052
+ tiers: [
1053
+ {
1054
+ inputPer1m: 0.25,
1055
+ outputPer1m: 1.5,
1056
+ cachedInputPer1m: 0.025,
1057
+ reasoningOutputPer1m: 1.5
1058
+ }
1059
+ ],
1060
+ batchMultiplier: 0.5,
1061
+ contextWindow: 1048576
1062
+ },
1063
+ {
1064
+ model: "gemini-3-flash",
1065
+ provider: "gemini",
1066
+ aliases: [
1067
+ "gemini-3-flash-preview"
1068
+ ],
1069
+ modality: "text",
1070
+ currency: "USD",
1071
+ asOf: "2026-07-27",
1072
+ source: "https://ai.google.dev/pricing/gemini-3",
1073
+ provenance: "litellm-catalog",
1074
+ tiers: [
1075
+ {
1076
+ inputPer1m: 0.5,
1077
+ outputPer1m: 3,
1078
+ cachedInputPer1m: 0.05,
1079
+ reasoningOutputPer1m: 3
1080
+ }
1081
+ ],
1082
+ contextWindow: 1048576
1083
+ },
1084
+ {
1085
+ model: "gemini-2.5-pro",
1086
+ provider: "gemini",
1087
+ modality: "text",
1088
+ currency: "USD",
1089
+ asOf: "2026-07-27",
1090
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1091
+ provenance: "litellm-catalog",
1092
+ tiers: [
1093
+ {
1094
+ maxInputTokens: 2e5,
1095
+ inputPer1m: 1.25,
1096
+ outputPer1m: 10,
1097
+ cachedInputPer1m: 0.125
1098
+ },
1099
+ {
1100
+ inputPer1m: 2.5,
1101
+ outputPer1m: 15,
1102
+ cachedInputPer1m: 0.25
1103
+ }
1104
+ ],
1105
+ contextWindow: 1048576
1106
+ },
1107
+ {
1108
+ model: "gemini-2.5-flash",
1109
+ provider: "gemini",
1110
+ modality: "text",
1111
+ currency: "USD",
1112
+ asOf: "2026-07-27",
1113
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
1114
+ provenance: "litellm-catalog",
1115
+ tiers: [
1116
+ {
1117
+ inputPer1m: 0.3,
1118
+ outputPer1m: 2.5,
1119
+ cachedInputPer1m: 0.03,
1120
+ reasoningOutputPer1m: 2.5
1121
+ }
1122
+ ],
1123
+ contextWindow: 1048576
1124
+ },
1125
+ {
1126
+ model: "gemini-2.5-flash-lite",
1127
+ provider: "gemini",
1128
+ modality: "text",
1129
+ currency: "USD",
1130
+ asOf: "2026-07-27",
1131
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
1132
+ provenance: "litellm-catalog",
1133
+ tiers: [
1134
+ {
1135
+ inputPer1m: 0.1,
1136
+ outputPer1m: 0.4,
1137
+ cachedInputPer1m: 0.01,
1138
+ reasoningOutputPer1m: 0.4
1139
+ }
1140
+ ],
1141
+ contextWindow: 1048576
1142
+ },
1143
+ {
1144
+ model: "gemini-2.0-flash",
1145
+ provider: "gemini",
1146
+ modality: "text",
1147
+ currency: "USD",
1148
+ asOf: "2026-07-27",
1149
+ source: "https://ai.google.dev/pricing#2_0flash",
1150
+ provenance: "litellm-catalog",
1151
+ tiers: [
1152
+ {
1153
+ inputPer1m: 0.1,
1154
+ outputPer1m: 0.4,
1155
+ cachedInputPer1m: 0.025
1156
+ }
1157
+ ],
1158
+ contextWindow: 1048576,
1159
+ notes: "Gemini Developer API price. Vertex AI lists $0.15/$0.60 per 1M for the same model \u2014 see the vertex entry."
1160
+ },
1161
+ {
1162
+ model: "gemini-2.0-flash-lite",
1163
+ provider: "gemini",
1164
+ modality: "text",
1165
+ currency: "USD",
1166
+ asOf: "2026-07-27",
1167
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite",
1168
+ provenance: "litellm-catalog",
1169
+ tiers: [
1170
+ {
1171
+ inputPer1m: 0.075,
1172
+ outputPer1m: 0.3,
1173
+ cachedInputPer1m: 0.01875
1174
+ }
1175
+ ],
1176
+ contextWindow: 1048576
1177
+ },
1178
+ {
1179
+ model: "gemini-2.0-flash",
1180
+ provider: "vertex",
1181
+ modality: "text",
1182
+ currency: "USD",
1183
+ asOf: "2026-07-27",
1184
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1185
+ provenance: "vendor-pricing-page",
1186
+ tiers: [
1187
+ {
1188
+ inputPer1m: 0.15,
1189
+ outputPer1m: 0.6
1190
+ }
1191
+ ],
1192
+ batchMultiplier: 0.5,
1193
+ notes: "Vertex AI lists $0.15/$0.60 per 1M; the Gemini Developer API lists $0.10/$0.40 for the same model."
1194
+ },
1195
+ {
1196
+ model: "gemini-3.1-pro",
1197
+ provider: "vertex",
1198
+ aliases: [
1199
+ "gemini-3.1-pro-preview"
1200
+ ],
1201
+ modality: "text",
1202
+ currency: "USD",
1203
+ asOf: "2026-07-27",
1204
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1205
+ provenance: "vendor-pricing-page",
1206
+ tiers: [
1207
+ {
1208
+ maxInputTokens: 2e5,
1209
+ inputPer1m: 2,
1210
+ outputPer1m: 12,
1211
+ cachedInputPer1m: 0.2,
1212
+ reasoningOutputPer1m: 12
1213
+ },
1214
+ {
1215
+ inputPer1m: 4,
1216
+ outputPer1m: 18,
1217
+ cachedInputPer1m: 0.4,
1218
+ reasoningOutputPer1m: 18
1219
+ }
1220
+ ],
1221
+ batchMultiplier: 0.5,
1222
+ contextWindow: 1048576
1223
+ },
1224
+ {
1225
+ model: "gemini-3.6-flash",
1226
+ provider: "vertex",
1227
+ modality: "text",
1228
+ currency: "USD",
1229
+ asOf: "2026-07-27",
1230
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1231
+ provenance: "vendor-pricing-page",
1232
+ tiers: [
1233
+ {
1234
+ inputPer1m: 1.5,
1235
+ outputPer1m: 7.5,
1236
+ cachedInputPer1m: 0.15,
1237
+ reasoningOutputPer1m: 7.5
1238
+ }
1239
+ ],
1240
+ batchMultiplier: 0.5,
1241
+ contextWindow: 1048576
1242
+ },
1243
+ {
1244
+ model: "gemini-2.5-pro",
1245
+ provider: "vertex",
1246
+ modality: "text",
1247
+ currency: "USD",
1248
+ asOf: "2026-07-27",
1249
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1250
+ provenance: "vendor-pricing-page",
1251
+ tiers: [
1252
+ {
1253
+ maxInputTokens: 2e5,
1254
+ inputPer1m: 1.25,
1255
+ outputPer1m: 10,
1256
+ cachedInputPer1m: 0.13
1257
+ },
1258
+ {
1259
+ inputPer1m: 2.5,
1260
+ outputPer1m: 15,
1261
+ cachedInputPer1m: 0.25
1262
+ }
1263
+ ],
1264
+ batchMultiplier: 0.5,
1265
+ contextWindow: 1048576
1266
+ },
1267
+ {
1268
+ model: "gemini-2.5-flash",
1269
+ provider: "vertex",
1270
+ modality: "text",
1271
+ currency: "USD",
1272
+ asOf: "2026-07-27",
1273
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1274
+ provenance: "vendor-pricing-page",
1275
+ tiers: [
1276
+ {
1277
+ inputPer1m: 0.3,
1278
+ outputPer1m: 2.5,
1279
+ cachedInputPer1m: 0.03,
1280
+ reasoningOutputPer1m: 2.5
1281
+ }
1282
+ ],
1283
+ batchMultiplier: 0.5,
1284
+ contextWindow: 1048576
1285
+ },
1286
+ {
1287
+ model: "claude-sonnet-4-6",
1288
+ provider: "openrouter",
1289
+ modality: "text",
1290
+ currency: "USD",
1291
+ asOf: "2026-07-27",
1292
+ source: "https://openrouter.ai/anthropic/claude-sonnet-4.6",
1293
+ provenance: "litellm-catalog",
1294
+ tiers: [
1295
+ {
1296
+ maxInputTokens: 2e5,
1297
+ inputPer1m: 3,
1298
+ outputPer1m: 15,
1299
+ cachedInputPer1m: 0.3,
1300
+ cacheWritePer1m: 3.75
1301
+ },
1302
+ {
1303
+ inputPer1m: 6,
1304
+ outputPer1m: 22.5,
1305
+ cachedInputPer1m: 0.6,
1306
+ cacheWritePer1m: 7.5
1307
+ }
1308
+ ],
1309
+ contextWindow: 1e6
1310
+ },
1311
+ {
1312
+ model: "claude-haiku-4-5",
1313
+ provider: "openrouter",
1314
+ modality: "text",
1315
+ currency: "USD",
1316
+ asOf: "2026-07-27",
1317
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1318
+ provenance: "litellm-catalog",
1319
+ tiers: [
1320
+ {
1321
+ inputPer1m: 1,
1322
+ outputPer1m: 5,
1323
+ cachedInputPer1m: 0.1,
1324
+ cacheWritePer1m: 1.25
1325
+ }
1326
+ ],
1327
+ contextWindow: 2e5
1328
+ },
1329
+ {
1330
+ model: "gpt-5",
1331
+ provider: "openrouter",
1332
+ modality: "text",
1333
+ currency: "USD",
1334
+ asOf: "2026-07-27",
1335
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1336
+ provenance: "litellm-catalog",
1337
+ tiers: [
1338
+ {
1339
+ inputPer1m: 1.25,
1340
+ outputPer1m: 10,
1341
+ cachedInputPer1m: 0.125
1342
+ }
1343
+ ],
1344
+ contextWindow: 272e3
1345
+ },
1346
+ {
1347
+ model: "gpt-5-mini",
1348
+ provider: "openrouter",
1349
+ modality: "text",
1350
+ currency: "USD",
1351
+ asOf: "2026-07-27",
1352
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1353
+ provenance: "litellm-catalog",
1354
+ tiers: [
1355
+ {
1356
+ inputPer1m: 0.25,
1357
+ outputPer1m: 2,
1358
+ cachedInputPer1m: 0.025
1359
+ }
1360
+ ],
1361
+ contextWindow: 272e3
1362
+ },
1363
+ {
1364
+ model: "gemini-3.1-pro",
1365
+ provider: "openrouter",
1366
+ aliases: [
1367
+ "gemini-3.1-pro-preview"
1368
+ ],
1369
+ modality: "text",
1370
+ currency: "USD",
1371
+ asOf: "2026-07-27",
1372
+ source: "https://openrouter.ai/google/gemini-3.1-pro-preview",
1373
+ provenance: "litellm-catalog",
1374
+ tiers: [
1375
+ {
1376
+ maxInputTokens: 2e5,
1377
+ inputPer1m: 2,
1378
+ outputPer1m: 12,
1379
+ cachedInputPer1m: 0.2
1380
+ },
1381
+ {
1382
+ inputPer1m: 4,
1383
+ outputPer1m: 18,
1384
+ cachedInputPer1m: 0.4,
1385
+ cacheWritePer1m: 0.25
1386
+ }
1387
+ ],
1388
+ contextWindow: 1048576
1389
+ },
1390
+ {
1391
+ model: "text-embedding-3-small",
1392
+ provider: "openai",
1393
+ modality: "embedding",
1394
+ currency: "USD",
1395
+ asOf: "2026-07-27",
1396
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1397
+ provenance: "litellm-catalog",
1398
+ tiers: [
1399
+ {
1400
+ inputPer1m: 0.02,
1401
+ outputPer1m: 0
1402
+ }
1403
+ ],
1404
+ batchMultiplier: 0.5,
1405
+ contextWindow: 8191
1406
+ },
1407
+ {
1408
+ model: "text-embedding-3-large",
1409
+ provider: "openai",
1410
+ modality: "embedding",
1411
+ currency: "USD",
1412
+ asOf: "2026-07-27",
1413
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1414
+ provenance: "litellm-catalog",
1415
+ tiers: [
1416
+ {
1417
+ inputPer1m: 0.13,
1418
+ outputPer1m: 0
1419
+ }
1420
+ ],
1421
+ batchMultiplier: 0.5,
1422
+ contextWindow: 8191
1423
+ },
1424
+ {
1425
+ model: "gemini-embedding-001",
1426
+ provider: "gemini",
1427
+ modality: "embedding",
1428
+ currency: "USD",
1429
+ asOf: "2026-07-27",
1430
+ source: "https://ai.google.dev/gemini-api/docs/embeddings#model-versions",
1431
+ provenance: "litellm-catalog",
1432
+ tiers: [
1433
+ {
1434
+ inputPer1m: 0.15,
1435
+ outputPer1m: 0
1436
+ }
1437
+ ],
1438
+ contextWindow: 2048
1439
+ },
1440
+ {
1441
+ model: "dall-e-3",
1442
+ provider: "openai",
1443
+ modality: "image",
1444
+ currency: "USD",
1445
+ asOf: "2026-07-27",
1446
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1447
+ provenance: "litellm-catalog",
1448
+ rates: [
1449
+ {
1450
+ unit: "per_image",
1451
+ amount: 0.04,
1452
+ variant: "1024x1024 standard"
1453
+ }
1454
+ ]
1455
+ },
1456
+ {
1457
+ model: "gemini-3-pro-image",
1458
+ provider: "gemini",
1459
+ modality: "image",
1460
+ currency: "USD",
1461
+ asOf: "2026-07-27",
1462
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1463
+ provenance: "litellm-catalog",
1464
+ rates: [
1465
+ {
1466
+ unit: "per_image",
1467
+ amount: 0.134,
1468
+ variant: "default output resolution"
1469
+ }
1470
+ ],
1471
+ 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."
1472
+ },
1473
+ {
1474
+ model: "imagen-4.0-generate-001",
1475
+ provider: "vertex",
1476
+ modality: "image",
1477
+ currency: "USD",
1478
+ asOf: "2026-07-27",
1479
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1480
+ provenance: "litellm-catalog",
1481
+ rates: [
1482
+ {
1483
+ unit: "per_image",
1484
+ amount: 0.04
1485
+ }
1486
+ ]
1487
+ },
1488
+ {
1489
+ model: "veo-3.1-generate-001",
1490
+ provider: "gemini",
1491
+ modality: "video",
1492
+ currency: "USD",
1493
+ asOf: "2026-07-27",
1494
+ source: "https://ai.google.dev/gemini-api/docs/video",
1495
+ provenance: "litellm-catalog",
1496
+ rates: [
1497
+ {
1498
+ unit: "per_second",
1499
+ amount: 0.4
1500
+ }
1501
+ ]
1502
+ },
1503
+ {
1504
+ model: "veo-3.1-generate-001",
1505
+ provider: "vertex",
1506
+ modality: "video",
1507
+ currency: "USD",
1508
+ asOf: "2026-07-27",
1509
+ source: "https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo",
1510
+ provenance: "litellm-catalog",
1511
+ rates: [
1512
+ {
1513
+ unit: "per_second",
1514
+ amount: 0.4
1515
+ }
1516
+ ]
1517
+ },
1518
+ {
1519
+ model: "tts-1",
1520
+ provider: "openai",
1521
+ modality: "speech",
1522
+ currency: "USD",
1523
+ asOf: "2026-07-27",
1524
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1525
+ provenance: "litellm-catalog",
1526
+ rates: [
1527
+ {
1528
+ unit: "per_character",
1529
+ amount: 15e-6
1530
+ }
1531
+ ],
1532
+ notes: "$15 per 1M characters."
1533
+ },
1534
+ {
1535
+ model: "tts-1-hd",
1536
+ provider: "openai",
1537
+ modality: "speech",
1538
+ currency: "USD",
1539
+ asOf: "2026-07-27",
1540
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1541
+ provenance: "litellm-catalog",
1542
+ rates: [
1543
+ {
1544
+ unit: "per_character",
1545
+ amount: 3e-5
1546
+ }
1547
+ ],
1548
+ notes: "$30 per 1M characters."
1549
+ },
1550
+ {
1551
+ model: "whisper-1",
1552
+ provider: "openai",
1553
+ modality: "transcription",
1554
+ currency: "USD",
1555
+ asOf: "2026-07-27",
1556
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1557
+ provenance: "litellm-catalog",
1558
+ rates: [
1559
+ {
1560
+ unit: "per_audio_minute",
1561
+ amount: 6e-3
1562
+ }
1563
+ ],
1564
+ notes: "litellm records $0.0001 per audio second; $0.006/minute is that rate x 60."
1565
+ }
1566
+ ]
1567
+ };
1568
+ }
1569
+ });
1570
+
1571
+ // src/core/router/pricing.ts
1572
+ function normalizeForPricing(id) {
1573
+ let s = id.trim().toLowerCase();
1574
+ s = s.replace(/^models\//, "");
1575
+ const slash = s.lastIndexOf("/");
1576
+ if (slash !== -1) s = s.slice(slash + 1);
1577
+ const tag = s.search(/[:@]/);
1578
+ if (tag !== -1) s = s.slice(0, tag);
1579
+ s = s.replace(/-latest$/, "");
1580
+ s = s.replace(/-preview(?:-\d{2}-\d{2})?$/, "");
1581
+ s = s.replace(/-\d{4}-\d{2}-\d{2}$/, "");
1582
+ s = s.replace(/-\d{8}$/, "");
1583
+ s = s.replace(/-preview$/, "");
1584
+ return s;
1585
+ }
1586
+ function indexKey(model, provider, region) {
1587
+ return `${provider}|${normalizeForPricing(model)}|${region ?? ""}`;
1588
+ }
1589
+ function findPricing(modelId, provider, opts = {}) {
1590
+ const ids = [modelId, opts.baseModelId].filter((x) => !!x);
1591
+ const providers = [provider, ...opts.fallbackProviders ?? []];
1592
+ for (const p of providers) {
1593
+ for (const id of ids) {
1594
+ const hit = (opts.region ? INDEX.get(indexKey(id, p, opts.region)) : void 0) ?? INDEX.get(indexKey(id, p));
1595
+ if (hit) return { entry: hit, matchedProvider: p, viaFallback: p !== provider };
1596
+ }
1597
+ }
1598
+ return null;
1599
+ }
1600
+ function pricingDatasetMeta() {
1601
+ const { schemaVersion, generatedAt, defaultCurrency } = DATASET;
1602
+ return { schemaVersion, generatedAt, defaultCurrency };
1603
+ }
1604
+ function tierFor(entry, inputTokens = 0) {
1605
+ const tiers = entry.tiers;
1606
+ if (!tiers?.length) return null;
1607
+ for (const t of tiers) {
1608
+ if (t.maxInputTokens === void 0 || inputTokens <= t.maxInputTokens) return t;
1609
+ }
1610
+ return tiers[tiers.length - 1] ?? null;
1611
+ }
1612
+ function tokenRatesPer1k(entry, opts = {}) {
1613
+ const tier = tierFor(entry, opts.inputTokens ?? 0);
1614
+ if (!tier) return null;
1615
+ const mult = (opts.batch ? entry.batchMultiplier ?? 1 : 1) * PER_1M_TO_PER_1K;
1616
+ const out = {
1617
+ input: tier.inputPer1m * mult,
1618
+ output: tier.outputPer1m * mult
1619
+ };
1620
+ if (tier.cachedInputPer1m !== void 0) out.cachedInput = tier.cachedInputPer1m * mult;
1621
+ if (tier.cacheWritePer1m !== void 0) out.cacheWrite = tier.cacheWritePer1m * mult;
1622
+ if (tier.reasoningOutputPer1m !== void 0) out.reasoningOutput = tier.reasoningOutputPer1m * mult;
1623
+ return out;
1624
+ }
1625
+ function isLoopbackOrPrivateHost(url) {
1626
+ if (!url) return false;
1627
+ try {
1628
+ const host = new URL(url).hostname.toLowerCase().replace(/^\[|\]$/g, "");
1629
+ if (LOOPBACK_HOSTS.has(host) || LOOPBACK_HOSTS.has(`[${host}]`)) return true;
1630
+ if (host.endsWith(".local")) return true;
1631
+ if (/^127\./.test(host)) return true;
1632
+ if (/^10\./.test(host)) return true;
1633
+ if (/^192\.168\./.test(host)) return true;
1634
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
1635
+ return false;
1636
+ } catch {
1637
+ return false;
1638
+ }
1639
+ }
1640
+ function isLocalEndpoint(cfg) {
1641
+ if (typeof cfg.local === "boolean") return cfg.local;
1642
+ if (cfg.type === "ollama") return true;
1643
+ if (cfg.type === "openai-compatible") return isLoopbackOrPrivateHost(cfg.baseUrl);
1644
+ return false;
1645
+ }
1646
+ function resolvePricing(model, opts = {}) {
1647
+ if (model.isLocal) return FREE;
1648
+ const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
1649
+ const match = findPricing(model.id, model.provider, {
1650
+ region: opts.region,
1651
+ baseModelId: model.baseModelId,
1652
+ fallbackProviders
1653
+ });
1654
+ if (!match) return UNKNOWN;
1655
+ const rates = tokenRatesPer1k(match.entry, { inputTokens: opts.inputTokens, batch: opts.batch });
1656
+ if (!rates) return UNKNOWN;
1657
+ return {
1658
+ input: rates.input,
1659
+ output: rates.output,
1660
+ unknown: false,
1661
+ free: false,
1662
+ entry: match.entry,
1663
+ ...match.viaFallback ? { estimatedFromProvider: match.matchedProvider } : {}
1664
+ };
1665
+ }
1666
+ function withResolvedPricing(model, opts = {}) {
1667
+ const p = resolvePricing(model, opts);
1668
+ return {
1669
+ ...model,
1670
+ inputCostPer1kTokens: p.input,
1671
+ outputCostPer1kTokens: p.output,
1672
+ ...p.unknown ? { pricingUnknown: true } : { pricingUnknown: false }
1673
+ };
1674
+ }
1675
+ function isPricingUnknown(model) {
1676
+ return model.pricingUnknown === true && !model.isLocal;
1677
+ }
1678
+ function blendedCostPer1k(model) {
1679
+ if (isPricingUnknown(model)) return BLENDED_COST_CEILING;
1680
+ return model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
1681
+ }
1682
+ function reconcilePrice(model, live) {
1683
+ const baseline = resolvePricing(model);
1684
+ const liveIsZero = !(live.input > 0) && !(live.output > 0);
1685
+ if (liveIsZero && !model.isLocal) {
1686
+ return { input: baseline.input, output: baseline.output, accepted: false };
1687
+ }
1688
+ if (baseline.unknown || !baseline.entry) {
1689
+ return { input: live.input, output: live.output, accepted: true };
1690
+ }
1691
+ const dsBlend = baseline.input + baseline.output * 2;
1692
+ const liveBlend = live.input + live.output * 2;
1693
+ const ratio = dsBlend > 0 ? liveBlend / dsBlend : Number.POSITIVE_INFINITY;
1694
+ const material = dsBlend > 0 && Math.abs(ratio - 1) > DISAGREEMENT_THRESHOLD;
1695
+ return {
1696
+ input: live.input,
1697
+ output: live.output,
1698
+ accepted: true,
1699
+ ...material ? {
1700
+ disagreement: {
1701
+ modelId: model.id,
1702
+ provider: model.provider,
1703
+ dataset: { input: baseline.input, output: baseline.output },
1704
+ live: { input: live.input, output: live.output },
1705
+ ratio: Math.round(ratio * 1e3) / 1e3,
1706
+ datasetAsOf: baseline.entry.asOf,
1707
+ datasetSource: baseline.entry.source
1708
+ }
1709
+ } : {}
1710
+ };
1711
+ }
1712
+ var DATASET, INDEX, PER_1M_TO_PER_1K, LOOPBACK_HOSTS, UNKNOWN, FREE, BLENDED_COST_CEILING, DISAGREEMENT_THRESHOLD;
1713
+ var init_pricing = __esm({
1714
+ "src/core/router/pricing.ts"() {
1715
+ init_pricing_data();
1716
+ DATASET = pricing_data_default;
1717
+ INDEX = /* @__PURE__ */ new Map();
1718
+ for (const entry of DATASET.entries) {
1719
+ for (const name of [entry.model, ...entry.aliases ?? []]) {
1720
+ const withRegion = indexKey(name, entry.provider, entry.region);
1721
+ if (!INDEX.has(withRegion)) INDEX.set(withRegion, entry);
1722
+ const bare = indexKey(name, entry.provider);
1723
+ const existing = INDEX.get(bare);
1724
+ if (!existing || existing.region && existing.region !== "global" && entry.region === "global") {
1725
+ INDEX.set(bare, entry);
1726
+ }
1727
+ }
1728
+ }
1729
+ PER_1M_TO_PER_1K = 1 / 1e3;
1730
+ LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
1731
+ UNKNOWN = { input: 0, output: 0, unknown: true, free: false };
1732
+ FREE = { input: 0, output: 0, unknown: false, free: true };
1733
+ BLENDED_COST_CEILING = 0.05;
1734
+ DISAGREEMENT_THRESHOLD = 0.02;
1735
+ }
1736
+ });
1737
+
62
1738
  // src/constants.ts
63
1739
  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;
64
1740
  var init_constants = __esm({
65
1741
  "src/constants.ts"() {
66
- CASCADE_VERSION = "0.48.1";
1742
+ init_pricing();
1743
+ CASCADE_VERSION = "0.50.0";
67
1744
  CASCADE_CONFIG_FILE = ".cascade/config.json";
68
1745
  CASCADE_DB_FILE = ".cascade/memory.db";
69
1746
  CASCADE_DASHBOARD_SECRET_FILE = ".cascade/dashboard-secret";
@@ -404,6 +2081,13 @@ var init_constants = __esm({
404
2081
  for (const _m of Object.values(MODELS)) {
405
2082
  if (_m.supportsToolUse === void 0) _m.supportsToolUse = !_m.isLocal;
406
2083
  }
2084
+ for (const _m of Object.values(MODELS)) {
2085
+ const priced = resolvePricing(_m);
2086
+ if (priced.unknown || priced.free || priced.estimatedFromProvider) continue;
2087
+ _m.inputCostPer1kTokens = priced.input;
2088
+ _m.outputCostPer1kTokens = priced.output;
2089
+ _m.pricingUnknown = false;
2090
+ }
407
2091
  T1_MODEL_PRIORITY = [
408
2092
  "gpt-5.5",
409
2093
  "gpt-5.4",
@@ -488,24 +2172,53 @@ var init_constants = __esm({
488
2172
 
489
2173
  // src/utils/cost.ts
490
2174
  function resolveModelPricing(model) {
491
- let input = model.inputCostPer1kTokens;
492
- let output = model.outputCostPer1kTokens;
493
- if (input === 0 && output === 0 && model.id && !model.isLocal) {
2175
+ if (model.isLocal) return { input: 0, output: 0, unknown: false };
2176
+ if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
2177
+ return {
2178
+ input: model.inputCostPer1kTokens,
2179
+ output: model.outputCostPer1kTokens,
2180
+ unknown: false
2181
+ };
2182
+ }
2183
+ const fromDataset = resolvePricing(model);
2184
+ if (!fromDataset.unknown) {
2185
+ return {
2186
+ input: fromDataset.input,
2187
+ output: fromDataset.output,
2188
+ unknown: false,
2189
+ ...fromDataset.estimatedFromProvider ? { estimatedFromProvider: fromDataset.estimatedFromProvider } : {}
2190
+ };
2191
+ }
2192
+ if (model.id) {
494
2193
  const known = Object.values(MODELS).find((m) => m.id === model.id && !m.isLocal);
495
- if (known) {
496
- input = known.inputCostPer1kTokens;
497
- output = known.outputCostPer1kTokens;
2194
+ if (known && (known.inputCostPer1kTokens > 0 || known.outputCostPer1kTokens > 0)) {
2195
+ return {
2196
+ input: known.inputCostPer1kTokens,
2197
+ output: known.outputCostPer1kTokens,
2198
+ unknown: false
2199
+ };
498
2200
  }
499
2201
  }
500
- return { input, output };
2202
+ return { input: 0, output: 0, unknown: true };
501
2203
  }
502
2204
  function calculateCost(inputTokens, outputTokens, model) {
503
2205
  const { input, output } = resolveModelPricing(model);
504
2206
  return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
505
2207
  }
2208
+ function buildTokenUsage(inputTokens, outputTokens, model) {
2209
+ const { input, output, unknown } = resolveModelPricing(model);
2210
+ return {
2211
+ inputTokens,
2212
+ outputTokens,
2213
+ totalTokens: inputTokens + outputTokens,
2214
+ estimatedCostUsd: inputTokens / 1e3 * input + outputTokens / 1e3 * output,
2215
+ ...unknown ? { costUnknown: true } : {}
2216
+ };
2217
+ }
506
2218
  var init_cost = __esm({
507
2219
  "src/utils/cost.ts"() {
508
2220
  init_constants();
2221
+ init_pricing();
509
2222
  }
510
2223
  });
511
2224
 
@@ -531,12 +2244,7 @@ var init_base = __esm({
531
2244
  return calculateCost(inputTokens, outputTokens, this.model);
532
2245
  }
533
2246
  makeUsage(inputTokens, outputTokens) {
534
- return {
535
- inputTokens,
536
- outputTokens,
537
- totalTokens: inputTokens + outputTokens,
538
- estimatedCostUsd: this.estimateCost(inputTokens, outputTokens)
539
- };
2247
+ return buildTokenUsage(inputTokens, outputTokens, this.model);
540
2248
  }
541
2249
  };
542
2250
  }
@@ -612,6 +2320,7 @@ var init_anthropic = __esm({
612
2320
  "src/providers/anthropic.ts"() {
613
2321
  init_constants();
614
2322
  init_base();
2323
+ init_pricing();
615
2324
  init_model_filter();
616
2325
  AnthropicProvider = class extends BaseProvider {
617
2326
  client;
@@ -722,7 +2431,7 @@ var init_anthropic = __esm({
722
2431
  return data.data.filter((m) => isChatModel(m.id)).map((m) => {
723
2432
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
724
2433
  if (known) return known;
725
- return {
2434
+ return withResolvedPricing({
726
2435
  id: m.id,
727
2436
  name: m.display_name || m.id,
728
2437
  provider: "anthropic",
@@ -733,7 +2442,7 @@ var init_anthropic = __esm({
733
2442
  maxOutputTokens: 8e3,
734
2443
  supportsStreaming: true,
735
2444
  isLocal: false
736
- };
2445
+ });
737
2446
  });
738
2447
  } catch {
739
2448
  return Object.values(MODELS).filter((m) => m.provider === "anthropic");
@@ -828,6 +2537,7 @@ var init_openai = __esm({
828
2537
  "src/providers/openai.ts"() {
829
2538
  init_constants();
830
2539
  init_base();
2540
+ init_pricing();
831
2541
  init_model_filter();
832
2542
  OpenAIProvider = class extends BaseProvider {
833
2543
  client;
@@ -953,7 +2663,7 @@ var init_openai = __esm({
953
2663
  return response.data.filter((m) => isChatModel(m.id)).map((m) => {
954
2664
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "openai");
955
2665
  if (known) return known;
956
- return {
2666
+ return withResolvedPricing({
957
2667
  id: m.id,
958
2668
  name: m.id,
959
2669
  provider: "openai",
@@ -964,7 +2674,7 @@ var init_openai = __esm({
964
2674
  maxOutputTokens: 4e3,
965
2675
  supportsStreaming: true,
966
2676
  isLocal: false
967
- };
2677
+ });
968
2678
  });
969
2679
  } catch {
970
2680
  return Object.values(MODELS).filter((m) => m.provider === "openai");
@@ -1067,6 +2777,10 @@ function azureModelForDeployment(cfg) {
1067
2777
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
1068
2778
  const base = baseModelId ? MODELS[baseModelId] : void 0;
1069
2779
  if (base) {
2780
+ const azurePrice = resolvePricing(
2781
+ { id, provider: "azure", isLocal: false, baseModelId },
2782
+ { region: cfg.region }
2783
+ );
1070
2784
  return {
1071
2785
  ...base,
1072
2786
  id,
@@ -1075,7 +2789,12 @@ function azureModelForDeployment(cfg) {
1075
2789
  provider: "azure",
1076
2790
  baseModelId,
1077
2791
  // real identity for benchmark + live pricing
1078
- supportsToolUse: base.supportsToolUse ?? true
2792
+ supportsToolUse: base.supportsToolUse ?? true,
2793
+ ...azurePrice.unknown ? {} : {
2794
+ inputCostPer1kTokens: azurePrice.input,
2795
+ outputCostPer1kTokens: azurePrice.output,
2796
+ pricingUnknown: false
2797
+ }
1079
2798
  };
1080
2799
  }
1081
2800
  return {
@@ -1098,6 +2817,7 @@ var init_azure = __esm({
1098
2817
  "src/providers/azure.ts"() {
1099
2818
  init_constants();
1100
2819
  init_openai();
2820
+ init_pricing();
1101
2821
  DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
1102
2822
  AzureOpenAIProvider = class extends OpenAIProvider {
1103
2823
  constructor(config, model) {
@@ -1158,6 +2878,7 @@ var init_gemini = __esm({
1158
2878
  "src/providers/gemini.ts"() {
1159
2879
  init_constants();
1160
2880
  init_base();
2881
+ init_pricing();
1161
2882
  init_model_filter();
1162
2883
  GeminiProvider = class extends BaseProvider {
1163
2884
  client;
@@ -1258,7 +2979,7 @@ var init_gemini = __esm({
1258
2979
  (km) => km.id === id && km.provider === "gemini"
1259
2980
  );
1260
2981
  if (known) return known;
1261
- return {
2982
+ return withResolvedPricing({
1262
2983
  id,
1263
2984
  name: m.displayName || id,
1264
2985
  provider: "gemini",
@@ -1269,7 +2990,7 @@ var init_gemini = __esm({
1269
2990
  maxOutputTokens: m.outputTokenLimit || 8e3,
1270
2991
  supportsStreaming: true,
1271
2992
  isLocal: false
1272
- };
2993
+ });
1273
2994
  });
1274
2995
  } catch {
1275
2996
  return Object.values(MODELS).filter((m) => m.provider === "gemini");
@@ -1475,6 +3196,7 @@ var init_ollama = __esm({
1475
3196
  init_constants();
1476
3197
  init_net();
1477
3198
  init_base();
3199
+ init_pricing();
1478
3200
  init_model_filter();
1479
3201
  TOOL_CAPABLE_FAMILIES = [
1480
3202
  "llama3.1",
@@ -1634,9 +3356,10 @@ var init_ollama = __esm({
1634
3356
  return supportedKeywords.some((k) => name.includes(k)) && isChatModel(name);
1635
3357
  });
1636
3358
  const shows = await Promise.all(entries.map((m) => this.showModel(m.name)));
3359
+ const local = isLocalEndpoint({ ...this.config, type: "ollama" });
1637
3360
  return entries.map((m, i) => {
1638
3361
  const show = shows[i];
1639
- return {
3362
+ return withResolvedPricing({
1640
3363
  id: m.name,
1641
3364
  name: m.name,
1642
3365
  provider: "ollama",
@@ -1646,10 +3369,10 @@ var init_ollama = __esm({
1646
3369
  outputCostPer1kTokens: 0,
1647
3370
  maxOutputTokens: 4e3,
1648
3371
  supportsStreaming: true,
1649
- isLocal: true,
3372
+ isLocal: local,
1650
3373
  supportsToolUse: show?.tools ?? isToolCapable(m.name),
1651
3374
  minSizeB: this.parseSizeB(m.details?.parameter_size)
1652
- };
3375
+ });
1653
3376
  });
1654
3377
  } catch {
1655
3378
  return [];
@@ -1733,6 +3456,7 @@ var init_openai_compatible = __esm({
1733
3456
  init_openai();
1734
3457
  init_model_filter();
1735
3458
  init_net();
3459
+ init_pricing();
1736
3460
  OpenAICompatibleProvider = class extends OpenAIProvider {
1737
3461
  constructor(config, model) {
1738
3462
  super({ ...config, apiKey: config.apiKey ?? "not-required" }, model);
@@ -1766,7 +3490,8 @@ var init_openai_compatible = __esm({
1766
3490
  }).filter((x) => typeof x === "string" && x.length > 0);
1767
3491
  if (ids.length === 0) return [this.model];
1768
3492
  const chatIds = ids.filter((id) => isChatModel(id));
1769
- return (chatIds.length ? chatIds : ids).map((id) => ({
3493
+ const local = isLocalEndpoint(this.config);
3494
+ return (chatIds.length ? chatIds : ids).map((id) => withResolvedPricing({
1770
3495
  id,
1771
3496
  name: id,
1772
3497
  provider: "openai-compatible",
@@ -1776,7 +3501,7 @@ var init_openai_compatible = __esm({
1776
3501
  outputCostPer1kTokens: 0,
1777
3502
  maxOutputTokens: 4e3,
1778
3503
  supportsStreaming: true,
1779
- isLocal: false
3504
+ isLocal: local
1780
3505
  }));
1781
3506
  }
1782
3507
  async isAvailable() {
@@ -3098,7 +4823,18 @@ var ProviderConfigSchema = z.object({
3098
4823
  apiVersion: z.string().optional(),
3099
4824
  model: z.string().optional(),
3100
4825
  authToken: z.string().optional(),
3101
- credentialSource: z.string().optional()
4826
+ credentialSource: z.string().optional(),
4827
+ /**
4828
+ * "This endpoint is self-hosted, so inference genuinely costs $0."
4829
+ * Only meaningful for `ollama` and `openai-compatible`. Unset ⇒ inferred:
4830
+ * ollama is local, openai-compatible is local when its baseUrl is
4831
+ * loopback/LAN. Set it explicitly to override — e.g. `false` for an Ollama
4832
+ * instance you rent, `true` for a llama.cpp box on your own LAN reached by
4833
+ * hostname.
4834
+ */
4835
+ local: z.boolean().optional(),
4836
+ /** Region for providers whose price varies by region (Azure: global | us | eu | …). */
4837
+ region: z.string().optional()
3102
4838
  });
3103
4839
  var ModelOverridesSchema = z.object({
3104
4840
  t1: z.string().optional(),
@@ -3947,6 +5683,7 @@ init_openai();
3947
5683
 
3948
5684
  // src/core/router/selector.ts
3949
5685
  init_constants();
5686
+ init_pricing();
3950
5687
  function normalizeModelId(id) {
3951
5688
  return id.replace(/^models\//, "").toLowerCase();
3952
5689
  }
@@ -4148,7 +5885,7 @@ var ModelSelector = class {
4148
5885
  else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
4149
5886
  }
4150
5887
  if (providerStr && this.availableProviders.has(providerStr)) {
4151
- const dynamicModel = {
5888
+ const dynamicModel = withResolvedPricing({
4152
5889
  id: actualId,
4153
5890
  name: actualId,
4154
5891
  provider: providerStr,
@@ -4159,7 +5896,7 @@ var ModelSelector = class {
4159
5896
  maxOutputTokens: 8e3,
4160
5897
  supportsStreaming: true,
4161
5898
  isLocal: providerStr === "ollama"
4162
- };
5899
+ });
4163
5900
  this.addDynamicModel(dynamicModel);
4164
5901
  return dynamicModel;
4165
5902
  }
@@ -4529,6 +6266,7 @@ function computeDelegationSavings(stats, t1Model) {
4529
6266
 
4530
6267
  // src/core/router/live-data.ts
4531
6268
  init_constants();
6269
+ init_pricing();
4532
6270
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
4533
6271
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
4534
6272
  var FETCH_TIMEOUT_MS = 8e3;
@@ -4546,6 +6284,8 @@ var LiveDataProvider = class {
4546
6284
  snapshot = null;
4547
6285
  prices = /* @__PURE__ */ new Map();
4548
6286
  capabilities = /* @__PURE__ */ new Map();
6287
+ /** Dataset-vs-live price mismatches, deduped by `provider:modelId`. */
6288
+ disagreements = /* @__PURE__ */ new Map();
4549
6289
  source = "bundled";
4550
6290
  fetchedAt = 0;
4551
6291
  loaded = false;
@@ -4699,14 +6439,45 @@ var LiveDataProvider = class {
4699
6439
  /**
4700
6440
  * Returns a price-corrected copy of each model when live pricing is known,
4701
6441
  * leaving the original untouched (so the shared catalog is never mutated).
6442
+ *
6443
+ * Reconciliation policy (see reconcilePrice in pricing.ts): the bundled
6444
+ * dataset is the authoritative baseline, and a live quote overrides it
6445
+ * because it is fresher by construction — but the disagreement is RECORDED,
6446
+ * not swallowed. A dataset/live mismatch is the best signal available that
6447
+ * the committed prices have gone stale, so it must be visible rather than
6448
+ * silently resolved in either direction. A live quote of $0 for a non-local
6449
+ * model is rejected outright: that is the very failure mode this change
6450
+ * exists to prevent, and a marketplace's free-tier alias must never zero out
6451
+ * the price of the paid model Cascade is actually calling.
4702
6452
  */
4703
6453
  applyLivePricing(models) {
4704
6454
  return models.map((m) => {
4705
6455
  const p = this.getLivePrice(m.baseModelId ?? m.id);
4706
6456
  if (!p) return m;
4707
- return { ...m, inputCostPer1kTokens: p.input, outputCostPer1kTokens: p.output };
6457
+ const reconciled = reconcilePrice(m, p);
6458
+ if (reconciled.disagreement) this.recordDisagreement(reconciled.disagreement);
6459
+ if (!reconciled.accepted) return m;
6460
+ return {
6461
+ ...m,
6462
+ inputCostPer1kTokens: reconciled.input,
6463
+ outputCostPer1kTokens: reconciled.output,
6464
+ pricingUnknown: false
6465
+ };
4708
6466
  });
4709
6467
  }
6468
+ recordDisagreement(d) {
6469
+ const key = `${d.provider}:${d.modelId}`;
6470
+ if (this.disagreements.has(key)) return;
6471
+ this.disagreements.set(key, d);
6472
+ }
6473
+ /**
6474
+ * Dataset-vs-live price mismatches seen this session, newest-model-first
6475
+ * insertion order. Surfaced by `cascade models --verbose`; a non-empty list
6476
+ * means src/core/router/pricing-data.json is probably due a refresh.
6477
+ */
6478
+ getPriceDisagreements() {
6479
+ return [...this.disagreements.values()];
6480
+ }
4710
6481
  /** Current capability facts for a model id, or null when unknown. */
4711
6482
  getCapability(modelId) {
4712
6483
  return this.capabilities.get(normalizeModelId2(modelId)) ?? null;
@@ -4903,7 +6674,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
4903
6674
  tokensByTier: {},
4904
6675
  inputTokensByTier: {},
4905
6676
  outputTokensByTier: {},
4906
- costByFeature: {}
6677
+ costByFeature: {},
6678
+ untrackedCostCalls: 0,
6679
+ untrackedCostModels: []
4907
6680
  };
4908
6681
  tierModels = /* @__PURE__ */ new Map();
4909
6682
  /**
@@ -5253,7 +7026,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
5253
7026
  `Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`
5254
7027
  );
5255
7028
  }
5256
- const correctedCost = calculateCost(
7029
+ const corrected = buildTokenUsage(
5257
7030
  result.usage.inputTokens,
5258
7031
  result.usage.outputTokens,
5259
7032
  model
@@ -5262,7 +7035,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
5262
7035
  ...result,
5263
7036
  usage: {
5264
7037
  ...result.usage,
5265
- estimatedCostUsd: correctedCost
7038
+ estimatedCostUsd: corrected.estimatedCostUsd,
7039
+ ...corrected.costUnknown ? { costUnknown: true } : { costUnknown: void 0 }
5266
7040
  }
5267
7041
  };
5268
7042
  if (!result || typeof result.content !== "string" || !result.usage) {
@@ -5449,7 +7223,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
5449
7223
  tokensByTier: { ...this.stats.tokensByTier },
5450
7224
  inputTokensByTier: { ...this.stats.inputTokensByTier },
5451
7225
  outputTokensByTier: { ...this.stats.outputTokensByTier },
5452
- costByFeature: { ...this.stats.costByFeature }
7226
+ costByFeature: { ...this.stats.costByFeature },
7227
+ untrackedCostCalls: this.stats.untrackedCostCalls,
7228
+ untrackedCostModels: [...this.stats.untrackedCostModels]
5453
7229
  };
5454
7230
  }
5455
7231
  /**
@@ -5500,7 +7276,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
5500
7276
  tokensByTier: {},
5501
7277
  inputTokensByTier: {},
5502
7278
  outputTokensByTier: {},
5503
- costByFeature: {}
7279
+ costByFeature: {},
7280
+ untrackedCostCalls: 0,
7281
+ untrackedCostModels: []
5504
7282
  };
5505
7283
  this.sessionCostUsd = 0;
5506
7284
  this.budgetState = "ok";
@@ -5685,6 +7463,18 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
5685
7463
  if (featureTag) {
5686
7464
  this.stats.costByFeature[featureTag] = (this.stats.costByFeature[featureTag] ?? 0) + usage.estimatedCostUsd;
5687
7465
  }
7466
+ if (usage.costUnknown) {
7467
+ this.stats.untrackedCostCalls += 1;
7468
+ if (!this.stats.untrackedCostModels.includes(model.id)) {
7469
+ this.stats.untrackedCostModels.push(model.id);
7470
+ const capped = this.config?.budget?.maxCostPerRunUsd != null || this.config?.budget?.sessionBudgetUsd != null;
7471
+ this.emit("cost:untracked", {
7472
+ modelId: model.id,
7473
+ provider: model.provider,
7474
+ 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.`
7475
+ });
7476
+ }
7477
+ }
5688
7478
  this.runTokens += usage.totalTokens;
5689
7479
  this.runCostUsd += usage.estimatedCostUsd;
5690
7480
  this.updateBudgetState();
@@ -5825,6 +7615,14 @@ var BaseTier = class extends EventEmitter {
5825
7615
  this.emit("tier:status", event);
5826
7616
  }
5827
7617
  /** Record the model serving this tier; future status events carry it. */
7618
+ /**
7619
+ * The model actually serving this tier ("provider:id"), once one has been
7620
+ * chosen. The run breaker reads it to attribute a failure to a model rather
7621
+ * than to the subtask that happened to hit it.
7622
+ */
7623
+ getServingModel() {
7624
+ return this.servingModel;
7625
+ }
5828
7626
  setServingModel(model) {
5829
7627
  this.servingModel = model || void 0;
5830
7628
  }
@@ -7561,6 +9359,7 @@ var T2Manager = class extends BaseTier {
7561
9359
  escalations = [];
7562
9360
  peerSyncBuffer = [];
7563
9361
  store;
9362
+ runBreaker;
7564
9363
  t3PeerBus = new PeerBus();
7565
9364
  // ← T3↔T3 bus (local to this T2)
7566
9365
  t2PeerBus;
@@ -7591,6 +9390,15 @@ var T2Manager = class extends BaseTier {
7591
9390
  this.router = router;
7592
9391
  this.toolRegistry = toolRegistry;
7593
9392
  }
9393
+ /**
9394
+ * Share the run-wide circuit breaker. Every T2 in a run gets the SAME
9395
+ * instance: three sections each failing once against a dead model is the
9396
+ * same fact as one section failing three times, and a per-section breaker
9397
+ * would never see it.
9398
+ */
9399
+ setRunBreaker(breaker) {
9400
+ this.runBreaker = breaker;
9401
+ }
7594
9402
  setStore(store) {
7595
9403
  this.store = store;
7596
9404
  }
@@ -7714,7 +9522,8 @@ Guidance (must be followed): ${decision.note}`
7714
9522
  const overallStatus = this.determineStatus(t3Results);
7715
9523
  const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
7716
9524
  this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
7717
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
9525
+ 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";
9526
+ this.sendStatusUpdate({ progressPct: 100, currentAction: sectionAction, status: "IN_PROGRESS", output: summary });
7718
9527
  const result = {
7719
9528
  sectionId: assignment.sectionId,
7720
9529
  sectionTitle: assignment.sectionTitle,
@@ -7884,6 +9693,24 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
7884
9693
  const reinforceCfg = this.router.getReinforcementsConfig?.() ?? { enabled: false, maxPerSection: 4 };
7885
9694
  let reinforcementsAdded = 0;
7886
9695
  while (remaining.size > 0) {
9696
+ if (this.runBreaker?.isOpen()) {
9697
+ const why = this.runBreaker.skipMessage();
9698
+ this.log(`Run breaker open \u2014 skipping ${remaining.size} remaining subtask(s)`);
9699
+ for (const id of remaining) {
9700
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
9701
+ resultMap.set(id, {
9702
+ subtaskId: id,
9703
+ status: "FAILED",
9704
+ output: why,
9705
+ testResults: { checksRun: [], passed: [], failed: [] },
9706
+ issues: [why],
9707
+ peerSyncsUsed: [],
9708
+ correctionAttempts: 0
9709
+ });
9710
+ }
9711
+ remaining.clear();
9712
+ break;
9713
+ }
7887
9714
  const runnableIds = [...remaining].filter((id) => (inDegree.get(id) ?? 0) === 0);
7888
9715
  if (runnableIds.length === 0) {
7889
9716
  const fallbackId = [...remaining].sort(
@@ -7986,8 +9813,32 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
7986
9813
  remaining.delete(id);
7987
9814
  const r = waveResults[i];
7988
9815
  if (r.status === "rejected") {
7989
- this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
7990
9816
  const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
9817
+ const failedModel = workerMap.get(id)?.getServingModel();
9818
+ const tripped = this.runBreaker?.record(r.reason, failedModel);
9819
+ if (tripped) {
9820
+ this.log(`Run breaker opened: ${tripped.failures} consecutive ${tripped.kind} failures on ${tripped.modelId} \u2014 stopping`);
9821
+ this.emit("run:breaker", tripped);
9822
+ }
9823
+ if (this.runBreaker?.isOpen()) {
9824
+ const why = this.runBreaker.skipMessage();
9825
+ this.log(`T3 worker ${id} failed and the run breaker is open \u2014 not retrying`);
9826
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
9827
+ resultMap.set(id, {
9828
+ subtaskId: id,
9829
+ status: "FAILED",
9830
+ output: why,
9831
+ testResults: { checksRun: [], passed: [], failed: [] },
9832
+ issues: [why],
9833
+ peerSyncsUsed: [],
9834
+ correctionAttempts: 0
9835
+ });
9836
+ for (const dependent of adj.get(id) ?? []) {
9837
+ inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
9838
+ }
9839
+ continue;
9840
+ }
9841
+ this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
7991
9842
  try {
7992
9843
  const retried = await this.retryT3(assignment, taskId);
7993
9844
  resultMap.set(id, retried);
@@ -8322,6 +10173,7 @@ var T1Administrator = class extends BaseTier {
8322
10173
  t2Managers = /* @__PURE__ */ new Map();
8323
10174
  escalations = [];
8324
10175
  store;
10176
+ runBreaker;
8325
10177
  t2PeerBus = new PeerBus();
8326
10178
  permissionEscalator;
8327
10179
  toolCreator;
@@ -8336,6 +10188,10 @@ var T1Administrator = class extends BaseTier {
8336
10188
  this.toolRegistry = toolRegistry;
8337
10189
  this.config = config;
8338
10190
  }
10191
+ /** Share the run-wide circuit breaker with every section manager. */
10192
+ setRunBreaker(breaker) {
10193
+ this.runBreaker = breaker;
10194
+ }
8339
10195
  setStore(store) {
8340
10196
  this.store = store;
8341
10197
  }
@@ -8698,6 +10554,7 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
8698
10554
  };
8699
10555
  const managers = sections.map((section) => {
8700
10556
  const manager = new T2Manager(this.router, this.toolRegistry, this.id);
10557
+ if (this.runBreaker) manager.setRunBreaker(this.runBreaker);
8701
10558
  manager.setHierarchyContext(`You are a T2 Manager for the section "${section.sectionTitle}". You are part of a COMPLEX task overseen by T1 Administrator.`);
8702
10559
  if (this.store) {
8703
10560
  manager.setStore(this.store);
@@ -8986,6 +10843,158 @@ Reply with exactly one word: YES, NO, or UNSURE.
8986
10843
  // src/core/cascade.ts
8987
10844
  init_cost();
8988
10845
 
10846
+ // src/core/router/provider-errors.ts
10847
+ var SYSTEMIC = /* @__PURE__ */ new Set([
10848
+ "rate_limit",
10849
+ "quota_exhausted",
10850
+ "auth",
10851
+ "model_unavailable"
10852
+ ]);
10853
+ function isSystemicKind(kind) {
10854
+ return SYSTEMIC.has(kind);
10855
+ }
10856
+ function statusOf(err) {
10857
+ if (!err || typeof err !== "object") return void 0;
10858
+ const e = err;
10859
+ for (const key of ["status", "statusCode", "code"]) {
10860
+ const v = e[key];
10861
+ if (typeof v === "number" && v >= 100 && v < 600) return v;
10862
+ }
10863
+ const resp = e["response"];
10864
+ if (resp && typeof resp["status"] === "number") return resp["status"];
10865
+ const inner = e["error"];
10866
+ if (inner && typeof inner["code"] === "number") return inner["code"];
10867
+ return void 0;
10868
+ }
10869
+ function messageOf(err) {
10870
+ if (err instanceof Error) return err.message;
10871
+ if (typeof err === "string") return err;
10872
+ if (err && typeof err === "object") {
10873
+ const e = err;
10874
+ if (typeof e["message"] === "string") return e["message"];
10875
+ const inner = e["error"];
10876
+ if (inner && typeof inner["message"] === "string") return inner["message"];
10877
+ try {
10878
+ return JSON.stringify(err);
10879
+ } catch {
10880
+ }
10881
+ }
10882
+ return String(err);
10883
+ }
10884
+ function classifyProviderError(err) {
10885
+ const raw = messageOf(err).trim();
10886
+ const status = statusOf(err);
10887
+ const m = raw.toLowerCase();
10888
+ const kind = (() => {
10889
+ if (status === 429) {
10890
+ return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
10891
+ }
10892
+ if (status === 401 || status === 403) return "auth";
10893
+ if (status === 404) return "model_unavailable";
10894
+ if (/rate limit|too many requests|resource_exhausted/.test(m)) {
10895
+ return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
10896
+ }
10897
+ if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
10898
+ if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
10899
+ if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
10900
+ return "model_unavailable";
10901
+ }
10902
+ if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
10903
+ if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
10904
+ if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
10905
+ return "unknown";
10906
+ })();
10907
+ return { kind, systemic: isSystemicKind(kind), status, raw };
10908
+ }
10909
+ function describeProviderError(c, modelId) {
10910
+ const on = ` on ${modelId}` ;
10911
+ const detail = c.raw ? ` Provider said: ${c.raw}` : "";
10912
+ switch (c.kind) {
10913
+ case "rate_limit":
10914
+ 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}`;
10915
+ case "quota_exhausted":
10916
+ 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}`;
10917
+ case "auth":
10918
+ 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}`;
10919
+ case "model_unavailable":
10920
+ 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}`;
10921
+ case "context_length":
10922
+ return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
10923
+ case "content_filter":
10924
+ return `The provider's safety filter refused this request${on}.${detail}`;
10925
+ case "network":
10926
+ return `Network error reaching the provider${on}. This is usually transient.${detail}`;
10927
+ default:
10928
+ return `The model call failed${on}.${detail}`;
10929
+ }
10930
+ }
10931
+
10932
+ // src/core/run-breaker.ts
10933
+ var DEFAULT_FAILURE_THRESHOLD = 3;
10934
+ var RunBreaker = class {
10935
+ threshold;
10936
+ /** modelId → consecutive systemic failures of the SAME kind. */
10937
+ counts = /* @__PURE__ */ new Map();
10938
+ trip = null;
10939
+ constructor(threshold = DEFAULT_FAILURE_THRESHOLD) {
10940
+ this.threshold = Math.max(1, threshold);
10941
+ }
10942
+ /**
10943
+ * Record a failed model call. Returns the trip if this failure is the one
10944
+ * that opened the breaker, otherwise null.
10945
+ *
10946
+ * A non-systemic failure RESETS the model's counter: it is evidence the model
10947
+ * is reachable and answering, which is exactly what a systemic streak claims
10948
+ * it is not.
10949
+ */
10950
+ record(err, modelId) {
10951
+ if (this.trip) return null;
10952
+ const id = modelId?.trim() || "unknown-model";
10953
+ const c = classifyProviderError(err);
10954
+ if (!c.systemic) {
10955
+ this.counts.delete(id);
10956
+ return null;
10957
+ }
10958
+ const prev = this.counts.get(id);
10959
+ const n = prev && prev.kind === c.kind ? prev.n + 1 : 1;
10960
+ this.counts.set(id, { kind: c.kind, n, last: c });
10961
+ if (n >= this.threshold) {
10962
+ this.trip = {
10963
+ modelId: id,
10964
+ kind: c.kind,
10965
+ failures: n,
10966
+ message: describeProviderError(c, id)
10967
+ };
10968
+ return this.trip;
10969
+ }
10970
+ return null;
10971
+ }
10972
+ /** True once the run should stop launching work that would fail identically. */
10973
+ isOpen() {
10974
+ return this.trip !== null;
10975
+ }
10976
+ /** Why the breaker opened, or null while it is closed. */
10977
+ reason() {
10978
+ return this.trip;
10979
+ }
10980
+ /**
10981
+ * One line for a worker/section that never ran because the breaker was
10982
+ * already open. Says the work was skipped deliberately — not that it failed
10983
+ * on its own merits, which is what an empty FAILED status would imply.
10984
+ */
10985
+ skipMessage() {
10986
+ const t = this.trip;
10987
+ if (!t) return "Skipped.";
10988
+ return `Skipped \u2014 the run stopped after ${t.failures} consecutive failures on ${t.modelId}. ${t.message}`;
10989
+ }
10990
+ /** Test/diagnostic view of the per-model streaks. */
10991
+ streaks() {
10992
+ const out = {};
10993
+ for (const [k, v] of this.counts) out[k] = { kind: v.kind, n: v.n };
10994
+ return out;
10995
+ }
10996
+ };
10997
+
8989
10998
  // src/tools/registry.ts
8990
10999
  init_constants();
8991
11000
 
@@ -11215,6 +13224,7 @@ var noopTelemetry = {
11215
13224
  };
11216
13225
 
11217
13226
  // src/core/router/task-analyzer.ts
13227
+ init_pricing();
11218
13228
  var CODE_SIGNALS = [
11219
13229
  /\b(?:function|class|interface|async|await|import|export|const|let|var|def|return|if|else|for|while|try|catch)\b/,
11220
13230
  /\b(?:typescript|javascript|python|rust|go|java|c\+\+|sql|bash|shell|dockerfile|kubernetes|terraform)\b/i,
@@ -11391,8 +13401,8 @@ var TaskAnalyzer = class {
11391
13401
  }
11392
13402
  costEfficiency(model, complexity) {
11393
13403
  if (this.tracker) return this.tracker.costEfficiencyScore(model, complexity);
11394
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
11395
- const normalised = Math.min(1, blended / 0.05);
13404
+ const blended = blendedCostPer1k(model);
13405
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
11396
13406
  const complexityWeight = (6 - complexity) / 5;
11397
13407
  return Math.max(0.1, 1 - normalised * complexityWeight);
11398
13408
  }
@@ -11407,6 +13417,9 @@ var TaskAnalyzer = class {
11407
13417
  analysisCache.clear();
11408
13418
  }
11409
13419
  };
13420
+
13421
+ // src/core/router/model-performance-tracker.ts
13422
+ init_pricing();
11410
13423
  var DEFAULT_STATS_FILE = path32.join(os10.homedir(), ".cascade", "model-perf.json");
11411
13424
  var ModelPerformanceTracker = class {
11412
13425
  stats = /* @__PURE__ */ new Map();
@@ -11527,8 +13540,8 @@ var ModelPerformanceTracker = class {
11527
13540
  * normalised over $0.05 blended as the "expensive" ceiling.
11528
13541
  */
11529
13542
  costEfficiencyScore(model, complexity) {
11530
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
11531
- const normalised = Math.min(1, blended / 0.05);
13543
+ const blended = blendedCostPer1k(model);
13544
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
11532
13545
  const complexityWeight = (6 - complexity) / 5;
11533
13546
  return Math.max(0.1, 1 - normalised * complexityWeight);
11534
13547
  }
@@ -14114,6 +16127,7 @@ ${prompt}` : prompt;
14114
16127
  if (toolCreator) toolCreator.setPermissionEscalator(escalator);
14115
16128
  const rootPrompt = buildContextualPrompt(options.prompt, options.conversationHistory);
14116
16129
  let finalOutput = "";
16130
+ const runBreaker = new RunBreaker();
14117
16131
  let t2Results = [];
14118
16132
  let runError = null;
14119
16133
  let identityPrompt = "";
@@ -14182,6 +16196,7 @@ ${prompt}` : prompt;
14182
16196
  this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
14183
16197
  } else if (complexity === "Moderate") {
14184
16198
  const t2 = new T2Manager(this.router, this.toolRegistry, "root");
16199
+ t2.setRunBreaker(runBreaker);
14185
16200
  t2.setPresenter(true);
14186
16201
  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.");
14187
16202
  if (identityPrompt) {
@@ -14242,6 +16257,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
14242
16257
  }
14243
16258
  } else {
14244
16259
  const t1 = new T1Administrator(this.router, this.toolRegistry, this.config);
16260
+ t1.setRunBreaker(runBreaker);
14245
16261
  t1.setPresenter(true);
14246
16262
  t1.setHierarchyContext("You are the top-level Administrator. You are responsible for the overall plan and supervising multiple T2 Managers.");
14247
16263
  if (identityPrompt) {
@@ -14266,6 +16282,20 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
14266
16282
  finalOutput = result.output;
14267
16283
  t2Results = result.t2Results;
14268
16284
  }
16285
+ const trip = runBreaker.reason();
16286
+ if (trip) {
16287
+ finalOutput = [
16288
+ `\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
16289
+ "",
16290
+ trip.message,
16291
+ "",
16292
+ `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.`,
16293
+ finalOutput ? `
16294
+ ---
16295
+
16296
+ ${finalOutput}` : ""
16297
+ ].join("\n").trimEnd();
16298
+ }
14269
16299
  } catch (err) {
14270
16300
  if (err instanceof CascadeCancelledError || err instanceof Error && err.name === "AbortError" || options.signal?.aborted) {
14271
16301
  this.emit("run:cancelled", {
@@ -14332,7 +16362,11 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
14332
16362
  inputTokens: 0,
14333
16363
  outputTokens: 0,
14334
16364
  totalTokens: stats.totalTokens,
14335
- estimatedCostUsd: stats.totalCostUsd
16365
+ estimatedCostUsd: stats.totalCostUsd,
16366
+ // At least one call ran on a model with no known price, so its spend is
16367
+ // missing from the total above. Say so rather than letting the caller
16368
+ // present an undercount as the final figure.
16369
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
14336
16370
  },
14337
16371
  t2Results,
14338
16372
  durationMs,
@@ -14402,7 +16436,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
14402
16436
  inputTokens: 0,
14403
16437
  outputTokens: 0,
14404
16438
  totalTokens: stats2.totalTokens,
14405
- estimatedCostUsd: stats2.totalCostUsd
16439
+ estimatedCostUsd: stats2.totalCostUsd,
16440
+ ...stats2.untrackedCostCalls > 0 ? { costUnknown: true } : {}
14406
16441
  },
14407
16442
  t2Results: [],
14408
16443
  durationMs: Date.now() - startMs,
@@ -14426,7 +16461,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
14426
16461
  inputTokens: result.usage?.inputTokens ?? 0,
14427
16462
  outputTokens: result.usage?.outputTokens ?? 0,
14428
16463
  totalTokens: stats.totalTokens,
14429
- estimatedCostUsd: stats.totalCostUsd
16464
+ estimatedCostUsd: stats.totalCostUsd,
16465
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
14430
16466
  },
14431
16467
  t2Results: [],
14432
16468
  durationMs: Date.now() - startMs,
@@ -19643,7 +21679,10 @@ var DashboardServer = class {
19643
21679
  this.socket.broadcast("cost:update", {
19644
21680
  sessionId,
19645
21681
  totalTokens: result.usage.totalTokens,
19646
- totalCostUsd: result.usage.estimatedCostUsd
21682
+ totalCostUsd: result.usage.estimatedCostUsd,
21683
+ // True when a model with no known price was used: the total above is
21684
+ // an undercount, and the UI must not present it as the full spend.
21685
+ costUnknown: result.usage.costUnknown === true
19647
21686
  });
19648
21687
  this.throttledBroadcast("workspace");
19649
21688
  } catch (err) {
@@ -19956,7 +21995,8 @@ var DashboardServer = class {
19956
21995
  this.socket.broadcast("cost:update", {
19957
21996
  sessionId,
19958
21997
  totalTokens: result.usage.totalTokens,
19959
- totalCostUsd: result.usage.estimatedCostUsd
21998
+ totalCostUsd: result.usage.estimatedCostUsd,
21999
+ costUnknown: result.usage.costUnknown === true
19960
22000
  });
19961
22001
  this.throttledBroadcast("workspace");
19962
22002
  } catch (err) {
@@ -20498,7 +22538,8 @@ ${prompt}`;
20498
22538
  this.socket.broadcast("cost:update", {
20499
22539
  sessionId,
20500
22540
  totalTokens: result.usage.totalTokens,
20501
- totalCostUsd: result.usage.estimatedCostUsd
22541
+ totalCostUsd: result.usage.estimatedCostUsd,
22542
+ costUnknown: result.usage.costUnknown === true
20502
22543
  });
20503
22544
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
20504
22545
  this.throttledBroadcast("workspace");
@@ -20894,6 +22935,7 @@ function makeIdentityCommand(workspacePath = process.cwd()) {
20894
22935
  });
20895
22936
  return identity;
20896
22937
  }
22938
+ init_pricing();
20897
22939
  async function modelsCommand(options = {}) {
20898
22940
  console.log(chalk7.magenta("\n \u25C8 Cascade Models\n"));
20899
22941
  const cm = new ConfigManager(process.cwd());
@@ -20915,17 +22957,20 @@ async function modelsCommand(options = {}) {
20915
22957
  { tier: "T3", label: "T3 Worker", color: chalk7.hex("#2DD4BF") }
20916
22958
  ];
20917
22959
  let anyMissing = false;
22960
+ let anyUnpriced = false;
20918
22961
  for (const { tier, label, color } of tiers) {
20919
22962
  const model = router.getTierModel(tier);
20920
22963
  if (model) {
20921
- const costIn = model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
20922
- const costOut = model.outputCostPer1kTokens === 0 ? "free" : `$${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
22964
+ const unpriced = isPricingUnknown(model);
22965
+ if (unpriced) anyUnpriced = true;
22966
+ const costIn = unpriced ? "cost not tracked" : model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
22967
+ const costOut = unpriced ? "" : model.outputCostPer1kTokens === 0 ? "free" : `, $${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
20923
22968
  const ctx = model.contextWindow >= 1e6 ? `${(model.contextWindow / 1e6).toFixed(1)}M ctx` : `${(model.contextWindow / 1e3).toFixed(0)}K ctx`;
20924
22969
  const local = model.isLocal ? chalk7.gray(" [local]") : "";
20925
22970
  const vision = model.isVisionCapable ? chalk7.gray(" \u{1F441}") : "";
20926
22971
  const bench = Math.round(benchmarkScore01(model, "mixed") * 100);
20927
22972
  console.log(
20928
- ` ${color.bold(tier)} ${chalk7.white(col(model.name, 24))}${chalk7.gray(col(model.provider, 16))}` + (options.verbose ? `${chalk7.gray(col(ctx, 12))}${chalk7.gray(col(`bench ${bench}/100`, 14))}${chalk7.gray(`${costIn}, ${costOut}`)}` : `${chalk7.gray(col(ctx, 10))}${chalk7.gray(`bench ${bench}/100`)}`) + local + vision
22973
+ ` ${color.bold(tier)} ${chalk7.white(col(model.name, 24))}${chalk7.gray(col(model.provider, 16))}` + (options.verbose ? `${chalk7.gray(col(ctx, 12))}${chalk7.gray(col(`bench ${bench}/100`, 14))}${chalk7.gray(`${costIn}${costOut}`)}` : `${chalk7.gray(col(ctx, 10))}${chalk7.gray(`bench ${bench}/100`)}`) + local + vision
20929
22974
  );
20930
22975
  } else {
20931
22976
  console.log(` ${color.bold(tier)} ${chalk7.red("No model available")} ${chalk7.gray(`(check provider config for ${label})`)}`);
@@ -20939,8 +22984,25 @@ async function modelsCommand(options = {}) {
20939
22984
  const src = liveData.getDataSource();
20940
22985
  const gen = liveData.getGeneratedAt();
20941
22986
  const srcLabel = src === "live" ? "live (just fetched)" : src === "cache" ? "cached" : "bundled";
22987
+ const pricingMeta = pricingDatasetMeta();
20942
22988
  console.log(chalk7.gray(
20943
- ` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "catalog"}`
22989
+ ` Benchmark data: ${srcLabel}` + (gen ? ` \xB7 updated ${gen.slice(0, 10)}` : "") + ` \xB7 pricing: ${liveData.hasLivePricing() ? "live (OpenRouter)" : "dataset"} (baseline ${pricingMeta.generatedAt})`
22990
+ ));
22991
+ const drift = liveData.getPriceDisagreements();
22992
+ if (drift.length) {
22993
+ console.log(chalk7.yellow(`
22994
+ ${drift.length} model${drift.length === 1 ? "" : "s"} priced differently by the live source than by the bundled dataset:`));
22995
+ for (const d of drift.slice(0, 10)) {
22996
+ console.log(chalk7.gray(
22997
+ ` ${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)`
22998
+ ));
22999
+ }
23000
+ console.log(chalk7.gray(" Live pricing is used. Refresh src/core/router/pricing-data.json to clear this."));
23001
+ }
23002
+ }
23003
+ if (anyUnpriced) {
23004
+ console.log(chalk7.yellow(
23005
+ '\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.'
20944
23006
  ));
20945
23007
  }
20946
23008
  if (options.verbose) {