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/index.js CHANGED
@@ -192,8 +192,1660 @@ var init_audit_logger = __esm({
192
192
  }
193
193
  });
194
194
 
195
+ // src/core/router/pricing-data.json
196
+ var pricing_data_default = {
197
+ entries: [
198
+ {
199
+ model: "claude-opus-5",
200
+ provider: "anthropic",
201
+ modality: "text",
202
+ currency: "USD",
203
+ asOf: "2026-07-27",
204
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
205
+ provenance: "vendor-pricing-page",
206
+ tiers: [
207
+ {
208
+ inputPer1m: 5,
209
+ outputPer1m: 25,
210
+ cachedInputPer1m: 0.5,
211
+ cacheWritePer1m: 6.25,
212
+ reasoningOutputPer1m: 25
213
+ }
214
+ ],
215
+ batchMultiplier: 0.5,
216
+ contextWindow: 1e6
217
+ },
218
+ {
219
+ model: "claude-opus-4-8",
220
+ provider: "anthropic",
221
+ modality: "text",
222
+ currency: "USD",
223
+ asOf: "2026-07-27",
224
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
225
+ provenance: "vendor-pricing-page",
226
+ tiers: [
227
+ {
228
+ inputPer1m: 5,
229
+ outputPer1m: 25,
230
+ cachedInputPer1m: 0.5,
231
+ cacheWritePer1m: 6.25,
232
+ reasoningOutputPer1m: 25
233
+ }
234
+ ],
235
+ batchMultiplier: 0.5,
236
+ contextWindow: 1e6
237
+ },
238
+ {
239
+ model: "claude-opus-4-7",
240
+ provider: "anthropic",
241
+ modality: "text",
242
+ currency: "USD",
243
+ asOf: "2026-07-27",
244
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
245
+ provenance: "vendor-pricing-page",
246
+ tiers: [
247
+ {
248
+ inputPer1m: 5,
249
+ outputPer1m: 25,
250
+ cachedInputPer1m: 0.5,
251
+ cacheWritePer1m: 6.25,
252
+ reasoningOutputPer1m: 25
253
+ }
254
+ ],
255
+ batchMultiplier: 0.5,
256
+ contextWindow: 1e6
257
+ },
258
+ {
259
+ model: "claude-opus-4-6",
260
+ provider: "anthropic",
261
+ modality: "text",
262
+ currency: "USD",
263
+ asOf: "2026-07-27",
264
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
265
+ provenance: "vendor-pricing-page",
266
+ tiers: [
267
+ {
268
+ inputPer1m: 5,
269
+ outputPer1m: 25,
270
+ cachedInputPer1m: 0.5,
271
+ cacheWritePer1m: 6.25,
272
+ reasoningOutputPer1m: 25
273
+ }
274
+ ],
275
+ batchMultiplier: 0.5,
276
+ contextWindow: 1e6
277
+ },
278
+ {
279
+ model: "claude-opus-4-5",
280
+ provider: "anthropic",
281
+ modality: "text",
282
+ currency: "USD",
283
+ asOf: "2026-07-27",
284
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
285
+ provenance: "vendor-pricing-page",
286
+ tiers: [
287
+ {
288
+ inputPer1m: 5,
289
+ outputPer1m: 25,
290
+ cachedInputPer1m: 0.5,
291
+ cacheWritePer1m: 6.25,
292
+ reasoningOutputPer1m: 25
293
+ }
294
+ ],
295
+ batchMultiplier: 0.5,
296
+ contextWindow: 2e5
297
+ },
298
+ {
299
+ model: "claude-opus-4-1",
300
+ provider: "anthropic",
301
+ modality: "text",
302
+ currency: "USD",
303
+ asOf: "2026-07-27",
304
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
305
+ provenance: "vendor-pricing-page",
306
+ tiers: [
307
+ {
308
+ inputPer1m: 15,
309
+ outputPer1m: 75,
310
+ cachedInputPer1m: 1.5,
311
+ cacheWritePer1m: 18.75,
312
+ reasoningOutputPer1m: 75
313
+ }
314
+ ],
315
+ batchMultiplier: 0.5,
316
+ contextWindow: 2e5,
317
+ notes: "Deprecated by Anthropic; priced for historical/cost-reporting accuracy."
318
+ },
319
+ {
320
+ model: "claude-opus-4",
321
+ provider: "anthropic",
322
+ modality: "text",
323
+ currency: "USD",
324
+ asOf: "2026-07-27",
325
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
326
+ provenance: "vendor-pricing-page",
327
+ tiers: [
328
+ {
329
+ inputPer1m: 15,
330
+ outputPer1m: 75,
331
+ cachedInputPer1m: 1.5,
332
+ cacheWritePer1m: 18.75,
333
+ reasoningOutputPer1m: 75
334
+ }
335
+ ],
336
+ batchMultiplier: 0.5,
337
+ contextWindow: 2e5,
338
+ notes: "Retired except on Google Cloud; priced for historical/cost-reporting accuracy."
339
+ },
340
+ {
341
+ model: "claude-sonnet-5",
342
+ provider: "anthropic",
343
+ modality: "text",
344
+ currency: "USD",
345
+ asOf: "2026-07-27",
346
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
347
+ provenance: "vendor-pricing-page",
348
+ tiers: [
349
+ {
350
+ inputPer1m: 2,
351
+ outputPer1m: 10,
352
+ cachedInputPer1m: 0.2,
353
+ cacheWritePer1m: 2.5,
354
+ reasoningOutputPer1m: 10
355
+ }
356
+ ],
357
+ batchMultiplier: 0.5,
358
+ contextWindow: 1e6,
359
+ 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."
360
+ },
361
+ {
362
+ model: "claude-sonnet-4-6",
363
+ provider: "anthropic",
364
+ modality: "text",
365
+ currency: "USD",
366
+ asOf: "2026-07-27",
367
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
368
+ provenance: "vendor-pricing-page",
369
+ tiers: [
370
+ {
371
+ inputPer1m: 3,
372
+ outputPer1m: 15,
373
+ cachedInputPer1m: 0.3,
374
+ cacheWritePer1m: 3.75,
375
+ reasoningOutputPer1m: 15
376
+ }
377
+ ],
378
+ batchMultiplier: 0.5,
379
+ contextWindow: 1e6
380
+ },
381
+ {
382
+ model: "claude-sonnet-4-5",
383
+ provider: "anthropic",
384
+ modality: "text",
385
+ currency: "USD",
386
+ asOf: "2026-07-27",
387
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
388
+ provenance: "vendor-pricing-page",
389
+ tiers: [
390
+ {
391
+ maxInputTokens: 2e5,
392
+ inputPer1m: 3,
393
+ outputPer1m: 15,
394
+ cachedInputPer1m: 0.3,
395
+ cacheWritePer1m: 3.75,
396
+ reasoningOutputPer1m: 15
397
+ },
398
+ {
399
+ inputPer1m: 6,
400
+ outputPer1m: 22.5,
401
+ cachedInputPer1m: 0.6,
402
+ cacheWritePer1m: 7.5,
403
+ reasoningOutputPer1m: 22.5
404
+ }
405
+ ],
406
+ batchMultiplier: 0.5,
407
+ contextWindow: 2e5,
408
+ 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)."
409
+ },
410
+ {
411
+ model: "claude-sonnet-4",
412
+ provider: "anthropic",
413
+ modality: "text",
414
+ currency: "USD",
415
+ asOf: "2026-07-27",
416
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
417
+ provenance: "vendor-pricing-page",
418
+ tiers: [
419
+ {
420
+ inputPer1m: 3,
421
+ outputPer1m: 15,
422
+ cachedInputPer1m: 0.3,
423
+ cacheWritePer1m: 3.75,
424
+ reasoningOutputPer1m: 15
425
+ }
426
+ ],
427
+ batchMultiplier: 0.5,
428
+ contextWindow: 2e5
429
+ },
430
+ {
431
+ model: "claude-haiku-4-5",
432
+ provider: "anthropic",
433
+ modality: "text",
434
+ currency: "USD",
435
+ asOf: "2026-07-27",
436
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
437
+ provenance: "vendor-pricing-page",
438
+ tiers: [
439
+ {
440
+ inputPer1m: 1,
441
+ outputPer1m: 5,
442
+ cachedInputPer1m: 0.1,
443
+ cacheWritePer1m: 1.25,
444
+ reasoningOutputPer1m: 5
445
+ }
446
+ ],
447
+ batchMultiplier: 0.5,
448
+ contextWindow: 2e5
449
+ },
450
+ {
451
+ model: "claude-haiku-3-5",
452
+ provider: "anthropic",
453
+ modality: "text",
454
+ currency: "USD",
455
+ asOf: "2026-07-27",
456
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
457
+ provenance: "vendor-pricing-page",
458
+ tiers: [
459
+ {
460
+ inputPer1m: 0.8,
461
+ outputPer1m: 4,
462
+ cachedInputPer1m: 0.08,
463
+ cacheWritePer1m: 1,
464
+ reasoningOutputPer1m: 4
465
+ }
466
+ ],
467
+ batchMultiplier: 0.5,
468
+ contextWindow: 2e5
469
+ },
470
+ {
471
+ model: "gpt-5.6-sol",
472
+ provider: "openai",
473
+ aliases: [
474
+ "gpt-5.6"
475
+ ],
476
+ modality: "text",
477
+ currency: "USD",
478
+ asOf: "2026-07-27",
479
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
480
+ provenance: "litellm-catalog",
481
+ tiers: [
482
+ {
483
+ maxInputTokens: 272e3,
484
+ inputPer1m: 5,
485
+ outputPer1m: 30,
486
+ cachedInputPer1m: 0.5,
487
+ cacheWritePer1m: 6.25
488
+ },
489
+ {
490
+ inputPer1m: 10,
491
+ outputPer1m: 45,
492
+ cachedInputPer1m: 1,
493
+ cacheWritePer1m: 12.5
494
+ }
495
+ ],
496
+ batchMultiplier: 0.5,
497
+ contextWindow: 105e4
498
+ },
499
+ {
500
+ model: "gpt-5.6-terra",
501
+ provider: "openai",
502
+ modality: "text",
503
+ currency: "USD",
504
+ asOf: "2026-07-27",
505
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
506
+ provenance: "litellm-catalog",
507
+ tiers: [
508
+ {
509
+ maxInputTokens: 272e3,
510
+ inputPer1m: 2.5,
511
+ outputPer1m: 15,
512
+ cachedInputPer1m: 0.25,
513
+ cacheWritePer1m: 3.125
514
+ },
515
+ {
516
+ inputPer1m: 5,
517
+ outputPer1m: 22.5,
518
+ cachedInputPer1m: 0.5,
519
+ cacheWritePer1m: 6.25
520
+ }
521
+ ],
522
+ batchMultiplier: 0.5,
523
+ contextWindow: 105e4
524
+ },
525
+ {
526
+ model: "gpt-5.6-luna",
527
+ provider: "openai",
528
+ modality: "text",
529
+ currency: "USD",
530
+ asOf: "2026-07-27",
531
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
532
+ provenance: "litellm-catalog",
533
+ tiers: [
534
+ {
535
+ maxInputTokens: 272e3,
536
+ inputPer1m: 1,
537
+ outputPer1m: 6,
538
+ cachedInputPer1m: 0.1,
539
+ cacheWritePer1m: 1.25
540
+ },
541
+ {
542
+ inputPer1m: 2,
543
+ outputPer1m: 9,
544
+ cachedInputPer1m: 0.2,
545
+ cacheWritePer1m: 2.5
546
+ }
547
+ ],
548
+ batchMultiplier: 0.5,
549
+ contextWindow: 105e4
550
+ },
551
+ {
552
+ model: "gpt-5.5",
553
+ provider: "openai",
554
+ modality: "text",
555
+ currency: "USD",
556
+ asOf: "2026-07-27",
557
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
558
+ provenance: "litellm-catalog",
559
+ tiers: [
560
+ {
561
+ maxInputTokens: 272e3,
562
+ inputPer1m: 5,
563
+ outputPer1m: 30,
564
+ cachedInputPer1m: 0.5
565
+ },
566
+ {
567
+ inputPer1m: 10,
568
+ outputPer1m: 45,
569
+ cachedInputPer1m: 1
570
+ }
571
+ ],
572
+ batchMultiplier: 0.5,
573
+ contextWindow: 105e4
574
+ },
575
+ {
576
+ model: "gpt-5.4",
577
+ provider: "openai",
578
+ modality: "text",
579
+ currency: "USD",
580
+ asOf: "2026-07-27",
581
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
582
+ provenance: "litellm-catalog",
583
+ tiers: [
584
+ {
585
+ maxInputTokens: 272e3,
586
+ inputPer1m: 2.5,
587
+ outputPer1m: 15,
588
+ cachedInputPer1m: 0.25
589
+ },
590
+ {
591
+ inputPer1m: 5,
592
+ outputPer1m: 22.5,
593
+ cachedInputPer1m: 0.5
594
+ }
595
+ ],
596
+ batchMultiplier: 0.5,
597
+ contextWindow: 105e4
598
+ },
599
+ {
600
+ model: "gpt-5.4-mini",
601
+ provider: "openai",
602
+ modality: "text",
603
+ currency: "USD",
604
+ asOf: "2026-07-27",
605
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
606
+ provenance: "litellm-catalog",
607
+ tiers: [
608
+ {
609
+ inputPer1m: 0.75,
610
+ outputPer1m: 4.5,
611
+ cachedInputPer1m: 0.075
612
+ }
613
+ ],
614
+ batchMultiplier: 0.5,
615
+ contextWindow: 105e4
616
+ },
617
+ {
618
+ model: "gpt-5",
619
+ provider: "openai",
620
+ modality: "text",
621
+ currency: "USD",
622
+ asOf: "2026-07-27",
623
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
624
+ provenance: "litellm-catalog",
625
+ tiers: [
626
+ {
627
+ inputPer1m: 1.25,
628
+ outputPer1m: 10,
629
+ cachedInputPer1m: 0.125
630
+ }
631
+ ],
632
+ contextWindow: 272e3
633
+ },
634
+ {
635
+ model: "gpt-5-mini",
636
+ provider: "openai",
637
+ modality: "text",
638
+ currency: "USD",
639
+ asOf: "2026-07-27",
640
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
641
+ provenance: "litellm-catalog",
642
+ tiers: [
643
+ {
644
+ inputPer1m: 0.25,
645
+ outputPer1m: 2,
646
+ cachedInputPer1m: 0.025
647
+ }
648
+ ],
649
+ contextWindow: 272e3
650
+ },
651
+ {
652
+ model: "gpt-5-nano",
653
+ provider: "openai",
654
+ modality: "text",
655
+ currency: "USD",
656
+ asOf: "2026-07-27",
657
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
658
+ provenance: "litellm-catalog",
659
+ tiers: [
660
+ {
661
+ inputPer1m: 0.05,
662
+ outputPer1m: 0.4,
663
+ cachedInputPer1m: 5e-3
664
+ }
665
+ ],
666
+ contextWindow: 272e3
667
+ },
668
+ {
669
+ model: "gpt-4.1",
670
+ provider: "openai",
671
+ modality: "text",
672
+ currency: "USD",
673
+ asOf: "2026-07-27",
674
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
675
+ provenance: "litellm-catalog",
676
+ tiers: [
677
+ {
678
+ inputPer1m: 2,
679
+ outputPer1m: 8,
680
+ cachedInputPer1m: 0.5
681
+ }
682
+ ],
683
+ batchMultiplier: 0.5,
684
+ contextWindow: 1047576
685
+ },
686
+ {
687
+ model: "gpt-4.1-mini",
688
+ provider: "openai",
689
+ modality: "text",
690
+ currency: "USD",
691
+ asOf: "2026-07-27",
692
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
693
+ provenance: "litellm-catalog",
694
+ tiers: [
695
+ {
696
+ inputPer1m: 0.4,
697
+ outputPer1m: 1.6,
698
+ cachedInputPer1m: 0.1
699
+ }
700
+ ],
701
+ batchMultiplier: 0.5,
702
+ contextWindow: 1047576
703
+ },
704
+ {
705
+ model: "gpt-4.1-nano",
706
+ provider: "openai",
707
+ modality: "text",
708
+ currency: "USD",
709
+ asOf: "2026-07-27",
710
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
711
+ provenance: "litellm-catalog",
712
+ tiers: [
713
+ {
714
+ inputPer1m: 0.1,
715
+ outputPer1m: 0.4,
716
+ cachedInputPer1m: 0.025
717
+ }
718
+ ],
719
+ batchMultiplier: 0.5,
720
+ contextWindow: 1047576
721
+ },
722
+ {
723
+ model: "gpt-4o",
724
+ provider: "openai",
725
+ modality: "text",
726
+ currency: "USD",
727
+ asOf: "2026-07-27",
728
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
729
+ provenance: "litellm-catalog",
730
+ tiers: [
731
+ {
732
+ inputPer1m: 2.5,
733
+ outputPer1m: 10,
734
+ cachedInputPer1m: 1.25
735
+ }
736
+ ],
737
+ batchMultiplier: 0.5,
738
+ contextWindow: 128e3
739
+ },
740
+ {
741
+ model: "gpt-4o-mini",
742
+ provider: "openai",
743
+ modality: "text",
744
+ currency: "USD",
745
+ asOf: "2026-07-27",
746
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
747
+ provenance: "litellm-catalog",
748
+ tiers: [
749
+ {
750
+ inputPer1m: 0.15,
751
+ outputPer1m: 0.6,
752
+ cachedInputPer1m: 0.075
753
+ }
754
+ ],
755
+ batchMultiplier: 0.5,
756
+ contextWindow: 128e3
757
+ },
758
+ {
759
+ model: "gpt-5.5",
760
+ provider: "azure",
761
+ region: "global",
762
+ modality: "text",
763
+ currency: "USD",
764
+ asOf: "2026-07-27",
765
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
766
+ provenance: "litellm-catalog",
767
+ tiers: [
768
+ {
769
+ maxInputTokens: 272e3,
770
+ inputPer1m: 5,
771
+ outputPer1m: 30,
772
+ cachedInputPer1m: 0.5
773
+ },
774
+ {
775
+ inputPer1m: 10,
776
+ outputPer1m: 45,
777
+ cachedInputPer1m: 1
778
+ }
779
+ ],
780
+ contextWindow: 105e4
781
+ },
782
+ {
783
+ model: "gpt-5.5",
784
+ provider: "azure",
785
+ region: "us",
786
+ modality: "text",
787
+ currency: "USD",
788
+ asOf: "2026-07-27",
789
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
790
+ provenance: "litellm-catalog",
791
+ tiers: [
792
+ {
793
+ maxInputTokens: 272e3,
794
+ inputPer1m: 5.5,
795
+ outputPer1m: 33,
796
+ cachedInputPer1m: 0.55
797
+ },
798
+ {
799
+ inputPer1m: 11,
800
+ outputPer1m: 49.5,
801
+ cachedInputPer1m: 1.1
802
+ }
803
+ ],
804
+ contextWindow: 105e4
805
+ },
806
+ {
807
+ model: "gpt-5.5",
808
+ provider: "azure",
809
+ region: "eu",
810
+ modality: "text",
811
+ currency: "USD",
812
+ asOf: "2026-07-27",
813
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
814
+ provenance: "litellm-catalog",
815
+ tiers: [
816
+ {
817
+ maxInputTokens: 272e3,
818
+ inputPer1m: 5.5,
819
+ outputPer1m: 33,
820
+ cachedInputPer1m: 0.55
821
+ },
822
+ {
823
+ inputPer1m: 11,
824
+ outputPer1m: 49.5,
825
+ cachedInputPer1m: 1.1
826
+ }
827
+ ],
828
+ contextWindow: 105e4
829
+ },
830
+ {
831
+ model: "gpt-5.4",
832
+ provider: "azure",
833
+ region: "global",
834
+ modality: "text",
835
+ currency: "USD",
836
+ asOf: "2026-07-27",
837
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
838
+ provenance: "litellm-catalog",
839
+ tiers: [
840
+ {
841
+ maxInputTokens: 272e3,
842
+ inputPer1m: 2.5,
843
+ outputPer1m: 15,
844
+ cachedInputPer1m: 0.25
845
+ },
846
+ {
847
+ inputPer1m: 5,
848
+ outputPer1m: 22.5,
849
+ cachedInputPer1m: 0.5
850
+ }
851
+ ],
852
+ contextWindow: 105e4
853
+ },
854
+ {
855
+ model: "gpt-5.4",
856
+ provider: "azure",
857
+ region: "us",
858
+ modality: "text",
859
+ currency: "USD",
860
+ asOf: "2026-07-27",
861
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
862
+ provenance: "litellm-catalog",
863
+ tiers: [
864
+ {
865
+ inputPer1m: 2.75,
866
+ outputPer1m: 16.5,
867
+ cachedInputPer1m: 0.28
868
+ }
869
+ ],
870
+ contextWindow: 105e4
871
+ },
872
+ {
873
+ model: "gpt-5.4",
874
+ provider: "azure",
875
+ region: "eu",
876
+ modality: "text",
877
+ currency: "USD",
878
+ asOf: "2026-07-27",
879
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
880
+ provenance: "litellm-catalog",
881
+ tiers: [
882
+ {
883
+ inputPer1m: 2.75,
884
+ outputPer1m: 16.5,
885
+ cachedInputPer1m: 0.28
886
+ }
887
+ ],
888
+ contextWindow: 105e4
889
+ },
890
+ {
891
+ model: "gpt-5.6-terra",
892
+ provider: "azure",
893
+ region: "global",
894
+ modality: "text",
895
+ currency: "USD",
896
+ asOf: "2026-07-27",
897
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
898
+ provenance: "litellm-catalog",
899
+ tiers: [
900
+ {
901
+ maxInputTokens: 272e3,
902
+ inputPer1m: 2.5,
903
+ outputPer1m: 15,
904
+ cachedInputPer1m: 0.25
905
+ },
906
+ {
907
+ inputPer1m: 5,
908
+ outputPer1m: 22.5,
909
+ cachedInputPer1m: 0.5
910
+ }
911
+ ],
912
+ contextWindow: 105e4
913
+ },
914
+ {
915
+ model: "gpt-5.6-terra",
916
+ provider: "azure",
917
+ region: "eu",
918
+ modality: "text",
919
+ currency: "USD",
920
+ asOf: "2026-07-27",
921
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
922
+ provenance: "litellm-catalog",
923
+ tiers: [
924
+ {
925
+ maxInputTokens: 272e3,
926
+ inputPer1m: 2.75,
927
+ outputPer1m: 16.5,
928
+ cachedInputPer1m: 0.275
929
+ },
930
+ {
931
+ inputPer1m: 5.5,
932
+ outputPer1m: 24.75,
933
+ cachedInputPer1m: 0.55
934
+ }
935
+ ],
936
+ contextWindow: 105e4
937
+ },
938
+ {
939
+ model: "gpt-5",
940
+ provider: "azure",
941
+ region: "global",
942
+ modality: "text",
943
+ currency: "USD",
944
+ asOf: "2026-07-27",
945
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
946
+ provenance: "litellm-catalog",
947
+ tiers: [
948
+ {
949
+ inputPer1m: 1.25,
950
+ outputPer1m: 10,
951
+ cachedInputPer1m: 0.125
952
+ }
953
+ ],
954
+ contextWindow: 272e3
955
+ },
956
+ {
957
+ model: "gpt-5-mini",
958
+ provider: "azure",
959
+ region: "global",
960
+ modality: "text",
961
+ currency: "USD",
962
+ asOf: "2026-07-27",
963
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
964
+ provenance: "litellm-catalog",
965
+ tiers: [
966
+ {
967
+ inputPer1m: 0.25,
968
+ outputPer1m: 2,
969
+ cachedInputPer1m: 0.025
970
+ }
971
+ ],
972
+ contextWindow: 272e3
973
+ },
974
+ {
975
+ model: "gpt-5-nano",
976
+ provider: "azure",
977
+ region: "global",
978
+ modality: "text",
979
+ currency: "USD",
980
+ asOf: "2026-07-27",
981
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
982
+ provenance: "litellm-catalog",
983
+ tiers: [
984
+ {
985
+ inputPer1m: 0.05,
986
+ outputPer1m: 0.4,
987
+ cachedInputPer1m: 5e-3
988
+ }
989
+ ],
990
+ contextWindow: 272e3
991
+ },
992
+ {
993
+ model: "gpt-4.1",
994
+ provider: "azure",
995
+ region: "global",
996
+ modality: "text",
997
+ currency: "USD",
998
+ asOf: "2026-07-27",
999
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1000
+ provenance: "litellm-catalog",
1001
+ tiers: [
1002
+ {
1003
+ inputPer1m: 2,
1004
+ outputPer1m: 8,
1005
+ cachedInputPer1m: 0.5
1006
+ }
1007
+ ],
1008
+ batchMultiplier: 0.5,
1009
+ contextWindow: 1047576
1010
+ },
1011
+ {
1012
+ model: "gpt-4o",
1013
+ provider: "azure",
1014
+ region: "global",
1015
+ modality: "text",
1016
+ currency: "USD",
1017
+ asOf: "2026-07-27",
1018
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1019
+ provenance: "litellm-catalog",
1020
+ tiers: [
1021
+ {
1022
+ inputPer1m: 2.5,
1023
+ outputPer1m: 10,
1024
+ cachedInputPer1m: 1.25
1025
+ }
1026
+ ],
1027
+ contextWindow: 128e3
1028
+ },
1029
+ {
1030
+ model: "gpt-4o-mini",
1031
+ provider: "azure",
1032
+ region: "global",
1033
+ modality: "text",
1034
+ currency: "USD",
1035
+ asOf: "2026-07-27",
1036
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1037
+ provenance: "litellm-catalog",
1038
+ tiers: [
1039
+ {
1040
+ inputPer1m: 0.165,
1041
+ outputPer1m: 0.66,
1042
+ cachedInputPer1m: 0.075
1043
+ }
1044
+ ],
1045
+ contextWindow: 128e3,
1046
+ 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."
1047
+ },
1048
+ {
1049
+ model: "gemini-3.1-pro",
1050
+ provider: "gemini",
1051
+ aliases: [
1052
+ "gemini-3.1-pro-preview"
1053
+ ],
1054
+ modality: "text",
1055
+ currency: "USD",
1056
+ asOf: "2026-07-27",
1057
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1058
+ provenance: "vendor-pricing-page",
1059
+ tiers: [
1060
+ {
1061
+ maxInputTokens: 2e5,
1062
+ inputPer1m: 2,
1063
+ outputPer1m: 12,
1064
+ cachedInputPer1m: 0.2,
1065
+ reasoningOutputPer1m: 12
1066
+ },
1067
+ {
1068
+ inputPer1m: 4,
1069
+ outputPer1m: 18,
1070
+ cachedInputPer1m: 0.4,
1071
+ reasoningOutputPer1m: 18
1072
+ }
1073
+ ],
1074
+ batchMultiplier: 0.5,
1075
+ contextWindow: 1048576,
1076
+ notes: "Long-context tier: $2/$12 per 1M up to 200K prompt tokens, $4/$18 above it."
1077
+ },
1078
+ {
1079
+ model: "gemini-3-pro",
1080
+ provider: "gemini",
1081
+ aliases: [
1082
+ "gemini-3-pro-preview"
1083
+ ],
1084
+ modality: "text",
1085
+ currency: "USD",
1086
+ asOf: "2026-07-27",
1087
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1088
+ provenance: "vendor-pricing-page",
1089
+ tiers: [
1090
+ {
1091
+ maxInputTokens: 2e5,
1092
+ inputPer1m: 2,
1093
+ outputPer1m: 12,
1094
+ cachedInputPer1m: 0.2,
1095
+ reasoningOutputPer1m: 12
1096
+ },
1097
+ {
1098
+ inputPer1m: 4,
1099
+ outputPer1m: 18,
1100
+ cachedInputPer1m: 0.4,
1101
+ reasoningOutputPer1m: 18
1102
+ }
1103
+ ],
1104
+ batchMultiplier: 0.5,
1105
+ contextWindow: 1048576
1106
+ },
1107
+ {
1108
+ model: "gemini-3.6-flash",
1109
+ provider: "gemini",
1110
+ modality: "text",
1111
+ currency: "USD",
1112
+ asOf: "2026-07-27",
1113
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1114
+ provenance: "vendor-pricing-page",
1115
+ tiers: [
1116
+ {
1117
+ inputPer1m: 1.5,
1118
+ outputPer1m: 7.5,
1119
+ cachedInputPer1m: 0.15,
1120
+ reasoningOutputPer1m: 7.5
1121
+ }
1122
+ ],
1123
+ batchMultiplier: 0.5,
1124
+ contextWindow: 1048576,
1125
+ notes: "Released 2026-07-21. Output pricing dropped ~17% from Gemini 3.5 Flash ($9.00 -> $7.50 per 1M); input unchanged."
1126
+ },
1127
+ {
1128
+ model: "gemini-3.5-flash",
1129
+ provider: "gemini",
1130
+ modality: "text",
1131
+ currency: "USD",
1132
+ asOf: "2026-07-27",
1133
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1134
+ provenance: "vendor-pricing-page",
1135
+ tiers: [
1136
+ {
1137
+ inputPer1m: 1.5,
1138
+ outputPer1m: 9,
1139
+ cachedInputPer1m: 0.15,
1140
+ reasoningOutputPer1m: 9
1141
+ }
1142
+ ],
1143
+ batchMultiplier: 0.5,
1144
+ contextWindow: 1048576
1145
+ },
1146
+ {
1147
+ model: "gemini-3.5-flash-lite",
1148
+ provider: "gemini",
1149
+ modality: "text",
1150
+ currency: "USD",
1151
+ asOf: "2026-07-27",
1152
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1153
+ provenance: "litellm-catalog",
1154
+ tiers: [
1155
+ {
1156
+ inputPer1m: 0.3,
1157
+ outputPer1m: 2.5,
1158
+ cachedInputPer1m: 0.03,
1159
+ reasoningOutputPer1m: 2.5
1160
+ }
1161
+ ],
1162
+ batchMultiplier: 0.5,
1163
+ contextWindow: 1048576
1164
+ },
1165
+ {
1166
+ model: "gemini-3.1-flash-lite",
1167
+ provider: "gemini",
1168
+ modality: "text",
1169
+ currency: "USD",
1170
+ asOf: "2026-07-27",
1171
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite",
1172
+ provenance: "litellm-catalog",
1173
+ tiers: [
1174
+ {
1175
+ inputPer1m: 0.25,
1176
+ outputPer1m: 1.5,
1177
+ cachedInputPer1m: 0.025,
1178
+ reasoningOutputPer1m: 1.5
1179
+ }
1180
+ ],
1181
+ batchMultiplier: 0.5,
1182
+ contextWindow: 1048576
1183
+ },
1184
+ {
1185
+ model: "gemini-3-flash",
1186
+ provider: "gemini",
1187
+ aliases: [
1188
+ "gemini-3-flash-preview"
1189
+ ],
1190
+ modality: "text",
1191
+ currency: "USD",
1192
+ asOf: "2026-07-27",
1193
+ source: "https://ai.google.dev/pricing/gemini-3",
1194
+ provenance: "litellm-catalog",
1195
+ tiers: [
1196
+ {
1197
+ inputPer1m: 0.5,
1198
+ outputPer1m: 3,
1199
+ cachedInputPer1m: 0.05,
1200
+ reasoningOutputPer1m: 3
1201
+ }
1202
+ ],
1203
+ contextWindow: 1048576
1204
+ },
1205
+ {
1206
+ model: "gemini-2.5-pro",
1207
+ provider: "gemini",
1208
+ modality: "text",
1209
+ currency: "USD",
1210
+ asOf: "2026-07-27",
1211
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1212
+ provenance: "litellm-catalog",
1213
+ tiers: [
1214
+ {
1215
+ maxInputTokens: 2e5,
1216
+ inputPer1m: 1.25,
1217
+ outputPer1m: 10,
1218
+ cachedInputPer1m: 0.125
1219
+ },
1220
+ {
1221
+ inputPer1m: 2.5,
1222
+ outputPer1m: 15,
1223
+ cachedInputPer1m: 0.25
1224
+ }
1225
+ ],
1226
+ contextWindow: 1048576
1227
+ },
1228
+ {
1229
+ model: "gemini-2.5-flash",
1230
+ provider: "gemini",
1231
+ modality: "text",
1232
+ currency: "USD",
1233
+ asOf: "2026-07-27",
1234
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
1235
+ provenance: "litellm-catalog",
1236
+ tiers: [
1237
+ {
1238
+ inputPer1m: 0.3,
1239
+ outputPer1m: 2.5,
1240
+ cachedInputPer1m: 0.03,
1241
+ reasoningOutputPer1m: 2.5
1242
+ }
1243
+ ],
1244
+ contextWindow: 1048576
1245
+ },
1246
+ {
1247
+ model: "gemini-2.5-flash-lite",
1248
+ provider: "gemini",
1249
+ modality: "text",
1250
+ currency: "USD",
1251
+ asOf: "2026-07-27",
1252
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
1253
+ provenance: "litellm-catalog",
1254
+ tiers: [
1255
+ {
1256
+ inputPer1m: 0.1,
1257
+ outputPer1m: 0.4,
1258
+ cachedInputPer1m: 0.01,
1259
+ reasoningOutputPer1m: 0.4
1260
+ }
1261
+ ],
1262
+ contextWindow: 1048576
1263
+ },
1264
+ {
1265
+ model: "gemini-2.0-flash",
1266
+ provider: "gemini",
1267
+ modality: "text",
1268
+ currency: "USD",
1269
+ asOf: "2026-07-27",
1270
+ source: "https://ai.google.dev/pricing#2_0flash",
1271
+ provenance: "litellm-catalog",
1272
+ tiers: [
1273
+ {
1274
+ inputPer1m: 0.1,
1275
+ outputPer1m: 0.4,
1276
+ cachedInputPer1m: 0.025
1277
+ }
1278
+ ],
1279
+ contextWindow: 1048576,
1280
+ notes: "Gemini Developer API price. Vertex AI lists $0.15/$0.60 per 1M for the same model \u2014 see the vertex entry."
1281
+ },
1282
+ {
1283
+ model: "gemini-2.0-flash-lite",
1284
+ provider: "gemini",
1285
+ modality: "text",
1286
+ currency: "USD",
1287
+ asOf: "2026-07-27",
1288
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite",
1289
+ provenance: "litellm-catalog",
1290
+ tiers: [
1291
+ {
1292
+ inputPer1m: 0.075,
1293
+ outputPer1m: 0.3,
1294
+ cachedInputPer1m: 0.01875
1295
+ }
1296
+ ],
1297
+ contextWindow: 1048576
1298
+ },
1299
+ {
1300
+ model: "gemini-2.0-flash",
1301
+ provider: "vertex",
1302
+ modality: "text",
1303
+ currency: "USD",
1304
+ asOf: "2026-07-27",
1305
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1306
+ provenance: "vendor-pricing-page",
1307
+ tiers: [
1308
+ {
1309
+ inputPer1m: 0.15,
1310
+ outputPer1m: 0.6
1311
+ }
1312
+ ],
1313
+ batchMultiplier: 0.5,
1314
+ notes: "Vertex AI lists $0.15/$0.60 per 1M; the Gemini Developer API lists $0.10/$0.40 for the same model."
1315
+ },
1316
+ {
1317
+ model: "gemini-3.1-pro",
1318
+ provider: "vertex",
1319
+ aliases: [
1320
+ "gemini-3.1-pro-preview"
1321
+ ],
1322
+ modality: "text",
1323
+ currency: "USD",
1324
+ asOf: "2026-07-27",
1325
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1326
+ provenance: "vendor-pricing-page",
1327
+ tiers: [
1328
+ {
1329
+ maxInputTokens: 2e5,
1330
+ inputPer1m: 2,
1331
+ outputPer1m: 12,
1332
+ cachedInputPer1m: 0.2,
1333
+ reasoningOutputPer1m: 12
1334
+ },
1335
+ {
1336
+ inputPer1m: 4,
1337
+ outputPer1m: 18,
1338
+ cachedInputPer1m: 0.4,
1339
+ reasoningOutputPer1m: 18
1340
+ }
1341
+ ],
1342
+ batchMultiplier: 0.5,
1343
+ contextWindow: 1048576
1344
+ },
1345
+ {
1346
+ model: "gemini-3.6-flash",
1347
+ provider: "vertex",
1348
+ modality: "text",
1349
+ currency: "USD",
1350
+ asOf: "2026-07-27",
1351
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1352
+ provenance: "vendor-pricing-page",
1353
+ tiers: [
1354
+ {
1355
+ inputPer1m: 1.5,
1356
+ outputPer1m: 7.5,
1357
+ cachedInputPer1m: 0.15,
1358
+ reasoningOutputPer1m: 7.5
1359
+ }
1360
+ ],
1361
+ batchMultiplier: 0.5,
1362
+ contextWindow: 1048576
1363
+ },
1364
+ {
1365
+ model: "gemini-2.5-pro",
1366
+ provider: "vertex",
1367
+ modality: "text",
1368
+ currency: "USD",
1369
+ asOf: "2026-07-27",
1370
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1371
+ provenance: "vendor-pricing-page",
1372
+ tiers: [
1373
+ {
1374
+ maxInputTokens: 2e5,
1375
+ inputPer1m: 1.25,
1376
+ outputPer1m: 10,
1377
+ cachedInputPer1m: 0.13
1378
+ },
1379
+ {
1380
+ inputPer1m: 2.5,
1381
+ outputPer1m: 15,
1382
+ cachedInputPer1m: 0.25
1383
+ }
1384
+ ],
1385
+ batchMultiplier: 0.5,
1386
+ contextWindow: 1048576
1387
+ },
1388
+ {
1389
+ model: "gemini-2.5-flash",
1390
+ provider: "vertex",
1391
+ modality: "text",
1392
+ currency: "USD",
1393
+ asOf: "2026-07-27",
1394
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1395
+ provenance: "vendor-pricing-page",
1396
+ tiers: [
1397
+ {
1398
+ inputPer1m: 0.3,
1399
+ outputPer1m: 2.5,
1400
+ cachedInputPer1m: 0.03,
1401
+ reasoningOutputPer1m: 2.5
1402
+ }
1403
+ ],
1404
+ batchMultiplier: 0.5,
1405
+ contextWindow: 1048576
1406
+ },
1407
+ {
1408
+ model: "claude-sonnet-4-6",
1409
+ provider: "openrouter",
1410
+ modality: "text",
1411
+ currency: "USD",
1412
+ asOf: "2026-07-27",
1413
+ source: "https://openrouter.ai/anthropic/claude-sonnet-4.6",
1414
+ provenance: "litellm-catalog",
1415
+ tiers: [
1416
+ {
1417
+ maxInputTokens: 2e5,
1418
+ inputPer1m: 3,
1419
+ outputPer1m: 15,
1420
+ cachedInputPer1m: 0.3,
1421
+ cacheWritePer1m: 3.75
1422
+ },
1423
+ {
1424
+ inputPer1m: 6,
1425
+ outputPer1m: 22.5,
1426
+ cachedInputPer1m: 0.6,
1427
+ cacheWritePer1m: 7.5
1428
+ }
1429
+ ],
1430
+ contextWindow: 1e6
1431
+ },
1432
+ {
1433
+ model: "claude-haiku-4-5",
1434
+ provider: "openrouter",
1435
+ modality: "text",
1436
+ currency: "USD",
1437
+ asOf: "2026-07-27",
1438
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1439
+ provenance: "litellm-catalog",
1440
+ tiers: [
1441
+ {
1442
+ inputPer1m: 1,
1443
+ outputPer1m: 5,
1444
+ cachedInputPer1m: 0.1,
1445
+ cacheWritePer1m: 1.25
1446
+ }
1447
+ ],
1448
+ contextWindow: 2e5
1449
+ },
1450
+ {
1451
+ model: "gpt-5",
1452
+ provider: "openrouter",
1453
+ modality: "text",
1454
+ currency: "USD",
1455
+ asOf: "2026-07-27",
1456
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1457
+ provenance: "litellm-catalog",
1458
+ tiers: [
1459
+ {
1460
+ inputPer1m: 1.25,
1461
+ outputPer1m: 10,
1462
+ cachedInputPer1m: 0.125
1463
+ }
1464
+ ],
1465
+ contextWindow: 272e3
1466
+ },
1467
+ {
1468
+ model: "gpt-5-mini",
1469
+ provider: "openrouter",
1470
+ modality: "text",
1471
+ currency: "USD",
1472
+ asOf: "2026-07-27",
1473
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1474
+ provenance: "litellm-catalog",
1475
+ tiers: [
1476
+ {
1477
+ inputPer1m: 0.25,
1478
+ outputPer1m: 2,
1479
+ cachedInputPer1m: 0.025
1480
+ }
1481
+ ],
1482
+ contextWindow: 272e3
1483
+ },
1484
+ {
1485
+ model: "gemini-3.1-pro",
1486
+ provider: "openrouter",
1487
+ aliases: [
1488
+ "gemini-3.1-pro-preview"
1489
+ ],
1490
+ modality: "text",
1491
+ currency: "USD",
1492
+ asOf: "2026-07-27",
1493
+ source: "https://openrouter.ai/google/gemini-3.1-pro-preview",
1494
+ provenance: "litellm-catalog",
1495
+ tiers: [
1496
+ {
1497
+ maxInputTokens: 2e5,
1498
+ inputPer1m: 2,
1499
+ outputPer1m: 12,
1500
+ cachedInputPer1m: 0.2
1501
+ },
1502
+ {
1503
+ inputPer1m: 4,
1504
+ outputPer1m: 18,
1505
+ cachedInputPer1m: 0.4,
1506
+ cacheWritePer1m: 0.25
1507
+ }
1508
+ ],
1509
+ contextWindow: 1048576
1510
+ },
1511
+ {
1512
+ model: "text-embedding-3-small",
1513
+ provider: "openai",
1514
+ modality: "embedding",
1515
+ currency: "USD",
1516
+ asOf: "2026-07-27",
1517
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1518
+ provenance: "litellm-catalog",
1519
+ tiers: [
1520
+ {
1521
+ inputPer1m: 0.02,
1522
+ outputPer1m: 0
1523
+ }
1524
+ ],
1525
+ batchMultiplier: 0.5,
1526
+ contextWindow: 8191
1527
+ },
1528
+ {
1529
+ model: "text-embedding-3-large",
1530
+ provider: "openai",
1531
+ modality: "embedding",
1532
+ currency: "USD",
1533
+ asOf: "2026-07-27",
1534
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1535
+ provenance: "litellm-catalog",
1536
+ tiers: [
1537
+ {
1538
+ inputPer1m: 0.13,
1539
+ outputPer1m: 0
1540
+ }
1541
+ ],
1542
+ batchMultiplier: 0.5,
1543
+ contextWindow: 8191
1544
+ },
1545
+ {
1546
+ model: "gemini-embedding-001",
1547
+ provider: "gemini",
1548
+ modality: "embedding",
1549
+ currency: "USD",
1550
+ asOf: "2026-07-27",
1551
+ source: "https://ai.google.dev/gemini-api/docs/embeddings#model-versions",
1552
+ provenance: "litellm-catalog",
1553
+ tiers: [
1554
+ {
1555
+ inputPer1m: 0.15,
1556
+ outputPer1m: 0
1557
+ }
1558
+ ],
1559
+ contextWindow: 2048
1560
+ },
1561
+ {
1562
+ model: "dall-e-3",
1563
+ provider: "openai",
1564
+ modality: "image",
1565
+ currency: "USD",
1566
+ asOf: "2026-07-27",
1567
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1568
+ provenance: "litellm-catalog",
1569
+ rates: [
1570
+ {
1571
+ unit: "per_image",
1572
+ amount: 0.04,
1573
+ variant: "1024x1024 standard"
1574
+ }
1575
+ ]
1576
+ },
1577
+ {
1578
+ model: "gemini-3-pro-image",
1579
+ provider: "gemini",
1580
+ modality: "image",
1581
+ currency: "USD",
1582
+ asOf: "2026-07-27",
1583
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1584
+ provenance: "litellm-catalog",
1585
+ rates: [
1586
+ {
1587
+ unit: "per_image",
1588
+ amount: 0.134,
1589
+ variant: "default output resolution"
1590
+ }
1591
+ ],
1592
+ 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."
1593
+ },
1594
+ {
1595
+ model: "imagen-4.0-generate-001",
1596
+ provider: "vertex",
1597
+ modality: "image",
1598
+ currency: "USD",
1599
+ asOf: "2026-07-27",
1600
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1601
+ provenance: "litellm-catalog",
1602
+ rates: [
1603
+ {
1604
+ unit: "per_image",
1605
+ amount: 0.04
1606
+ }
1607
+ ]
1608
+ },
1609
+ {
1610
+ model: "veo-3.1-generate-001",
1611
+ provider: "gemini",
1612
+ modality: "video",
1613
+ currency: "USD",
1614
+ asOf: "2026-07-27",
1615
+ source: "https://ai.google.dev/gemini-api/docs/video",
1616
+ provenance: "litellm-catalog",
1617
+ rates: [
1618
+ {
1619
+ unit: "per_second",
1620
+ amount: 0.4
1621
+ }
1622
+ ]
1623
+ },
1624
+ {
1625
+ model: "veo-3.1-generate-001",
1626
+ provider: "vertex",
1627
+ modality: "video",
1628
+ currency: "USD",
1629
+ asOf: "2026-07-27",
1630
+ source: "https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo",
1631
+ provenance: "litellm-catalog",
1632
+ rates: [
1633
+ {
1634
+ unit: "per_second",
1635
+ amount: 0.4
1636
+ }
1637
+ ]
1638
+ },
1639
+ {
1640
+ model: "tts-1",
1641
+ provider: "openai",
1642
+ modality: "speech",
1643
+ currency: "USD",
1644
+ asOf: "2026-07-27",
1645
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1646
+ provenance: "litellm-catalog",
1647
+ rates: [
1648
+ {
1649
+ unit: "per_character",
1650
+ amount: 15e-6
1651
+ }
1652
+ ],
1653
+ notes: "$15 per 1M characters."
1654
+ },
1655
+ {
1656
+ model: "tts-1-hd",
1657
+ provider: "openai",
1658
+ modality: "speech",
1659
+ currency: "USD",
1660
+ asOf: "2026-07-27",
1661
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1662
+ provenance: "litellm-catalog",
1663
+ rates: [
1664
+ {
1665
+ unit: "per_character",
1666
+ amount: 3e-5
1667
+ }
1668
+ ],
1669
+ notes: "$30 per 1M characters."
1670
+ },
1671
+ {
1672
+ model: "whisper-1",
1673
+ provider: "openai",
1674
+ modality: "transcription",
1675
+ currency: "USD",
1676
+ asOf: "2026-07-27",
1677
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1678
+ provenance: "litellm-catalog",
1679
+ rates: [
1680
+ {
1681
+ unit: "per_audio_minute",
1682
+ amount: 6e-3
1683
+ }
1684
+ ],
1685
+ notes: "litellm records $0.0001 per audio second; $0.006/minute is that rate x 60."
1686
+ }
1687
+ ]
1688
+ };
1689
+
1690
+ // src/core/router/pricing.ts
1691
+ var DATASET = pricing_data_default;
1692
+ function normalizeForPricing(id) {
1693
+ let s = id.trim().toLowerCase();
1694
+ s = s.replace(/^models\//, "");
1695
+ const slash = s.lastIndexOf("/");
1696
+ if (slash !== -1) s = s.slice(slash + 1);
1697
+ const tag = s.search(/[:@]/);
1698
+ if (tag !== -1) s = s.slice(0, tag);
1699
+ s = s.replace(/-latest$/, "");
1700
+ s = s.replace(/-preview(?:-\d{2}-\d{2})?$/, "");
1701
+ s = s.replace(/-\d{4}-\d{2}-\d{2}$/, "");
1702
+ s = s.replace(/-\d{8}$/, "");
1703
+ s = s.replace(/-preview$/, "");
1704
+ return s;
1705
+ }
1706
+ function indexKey(model, provider, region) {
1707
+ return `${provider}|${normalizeForPricing(model)}|${region ?? ""}`;
1708
+ }
1709
+ var INDEX = /* @__PURE__ */ new Map();
1710
+ for (const entry of DATASET.entries) {
1711
+ for (const name of [entry.model, ...entry.aliases ?? []]) {
1712
+ const withRegion = indexKey(name, entry.provider, entry.region);
1713
+ if (!INDEX.has(withRegion)) INDEX.set(withRegion, entry);
1714
+ const bare = indexKey(name, entry.provider);
1715
+ const existing = INDEX.get(bare);
1716
+ if (!existing || existing.region && existing.region !== "global" && entry.region === "global") {
1717
+ INDEX.set(bare, entry);
1718
+ }
1719
+ }
1720
+ }
1721
+ function findPricing(modelId, provider, opts = {}) {
1722
+ const ids = [modelId, opts.baseModelId].filter((x) => !!x);
1723
+ const providers = [provider, ...opts.fallbackProviders ?? []];
1724
+ for (const p of providers) {
1725
+ for (const id of ids) {
1726
+ const hit = (opts.region ? INDEX.get(indexKey(id, p, opts.region)) : void 0) ?? INDEX.get(indexKey(id, p));
1727
+ if (hit) return { entry: hit, matchedProvider: p, viaFallback: p !== provider };
1728
+ }
1729
+ }
1730
+ return null;
1731
+ }
1732
+ function tierFor(entry, inputTokens = 0) {
1733
+ const tiers = entry.tiers;
1734
+ if (!tiers?.length) return null;
1735
+ for (const t of tiers) {
1736
+ if (t.maxInputTokens === void 0 || inputTokens <= t.maxInputTokens) return t;
1737
+ }
1738
+ return tiers[tiers.length - 1] ?? null;
1739
+ }
1740
+ var PER_1M_TO_PER_1K = 1 / 1e3;
1741
+ function tokenRatesPer1k(entry, opts = {}) {
1742
+ const tier = tierFor(entry, opts.inputTokens ?? 0);
1743
+ if (!tier) return null;
1744
+ const mult = (opts.batch ? entry.batchMultiplier ?? 1 : 1) * PER_1M_TO_PER_1K;
1745
+ const out = {
1746
+ input: tier.inputPer1m * mult,
1747
+ output: tier.outputPer1m * mult
1748
+ };
1749
+ if (tier.cachedInputPer1m !== void 0) out.cachedInput = tier.cachedInputPer1m * mult;
1750
+ if (tier.cacheWritePer1m !== void 0) out.cacheWrite = tier.cacheWritePer1m * mult;
1751
+ if (tier.reasoningOutputPer1m !== void 0) out.reasoningOutput = tier.reasoningOutputPer1m * mult;
1752
+ return out;
1753
+ }
1754
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
1755
+ function isLoopbackOrPrivateHost(url) {
1756
+ if (!url) return false;
1757
+ try {
1758
+ const host = new URL(url).hostname.toLowerCase().replace(/^\[|\]$/g, "");
1759
+ if (LOOPBACK_HOSTS.has(host) || LOOPBACK_HOSTS.has(`[${host}]`)) return true;
1760
+ if (host.endsWith(".local")) return true;
1761
+ if (/^127\./.test(host)) return true;
1762
+ if (/^10\./.test(host)) return true;
1763
+ if (/^192\.168\./.test(host)) return true;
1764
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
1765
+ return false;
1766
+ } catch {
1767
+ return false;
1768
+ }
1769
+ }
1770
+ function isLocalEndpoint(cfg) {
1771
+ if (typeof cfg.local === "boolean") return cfg.local;
1772
+ if (cfg.type === "ollama") return true;
1773
+ if (cfg.type === "openai-compatible") return isLoopbackOrPrivateHost(cfg.baseUrl);
1774
+ return false;
1775
+ }
1776
+ var UNKNOWN = { input: 0, output: 0, unknown: true, free: false };
1777
+ var FREE = { input: 0, output: 0, unknown: false, free: true };
1778
+ function resolvePricing(model, opts = {}) {
1779
+ if (model.isLocal) return FREE;
1780
+ const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
1781
+ const match = findPricing(model.id, model.provider, {
1782
+ region: opts.region,
1783
+ baseModelId: model.baseModelId,
1784
+ fallbackProviders
1785
+ });
1786
+ if (!match) return UNKNOWN;
1787
+ const rates = tokenRatesPer1k(match.entry, { inputTokens: opts.inputTokens, batch: opts.batch });
1788
+ if (!rates) return UNKNOWN;
1789
+ return {
1790
+ input: rates.input,
1791
+ output: rates.output,
1792
+ unknown: false,
1793
+ free: false,
1794
+ entry: match.entry,
1795
+ ...match.viaFallback ? { estimatedFromProvider: match.matchedProvider } : {}
1796
+ };
1797
+ }
1798
+ function withResolvedPricing(model, opts = {}) {
1799
+ const p = resolvePricing(model, opts);
1800
+ return {
1801
+ ...model,
1802
+ inputCostPer1kTokens: p.input,
1803
+ outputCostPer1kTokens: p.output,
1804
+ ...p.unknown ? { pricingUnknown: true } : { pricingUnknown: false }
1805
+ };
1806
+ }
1807
+ function isPricingUnknown(model) {
1808
+ return model.pricingUnknown === true && !model.isLocal;
1809
+ }
1810
+ var BLENDED_COST_CEILING = 0.05;
1811
+ function blendedCostPer1k(model) {
1812
+ if (isPricingUnknown(model)) return BLENDED_COST_CEILING;
1813
+ return model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
1814
+ }
1815
+ var DISAGREEMENT_THRESHOLD = 0.02;
1816
+ function reconcilePrice(model, live) {
1817
+ const baseline = resolvePricing(model);
1818
+ const liveIsZero = !(live.input > 0) && !(live.output > 0);
1819
+ if (liveIsZero && !model.isLocal) {
1820
+ return { input: baseline.input, output: baseline.output, accepted: false };
1821
+ }
1822
+ if (baseline.unknown || !baseline.entry) {
1823
+ return { input: live.input, output: live.output, accepted: true };
1824
+ }
1825
+ const dsBlend = baseline.input + baseline.output * 2;
1826
+ const liveBlend = live.input + live.output * 2;
1827
+ const ratio = dsBlend > 0 ? liveBlend / dsBlend : Number.POSITIVE_INFINITY;
1828
+ const material = dsBlend > 0 && Math.abs(ratio - 1) > DISAGREEMENT_THRESHOLD;
1829
+ return {
1830
+ input: live.input,
1831
+ output: live.output,
1832
+ accepted: true,
1833
+ ...material ? {
1834
+ disagreement: {
1835
+ modelId: model.id,
1836
+ provider: model.provider,
1837
+ dataset: { input: baseline.input, output: baseline.output },
1838
+ live: { input: live.input, output: live.output },
1839
+ ratio: Math.round(ratio * 1e3) / 1e3,
1840
+ datasetAsOf: baseline.entry.asOf,
1841
+ datasetSource: baseline.entry.source
1842
+ }
1843
+ } : {}
1844
+ };
1845
+ }
1846
+
195
1847
  // src/constants.ts
196
- var CASCADE_VERSION = "0.48.1";
1848
+ var CASCADE_VERSION = "0.50.0";
197
1849
  var CASCADE_CONFIG_DIR = ".cascade";
198
1850
  var CASCADE_MD_FILE = "CASCADE.md";
199
1851
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -542,6 +2194,13 @@ var MODELS = {
542
2194
  for (const _m of Object.values(MODELS)) {
543
2195
  if (_m.supportsToolUse === void 0) _m.supportsToolUse = !_m.isLocal;
544
2196
  }
2197
+ for (const _m of Object.values(MODELS)) {
2198
+ const priced = resolvePricing(_m);
2199
+ if (priced.unknown || priced.free || priced.estimatedFromProvider) continue;
2200
+ _m.inputCostPer1kTokens = priced.input;
2201
+ _m.outputCostPer1kTokens = priced.output;
2202
+ _m.pricingUnknown = false;
2203
+ }
545
2204
  var T1_MODEL_PRIORITY = [
546
2205
  "gpt-5.5",
547
2206
  "gpt-5.4",
@@ -632,21 +2291,49 @@ var PROVIDER_DISPLAY_NAMES = {
632
2291
 
633
2292
  // src/utils/cost.ts
634
2293
  function resolveModelPricing(model) {
635
- let input = model.inputCostPer1kTokens;
636
- let output = model.outputCostPer1kTokens;
637
- if (input === 0 && output === 0 && model.id && !model.isLocal) {
2294
+ if (model.isLocal) return { input: 0, output: 0, unknown: false };
2295
+ if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
2296
+ return {
2297
+ input: model.inputCostPer1kTokens,
2298
+ output: model.outputCostPer1kTokens,
2299
+ unknown: false
2300
+ };
2301
+ }
2302
+ const fromDataset = resolvePricing(model);
2303
+ if (!fromDataset.unknown) {
2304
+ return {
2305
+ input: fromDataset.input,
2306
+ output: fromDataset.output,
2307
+ unknown: false,
2308
+ ...fromDataset.estimatedFromProvider ? { estimatedFromProvider: fromDataset.estimatedFromProvider } : {}
2309
+ };
2310
+ }
2311
+ if (model.id) {
638
2312
  const known = Object.values(MODELS).find((m) => m.id === model.id && !m.isLocal);
639
- if (known) {
640
- input = known.inputCostPer1kTokens;
641
- output = known.outputCostPer1kTokens;
2313
+ if (known && (known.inputCostPer1kTokens > 0 || known.outputCostPer1kTokens > 0)) {
2314
+ return {
2315
+ input: known.inputCostPer1kTokens,
2316
+ output: known.outputCostPer1kTokens,
2317
+ unknown: false
2318
+ };
642
2319
  }
643
2320
  }
644
- return { input, output };
2321
+ return { input: 0, output: 0, unknown: true };
645
2322
  }
646
2323
  function calculateCost(inputTokens, outputTokens, model) {
647
2324
  const { input, output } = resolveModelPricing(model);
648
2325
  return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
649
2326
  }
2327
+ function buildTokenUsage(inputTokens, outputTokens, model) {
2328
+ const { input, output, unknown } = resolveModelPricing(model);
2329
+ return {
2330
+ inputTokens,
2331
+ outputTokens,
2332
+ totalTokens: inputTokens + outputTokens,
2333
+ estimatedCostUsd: inputTokens / 1e3 * input + outputTokens / 1e3 * output,
2334
+ ...unknown ? { costUnknown: true } : {}
2335
+ };
2336
+ }
650
2337
 
651
2338
  // src/providers/base.ts
652
2339
  var BaseProvider = class {
@@ -666,12 +2353,7 @@ var BaseProvider = class {
666
2353
  return calculateCost(inputTokens, outputTokens, this.model);
667
2354
  }
668
2355
  makeUsage(inputTokens, outputTokens) {
669
- return {
670
- inputTokens,
671
- outputTokens,
672
- totalTokens: inputTokens + outputTokens,
673
- estimatedCostUsd: this.estimateCost(inputTokens, outputTokens)
674
- };
2356
+ return buildTokenUsage(inputTokens, outputTokens, this.model);
675
2357
  }
676
2358
  };
677
2359
 
@@ -840,7 +2522,7 @@ var AnthropicProvider = class extends BaseProvider {
840
2522
  return data.data.filter((m) => isChatModel(m.id)).map((m) => {
841
2523
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
842
2524
  if (known) return known;
843
- return {
2525
+ return withResolvedPricing({
844
2526
  id: m.id,
845
2527
  name: m.display_name || m.id,
846
2528
  provider: "anthropic",
@@ -851,7 +2533,7 @@ var AnthropicProvider = class extends BaseProvider {
851
2533
  maxOutputTokens: 8e3,
852
2534
  supportsStreaming: true,
853
2535
  isLocal: false
854
- };
2536
+ });
855
2537
  });
856
2538
  } catch {
857
2539
  return Object.values(MODELS).filter((m) => m.provider === "anthropic");
@@ -1055,7 +2737,7 @@ var OpenAIProvider = class extends BaseProvider {
1055
2737
  return response.data.filter((m) => isChatModel(m.id)).map((m) => {
1056
2738
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "openai");
1057
2739
  if (known) return known;
1058
- return {
2740
+ return withResolvedPricing({
1059
2741
  id: m.id,
1060
2742
  name: m.id,
1061
2743
  provider: "openai",
@@ -1066,7 +2748,7 @@ var OpenAIProvider = class extends BaseProvider {
1066
2748
  maxOutputTokens: 4e3,
1067
2749
  supportsStreaming: true,
1068
2750
  isLocal: false
1069
- };
2751
+ });
1070
2752
  });
1071
2753
  } catch {
1072
2754
  return Object.values(MODELS).filter((m) => m.provider === "openai");
@@ -1162,6 +2844,10 @@ function azureModelForDeployment(cfg) {
1162
2844
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
1163
2845
  const base = baseModelId ? MODELS[baseModelId] : void 0;
1164
2846
  if (base) {
2847
+ const azurePrice = resolvePricing(
2848
+ { id, provider: "azure", isLocal: false, baseModelId },
2849
+ { region: cfg.region }
2850
+ );
1165
2851
  return {
1166
2852
  ...base,
1167
2853
  id,
@@ -1170,7 +2856,12 @@ function azureModelForDeployment(cfg) {
1170
2856
  provider: "azure",
1171
2857
  baseModelId,
1172
2858
  // real identity for benchmark + live pricing
1173
- supportsToolUse: base.supportsToolUse ?? true
2859
+ supportsToolUse: base.supportsToolUse ?? true,
2860
+ ...azurePrice.unknown ? {} : {
2861
+ inputCostPer1kTokens: azurePrice.input,
2862
+ outputCostPer1kTokens: azurePrice.output,
2863
+ pricingUnknown: false
2864
+ }
1174
2865
  };
1175
2866
  }
1176
2867
  return {
@@ -1333,7 +3024,7 @@ var GeminiProvider = class extends BaseProvider {
1333
3024
  (km) => km.id === id && km.provider === "gemini"
1334
3025
  );
1335
3026
  if (known) return known;
1336
- return {
3027
+ return withResolvedPricing({
1337
3028
  id,
1338
3029
  name: m.displayName || id,
1339
3030
  provider: "gemini",
@@ -1344,7 +3035,7 @@ var GeminiProvider = class extends BaseProvider {
1344
3035
  maxOutputTokens: m.outputTokenLimit || 8e3,
1345
3036
  supportsStreaming: true,
1346
3037
  isLocal: false
1347
- };
3038
+ });
1348
3039
  });
1349
3040
  } catch {
1350
3041
  return Object.values(MODELS).filter((m) => m.provider === "gemini");
@@ -1691,9 +3382,10 @@ var OllamaProvider = class extends BaseProvider {
1691
3382
  return supportedKeywords.some((k) => name.includes(k)) && isChatModel(name);
1692
3383
  });
1693
3384
  const shows = await Promise.all(entries.map((m) => this.showModel(m.name)));
3385
+ const local = isLocalEndpoint({ ...this.config, type: "ollama" });
1694
3386
  return entries.map((m, i) => {
1695
3387
  const show = shows[i];
1696
- return {
3388
+ return withResolvedPricing({
1697
3389
  id: m.name,
1698
3390
  name: m.name,
1699
3391
  provider: "ollama",
@@ -1703,10 +3395,10 @@ var OllamaProvider = class extends BaseProvider {
1703
3395
  outputCostPer1kTokens: 0,
1704
3396
  maxOutputTokens: 4e3,
1705
3397
  supportsStreaming: true,
1706
- isLocal: true,
3398
+ isLocal: local,
1707
3399
  supportsToolUse: show?.tools ?? isToolCapable(m.name),
1708
3400
  minSizeB: this.parseSizeB(m.details?.parameter_size)
1709
- };
3401
+ });
1710
3402
  });
1711
3403
  } catch {
1712
3404
  return [];
@@ -1809,7 +3501,8 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
1809
3501
  }).filter((x) => typeof x === "string" && x.length > 0);
1810
3502
  if (ids.length === 0) return [this.model];
1811
3503
  const chatIds = ids.filter((id) => isChatModel(id));
1812
- return (chatIds.length ? chatIds : ids).map((id) => ({
3504
+ const local = isLocalEndpoint(this.config);
3505
+ return (chatIds.length ? chatIds : ids).map((id) => withResolvedPricing({
1813
3506
  id,
1814
3507
  name: id,
1815
3508
  provider: "openai-compatible",
@@ -1819,7 +3512,7 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
1819
3512
  outputCostPer1kTokens: 0,
1820
3513
  maxOutputTokens: 4e3,
1821
3514
  supportsStreaming: true,
1822
- isLocal: false
3515
+ isLocal: local
1823
3516
  }));
1824
3517
  }
1825
3518
  async isAvailable() {
@@ -2034,7 +3727,7 @@ var ModelSelector = class {
2034
3727
  else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
2035
3728
  }
2036
3729
  if (providerStr && this.availableProviders.has(providerStr)) {
2037
- const dynamicModel = {
3730
+ const dynamicModel = withResolvedPricing({
2038
3731
  id: actualId,
2039
3732
  name: actualId,
2040
3733
  provider: providerStr,
@@ -2045,7 +3738,7 @@ var ModelSelector = class {
2045
3738
  maxOutputTokens: 8e3,
2046
3739
  supportsStreaming: true,
2047
3740
  isLocal: providerStr === "ollama"
2048
- };
3741
+ });
2049
3742
  this.addDynamicModel(dynamicModel);
2050
3743
  return dynamicModel;
2051
3744
  }
@@ -2437,6 +4130,8 @@ var LiveDataProvider = class {
2437
4130
  snapshot = null;
2438
4131
  prices = /* @__PURE__ */ new Map();
2439
4132
  capabilities = /* @__PURE__ */ new Map();
4133
+ /** Dataset-vs-live price mismatches, deduped by `provider:modelId`. */
4134
+ disagreements = /* @__PURE__ */ new Map();
2440
4135
  source = "bundled";
2441
4136
  fetchedAt = 0;
2442
4137
  loaded = false;
@@ -2590,14 +4285,45 @@ var LiveDataProvider = class {
2590
4285
  /**
2591
4286
  * Returns a price-corrected copy of each model when live pricing is known,
2592
4287
  * leaving the original untouched (so the shared catalog is never mutated).
4288
+ *
4289
+ * Reconciliation policy (see reconcilePrice in pricing.ts): the bundled
4290
+ * dataset is the authoritative baseline, and a live quote overrides it
4291
+ * because it is fresher by construction — but the disagreement is RECORDED,
4292
+ * not swallowed. A dataset/live mismatch is the best signal available that
4293
+ * the committed prices have gone stale, so it must be visible rather than
4294
+ * silently resolved in either direction. A live quote of $0 for a non-local
4295
+ * model is rejected outright: that is the very failure mode this change
4296
+ * exists to prevent, and a marketplace's free-tier alias must never zero out
4297
+ * the price of the paid model Cascade is actually calling.
2593
4298
  */
2594
4299
  applyLivePricing(models) {
2595
4300
  return models.map((m) => {
2596
4301
  const p = this.getLivePrice(m.baseModelId ?? m.id);
2597
4302
  if (!p) return m;
2598
- return { ...m, inputCostPer1kTokens: p.input, outputCostPer1kTokens: p.output };
4303
+ const reconciled = reconcilePrice(m, p);
4304
+ if (reconciled.disagreement) this.recordDisagreement(reconciled.disagreement);
4305
+ if (!reconciled.accepted) return m;
4306
+ return {
4307
+ ...m,
4308
+ inputCostPer1kTokens: reconciled.input,
4309
+ outputCostPer1kTokens: reconciled.output,
4310
+ pricingUnknown: false
4311
+ };
2599
4312
  });
2600
4313
  }
4314
+ recordDisagreement(d) {
4315
+ const key = `${d.provider}:${d.modelId}`;
4316
+ if (this.disagreements.has(key)) return;
4317
+ this.disagreements.set(key, d);
4318
+ }
4319
+ /**
4320
+ * Dataset-vs-live price mismatches seen this session, newest-model-first
4321
+ * insertion order. Surfaced by `cascade models --verbose`; a non-empty list
4322
+ * means src/core/router/pricing-data.json is probably due a refresh.
4323
+ */
4324
+ getPriceDisagreements() {
4325
+ return [...this.disagreements.values()];
4326
+ }
2601
4327
  /** Current capability facts for a model id, or null when unknown. */
2602
4328
  getCapability(modelId) {
2603
4329
  return this.capabilities.get(normalizeModelId2(modelId)) ?? null;
@@ -2794,7 +4520,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
2794
4520
  tokensByTier: {},
2795
4521
  inputTokensByTier: {},
2796
4522
  outputTokensByTier: {},
2797
- costByFeature: {}
4523
+ costByFeature: {},
4524
+ untrackedCostCalls: 0,
4525
+ untrackedCostModels: []
2798
4526
  };
2799
4527
  tierModels = /* @__PURE__ */ new Map();
2800
4528
  /**
@@ -3144,7 +4872,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3144
4872
  `Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`
3145
4873
  );
3146
4874
  }
3147
- const correctedCost = calculateCost(
4875
+ const corrected = buildTokenUsage(
3148
4876
  result.usage.inputTokens,
3149
4877
  result.usage.outputTokens,
3150
4878
  model
@@ -3153,7 +4881,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3153
4881
  ...result,
3154
4882
  usage: {
3155
4883
  ...result.usage,
3156
- estimatedCostUsd: correctedCost
4884
+ estimatedCostUsd: corrected.estimatedCostUsd,
4885
+ ...corrected.costUnknown ? { costUnknown: true } : { costUnknown: void 0 }
3157
4886
  }
3158
4887
  };
3159
4888
  if (!result || typeof result.content !== "string" || !result.usage) {
@@ -3340,7 +5069,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3340
5069
  tokensByTier: { ...this.stats.tokensByTier },
3341
5070
  inputTokensByTier: { ...this.stats.inputTokensByTier },
3342
5071
  outputTokensByTier: { ...this.stats.outputTokensByTier },
3343
- costByFeature: { ...this.stats.costByFeature }
5072
+ costByFeature: { ...this.stats.costByFeature },
5073
+ untrackedCostCalls: this.stats.untrackedCostCalls,
5074
+ untrackedCostModels: [...this.stats.untrackedCostModels]
3344
5075
  };
3345
5076
  }
3346
5077
  /**
@@ -3391,7 +5122,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3391
5122
  tokensByTier: {},
3392
5123
  inputTokensByTier: {},
3393
5124
  outputTokensByTier: {},
3394
- costByFeature: {}
5125
+ costByFeature: {},
5126
+ untrackedCostCalls: 0,
5127
+ untrackedCostModels: []
3395
5128
  };
3396
5129
  this.sessionCostUsd = 0;
3397
5130
  this.budgetState = "ok";
@@ -3576,6 +5309,18 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter {
3576
5309
  if (featureTag) {
3577
5310
  this.stats.costByFeature[featureTag] = (this.stats.costByFeature[featureTag] ?? 0) + usage.estimatedCostUsd;
3578
5311
  }
5312
+ if (usage.costUnknown) {
5313
+ this.stats.untrackedCostCalls += 1;
5314
+ if (!this.stats.untrackedCostModels.includes(model.id)) {
5315
+ this.stats.untrackedCostModels.push(model.id);
5316
+ const capped = this.config?.budget?.maxCostPerRunUsd != null || this.config?.budget?.sessionBudgetUsd != null;
5317
+ this.emit("cost:untracked", {
5318
+ modelId: model.id,
5319
+ provider: model.provider,
5320
+ 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.`
5321
+ });
5322
+ }
5323
+ }
3579
5324
  this.runTokens += usage.totalTokens;
3580
5325
  this.runCostUsd += usage.estimatedCostUsd;
3581
5326
  this.updateBudgetState();
@@ -3716,6 +5461,14 @@ var BaseTier = class extends EventEmitter {
3716
5461
  this.emit("tier:status", event);
3717
5462
  }
3718
5463
  /** Record the model serving this tier; future status events carry it. */
5464
+ /**
5465
+ * The model actually serving this tier ("provider:id"), once one has been
5466
+ * chosen. The run breaker reads it to attribute a failure to a model rather
5467
+ * than to the subtask that happened to hit it.
5468
+ */
5469
+ getServingModel() {
5470
+ return this.servingModel;
5471
+ }
3719
5472
  setServingModel(model) {
3720
5473
  this.servingModel = model || void 0;
3721
5474
  }
@@ -5451,6 +7204,7 @@ var T2Manager = class extends BaseTier {
5451
7204
  escalations = [];
5452
7205
  peerSyncBuffer = [];
5453
7206
  store;
7207
+ runBreaker;
5454
7208
  t3PeerBus = new PeerBus();
5455
7209
  // ← T3↔T3 bus (local to this T2)
5456
7210
  t2PeerBus;
@@ -5481,6 +7235,15 @@ var T2Manager = class extends BaseTier {
5481
7235
  this.router = router;
5482
7236
  this.toolRegistry = toolRegistry;
5483
7237
  }
7238
+ /**
7239
+ * Share the run-wide circuit breaker. Every T2 in a run gets the SAME
7240
+ * instance: three sections each failing once against a dead model is the
7241
+ * same fact as one section failing three times, and a per-section breaker
7242
+ * would never see it.
7243
+ */
7244
+ setRunBreaker(breaker) {
7245
+ this.runBreaker = breaker;
7246
+ }
5484
7247
  setStore(store) {
5485
7248
  this.store = store;
5486
7249
  }
@@ -5604,7 +7367,8 @@ Guidance (must be followed): ${decision.note}`
5604
7367
  const overallStatus = this.determineStatus(t3Results);
5605
7368
  const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
5606
7369
  this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
5607
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
7370
+ 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";
7371
+ this.sendStatusUpdate({ progressPct: 100, currentAction: sectionAction, status: "IN_PROGRESS", output: summary });
5608
7372
  const result = {
5609
7373
  sectionId: assignment.sectionId,
5610
7374
  sectionTitle: assignment.sectionTitle,
@@ -5774,6 +7538,24 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
5774
7538
  const reinforceCfg = this.router.getReinforcementsConfig?.() ?? { enabled: false, maxPerSection: 4 };
5775
7539
  let reinforcementsAdded = 0;
5776
7540
  while (remaining.size > 0) {
7541
+ if (this.runBreaker?.isOpen()) {
7542
+ const why = this.runBreaker.skipMessage();
7543
+ this.log(`Run breaker open \u2014 skipping ${remaining.size} remaining subtask(s)`);
7544
+ for (const id of remaining) {
7545
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
7546
+ resultMap.set(id, {
7547
+ subtaskId: id,
7548
+ status: "FAILED",
7549
+ output: why,
7550
+ testResults: { checksRun: [], passed: [], failed: [] },
7551
+ issues: [why],
7552
+ peerSyncsUsed: [],
7553
+ correctionAttempts: 0
7554
+ });
7555
+ }
7556
+ remaining.clear();
7557
+ break;
7558
+ }
5777
7559
  const runnableIds = [...remaining].filter((id) => (inDegree.get(id) ?? 0) === 0);
5778
7560
  if (runnableIds.length === 0) {
5779
7561
  const fallbackId = [...remaining].sort(
@@ -5876,8 +7658,32 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
5876
7658
  remaining.delete(id);
5877
7659
  const r = waveResults[i];
5878
7660
  if (r.status === "rejected") {
5879
- this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
5880
7661
  const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
7662
+ const failedModel = workerMap.get(id)?.getServingModel();
7663
+ const tripped = this.runBreaker?.record(r.reason, failedModel);
7664
+ if (tripped) {
7665
+ this.log(`Run breaker opened: ${tripped.failures} consecutive ${tripped.kind} failures on ${tripped.modelId} \u2014 stopping`);
7666
+ this.emit("run:breaker", tripped);
7667
+ }
7668
+ if (this.runBreaker?.isOpen()) {
7669
+ const why = this.runBreaker.skipMessage();
7670
+ this.log(`T3 worker ${id} failed and the run breaker is open \u2014 not retrying`);
7671
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
7672
+ resultMap.set(id, {
7673
+ subtaskId: id,
7674
+ status: "FAILED",
7675
+ output: why,
7676
+ testResults: { checksRun: [], passed: [], failed: [] },
7677
+ issues: [why],
7678
+ peerSyncsUsed: [],
7679
+ correctionAttempts: 0
7680
+ });
7681
+ for (const dependent of adj.get(id) ?? []) {
7682
+ inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
7683
+ }
7684
+ continue;
7685
+ }
7686
+ this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
5881
7687
  try {
5882
7688
  const retried = await this.retryT3(assignment, taskId);
5883
7689
  resultMap.set(id, retried);
@@ -6209,6 +8015,7 @@ var T1Administrator = class extends BaseTier {
6209
8015
  t2Managers = /* @__PURE__ */ new Map();
6210
8016
  escalations = [];
6211
8017
  store;
8018
+ runBreaker;
6212
8019
  t2PeerBus = new PeerBus();
6213
8020
  permissionEscalator;
6214
8021
  toolCreator;
@@ -6223,6 +8030,10 @@ var T1Administrator = class extends BaseTier {
6223
8030
  this.toolRegistry = toolRegistry;
6224
8031
  this.config = config;
6225
8032
  }
8033
+ /** Share the run-wide circuit breaker with every section manager. */
8034
+ setRunBreaker(breaker) {
8035
+ this.runBreaker = breaker;
8036
+ }
6226
8037
  setStore(store) {
6227
8038
  this.store = store;
6228
8039
  }
@@ -6585,6 +8396,7 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
6585
8396
  };
6586
8397
  const managers = sections.map((section) => {
6587
8398
  const manager = new T2Manager(this.router, this.toolRegistry, this.id);
8399
+ if (this.runBreaker) manager.setRunBreaker(this.runBreaker);
6588
8400
  manager.setHierarchyContext(`You are a T2 Manager for the section "${section.sectionTitle}". You are part of a COMPLEX task overseen by T1 Administrator.`);
6589
8401
  if (this.store) {
6590
8402
  manager.setStore(this.store);
@@ -6870,6 +8682,158 @@ Reply with exactly one word: YES, NO, or UNSURE.
6870
8682
  }
6871
8683
  };
6872
8684
 
8685
+ // src/core/router/provider-errors.ts
8686
+ var SYSTEMIC = /* @__PURE__ */ new Set([
8687
+ "rate_limit",
8688
+ "quota_exhausted",
8689
+ "auth",
8690
+ "model_unavailable"
8691
+ ]);
8692
+ function isSystemicKind(kind) {
8693
+ return SYSTEMIC.has(kind);
8694
+ }
8695
+ function statusOf(err) {
8696
+ if (!err || typeof err !== "object") return void 0;
8697
+ const e = err;
8698
+ for (const key of ["status", "statusCode", "code"]) {
8699
+ const v = e[key];
8700
+ if (typeof v === "number" && v >= 100 && v < 600) return v;
8701
+ }
8702
+ const resp = e["response"];
8703
+ if (resp && typeof resp["status"] === "number") return resp["status"];
8704
+ const inner = e["error"];
8705
+ if (inner && typeof inner["code"] === "number") return inner["code"];
8706
+ return void 0;
8707
+ }
8708
+ function messageOf(err) {
8709
+ if (err instanceof Error) return err.message;
8710
+ if (typeof err === "string") return err;
8711
+ if (err && typeof err === "object") {
8712
+ const e = err;
8713
+ if (typeof e["message"] === "string") return e["message"];
8714
+ const inner = e["error"];
8715
+ if (inner && typeof inner["message"] === "string") return inner["message"];
8716
+ try {
8717
+ return JSON.stringify(err);
8718
+ } catch {
8719
+ }
8720
+ }
8721
+ return String(err);
8722
+ }
8723
+ function classifyProviderError(err) {
8724
+ const raw = messageOf(err).trim();
8725
+ const status = statusOf(err);
8726
+ const m = raw.toLowerCase();
8727
+ const kind = (() => {
8728
+ if (status === 429) {
8729
+ return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
8730
+ }
8731
+ if (status === 401 || status === 403) return "auth";
8732
+ if (status === 404) return "model_unavailable";
8733
+ if (/rate limit|too many requests|resource_exhausted/.test(m)) {
8734
+ return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
8735
+ }
8736
+ if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
8737
+ if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
8738
+ if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
8739
+ return "model_unavailable";
8740
+ }
8741
+ if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
8742
+ if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
8743
+ if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
8744
+ return "unknown";
8745
+ })();
8746
+ return { kind, systemic: isSystemicKind(kind), status, raw };
8747
+ }
8748
+ function describeProviderError(c, modelId) {
8749
+ const on = ` on ${modelId}` ;
8750
+ const detail = c.raw ? ` Provider said: ${c.raw}` : "";
8751
+ switch (c.kind) {
8752
+ case "rate_limit":
8753
+ 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}`;
8754
+ case "quota_exhausted":
8755
+ 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}`;
8756
+ case "auth":
8757
+ 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}`;
8758
+ case "model_unavailable":
8759
+ 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}`;
8760
+ case "context_length":
8761
+ return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
8762
+ case "content_filter":
8763
+ return `The provider's safety filter refused this request${on}.${detail}`;
8764
+ case "network":
8765
+ return `Network error reaching the provider${on}. This is usually transient.${detail}`;
8766
+ default:
8767
+ return `The model call failed${on}.${detail}`;
8768
+ }
8769
+ }
8770
+
8771
+ // src/core/run-breaker.ts
8772
+ var DEFAULT_FAILURE_THRESHOLD = 3;
8773
+ var RunBreaker = class {
8774
+ threshold;
8775
+ /** modelId → consecutive systemic failures of the SAME kind. */
8776
+ counts = /* @__PURE__ */ new Map();
8777
+ trip = null;
8778
+ constructor(threshold = DEFAULT_FAILURE_THRESHOLD) {
8779
+ this.threshold = Math.max(1, threshold);
8780
+ }
8781
+ /**
8782
+ * Record a failed model call. Returns the trip if this failure is the one
8783
+ * that opened the breaker, otherwise null.
8784
+ *
8785
+ * A non-systemic failure RESETS the model's counter: it is evidence the model
8786
+ * is reachable and answering, which is exactly what a systemic streak claims
8787
+ * it is not.
8788
+ */
8789
+ record(err, modelId) {
8790
+ if (this.trip) return null;
8791
+ const id = modelId?.trim() || "unknown-model";
8792
+ const c = classifyProviderError(err);
8793
+ if (!c.systemic) {
8794
+ this.counts.delete(id);
8795
+ return null;
8796
+ }
8797
+ const prev = this.counts.get(id);
8798
+ const n = prev && prev.kind === c.kind ? prev.n + 1 : 1;
8799
+ this.counts.set(id, { kind: c.kind, n, last: c });
8800
+ if (n >= this.threshold) {
8801
+ this.trip = {
8802
+ modelId: id,
8803
+ kind: c.kind,
8804
+ failures: n,
8805
+ message: describeProviderError(c, id)
8806
+ };
8807
+ return this.trip;
8808
+ }
8809
+ return null;
8810
+ }
8811
+ /** True once the run should stop launching work that would fail identically. */
8812
+ isOpen() {
8813
+ return this.trip !== null;
8814
+ }
8815
+ /** Why the breaker opened, or null while it is closed. */
8816
+ reason() {
8817
+ return this.trip;
8818
+ }
8819
+ /**
8820
+ * One line for a worker/section that never ran because the breaker was
8821
+ * already open. Says the work was skipped deliberately — not that it failed
8822
+ * on its own merits, which is what an empty FAILED status would imply.
8823
+ */
8824
+ skipMessage() {
8825
+ const t = this.trip;
8826
+ if (!t) return "Skipped.";
8827
+ return `Skipped \u2014 the run stopped after ${t.failures} consecutive failures on ${t.modelId}. ${t.message}`;
8828
+ }
8829
+ /** Test/diagnostic view of the per-model streaks. */
8830
+ streaks() {
8831
+ const out = {};
8832
+ for (const [k, v] of this.counts) out[k] = { kind: v.kind, n: v.n };
8833
+ return out;
8834
+ }
8835
+ };
8836
+
6873
8837
  // src/tools/base.ts
6874
8838
  var BaseTool = class {
6875
8839
  workspaceRoot = process.cwd();
@@ -9083,7 +11047,18 @@ var ProviderConfigSchema = z.object({
9083
11047
  apiVersion: z.string().optional(),
9084
11048
  model: z.string().optional(),
9085
11049
  authToken: z.string().optional(),
9086
- credentialSource: z.string().optional()
11050
+ credentialSource: z.string().optional(),
11051
+ /**
11052
+ * "This endpoint is self-hosted, so inference genuinely costs $0."
11053
+ * Only meaningful for `ollama` and `openai-compatible`. Unset ⇒ inferred:
11054
+ * ollama is local, openai-compatible is local when its baseUrl is
11055
+ * loopback/LAN. Set it explicitly to override — e.g. `false` for an Ollama
11056
+ * instance you rent, `true` for a llama.cpp box on your own LAN reached by
11057
+ * hostname.
11058
+ */
11059
+ local: z.boolean().optional(),
11060
+ /** Region for providers whose price varies by region (Azure: global | us | eu | …). */
11061
+ region: z.string().optional()
9087
11062
  });
9088
11063
  var ModelOverridesSchema = z.object({
9089
11064
  t1: z.string().optional(),
@@ -9669,8 +11644,8 @@ var TaskAnalyzer = class {
9669
11644
  }
9670
11645
  costEfficiency(model, complexity) {
9671
11646
  if (this.tracker) return this.tracker.costEfficiencyScore(model, complexity);
9672
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
9673
- const normalised = Math.min(1, blended / 0.05);
11647
+ const blended = blendedCostPer1k(model);
11648
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
9674
11649
  const complexityWeight = (6 - complexity) / 5;
9675
11650
  return Math.max(0.1, 1 - normalised * complexityWeight);
9676
11651
  }
@@ -9805,8 +11780,8 @@ var ModelPerformanceTracker = class {
9805
11780
  * normalised over $0.05 blended as the "expensive" ceiling.
9806
11781
  */
9807
11782
  costEfficiencyScore(model, complexity) {
9808
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
9809
- const normalised = Math.min(1, blended / 0.05);
11783
+ const blended = blendedCostPer1k(model);
11784
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
9810
11785
  const complexityWeight = (6 - complexity) / 5;
9811
11786
  return Math.max(0.1, 1 - normalised * complexityWeight);
9812
11787
  }
@@ -12431,6 +14406,7 @@ ${prompt}` : prompt;
12431
14406
  if (toolCreator) toolCreator.setPermissionEscalator(escalator);
12432
14407
  const rootPrompt = buildContextualPrompt(options.prompt, options.conversationHistory);
12433
14408
  let finalOutput = "";
14409
+ const runBreaker = new RunBreaker();
12434
14410
  let t2Results = [];
12435
14411
  let runError = null;
12436
14412
  let identityPrompt = "";
@@ -12499,6 +14475,7 @@ ${prompt}` : prompt;
12499
14475
  this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
12500
14476
  } else if (complexity === "Moderate") {
12501
14477
  const t2 = new T2Manager(this.router, this.toolRegistry, "root");
14478
+ t2.setRunBreaker(runBreaker);
12502
14479
  t2.setPresenter(true);
12503
14480
  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.");
12504
14481
  if (identityPrompt) {
@@ -12559,6 +14536,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12559
14536
  }
12560
14537
  } else {
12561
14538
  const t1 = new T1Administrator(this.router, this.toolRegistry, this.config);
14539
+ t1.setRunBreaker(runBreaker);
12562
14540
  t1.setPresenter(true);
12563
14541
  t1.setHierarchyContext("You are the top-level Administrator. You are responsible for the overall plan and supervising multiple T2 Managers.");
12564
14542
  if (identityPrompt) {
@@ -12583,6 +14561,20 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12583
14561
  finalOutput = result.output;
12584
14562
  t2Results = result.t2Results;
12585
14563
  }
14564
+ const trip = runBreaker.reason();
14565
+ if (trip) {
14566
+ finalOutput = [
14567
+ `\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
14568
+ "",
14569
+ trip.message,
14570
+ "",
14571
+ `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.`,
14572
+ finalOutput ? `
14573
+ ---
14574
+
14575
+ ${finalOutput}` : ""
14576
+ ].join("\n").trimEnd();
14577
+ }
12586
14578
  } catch (err) {
12587
14579
  if (err instanceof CascadeCancelledError || err instanceof Error && err.name === "AbortError" || options.signal?.aborted) {
12588
14580
  this.emit("run:cancelled", {
@@ -12649,7 +14641,11 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12649
14641
  inputTokens: 0,
12650
14642
  outputTokens: 0,
12651
14643
  totalTokens: stats.totalTokens,
12652
- estimatedCostUsd: stats.totalCostUsd
14644
+ estimatedCostUsd: stats.totalCostUsd,
14645
+ // At least one call ran on a model with no known price, so its spend is
14646
+ // missing from the total above. Say so rather than letting the caller
14647
+ // present an undercount as the final figure.
14648
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12653
14649
  },
12654
14650
  t2Results,
12655
14651
  durationMs,
@@ -12719,7 +14715,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12719
14715
  inputTokens: 0,
12720
14716
  outputTokens: 0,
12721
14717
  totalTokens: stats2.totalTokens,
12722
- estimatedCostUsd: stats2.totalCostUsd
14718
+ estimatedCostUsd: stats2.totalCostUsd,
14719
+ ...stats2.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12723
14720
  },
12724
14721
  t2Results: [],
12725
14722
  durationMs: Date.now() - startMs,
@@ -12743,7 +14740,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12743
14740
  inputTokens: result.usage?.inputTokens ?? 0,
12744
14741
  outputTokens: result.usage?.outputTokens ?? 0,
12745
14742
  totalTokens: stats.totalTokens,
12746
- estimatedCostUsd: stats.totalCostUsd
14743
+ estimatedCostUsd: stats.totalCostUsd,
14744
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12747
14745
  },
12748
14746
  t2Results: [],
12749
14747
  durationMs: Date.now() - startMs,
@@ -14527,7 +16525,10 @@ var DashboardServer = class {
14527
16525
  this.socket.broadcast("cost:update", {
14528
16526
  sessionId,
14529
16527
  totalTokens: result.usage.totalTokens,
14530
- totalCostUsd: result.usage.estimatedCostUsd
16528
+ totalCostUsd: result.usage.estimatedCostUsd,
16529
+ // True when a model with no known price was used: the total above is
16530
+ // an undercount, and the UI must not present it as the full spend.
16531
+ costUnknown: result.usage.costUnknown === true
14531
16532
  });
14532
16533
  this.throttledBroadcast("workspace");
14533
16534
  } catch (err) {
@@ -14840,7 +16841,8 @@ var DashboardServer = class {
14840
16841
  this.socket.broadcast("cost:update", {
14841
16842
  sessionId,
14842
16843
  totalTokens: result.usage.totalTokens,
14843
- totalCostUsd: result.usage.estimatedCostUsd
16844
+ totalCostUsd: result.usage.estimatedCostUsd,
16845
+ costUnknown: result.usage.costUnknown === true
14844
16846
  });
14845
16847
  this.throttledBroadcast("workspace");
14846
16848
  } catch (err) {
@@ -15382,7 +17384,8 @@ ${prompt}`;
15382
17384
  this.socket.broadcast("cost:update", {
15383
17385
  sessionId,
15384
17386
  totalTokens: result.usage.totalTokens,
15385
- totalCostUsd: result.usage.estimatedCostUsd
17387
+ totalCostUsd: result.usage.estimatedCostUsd,
17388
+ costUnknown: result.usage.costUnknown === true
15386
17389
  });
15387
17390
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
15388
17391
  this.throttledBroadcast("workspace");