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.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
- CASCADE_VERSION = "0.48.1";
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
- let input = model.inputCostPer1kTokens;
545
- let output = model.outputCostPer1kTokens;
546
- if (input === 0 && output === 0 && model.id && !model.isLocal) {
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
- input = known.inputCostPer1kTokens;
550
- output = known.outputCostPer1kTokens;
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: true,
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
- return (chatIds.length ? chatIds : ids).map((id) => ({
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: false
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
- return { ...m, inputCostPer1kTokens: p.input, outputCostPer1kTokens: p.output };
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 correctedCost = calculateCost(
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: correctedCost
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
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
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.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
11448
- const normalised = Math.min(1, blended / 0.05);
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.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
11584
- const normalised = Math.min(1, blended / 0.05);
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 costIn = model.inputCostPer1kTokens === 0 ? "free" : `$${model.inputCostPer1kTokens.toFixed(4)}/1K in`;
20975
- const costOut = model.outputCostPer1kTokens === 0 ? "free" : `$${model.outputCostPer1kTokens.toFixed(4)}/1K out`;
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}, ${costOut}`)}` : `${chalk7__default.default.gray(col(ctx, 10))}${chalk7__default.default.gray(`bench ${bench}/100`)}`) + local + vision
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)" : "catalog"}`
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) {