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.cjs CHANGED
@@ -238,8 +238,1660 @@ var init_audit_logger = __esm({
238
238
  }
239
239
  });
240
240
 
241
+ // src/core/router/pricing-data.json
242
+ var pricing_data_default = {
243
+ entries: [
244
+ {
245
+ model: "claude-opus-5",
246
+ provider: "anthropic",
247
+ modality: "text",
248
+ currency: "USD",
249
+ asOf: "2026-07-27",
250
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
251
+ provenance: "vendor-pricing-page",
252
+ tiers: [
253
+ {
254
+ inputPer1m: 5,
255
+ outputPer1m: 25,
256
+ cachedInputPer1m: 0.5,
257
+ cacheWritePer1m: 6.25,
258
+ reasoningOutputPer1m: 25
259
+ }
260
+ ],
261
+ batchMultiplier: 0.5,
262
+ contextWindow: 1e6
263
+ },
264
+ {
265
+ model: "claude-opus-4-8",
266
+ provider: "anthropic",
267
+ modality: "text",
268
+ currency: "USD",
269
+ asOf: "2026-07-27",
270
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
271
+ provenance: "vendor-pricing-page",
272
+ tiers: [
273
+ {
274
+ inputPer1m: 5,
275
+ outputPer1m: 25,
276
+ cachedInputPer1m: 0.5,
277
+ cacheWritePer1m: 6.25,
278
+ reasoningOutputPer1m: 25
279
+ }
280
+ ],
281
+ batchMultiplier: 0.5,
282
+ contextWindow: 1e6
283
+ },
284
+ {
285
+ model: "claude-opus-4-7",
286
+ provider: "anthropic",
287
+ modality: "text",
288
+ currency: "USD",
289
+ asOf: "2026-07-27",
290
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
291
+ provenance: "vendor-pricing-page",
292
+ tiers: [
293
+ {
294
+ inputPer1m: 5,
295
+ outputPer1m: 25,
296
+ cachedInputPer1m: 0.5,
297
+ cacheWritePer1m: 6.25,
298
+ reasoningOutputPer1m: 25
299
+ }
300
+ ],
301
+ batchMultiplier: 0.5,
302
+ contextWindow: 1e6
303
+ },
304
+ {
305
+ model: "claude-opus-4-6",
306
+ provider: "anthropic",
307
+ modality: "text",
308
+ currency: "USD",
309
+ asOf: "2026-07-27",
310
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
311
+ provenance: "vendor-pricing-page",
312
+ tiers: [
313
+ {
314
+ inputPer1m: 5,
315
+ outputPer1m: 25,
316
+ cachedInputPer1m: 0.5,
317
+ cacheWritePer1m: 6.25,
318
+ reasoningOutputPer1m: 25
319
+ }
320
+ ],
321
+ batchMultiplier: 0.5,
322
+ contextWindow: 1e6
323
+ },
324
+ {
325
+ model: "claude-opus-4-5",
326
+ provider: "anthropic",
327
+ modality: "text",
328
+ currency: "USD",
329
+ asOf: "2026-07-27",
330
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
331
+ provenance: "vendor-pricing-page",
332
+ tiers: [
333
+ {
334
+ inputPer1m: 5,
335
+ outputPer1m: 25,
336
+ cachedInputPer1m: 0.5,
337
+ cacheWritePer1m: 6.25,
338
+ reasoningOutputPer1m: 25
339
+ }
340
+ ],
341
+ batchMultiplier: 0.5,
342
+ contextWindow: 2e5
343
+ },
344
+ {
345
+ model: "claude-opus-4-1",
346
+ provider: "anthropic",
347
+ modality: "text",
348
+ currency: "USD",
349
+ asOf: "2026-07-27",
350
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
351
+ provenance: "vendor-pricing-page",
352
+ tiers: [
353
+ {
354
+ inputPer1m: 15,
355
+ outputPer1m: 75,
356
+ cachedInputPer1m: 1.5,
357
+ cacheWritePer1m: 18.75,
358
+ reasoningOutputPer1m: 75
359
+ }
360
+ ],
361
+ batchMultiplier: 0.5,
362
+ contextWindow: 2e5,
363
+ notes: "Deprecated by Anthropic; priced for historical/cost-reporting accuracy."
364
+ },
365
+ {
366
+ model: "claude-opus-4",
367
+ provider: "anthropic",
368
+ modality: "text",
369
+ currency: "USD",
370
+ asOf: "2026-07-27",
371
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
372
+ provenance: "vendor-pricing-page",
373
+ tiers: [
374
+ {
375
+ inputPer1m: 15,
376
+ outputPer1m: 75,
377
+ cachedInputPer1m: 1.5,
378
+ cacheWritePer1m: 18.75,
379
+ reasoningOutputPer1m: 75
380
+ }
381
+ ],
382
+ batchMultiplier: 0.5,
383
+ contextWindow: 2e5,
384
+ notes: "Retired except on Google Cloud; priced for historical/cost-reporting accuracy."
385
+ },
386
+ {
387
+ model: "claude-sonnet-5",
388
+ provider: "anthropic",
389
+ modality: "text",
390
+ currency: "USD",
391
+ asOf: "2026-07-27",
392
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
393
+ provenance: "vendor-pricing-page",
394
+ tiers: [
395
+ {
396
+ inputPer1m: 2,
397
+ outputPer1m: 10,
398
+ cachedInputPer1m: 0.2,
399
+ cacheWritePer1m: 2.5,
400
+ reasoningOutputPer1m: 10
401
+ }
402
+ ],
403
+ batchMultiplier: 0.5,
404
+ contextWindow: 1e6,
405
+ 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."
406
+ },
407
+ {
408
+ model: "claude-sonnet-4-6",
409
+ provider: "anthropic",
410
+ modality: "text",
411
+ currency: "USD",
412
+ asOf: "2026-07-27",
413
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
414
+ provenance: "vendor-pricing-page",
415
+ tiers: [
416
+ {
417
+ inputPer1m: 3,
418
+ outputPer1m: 15,
419
+ cachedInputPer1m: 0.3,
420
+ cacheWritePer1m: 3.75,
421
+ reasoningOutputPer1m: 15
422
+ }
423
+ ],
424
+ batchMultiplier: 0.5,
425
+ contextWindow: 1e6
426
+ },
427
+ {
428
+ model: "claude-sonnet-4-5",
429
+ provider: "anthropic",
430
+ modality: "text",
431
+ currency: "USD",
432
+ asOf: "2026-07-27",
433
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
434
+ provenance: "vendor-pricing-page",
435
+ tiers: [
436
+ {
437
+ maxInputTokens: 2e5,
438
+ inputPer1m: 3,
439
+ outputPer1m: 15,
440
+ cachedInputPer1m: 0.3,
441
+ cacheWritePer1m: 3.75,
442
+ reasoningOutputPer1m: 15
443
+ },
444
+ {
445
+ inputPer1m: 6,
446
+ outputPer1m: 22.5,
447
+ cachedInputPer1m: 0.6,
448
+ cacheWritePer1m: 7.5,
449
+ reasoningOutputPer1m: 22.5
450
+ }
451
+ ],
452
+ batchMultiplier: 0.5,
453
+ contextWindow: 2e5,
454
+ 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)."
455
+ },
456
+ {
457
+ model: "claude-sonnet-4",
458
+ provider: "anthropic",
459
+ modality: "text",
460
+ currency: "USD",
461
+ asOf: "2026-07-27",
462
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
463
+ provenance: "vendor-pricing-page",
464
+ tiers: [
465
+ {
466
+ inputPer1m: 3,
467
+ outputPer1m: 15,
468
+ cachedInputPer1m: 0.3,
469
+ cacheWritePer1m: 3.75,
470
+ reasoningOutputPer1m: 15
471
+ }
472
+ ],
473
+ batchMultiplier: 0.5,
474
+ contextWindow: 2e5
475
+ },
476
+ {
477
+ model: "claude-haiku-4-5",
478
+ provider: "anthropic",
479
+ modality: "text",
480
+ currency: "USD",
481
+ asOf: "2026-07-27",
482
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
483
+ provenance: "vendor-pricing-page",
484
+ tiers: [
485
+ {
486
+ inputPer1m: 1,
487
+ outputPer1m: 5,
488
+ cachedInputPer1m: 0.1,
489
+ cacheWritePer1m: 1.25,
490
+ reasoningOutputPer1m: 5
491
+ }
492
+ ],
493
+ batchMultiplier: 0.5,
494
+ contextWindow: 2e5
495
+ },
496
+ {
497
+ model: "claude-haiku-3-5",
498
+ provider: "anthropic",
499
+ modality: "text",
500
+ currency: "USD",
501
+ asOf: "2026-07-27",
502
+ source: "https://platform.claude.com/docs/en/about-claude/pricing",
503
+ provenance: "vendor-pricing-page",
504
+ tiers: [
505
+ {
506
+ inputPer1m: 0.8,
507
+ outputPer1m: 4,
508
+ cachedInputPer1m: 0.08,
509
+ cacheWritePer1m: 1,
510
+ reasoningOutputPer1m: 4
511
+ }
512
+ ],
513
+ batchMultiplier: 0.5,
514
+ contextWindow: 2e5
515
+ },
516
+ {
517
+ model: "gpt-5.6-sol",
518
+ provider: "openai",
519
+ aliases: [
520
+ "gpt-5.6"
521
+ ],
522
+ modality: "text",
523
+ currency: "USD",
524
+ asOf: "2026-07-27",
525
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
526
+ provenance: "litellm-catalog",
527
+ tiers: [
528
+ {
529
+ maxInputTokens: 272e3,
530
+ inputPer1m: 5,
531
+ outputPer1m: 30,
532
+ cachedInputPer1m: 0.5,
533
+ cacheWritePer1m: 6.25
534
+ },
535
+ {
536
+ inputPer1m: 10,
537
+ outputPer1m: 45,
538
+ cachedInputPer1m: 1,
539
+ cacheWritePer1m: 12.5
540
+ }
541
+ ],
542
+ batchMultiplier: 0.5,
543
+ contextWindow: 105e4
544
+ },
545
+ {
546
+ model: "gpt-5.6-terra",
547
+ provider: "openai",
548
+ modality: "text",
549
+ currency: "USD",
550
+ asOf: "2026-07-27",
551
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
552
+ provenance: "litellm-catalog",
553
+ tiers: [
554
+ {
555
+ maxInputTokens: 272e3,
556
+ inputPer1m: 2.5,
557
+ outputPer1m: 15,
558
+ cachedInputPer1m: 0.25,
559
+ cacheWritePer1m: 3.125
560
+ },
561
+ {
562
+ inputPer1m: 5,
563
+ outputPer1m: 22.5,
564
+ cachedInputPer1m: 0.5,
565
+ cacheWritePer1m: 6.25
566
+ }
567
+ ],
568
+ batchMultiplier: 0.5,
569
+ contextWindow: 105e4
570
+ },
571
+ {
572
+ model: "gpt-5.6-luna",
573
+ provider: "openai",
574
+ modality: "text",
575
+ currency: "USD",
576
+ asOf: "2026-07-27",
577
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
578
+ provenance: "litellm-catalog",
579
+ tiers: [
580
+ {
581
+ maxInputTokens: 272e3,
582
+ inputPer1m: 1,
583
+ outputPer1m: 6,
584
+ cachedInputPer1m: 0.1,
585
+ cacheWritePer1m: 1.25
586
+ },
587
+ {
588
+ inputPer1m: 2,
589
+ outputPer1m: 9,
590
+ cachedInputPer1m: 0.2,
591
+ cacheWritePer1m: 2.5
592
+ }
593
+ ],
594
+ batchMultiplier: 0.5,
595
+ contextWindow: 105e4
596
+ },
597
+ {
598
+ model: "gpt-5.5",
599
+ provider: "openai",
600
+ modality: "text",
601
+ currency: "USD",
602
+ asOf: "2026-07-27",
603
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
604
+ provenance: "litellm-catalog",
605
+ tiers: [
606
+ {
607
+ maxInputTokens: 272e3,
608
+ inputPer1m: 5,
609
+ outputPer1m: 30,
610
+ cachedInputPer1m: 0.5
611
+ },
612
+ {
613
+ inputPer1m: 10,
614
+ outputPer1m: 45,
615
+ cachedInputPer1m: 1
616
+ }
617
+ ],
618
+ batchMultiplier: 0.5,
619
+ contextWindow: 105e4
620
+ },
621
+ {
622
+ model: "gpt-5.4",
623
+ provider: "openai",
624
+ modality: "text",
625
+ currency: "USD",
626
+ asOf: "2026-07-27",
627
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
628
+ provenance: "litellm-catalog",
629
+ tiers: [
630
+ {
631
+ maxInputTokens: 272e3,
632
+ inputPer1m: 2.5,
633
+ outputPer1m: 15,
634
+ cachedInputPer1m: 0.25
635
+ },
636
+ {
637
+ inputPer1m: 5,
638
+ outputPer1m: 22.5,
639
+ cachedInputPer1m: 0.5
640
+ }
641
+ ],
642
+ batchMultiplier: 0.5,
643
+ contextWindow: 105e4
644
+ },
645
+ {
646
+ model: "gpt-5.4-mini",
647
+ provider: "openai",
648
+ modality: "text",
649
+ currency: "USD",
650
+ asOf: "2026-07-27",
651
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
652
+ provenance: "litellm-catalog",
653
+ tiers: [
654
+ {
655
+ inputPer1m: 0.75,
656
+ outputPer1m: 4.5,
657
+ cachedInputPer1m: 0.075
658
+ }
659
+ ],
660
+ batchMultiplier: 0.5,
661
+ contextWindow: 105e4
662
+ },
663
+ {
664
+ model: "gpt-5",
665
+ provider: "openai",
666
+ modality: "text",
667
+ currency: "USD",
668
+ asOf: "2026-07-27",
669
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
670
+ provenance: "litellm-catalog",
671
+ tiers: [
672
+ {
673
+ inputPer1m: 1.25,
674
+ outputPer1m: 10,
675
+ cachedInputPer1m: 0.125
676
+ }
677
+ ],
678
+ contextWindow: 272e3
679
+ },
680
+ {
681
+ model: "gpt-5-mini",
682
+ provider: "openai",
683
+ modality: "text",
684
+ currency: "USD",
685
+ asOf: "2026-07-27",
686
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
687
+ provenance: "litellm-catalog",
688
+ tiers: [
689
+ {
690
+ inputPer1m: 0.25,
691
+ outputPer1m: 2,
692
+ cachedInputPer1m: 0.025
693
+ }
694
+ ],
695
+ contextWindow: 272e3
696
+ },
697
+ {
698
+ model: "gpt-5-nano",
699
+ provider: "openai",
700
+ modality: "text",
701
+ currency: "USD",
702
+ asOf: "2026-07-27",
703
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
704
+ provenance: "litellm-catalog",
705
+ tiers: [
706
+ {
707
+ inputPer1m: 0.05,
708
+ outputPer1m: 0.4,
709
+ cachedInputPer1m: 5e-3
710
+ }
711
+ ],
712
+ contextWindow: 272e3
713
+ },
714
+ {
715
+ model: "gpt-4.1",
716
+ provider: "openai",
717
+ modality: "text",
718
+ currency: "USD",
719
+ asOf: "2026-07-27",
720
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
721
+ provenance: "litellm-catalog",
722
+ tiers: [
723
+ {
724
+ inputPer1m: 2,
725
+ outputPer1m: 8,
726
+ cachedInputPer1m: 0.5
727
+ }
728
+ ],
729
+ batchMultiplier: 0.5,
730
+ contextWindow: 1047576
731
+ },
732
+ {
733
+ model: "gpt-4.1-mini",
734
+ provider: "openai",
735
+ modality: "text",
736
+ currency: "USD",
737
+ asOf: "2026-07-27",
738
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
739
+ provenance: "litellm-catalog",
740
+ tiers: [
741
+ {
742
+ inputPer1m: 0.4,
743
+ outputPer1m: 1.6,
744
+ cachedInputPer1m: 0.1
745
+ }
746
+ ],
747
+ batchMultiplier: 0.5,
748
+ contextWindow: 1047576
749
+ },
750
+ {
751
+ model: "gpt-4.1-nano",
752
+ provider: "openai",
753
+ modality: "text",
754
+ currency: "USD",
755
+ asOf: "2026-07-27",
756
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
757
+ provenance: "litellm-catalog",
758
+ tiers: [
759
+ {
760
+ inputPer1m: 0.1,
761
+ outputPer1m: 0.4,
762
+ cachedInputPer1m: 0.025
763
+ }
764
+ ],
765
+ batchMultiplier: 0.5,
766
+ contextWindow: 1047576
767
+ },
768
+ {
769
+ model: "gpt-4o",
770
+ provider: "openai",
771
+ modality: "text",
772
+ currency: "USD",
773
+ asOf: "2026-07-27",
774
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
775
+ provenance: "litellm-catalog",
776
+ tiers: [
777
+ {
778
+ inputPer1m: 2.5,
779
+ outputPer1m: 10,
780
+ cachedInputPer1m: 1.25
781
+ }
782
+ ],
783
+ batchMultiplier: 0.5,
784
+ contextWindow: 128e3
785
+ },
786
+ {
787
+ model: "gpt-4o-mini",
788
+ provider: "openai",
789
+ modality: "text",
790
+ currency: "USD",
791
+ asOf: "2026-07-27",
792
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
793
+ provenance: "litellm-catalog",
794
+ tiers: [
795
+ {
796
+ inputPer1m: 0.15,
797
+ outputPer1m: 0.6,
798
+ cachedInputPer1m: 0.075
799
+ }
800
+ ],
801
+ batchMultiplier: 0.5,
802
+ contextWindow: 128e3
803
+ },
804
+ {
805
+ model: "gpt-5.5",
806
+ provider: "azure",
807
+ region: "global",
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
+ maxInputTokens: 272e3,
816
+ inputPer1m: 5,
817
+ outputPer1m: 30,
818
+ cachedInputPer1m: 0.5
819
+ },
820
+ {
821
+ inputPer1m: 10,
822
+ outputPer1m: 45,
823
+ cachedInputPer1m: 1
824
+ }
825
+ ],
826
+ contextWindow: 105e4
827
+ },
828
+ {
829
+ model: "gpt-5.5",
830
+ provider: "azure",
831
+ region: "us",
832
+ modality: "text",
833
+ currency: "USD",
834
+ asOf: "2026-07-27",
835
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
836
+ provenance: "litellm-catalog",
837
+ tiers: [
838
+ {
839
+ maxInputTokens: 272e3,
840
+ inputPer1m: 5.5,
841
+ outputPer1m: 33,
842
+ cachedInputPer1m: 0.55
843
+ },
844
+ {
845
+ inputPer1m: 11,
846
+ outputPer1m: 49.5,
847
+ cachedInputPer1m: 1.1
848
+ }
849
+ ],
850
+ contextWindow: 105e4
851
+ },
852
+ {
853
+ model: "gpt-5.5",
854
+ provider: "azure",
855
+ region: "eu",
856
+ modality: "text",
857
+ currency: "USD",
858
+ asOf: "2026-07-27",
859
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
860
+ provenance: "litellm-catalog",
861
+ tiers: [
862
+ {
863
+ maxInputTokens: 272e3,
864
+ inputPer1m: 5.5,
865
+ outputPer1m: 33,
866
+ cachedInputPer1m: 0.55
867
+ },
868
+ {
869
+ inputPer1m: 11,
870
+ outputPer1m: 49.5,
871
+ cachedInputPer1m: 1.1
872
+ }
873
+ ],
874
+ contextWindow: 105e4
875
+ },
876
+ {
877
+ model: "gpt-5.4",
878
+ provider: "azure",
879
+ region: "global",
880
+ modality: "text",
881
+ currency: "USD",
882
+ asOf: "2026-07-27",
883
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
884
+ provenance: "litellm-catalog",
885
+ tiers: [
886
+ {
887
+ maxInputTokens: 272e3,
888
+ inputPer1m: 2.5,
889
+ outputPer1m: 15,
890
+ cachedInputPer1m: 0.25
891
+ },
892
+ {
893
+ inputPer1m: 5,
894
+ outputPer1m: 22.5,
895
+ cachedInputPer1m: 0.5
896
+ }
897
+ ],
898
+ contextWindow: 105e4
899
+ },
900
+ {
901
+ model: "gpt-5.4",
902
+ provider: "azure",
903
+ region: "us",
904
+ modality: "text",
905
+ currency: "USD",
906
+ asOf: "2026-07-27",
907
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
908
+ provenance: "litellm-catalog",
909
+ tiers: [
910
+ {
911
+ inputPer1m: 2.75,
912
+ outputPer1m: 16.5,
913
+ cachedInputPer1m: 0.28
914
+ }
915
+ ],
916
+ contextWindow: 105e4
917
+ },
918
+ {
919
+ model: "gpt-5.4",
920
+ provider: "azure",
921
+ region: "eu",
922
+ modality: "text",
923
+ currency: "USD",
924
+ asOf: "2026-07-27",
925
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
926
+ provenance: "litellm-catalog",
927
+ tiers: [
928
+ {
929
+ inputPer1m: 2.75,
930
+ outputPer1m: 16.5,
931
+ cachedInputPer1m: 0.28
932
+ }
933
+ ],
934
+ contextWindow: 105e4
935
+ },
936
+ {
937
+ model: "gpt-5.6-terra",
938
+ provider: "azure",
939
+ region: "global",
940
+ modality: "text",
941
+ currency: "USD",
942
+ asOf: "2026-07-27",
943
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
944
+ provenance: "litellm-catalog",
945
+ tiers: [
946
+ {
947
+ maxInputTokens: 272e3,
948
+ inputPer1m: 2.5,
949
+ outputPer1m: 15,
950
+ cachedInputPer1m: 0.25
951
+ },
952
+ {
953
+ inputPer1m: 5,
954
+ outputPer1m: 22.5,
955
+ cachedInputPer1m: 0.5
956
+ }
957
+ ],
958
+ contextWindow: 105e4
959
+ },
960
+ {
961
+ model: "gpt-5.6-terra",
962
+ provider: "azure",
963
+ region: "eu",
964
+ modality: "text",
965
+ currency: "USD",
966
+ asOf: "2026-07-27",
967
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
968
+ provenance: "litellm-catalog",
969
+ tiers: [
970
+ {
971
+ maxInputTokens: 272e3,
972
+ inputPer1m: 2.75,
973
+ outputPer1m: 16.5,
974
+ cachedInputPer1m: 0.275
975
+ },
976
+ {
977
+ inputPer1m: 5.5,
978
+ outputPer1m: 24.75,
979
+ cachedInputPer1m: 0.55
980
+ }
981
+ ],
982
+ contextWindow: 105e4
983
+ },
984
+ {
985
+ model: "gpt-5",
986
+ provider: "azure",
987
+ region: "global",
988
+ modality: "text",
989
+ currency: "USD",
990
+ asOf: "2026-07-27",
991
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
992
+ provenance: "litellm-catalog",
993
+ tiers: [
994
+ {
995
+ inputPer1m: 1.25,
996
+ outputPer1m: 10,
997
+ cachedInputPer1m: 0.125
998
+ }
999
+ ],
1000
+ contextWindow: 272e3
1001
+ },
1002
+ {
1003
+ model: "gpt-5-mini",
1004
+ provider: "azure",
1005
+ region: "global",
1006
+ modality: "text",
1007
+ currency: "USD",
1008
+ asOf: "2026-07-27",
1009
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1010
+ provenance: "litellm-catalog",
1011
+ tiers: [
1012
+ {
1013
+ inputPer1m: 0.25,
1014
+ outputPer1m: 2,
1015
+ cachedInputPer1m: 0.025
1016
+ }
1017
+ ],
1018
+ contextWindow: 272e3
1019
+ },
1020
+ {
1021
+ model: "gpt-5-nano",
1022
+ provider: "azure",
1023
+ region: "global",
1024
+ modality: "text",
1025
+ currency: "USD",
1026
+ asOf: "2026-07-27",
1027
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1028
+ provenance: "litellm-catalog",
1029
+ tiers: [
1030
+ {
1031
+ inputPer1m: 0.05,
1032
+ outputPer1m: 0.4,
1033
+ cachedInputPer1m: 5e-3
1034
+ }
1035
+ ],
1036
+ contextWindow: 272e3
1037
+ },
1038
+ {
1039
+ model: "gpt-4.1",
1040
+ provider: "azure",
1041
+ region: "global",
1042
+ modality: "text",
1043
+ currency: "USD",
1044
+ asOf: "2026-07-27",
1045
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1046
+ provenance: "litellm-catalog",
1047
+ tiers: [
1048
+ {
1049
+ inputPer1m: 2,
1050
+ outputPer1m: 8,
1051
+ cachedInputPer1m: 0.5
1052
+ }
1053
+ ],
1054
+ batchMultiplier: 0.5,
1055
+ contextWindow: 1047576
1056
+ },
1057
+ {
1058
+ model: "gpt-4o",
1059
+ provider: "azure",
1060
+ region: "global",
1061
+ modality: "text",
1062
+ currency: "USD",
1063
+ asOf: "2026-07-27",
1064
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1065
+ provenance: "litellm-catalog",
1066
+ tiers: [
1067
+ {
1068
+ inputPer1m: 2.5,
1069
+ outputPer1m: 10,
1070
+ cachedInputPer1m: 1.25
1071
+ }
1072
+ ],
1073
+ contextWindow: 128e3
1074
+ },
1075
+ {
1076
+ model: "gpt-4o-mini",
1077
+ provider: "azure",
1078
+ region: "global",
1079
+ modality: "text",
1080
+ currency: "USD",
1081
+ asOf: "2026-07-27",
1082
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1083
+ provenance: "litellm-catalog",
1084
+ tiers: [
1085
+ {
1086
+ inputPer1m: 0.165,
1087
+ outputPer1m: 0.66,
1088
+ cachedInputPer1m: 0.075
1089
+ }
1090
+ ],
1091
+ contextWindow: 128e3,
1092
+ 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."
1093
+ },
1094
+ {
1095
+ model: "gemini-3.1-pro",
1096
+ provider: "gemini",
1097
+ aliases: [
1098
+ "gemini-3.1-pro-preview"
1099
+ ],
1100
+ modality: "text",
1101
+ currency: "USD",
1102
+ asOf: "2026-07-27",
1103
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1104
+ provenance: "vendor-pricing-page",
1105
+ tiers: [
1106
+ {
1107
+ maxInputTokens: 2e5,
1108
+ inputPer1m: 2,
1109
+ outputPer1m: 12,
1110
+ cachedInputPer1m: 0.2,
1111
+ reasoningOutputPer1m: 12
1112
+ },
1113
+ {
1114
+ inputPer1m: 4,
1115
+ outputPer1m: 18,
1116
+ cachedInputPer1m: 0.4,
1117
+ reasoningOutputPer1m: 18
1118
+ }
1119
+ ],
1120
+ batchMultiplier: 0.5,
1121
+ contextWindow: 1048576,
1122
+ notes: "Long-context tier: $2/$12 per 1M up to 200K prompt tokens, $4/$18 above it."
1123
+ },
1124
+ {
1125
+ model: "gemini-3-pro",
1126
+ provider: "gemini",
1127
+ aliases: [
1128
+ "gemini-3-pro-preview"
1129
+ ],
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
+ maxInputTokens: 2e5,
1138
+ inputPer1m: 2,
1139
+ outputPer1m: 12,
1140
+ cachedInputPer1m: 0.2,
1141
+ reasoningOutputPer1m: 12
1142
+ },
1143
+ {
1144
+ inputPer1m: 4,
1145
+ outputPer1m: 18,
1146
+ cachedInputPer1m: 0.4,
1147
+ reasoningOutputPer1m: 18
1148
+ }
1149
+ ],
1150
+ batchMultiplier: 0.5,
1151
+ contextWindow: 1048576
1152
+ },
1153
+ {
1154
+ model: "gemini-3.6-flash",
1155
+ provider: "gemini",
1156
+ modality: "text",
1157
+ currency: "USD",
1158
+ asOf: "2026-07-27",
1159
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1160
+ provenance: "vendor-pricing-page",
1161
+ tiers: [
1162
+ {
1163
+ inputPer1m: 1.5,
1164
+ outputPer1m: 7.5,
1165
+ cachedInputPer1m: 0.15,
1166
+ reasoningOutputPer1m: 7.5
1167
+ }
1168
+ ],
1169
+ batchMultiplier: 0.5,
1170
+ contextWindow: 1048576,
1171
+ notes: "Released 2026-07-21. Output pricing dropped ~17% from Gemini 3.5 Flash ($9.00 -> $7.50 per 1M); input unchanged."
1172
+ },
1173
+ {
1174
+ model: "gemini-3.5-flash",
1175
+ provider: "gemini",
1176
+ modality: "text",
1177
+ currency: "USD",
1178
+ asOf: "2026-07-27",
1179
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1180
+ provenance: "vendor-pricing-page",
1181
+ tiers: [
1182
+ {
1183
+ inputPer1m: 1.5,
1184
+ outputPer1m: 9,
1185
+ cachedInputPer1m: 0.15,
1186
+ reasoningOutputPer1m: 9
1187
+ }
1188
+ ],
1189
+ batchMultiplier: 0.5,
1190
+ contextWindow: 1048576
1191
+ },
1192
+ {
1193
+ model: "gemini-3.5-flash-lite",
1194
+ provider: "gemini",
1195
+ modality: "text",
1196
+ currency: "USD",
1197
+ asOf: "2026-07-27",
1198
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1199
+ provenance: "litellm-catalog",
1200
+ tiers: [
1201
+ {
1202
+ inputPer1m: 0.3,
1203
+ outputPer1m: 2.5,
1204
+ cachedInputPer1m: 0.03,
1205
+ reasoningOutputPer1m: 2.5
1206
+ }
1207
+ ],
1208
+ batchMultiplier: 0.5,
1209
+ contextWindow: 1048576
1210
+ },
1211
+ {
1212
+ model: "gemini-3.1-flash-lite",
1213
+ provider: "gemini",
1214
+ modality: "text",
1215
+ currency: "USD",
1216
+ asOf: "2026-07-27",
1217
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-3.1-flash-lite",
1218
+ provenance: "litellm-catalog",
1219
+ tiers: [
1220
+ {
1221
+ inputPer1m: 0.25,
1222
+ outputPer1m: 1.5,
1223
+ cachedInputPer1m: 0.025,
1224
+ reasoningOutputPer1m: 1.5
1225
+ }
1226
+ ],
1227
+ batchMultiplier: 0.5,
1228
+ contextWindow: 1048576
1229
+ },
1230
+ {
1231
+ model: "gemini-3-flash",
1232
+ provider: "gemini",
1233
+ aliases: [
1234
+ "gemini-3-flash-preview"
1235
+ ],
1236
+ modality: "text",
1237
+ currency: "USD",
1238
+ asOf: "2026-07-27",
1239
+ source: "https://ai.google.dev/pricing/gemini-3",
1240
+ provenance: "litellm-catalog",
1241
+ tiers: [
1242
+ {
1243
+ inputPer1m: 0.5,
1244
+ outputPer1m: 3,
1245
+ cachedInputPer1m: 0.05,
1246
+ reasoningOutputPer1m: 3
1247
+ }
1248
+ ],
1249
+ contextWindow: 1048576
1250
+ },
1251
+ {
1252
+ model: "gemini-2.5-pro",
1253
+ provider: "gemini",
1254
+ modality: "text",
1255
+ currency: "USD",
1256
+ asOf: "2026-07-27",
1257
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1258
+ provenance: "litellm-catalog",
1259
+ tiers: [
1260
+ {
1261
+ maxInputTokens: 2e5,
1262
+ inputPer1m: 1.25,
1263
+ outputPer1m: 10,
1264
+ cachedInputPer1m: 0.125
1265
+ },
1266
+ {
1267
+ inputPer1m: 2.5,
1268
+ outputPer1m: 15,
1269
+ cachedInputPer1m: 0.25
1270
+ }
1271
+ ],
1272
+ contextWindow: 1048576
1273
+ },
1274
+ {
1275
+ model: "gemini-2.5-flash",
1276
+ provider: "gemini",
1277
+ modality: "text",
1278
+ currency: "USD",
1279
+ asOf: "2026-07-27",
1280
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-preview",
1281
+ provenance: "litellm-catalog",
1282
+ tiers: [
1283
+ {
1284
+ inputPer1m: 0.3,
1285
+ outputPer1m: 2.5,
1286
+ cachedInputPer1m: 0.03,
1287
+ reasoningOutputPer1m: 2.5
1288
+ }
1289
+ ],
1290
+ contextWindow: 1048576
1291
+ },
1292
+ {
1293
+ model: "gemini-2.5-flash-lite",
1294
+ provider: "gemini",
1295
+ modality: "text",
1296
+ currency: "USD",
1297
+ asOf: "2026-07-27",
1298
+ source: "https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-lite",
1299
+ provenance: "litellm-catalog",
1300
+ tiers: [
1301
+ {
1302
+ inputPer1m: 0.1,
1303
+ outputPer1m: 0.4,
1304
+ cachedInputPer1m: 0.01,
1305
+ reasoningOutputPer1m: 0.4
1306
+ }
1307
+ ],
1308
+ contextWindow: 1048576
1309
+ },
1310
+ {
1311
+ model: "gemini-2.0-flash",
1312
+ provider: "gemini",
1313
+ modality: "text",
1314
+ currency: "USD",
1315
+ asOf: "2026-07-27",
1316
+ source: "https://ai.google.dev/pricing#2_0flash",
1317
+ provenance: "litellm-catalog",
1318
+ tiers: [
1319
+ {
1320
+ inputPer1m: 0.1,
1321
+ outputPer1m: 0.4,
1322
+ cachedInputPer1m: 0.025
1323
+ }
1324
+ ],
1325
+ contextWindow: 1048576,
1326
+ notes: "Gemini Developer API price. Vertex AI lists $0.15/$0.60 per 1M for the same model \u2014 see the vertex entry."
1327
+ },
1328
+ {
1329
+ model: "gemini-2.0-flash-lite",
1330
+ provider: "gemini",
1331
+ modality: "text",
1332
+ currency: "USD",
1333
+ asOf: "2026-07-27",
1334
+ source: "https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite",
1335
+ provenance: "litellm-catalog",
1336
+ tiers: [
1337
+ {
1338
+ inputPer1m: 0.075,
1339
+ outputPer1m: 0.3,
1340
+ cachedInputPer1m: 0.01875
1341
+ }
1342
+ ],
1343
+ contextWindow: 1048576
1344
+ },
1345
+ {
1346
+ model: "gemini-2.0-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: 0.15,
1356
+ outputPer1m: 0.6
1357
+ }
1358
+ ],
1359
+ batchMultiplier: 0.5,
1360
+ notes: "Vertex AI lists $0.15/$0.60 per 1M; the Gemini Developer API lists $0.10/$0.40 for the same model."
1361
+ },
1362
+ {
1363
+ model: "gemini-3.1-pro",
1364
+ provider: "vertex",
1365
+ aliases: [
1366
+ "gemini-3.1-pro-preview"
1367
+ ],
1368
+ modality: "text",
1369
+ currency: "USD",
1370
+ asOf: "2026-07-27",
1371
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1372
+ provenance: "vendor-pricing-page",
1373
+ tiers: [
1374
+ {
1375
+ maxInputTokens: 2e5,
1376
+ inputPer1m: 2,
1377
+ outputPer1m: 12,
1378
+ cachedInputPer1m: 0.2,
1379
+ reasoningOutputPer1m: 12
1380
+ },
1381
+ {
1382
+ inputPer1m: 4,
1383
+ outputPer1m: 18,
1384
+ cachedInputPer1m: 0.4,
1385
+ reasoningOutputPer1m: 18
1386
+ }
1387
+ ],
1388
+ batchMultiplier: 0.5,
1389
+ contextWindow: 1048576
1390
+ },
1391
+ {
1392
+ model: "gemini-3.6-flash",
1393
+ provider: "vertex",
1394
+ modality: "text",
1395
+ currency: "USD",
1396
+ asOf: "2026-07-27",
1397
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1398
+ provenance: "vendor-pricing-page",
1399
+ tiers: [
1400
+ {
1401
+ inputPer1m: 1.5,
1402
+ outputPer1m: 7.5,
1403
+ cachedInputPer1m: 0.15,
1404
+ reasoningOutputPer1m: 7.5
1405
+ }
1406
+ ],
1407
+ batchMultiplier: 0.5,
1408
+ contextWindow: 1048576
1409
+ },
1410
+ {
1411
+ model: "gemini-2.5-pro",
1412
+ provider: "vertex",
1413
+ modality: "text",
1414
+ currency: "USD",
1415
+ asOf: "2026-07-27",
1416
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1417
+ provenance: "vendor-pricing-page",
1418
+ tiers: [
1419
+ {
1420
+ maxInputTokens: 2e5,
1421
+ inputPer1m: 1.25,
1422
+ outputPer1m: 10,
1423
+ cachedInputPer1m: 0.13
1424
+ },
1425
+ {
1426
+ inputPer1m: 2.5,
1427
+ outputPer1m: 15,
1428
+ cachedInputPer1m: 0.25
1429
+ }
1430
+ ],
1431
+ batchMultiplier: 0.5,
1432
+ contextWindow: 1048576
1433
+ },
1434
+ {
1435
+ model: "gemini-2.5-flash",
1436
+ provider: "vertex",
1437
+ modality: "text",
1438
+ currency: "USD",
1439
+ asOf: "2026-07-27",
1440
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1441
+ provenance: "vendor-pricing-page",
1442
+ tiers: [
1443
+ {
1444
+ inputPer1m: 0.3,
1445
+ outputPer1m: 2.5,
1446
+ cachedInputPer1m: 0.03,
1447
+ reasoningOutputPer1m: 2.5
1448
+ }
1449
+ ],
1450
+ batchMultiplier: 0.5,
1451
+ contextWindow: 1048576
1452
+ },
1453
+ {
1454
+ model: "claude-sonnet-4-6",
1455
+ provider: "openrouter",
1456
+ modality: "text",
1457
+ currency: "USD",
1458
+ asOf: "2026-07-27",
1459
+ source: "https://openrouter.ai/anthropic/claude-sonnet-4.6",
1460
+ provenance: "litellm-catalog",
1461
+ tiers: [
1462
+ {
1463
+ maxInputTokens: 2e5,
1464
+ inputPer1m: 3,
1465
+ outputPer1m: 15,
1466
+ cachedInputPer1m: 0.3,
1467
+ cacheWritePer1m: 3.75
1468
+ },
1469
+ {
1470
+ inputPer1m: 6,
1471
+ outputPer1m: 22.5,
1472
+ cachedInputPer1m: 0.6,
1473
+ cacheWritePer1m: 7.5
1474
+ }
1475
+ ],
1476
+ contextWindow: 1e6
1477
+ },
1478
+ {
1479
+ model: "claude-haiku-4-5",
1480
+ provider: "openrouter",
1481
+ modality: "text",
1482
+ currency: "USD",
1483
+ asOf: "2026-07-27",
1484
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1485
+ provenance: "litellm-catalog",
1486
+ tiers: [
1487
+ {
1488
+ inputPer1m: 1,
1489
+ outputPer1m: 5,
1490
+ cachedInputPer1m: 0.1,
1491
+ cacheWritePer1m: 1.25
1492
+ }
1493
+ ],
1494
+ contextWindow: 2e5
1495
+ },
1496
+ {
1497
+ model: "gpt-5",
1498
+ provider: "openrouter",
1499
+ modality: "text",
1500
+ currency: "USD",
1501
+ asOf: "2026-07-27",
1502
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1503
+ provenance: "litellm-catalog",
1504
+ tiers: [
1505
+ {
1506
+ inputPer1m: 1.25,
1507
+ outputPer1m: 10,
1508
+ cachedInputPer1m: 0.125
1509
+ }
1510
+ ],
1511
+ contextWindow: 272e3
1512
+ },
1513
+ {
1514
+ model: "gpt-5-mini",
1515
+ provider: "openrouter",
1516
+ modality: "text",
1517
+ currency: "USD",
1518
+ asOf: "2026-07-27",
1519
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1520
+ provenance: "litellm-catalog",
1521
+ tiers: [
1522
+ {
1523
+ inputPer1m: 0.25,
1524
+ outputPer1m: 2,
1525
+ cachedInputPer1m: 0.025
1526
+ }
1527
+ ],
1528
+ contextWindow: 272e3
1529
+ },
1530
+ {
1531
+ model: "gemini-3.1-pro",
1532
+ provider: "openrouter",
1533
+ aliases: [
1534
+ "gemini-3.1-pro-preview"
1535
+ ],
1536
+ modality: "text",
1537
+ currency: "USD",
1538
+ asOf: "2026-07-27",
1539
+ source: "https://openrouter.ai/google/gemini-3.1-pro-preview",
1540
+ provenance: "litellm-catalog",
1541
+ tiers: [
1542
+ {
1543
+ maxInputTokens: 2e5,
1544
+ inputPer1m: 2,
1545
+ outputPer1m: 12,
1546
+ cachedInputPer1m: 0.2
1547
+ },
1548
+ {
1549
+ inputPer1m: 4,
1550
+ outputPer1m: 18,
1551
+ cachedInputPer1m: 0.4,
1552
+ cacheWritePer1m: 0.25
1553
+ }
1554
+ ],
1555
+ contextWindow: 1048576
1556
+ },
1557
+ {
1558
+ model: "text-embedding-3-small",
1559
+ provider: "openai",
1560
+ modality: "embedding",
1561
+ currency: "USD",
1562
+ asOf: "2026-07-27",
1563
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1564
+ provenance: "litellm-catalog",
1565
+ tiers: [
1566
+ {
1567
+ inputPer1m: 0.02,
1568
+ outputPer1m: 0
1569
+ }
1570
+ ],
1571
+ batchMultiplier: 0.5,
1572
+ contextWindow: 8191
1573
+ },
1574
+ {
1575
+ model: "text-embedding-3-large",
1576
+ provider: "openai",
1577
+ modality: "embedding",
1578
+ currency: "USD",
1579
+ asOf: "2026-07-27",
1580
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1581
+ provenance: "litellm-catalog",
1582
+ tiers: [
1583
+ {
1584
+ inputPer1m: 0.13,
1585
+ outputPer1m: 0
1586
+ }
1587
+ ],
1588
+ batchMultiplier: 0.5,
1589
+ contextWindow: 8191
1590
+ },
1591
+ {
1592
+ model: "gemini-embedding-001",
1593
+ provider: "gemini",
1594
+ modality: "embedding",
1595
+ currency: "USD",
1596
+ asOf: "2026-07-27",
1597
+ source: "https://ai.google.dev/gemini-api/docs/embeddings#model-versions",
1598
+ provenance: "litellm-catalog",
1599
+ tiers: [
1600
+ {
1601
+ inputPer1m: 0.15,
1602
+ outputPer1m: 0
1603
+ }
1604
+ ],
1605
+ contextWindow: 2048
1606
+ },
1607
+ {
1608
+ model: "dall-e-3",
1609
+ provider: "openai",
1610
+ modality: "image",
1611
+ currency: "USD",
1612
+ asOf: "2026-07-27",
1613
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1614
+ provenance: "litellm-catalog",
1615
+ rates: [
1616
+ {
1617
+ unit: "per_image",
1618
+ amount: 0.04,
1619
+ variant: "1024x1024 standard"
1620
+ }
1621
+ ]
1622
+ },
1623
+ {
1624
+ model: "gemini-3-pro-image",
1625
+ provider: "gemini",
1626
+ modality: "image",
1627
+ currency: "USD",
1628
+ asOf: "2026-07-27",
1629
+ source: "https://ai.google.dev/gemini-api/docs/pricing",
1630
+ provenance: "litellm-catalog",
1631
+ rates: [
1632
+ {
1633
+ unit: "per_image",
1634
+ amount: 0.134,
1635
+ variant: "default output resolution"
1636
+ }
1637
+ ],
1638
+ 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."
1639
+ },
1640
+ {
1641
+ model: "imagen-4.0-generate-001",
1642
+ provider: "vertex",
1643
+ modality: "image",
1644
+ currency: "USD",
1645
+ asOf: "2026-07-27",
1646
+ source: "https://cloud.google.com/vertex-ai/generative-ai/pricing",
1647
+ provenance: "litellm-catalog",
1648
+ rates: [
1649
+ {
1650
+ unit: "per_image",
1651
+ amount: 0.04
1652
+ }
1653
+ ]
1654
+ },
1655
+ {
1656
+ model: "veo-3.1-generate-001",
1657
+ provider: "gemini",
1658
+ modality: "video",
1659
+ currency: "USD",
1660
+ asOf: "2026-07-27",
1661
+ source: "https://ai.google.dev/gemini-api/docs/video",
1662
+ provenance: "litellm-catalog",
1663
+ rates: [
1664
+ {
1665
+ unit: "per_second",
1666
+ amount: 0.4
1667
+ }
1668
+ ]
1669
+ },
1670
+ {
1671
+ model: "veo-3.1-generate-001",
1672
+ provider: "vertex",
1673
+ modality: "video",
1674
+ currency: "USD",
1675
+ asOf: "2026-07-27",
1676
+ source: "https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/veo",
1677
+ provenance: "litellm-catalog",
1678
+ rates: [
1679
+ {
1680
+ unit: "per_second",
1681
+ amount: 0.4
1682
+ }
1683
+ ]
1684
+ },
1685
+ {
1686
+ model: "tts-1",
1687
+ provider: "openai",
1688
+ modality: "speech",
1689
+ currency: "USD",
1690
+ asOf: "2026-07-27",
1691
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1692
+ provenance: "litellm-catalog",
1693
+ rates: [
1694
+ {
1695
+ unit: "per_character",
1696
+ amount: 15e-6
1697
+ }
1698
+ ],
1699
+ notes: "$15 per 1M characters."
1700
+ },
1701
+ {
1702
+ model: "tts-1-hd",
1703
+ provider: "openai",
1704
+ modality: "speech",
1705
+ currency: "USD",
1706
+ asOf: "2026-07-27",
1707
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1708
+ provenance: "litellm-catalog",
1709
+ rates: [
1710
+ {
1711
+ unit: "per_character",
1712
+ amount: 3e-5
1713
+ }
1714
+ ],
1715
+ notes: "$30 per 1M characters."
1716
+ },
1717
+ {
1718
+ model: "whisper-1",
1719
+ provider: "openai",
1720
+ modality: "transcription",
1721
+ currency: "USD",
1722
+ asOf: "2026-07-27",
1723
+ source: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json",
1724
+ provenance: "litellm-catalog",
1725
+ rates: [
1726
+ {
1727
+ unit: "per_audio_minute",
1728
+ amount: 6e-3
1729
+ }
1730
+ ],
1731
+ notes: "litellm records $0.0001 per audio second; $0.006/minute is that rate x 60."
1732
+ }
1733
+ ]
1734
+ };
1735
+
1736
+ // src/core/router/pricing.ts
1737
+ var DATASET = pricing_data_default;
1738
+ function normalizeForPricing(id) {
1739
+ let s = id.trim().toLowerCase();
1740
+ s = s.replace(/^models\//, "");
1741
+ const slash = s.lastIndexOf("/");
1742
+ if (slash !== -1) s = s.slice(slash + 1);
1743
+ const tag = s.search(/[:@]/);
1744
+ if (tag !== -1) s = s.slice(0, tag);
1745
+ s = s.replace(/-latest$/, "");
1746
+ s = s.replace(/-preview(?:-\d{2}-\d{2})?$/, "");
1747
+ s = s.replace(/-\d{4}-\d{2}-\d{2}$/, "");
1748
+ s = s.replace(/-\d{8}$/, "");
1749
+ s = s.replace(/-preview$/, "");
1750
+ return s;
1751
+ }
1752
+ function indexKey(model, provider, region) {
1753
+ return `${provider}|${normalizeForPricing(model)}|${region ?? ""}`;
1754
+ }
1755
+ var INDEX = /* @__PURE__ */ new Map();
1756
+ for (const entry of DATASET.entries) {
1757
+ for (const name of [entry.model, ...entry.aliases ?? []]) {
1758
+ const withRegion = indexKey(name, entry.provider, entry.region);
1759
+ if (!INDEX.has(withRegion)) INDEX.set(withRegion, entry);
1760
+ const bare = indexKey(name, entry.provider);
1761
+ const existing = INDEX.get(bare);
1762
+ if (!existing || existing.region && existing.region !== "global" && entry.region === "global") {
1763
+ INDEX.set(bare, entry);
1764
+ }
1765
+ }
1766
+ }
1767
+ function findPricing(modelId, provider, opts = {}) {
1768
+ const ids = [modelId, opts.baseModelId].filter((x) => !!x);
1769
+ const providers = [provider, ...opts.fallbackProviders ?? []];
1770
+ for (const p of providers) {
1771
+ for (const id of ids) {
1772
+ const hit = (opts.region ? INDEX.get(indexKey(id, p, opts.region)) : void 0) ?? INDEX.get(indexKey(id, p));
1773
+ if (hit) return { entry: hit, matchedProvider: p, viaFallback: p !== provider };
1774
+ }
1775
+ }
1776
+ return null;
1777
+ }
1778
+ function tierFor(entry, inputTokens = 0) {
1779
+ const tiers = entry.tiers;
1780
+ if (!tiers?.length) return null;
1781
+ for (const t of tiers) {
1782
+ if (t.maxInputTokens === void 0 || inputTokens <= t.maxInputTokens) return t;
1783
+ }
1784
+ return tiers[tiers.length - 1] ?? null;
1785
+ }
1786
+ var PER_1M_TO_PER_1K = 1 / 1e3;
1787
+ function tokenRatesPer1k(entry, opts = {}) {
1788
+ const tier = tierFor(entry, opts.inputTokens ?? 0);
1789
+ if (!tier) return null;
1790
+ const mult = (opts.batch ? entry.batchMultiplier ?? 1 : 1) * PER_1M_TO_PER_1K;
1791
+ const out = {
1792
+ input: tier.inputPer1m * mult,
1793
+ output: tier.outputPer1m * mult
1794
+ };
1795
+ if (tier.cachedInputPer1m !== void 0) out.cachedInput = tier.cachedInputPer1m * mult;
1796
+ if (tier.cacheWritePer1m !== void 0) out.cacheWrite = tier.cacheWritePer1m * mult;
1797
+ if (tier.reasoningOutputPer1m !== void 0) out.reasoningOutput = tier.reasoningOutputPer1m * mult;
1798
+ return out;
1799
+ }
1800
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1", "[::1]"]);
1801
+ function isLoopbackOrPrivateHost(url) {
1802
+ if (!url) return false;
1803
+ try {
1804
+ const host = new URL(url).hostname.toLowerCase().replace(/^\[|\]$/g, "");
1805
+ if (LOOPBACK_HOSTS.has(host) || LOOPBACK_HOSTS.has(`[${host}]`)) return true;
1806
+ if (host.endsWith(".local")) return true;
1807
+ if (/^127\./.test(host)) return true;
1808
+ if (/^10\./.test(host)) return true;
1809
+ if (/^192\.168\./.test(host)) return true;
1810
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) return true;
1811
+ return false;
1812
+ } catch {
1813
+ return false;
1814
+ }
1815
+ }
1816
+ function isLocalEndpoint(cfg) {
1817
+ if (typeof cfg.local === "boolean") return cfg.local;
1818
+ if (cfg.type === "ollama") return true;
1819
+ if (cfg.type === "openai-compatible") return isLoopbackOrPrivateHost(cfg.baseUrl);
1820
+ return false;
1821
+ }
1822
+ var UNKNOWN = { input: 0, output: 0, unknown: true, free: false };
1823
+ var FREE = { input: 0, output: 0, unknown: false, free: true };
1824
+ function resolvePricing(model, opts = {}) {
1825
+ if (model.isLocal) return FREE;
1826
+ const fallbackProviders = model.provider === "azure" ? ["openai"] : model.provider === "gemini" ? ["vertex"] : [];
1827
+ const match = findPricing(model.id, model.provider, {
1828
+ region: opts.region,
1829
+ baseModelId: model.baseModelId,
1830
+ fallbackProviders
1831
+ });
1832
+ if (!match) return UNKNOWN;
1833
+ const rates = tokenRatesPer1k(match.entry, { inputTokens: opts.inputTokens, batch: opts.batch });
1834
+ if (!rates) return UNKNOWN;
1835
+ return {
1836
+ input: rates.input,
1837
+ output: rates.output,
1838
+ unknown: false,
1839
+ free: false,
1840
+ entry: match.entry,
1841
+ ...match.viaFallback ? { estimatedFromProvider: match.matchedProvider } : {}
1842
+ };
1843
+ }
1844
+ function withResolvedPricing(model, opts = {}) {
1845
+ const p = resolvePricing(model, opts);
1846
+ return {
1847
+ ...model,
1848
+ inputCostPer1kTokens: p.input,
1849
+ outputCostPer1kTokens: p.output,
1850
+ ...p.unknown ? { pricingUnknown: true } : { pricingUnknown: false }
1851
+ };
1852
+ }
1853
+ function isPricingUnknown(model) {
1854
+ return model.pricingUnknown === true && !model.isLocal;
1855
+ }
1856
+ var BLENDED_COST_CEILING = 0.05;
1857
+ function blendedCostPer1k(model) {
1858
+ if (isPricingUnknown(model)) return BLENDED_COST_CEILING;
1859
+ return model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
1860
+ }
1861
+ var DISAGREEMENT_THRESHOLD = 0.02;
1862
+ function reconcilePrice(model, live) {
1863
+ const baseline = resolvePricing(model);
1864
+ const liveIsZero = !(live.input > 0) && !(live.output > 0);
1865
+ if (liveIsZero && !model.isLocal) {
1866
+ return { input: baseline.input, output: baseline.output, accepted: false };
1867
+ }
1868
+ if (baseline.unknown || !baseline.entry) {
1869
+ return { input: live.input, output: live.output, accepted: true };
1870
+ }
1871
+ const dsBlend = baseline.input + baseline.output * 2;
1872
+ const liveBlend = live.input + live.output * 2;
1873
+ const ratio = dsBlend > 0 ? liveBlend / dsBlend : Number.POSITIVE_INFINITY;
1874
+ const material = dsBlend > 0 && Math.abs(ratio - 1) > DISAGREEMENT_THRESHOLD;
1875
+ return {
1876
+ input: live.input,
1877
+ output: live.output,
1878
+ accepted: true,
1879
+ ...material ? {
1880
+ disagreement: {
1881
+ modelId: model.id,
1882
+ provider: model.provider,
1883
+ dataset: { input: baseline.input, output: baseline.output },
1884
+ live: { input: live.input, output: live.output },
1885
+ ratio: Math.round(ratio * 1e3) / 1e3,
1886
+ datasetAsOf: baseline.entry.asOf,
1887
+ datasetSource: baseline.entry.source
1888
+ }
1889
+ } : {}
1890
+ };
1891
+ }
1892
+
241
1893
  // src/constants.ts
242
- var CASCADE_VERSION = "0.48.1";
1894
+ var CASCADE_VERSION = "0.50.0";
243
1895
  var CASCADE_CONFIG_DIR = ".cascade";
244
1896
  var CASCADE_MD_FILE = "CASCADE.md";
245
1897
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -588,6 +2240,13 @@ var MODELS = {
588
2240
  for (const _m of Object.values(MODELS)) {
589
2241
  if (_m.supportsToolUse === void 0) _m.supportsToolUse = !_m.isLocal;
590
2242
  }
2243
+ for (const _m of Object.values(MODELS)) {
2244
+ const priced = resolvePricing(_m);
2245
+ if (priced.unknown || priced.free || priced.estimatedFromProvider) continue;
2246
+ _m.inputCostPer1kTokens = priced.input;
2247
+ _m.outputCostPer1kTokens = priced.output;
2248
+ _m.pricingUnknown = false;
2249
+ }
591
2250
  var T1_MODEL_PRIORITY = [
592
2251
  "gpt-5.5",
593
2252
  "gpt-5.4",
@@ -678,21 +2337,49 @@ var PROVIDER_DISPLAY_NAMES = {
678
2337
 
679
2338
  // src/utils/cost.ts
680
2339
  function resolveModelPricing(model) {
681
- let input = model.inputCostPer1kTokens;
682
- let output = model.outputCostPer1kTokens;
683
- if (input === 0 && output === 0 && model.id && !model.isLocal) {
2340
+ if (model.isLocal) return { input: 0, output: 0, unknown: false };
2341
+ if (model.inputCostPer1kTokens > 0 || model.outputCostPer1kTokens > 0) {
2342
+ return {
2343
+ input: model.inputCostPer1kTokens,
2344
+ output: model.outputCostPer1kTokens,
2345
+ unknown: false
2346
+ };
2347
+ }
2348
+ const fromDataset = resolvePricing(model);
2349
+ if (!fromDataset.unknown) {
2350
+ return {
2351
+ input: fromDataset.input,
2352
+ output: fromDataset.output,
2353
+ unknown: false,
2354
+ ...fromDataset.estimatedFromProvider ? { estimatedFromProvider: fromDataset.estimatedFromProvider } : {}
2355
+ };
2356
+ }
2357
+ if (model.id) {
684
2358
  const known = Object.values(MODELS).find((m) => m.id === model.id && !m.isLocal);
685
- if (known) {
686
- input = known.inputCostPer1kTokens;
687
- output = known.outputCostPer1kTokens;
2359
+ if (known && (known.inputCostPer1kTokens > 0 || known.outputCostPer1kTokens > 0)) {
2360
+ return {
2361
+ input: known.inputCostPer1kTokens,
2362
+ output: known.outputCostPer1kTokens,
2363
+ unknown: false
2364
+ };
688
2365
  }
689
2366
  }
690
- return { input, output };
2367
+ return { input: 0, output: 0, unknown: true };
691
2368
  }
692
2369
  function calculateCost(inputTokens, outputTokens, model) {
693
2370
  const { input, output } = resolveModelPricing(model);
694
2371
  return inputTokens / 1e3 * input + outputTokens / 1e3 * output;
695
2372
  }
2373
+ function buildTokenUsage(inputTokens, outputTokens, model) {
2374
+ const { input, output, unknown } = resolveModelPricing(model);
2375
+ return {
2376
+ inputTokens,
2377
+ outputTokens,
2378
+ totalTokens: inputTokens + outputTokens,
2379
+ estimatedCostUsd: inputTokens / 1e3 * input + outputTokens / 1e3 * output,
2380
+ ...unknown ? { costUnknown: true } : {}
2381
+ };
2382
+ }
696
2383
 
697
2384
  // src/providers/base.ts
698
2385
  var BaseProvider = class {
@@ -712,12 +2399,7 @@ var BaseProvider = class {
712
2399
  return calculateCost(inputTokens, outputTokens, this.model);
713
2400
  }
714
2401
  makeUsage(inputTokens, outputTokens) {
715
- return {
716
- inputTokens,
717
- outputTokens,
718
- totalTokens: inputTokens + outputTokens,
719
- estimatedCostUsd: this.estimateCost(inputTokens, outputTokens)
720
- };
2402
+ return buildTokenUsage(inputTokens, outputTokens, this.model);
721
2403
  }
722
2404
  };
723
2405
 
@@ -886,7 +2568,7 @@ var AnthropicProvider = class extends BaseProvider {
886
2568
  return data.data.filter((m) => isChatModel(m.id)).map((m) => {
887
2569
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "anthropic");
888
2570
  if (known) return known;
889
- return {
2571
+ return withResolvedPricing({
890
2572
  id: m.id,
891
2573
  name: m.display_name || m.id,
892
2574
  provider: "anthropic",
@@ -897,7 +2579,7 @@ var AnthropicProvider = class extends BaseProvider {
897
2579
  maxOutputTokens: 8e3,
898
2580
  supportsStreaming: true,
899
2581
  isLocal: false
900
- };
2582
+ });
901
2583
  });
902
2584
  } catch {
903
2585
  return Object.values(MODELS).filter((m) => m.provider === "anthropic");
@@ -1101,7 +2783,7 @@ var OpenAIProvider = class extends BaseProvider {
1101
2783
  return response.data.filter((m) => isChatModel(m.id)).map((m) => {
1102
2784
  const known = Object.values(MODELS).find((km) => km.id === m.id && km.provider === "openai");
1103
2785
  if (known) return known;
1104
- return {
2786
+ return withResolvedPricing({
1105
2787
  id: m.id,
1106
2788
  name: m.id,
1107
2789
  provider: "openai",
@@ -1112,7 +2794,7 @@ var OpenAIProvider = class extends BaseProvider {
1112
2794
  maxOutputTokens: 4e3,
1113
2795
  supportsStreaming: true,
1114
2796
  isLocal: false
1115
- };
2797
+ });
1116
2798
  });
1117
2799
  } catch {
1118
2800
  return Object.values(MODELS).filter((m) => m.provider === "openai");
@@ -1208,6 +2890,10 @@ function azureModelForDeployment(cfg) {
1208
2890
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
1209
2891
  const base = baseModelId ? MODELS[baseModelId] : void 0;
1210
2892
  if (base) {
2893
+ const azurePrice = resolvePricing(
2894
+ { id, provider: "azure", isLocal: false, baseModelId },
2895
+ { region: cfg.region }
2896
+ );
1211
2897
  return {
1212
2898
  ...base,
1213
2899
  id,
@@ -1216,7 +2902,12 @@ function azureModelForDeployment(cfg) {
1216
2902
  provider: "azure",
1217
2903
  baseModelId,
1218
2904
  // real identity for benchmark + live pricing
1219
- supportsToolUse: base.supportsToolUse ?? true
2905
+ supportsToolUse: base.supportsToolUse ?? true,
2906
+ ...azurePrice.unknown ? {} : {
2907
+ inputCostPer1kTokens: azurePrice.input,
2908
+ outputCostPer1kTokens: azurePrice.output,
2909
+ pricingUnknown: false
2910
+ }
1220
2911
  };
1221
2912
  }
1222
2913
  return {
@@ -1379,7 +3070,7 @@ var GeminiProvider = class extends BaseProvider {
1379
3070
  (km) => km.id === id && km.provider === "gemini"
1380
3071
  );
1381
3072
  if (known) return known;
1382
- return {
3073
+ return withResolvedPricing({
1383
3074
  id,
1384
3075
  name: m.displayName || id,
1385
3076
  provider: "gemini",
@@ -1390,7 +3081,7 @@ var GeminiProvider = class extends BaseProvider {
1390
3081
  maxOutputTokens: m.outputTokenLimit || 8e3,
1391
3082
  supportsStreaming: true,
1392
3083
  isLocal: false
1393
- };
3084
+ });
1394
3085
  });
1395
3086
  } catch {
1396
3087
  return Object.values(MODELS).filter((m) => m.provider === "gemini");
@@ -1737,9 +3428,10 @@ var OllamaProvider = class extends BaseProvider {
1737
3428
  return supportedKeywords.some((k) => name.includes(k)) && isChatModel(name);
1738
3429
  });
1739
3430
  const shows = await Promise.all(entries.map((m) => this.showModel(m.name)));
3431
+ const local = isLocalEndpoint({ ...this.config, type: "ollama" });
1740
3432
  return entries.map((m, i) => {
1741
3433
  const show = shows[i];
1742
- return {
3434
+ return withResolvedPricing({
1743
3435
  id: m.name,
1744
3436
  name: m.name,
1745
3437
  provider: "ollama",
@@ -1749,10 +3441,10 @@ var OllamaProvider = class extends BaseProvider {
1749
3441
  outputCostPer1kTokens: 0,
1750
3442
  maxOutputTokens: 4e3,
1751
3443
  supportsStreaming: true,
1752
- isLocal: true,
3444
+ isLocal: local,
1753
3445
  supportsToolUse: show?.tools ?? isToolCapable(m.name),
1754
3446
  minSizeB: this.parseSizeB(m.details?.parameter_size)
1755
- };
3447
+ });
1756
3448
  });
1757
3449
  } catch {
1758
3450
  return [];
@@ -1855,7 +3547,8 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
1855
3547
  }).filter((x) => typeof x === "string" && x.length > 0);
1856
3548
  if (ids.length === 0) return [this.model];
1857
3549
  const chatIds = ids.filter((id) => isChatModel(id));
1858
- return (chatIds.length ? chatIds : ids).map((id) => ({
3550
+ const local = isLocalEndpoint(this.config);
3551
+ return (chatIds.length ? chatIds : ids).map((id) => withResolvedPricing({
1859
3552
  id,
1860
3553
  name: id,
1861
3554
  provider: "openai-compatible",
@@ -1865,7 +3558,7 @@ var OpenAICompatibleProvider = class extends OpenAIProvider {
1865
3558
  outputCostPer1kTokens: 0,
1866
3559
  maxOutputTokens: 4e3,
1867
3560
  supportsStreaming: true,
1868
- isLocal: false
3561
+ isLocal: local
1869
3562
  }));
1870
3563
  }
1871
3564
  async isAvailable() {
@@ -2080,7 +3773,7 @@ var ModelSelector = class {
2080
3773
  else if (this.availableProviders.size === 1) providerStr = Array.from(this.availableProviders)[0];
2081
3774
  }
2082
3775
  if (providerStr && this.availableProviders.has(providerStr)) {
2083
- const dynamicModel = {
3776
+ const dynamicModel = withResolvedPricing({
2084
3777
  id: actualId,
2085
3778
  name: actualId,
2086
3779
  provider: providerStr,
@@ -2091,7 +3784,7 @@ var ModelSelector = class {
2091
3784
  maxOutputTokens: 8e3,
2092
3785
  supportsStreaming: true,
2093
3786
  isLocal: providerStr === "ollama"
2094
- };
3787
+ });
2095
3788
  this.addDynamicModel(dynamicModel);
2096
3789
  return dynamicModel;
2097
3790
  }
@@ -2483,6 +4176,8 @@ var LiveDataProvider = class {
2483
4176
  snapshot = null;
2484
4177
  prices = /* @__PURE__ */ new Map();
2485
4178
  capabilities = /* @__PURE__ */ new Map();
4179
+ /** Dataset-vs-live price mismatches, deduped by `provider:modelId`. */
4180
+ disagreements = /* @__PURE__ */ new Map();
2486
4181
  source = "bundled";
2487
4182
  fetchedAt = 0;
2488
4183
  loaded = false;
@@ -2636,14 +4331,45 @@ var LiveDataProvider = class {
2636
4331
  /**
2637
4332
  * Returns a price-corrected copy of each model when live pricing is known,
2638
4333
  * leaving the original untouched (so the shared catalog is never mutated).
4334
+ *
4335
+ * Reconciliation policy (see reconcilePrice in pricing.ts): the bundled
4336
+ * dataset is the authoritative baseline, and a live quote overrides it
4337
+ * because it is fresher by construction — but the disagreement is RECORDED,
4338
+ * not swallowed. A dataset/live mismatch is the best signal available that
4339
+ * the committed prices have gone stale, so it must be visible rather than
4340
+ * silently resolved in either direction. A live quote of $0 for a non-local
4341
+ * model is rejected outright: that is the very failure mode this change
4342
+ * exists to prevent, and a marketplace's free-tier alias must never zero out
4343
+ * the price of the paid model Cascade is actually calling.
2639
4344
  */
2640
4345
  applyLivePricing(models) {
2641
4346
  return models.map((m) => {
2642
4347
  const p = this.getLivePrice(m.baseModelId ?? m.id);
2643
4348
  if (!p) return m;
2644
- return { ...m, inputCostPer1kTokens: p.input, outputCostPer1kTokens: p.output };
4349
+ const reconciled = reconcilePrice(m, p);
4350
+ if (reconciled.disagreement) this.recordDisagreement(reconciled.disagreement);
4351
+ if (!reconciled.accepted) return m;
4352
+ return {
4353
+ ...m,
4354
+ inputCostPer1kTokens: reconciled.input,
4355
+ outputCostPer1kTokens: reconciled.output,
4356
+ pricingUnknown: false
4357
+ };
2645
4358
  });
2646
4359
  }
4360
+ recordDisagreement(d) {
4361
+ const key = `${d.provider}:${d.modelId}`;
4362
+ if (this.disagreements.has(key)) return;
4363
+ this.disagreements.set(key, d);
4364
+ }
4365
+ /**
4366
+ * Dataset-vs-live price mismatches seen this session, newest-model-first
4367
+ * insertion order. Surfaced by `cascade models --verbose`; a non-empty list
4368
+ * means src/core/router/pricing-data.json is probably due a refresh.
4369
+ */
4370
+ getPriceDisagreements() {
4371
+ return [...this.disagreements.values()];
4372
+ }
2647
4373
  /** Current capability facts for a model id, or null when unknown. */
2648
4374
  getCapability(modelId) {
2649
4375
  return this.capabilities.get(normalizeModelId2(modelId)) ?? null;
@@ -2840,7 +4566,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
2840
4566
  tokensByTier: {},
2841
4567
  inputTokensByTier: {},
2842
4568
  outputTokensByTier: {},
2843
- costByFeature: {}
4569
+ costByFeature: {},
4570
+ untrackedCostCalls: 0,
4571
+ untrackedCostModels: []
2844
4572
  };
2845
4573
  tierModels = /* @__PURE__ */ new Map();
2846
4574
  /**
@@ -3190,7 +4918,7 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3190
4918
  `Model ${model.id} inference timed out after ${cloudTimeoutMs}ms`
3191
4919
  );
3192
4920
  }
3193
- const correctedCost = calculateCost(
4921
+ const corrected = buildTokenUsage(
3194
4922
  result.usage.inputTokens,
3195
4923
  result.usage.outputTokens,
3196
4924
  model
@@ -3199,7 +4927,8 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3199
4927
  ...result,
3200
4928
  usage: {
3201
4929
  ...result.usage,
3202
- estimatedCostUsd: correctedCost
4930
+ estimatedCostUsd: corrected.estimatedCostUsd,
4931
+ ...corrected.costUnknown ? { costUnknown: true } : { costUnknown: void 0 }
3203
4932
  }
3204
4933
  };
3205
4934
  if (!result || typeof result.content !== "string" || !result.usage) {
@@ -3386,7 +5115,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3386
5115
  tokensByTier: { ...this.stats.tokensByTier },
3387
5116
  inputTokensByTier: { ...this.stats.inputTokensByTier },
3388
5117
  outputTokensByTier: { ...this.stats.outputTokensByTier },
3389
- costByFeature: { ...this.stats.costByFeature }
5118
+ costByFeature: { ...this.stats.costByFeature },
5119
+ untrackedCostCalls: this.stats.untrackedCostCalls,
5120
+ untrackedCostModels: [...this.stats.untrackedCostModels]
3390
5121
  };
3391
5122
  }
3392
5123
  /**
@@ -3437,7 +5168,9 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3437
5168
  tokensByTier: {},
3438
5169
  inputTokensByTier: {},
3439
5170
  outputTokensByTier: {},
3440
- costByFeature: {}
5171
+ costByFeature: {},
5172
+ untrackedCostCalls: 0,
5173
+ untrackedCostModels: []
3441
5174
  };
3442
5175
  this.sessionCostUsd = 0;
3443
5176
  this.budgetState = "ok";
@@ -3622,6 +5355,18 @@ var CascadeRouter = class _CascadeRouter extends EventEmitter__default.default {
3622
5355
  if (featureTag) {
3623
5356
  this.stats.costByFeature[featureTag] = (this.stats.costByFeature[featureTag] ?? 0) + usage.estimatedCostUsd;
3624
5357
  }
5358
+ if (usage.costUnknown) {
5359
+ this.stats.untrackedCostCalls += 1;
5360
+ if (!this.stats.untrackedCostModels.includes(model.id)) {
5361
+ this.stats.untrackedCostModels.push(model.id);
5362
+ const capped = this.config?.budget?.maxCostPerRunUsd != null || this.config?.budget?.sessionBudgetUsd != null;
5363
+ this.emit("cost:untracked", {
5364
+ modelId: model.id,
5365
+ provider: model.provider,
5366
+ 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.`
5367
+ });
5368
+ }
5369
+ }
3625
5370
  this.runTokens += usage.totalTokens;
3626
5371
  this.runCostUsd += usage.estimatedCostUsd;
3627
5372
  this.updateBudgetState();
@@ -3762,6 +5507,14 @@ var BaseTier = class extends EventEmitter__default.default {
3762
5507
  this.emit("tier:status", event);
3763
5508
  }
3764
5509
  /** Record the model serving this tier; future status events carry it. */
5510
+ /**
5511
+ * The model actually serving this tier ("provider:id"), once one has been
5512
+ * chosen. The run breaker reads it to attribute a failure to a model rather
5513
+ * than to the subtask that happened to hit it.
5514
+ */
5515
+ getServingModel() {
5516
+ return this.servingModel;
5517
+ }
3765
5518
  setServingModel(model) {
3766
5519
  this.servingModel = model || void 0;
3767
5520
  }
@@ -5497,6 +7250,7 @@ var T2Manager = class extends BaseTier {
5497
7250
  escalations = [];
5498
7251
  peerSyncBuffer = [];
5499
7252
  store;
7253
+ runBreaker;
5500
7254
  t3PeerBus = new PeerBus();
5501
7255
  // ← T3↔T3 bus (local to this T2)
5502
7256
  t2PeerBus;
@@ -5527,6 +7281,15 @@ var T2Manager = class extends BaseTier {
5527
7281
  this.router = router;
5528
7282
  this.toolRegistry = toolRegistry;
5529
7283
  }
7284
+ /**
7285
+ * Share the run-wide circuit breaker. Every T2 in a run gets the SAME
7286
+ * instance: three sections each failing once against a dead model is the
7287
+ * same fact as one section failing three times, and a per-section breaker
7288
+ * would never see it.
7289
+ */
7290
+ setRunBreaker(breaker) {
7291
+ this.runBreaker = breaker;
7292
+ }
5530
7293
  setStore(store) {
5531
7294
  this.store = store;
5532
7295
  }
@@ -5650,7 +7413,8 @@ Guidance (must be followed): ${decision.note}`
5650
7413
  const overallStatus = this.determineStatus(t3Results);
5651
7414
  const isOk = overallStatus === "COMPLETED" || overallStatus === "PARTIAL";
5652
7415
  this.setStatus(isOk ? "COMPLETED" : "FAILED", summary);
5653
- this.sendStatusUpdate({ progressPct: 100, currentAction: "Section complete", status: "IN_PROGRESS", output: summary });
7416
+ 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";
7417
+ this.sendStatusUpdate({ progressPct: 100, currentAction: sectionAction, status: "IN_PROGRESS", output: summary });
5654
7418
  const result = {
5655
7419
  sectionId: assignment.sectionId,
5656
7420
  sectionTitle: assignment.sectionTitle,
@@ -5820,6 +7584,24 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
5820
7584
  const reinforceCfg = this.router.getReinforcementsConfig?.() ?? { enabled: false, maxPerSection: 4 };
5821
7585
  let reinforcementsAdded = 0;
5822
7586
  while (remaining.size > 0) {
7587
+ if (this.runBreaker?.isOpen()) {
7588
+ const why = this.runBreaker.skipMessage();
7589
+ this.log(`Run breaker open \u2014 skipping ${remaining.size} remaining subtask(s)`);
7590
+ for (const id of remaining) {
7591
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
7592
+ resultMap.set(id, {
7593
+ subtaskId: id,
7594
+ status: "FAILED",
7595
+ output: why,
7596
+ testResults: { checksRun: [], passed: [], failed: [] },
7597
+ issues: [why],
7598
+ peerSyncsUsed: [],
7599
+ correctionAttempts: 0
7600
+ });
7601
+ }
7602
+ remaining.clear();
7603
+ break;
7604
+ }
5823
7605
  const runnableIds = [...remaining].filter((id) => (inDegree.get(id) ?? 0) === 0);
5824
7606
  if (runnableIds.length === 0) {
5825
7607
  const fallbackId = [...remaining].sort(
@@ -5922,8 +7704,32 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
5922
7704
  remaining.delete(id);
5923
7705
  const r = waveResults[i];
5924
7706
  if (r.status === "rejected") {
5925
- this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
5926
7707
  const assignment = sanitizedAssignments.find((a) => a.subtaskId === id);
7708
+ const failedModel = workerMap.get(id)?.getServingModel();
7709
+ const tripped = this.runBreaker?.record(r.reason, failedModel);
7710
+ if (tripped) {
7711
+ this.log(`Run breaker opened: ${tripped.failures} consecutive ${tripped.kind} failures on ${tripped.modelId} \u2014 stopping`);
7712
+ this.emit("run:breaker", tripped);
7713
+ }
7714
+ if (this.runBreaker?.isOpen()) {
7715
+ const why = this.runBreaker.skipMessage();
7716
+ this.log(`T3 worker ${id} failed and the run breaker is open \u2014 not retrying`);
7717
+ this.t3PeerBus.publish(this.id, id, why, "FAILED");
7718
+ resultMap.set(id, {
7719
+ subtaskId: id,
7720
+ status: "FAILED",
7721
+ output: why,
7722
+ testResults: { checksRun: [], passed: [], failed: [] },
7723
+ issues: [why],
7724
+ peerSyncsUsed: [],
7725
+ correctionAttempts: 0
7726
+ });
7727
+ for (const dependent of adj.get(id) ?? []) {
7728
+ inDegree.set(dependent, Math.max(0, (inDegree.get(dependent) ?? 0) - 1));
7729
+ }
7730
+ continue;
7731
+ }
7732
+ this.log(`T3 worker ${id} failed: ${r.reason instanceof Error ? r.reason.message : String(r.reason)} \u2014 retrying once`);
5927
7733
  try {
5928
7734
  const retried = await this.retryT3(assignment, taskId);
5929
7735
  resultMap.set(id, retried);
@@ -6255,6 +8061,7 @@ var T1Administrator = class extends BaseTier {
6255
8061
  t2Managers = /* @__PURE__ */ new Map();
6256
8062
  escalations = [];
6257
8063
  store;
8064
+ runBreaker;
6258
8065
  t2PeerBus = new PeerBus();
6259
8066
  permissionEscalator;
6260
8067
  toolCreator;
@@ -6269,6 +8076,10 @@ var T1Administrator = class extends BaseTier {
6269
8076
  this.toolRegistry = toolRegistry;
6270
8077
  this.config = config;
6271
8078
  }
8079
+ /** Share the run-wide circuit breaker with every section manager. */
8080
+ setRunBreaker(breaker) {
8081
+ this.runBreaker = breaker;
8082
+ }
6272
8083
  setStore(store) {
6273
8084
  this.store = store;
6274
8085
  }
@@ -6631,6 +8442,7 @@ SPEC RULES \u2014 each subtask is a self-contained spec slice (workers execute f
6631
8442
  };
6632
8443
  const managers = sections.map((section) => {
6633
8444
  const manager = new T2Manager(this.router, this.toolRegistry, this.id);
8445
+ if (this.runBreaker) manager.setRunBreaker(this.runBreaker);
6634
8446
  manager.setHierarchyContext(`You are a T2 Manager for the section "${section.sectionTitle}". You are part of a COMPLEX task overseen by T1 Administrator.`);
6635
8447
  if (this.store) {
6636
8448
  manager.setStore(this.store);
@@ -6916,6 +8728,158 @@ Reply with exactly one word: YES, NO, or UNSURE.
6916
8728
  }
6917
8729
  };
6918
8730
 
8731
+ // src/core/router/provider-errors.ts
8732
+ var SYSTEMIC = /* @__PURE__ */ new Set([
8733
+ "rate_limit",
8734
+ "quota_exhausted",
8735
+ "auth",
8736
+ "model_unavailable"
8737
+ ]);
8738
+ function isSystemicKind(kind) {
8739
+ return SYSTEMIC.has(kind);
8740
+ }
8741
+ function statusOf(err) {
8742
+ if (!err || typeof err !== "object") return void 0;
8743
+ const e = err;
8744
+ for (const key of ["status", "statusCode", "code"]) {
8745
+ const v = e[key];
8746
+ if (typeof v === "number" && v >= 100 && v < 600) return v;
8747
+ }
8748
+ const resp = e["response"];
8749
+ if (resp && typeof resp["status"] === "number") return resp["status"];
8750
+ const inner = e["error"];
8751
+ if (inner && typeof inner["code"] === "number") return inner["code"];
8752
+ return void 0;
8753
+ }
8754
+ function messageOf(err) {
8755
+ if (err instanceof Error) return err.message;
8756
+ if (typeof err === "string") return err;
8757
+ if (err && typeof err === "object") {
8758
+ const e = err;
8759
+ if (typeof e["message"] === "string") return e["message"];
8760
+ const inner = e["error"];
8761
+ if (inner && typeof inner["message"] === "string") return inner["message"];
8762
+ try {
8763
+ return JSON.stringify(err);
8764
+ } catch {
8765
+ }
8766
+ }
8767
+ return String(err);
8768
+ }
8769
+ function classifyProviderError(err) {
8770
+ const raw = messageOf(err).trim();
8771
+ const status = statusOf(err);
8772
+ const m = raw.toLowerCase();
8773
+ const kind = (() => {
8774
+ if (status === 429) {
8775
+ return /quota|billing|credit|exceeded your current quota|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
8776
+ }
8777
+ if (status === 401 || status === 403) return "auth";
8778
+ if (status === 404) return "model_unavailable";
8779
+ if (/rate limit|too many requests|resource_exhausted/.test(m)) {
8780
+ return /quota|billing|credit|insufficient/.test(m) ? "quota_exhausted" : "rate_limit";
8781
+ }
8782
+ if (/quota|billing|insufficient_quota|insufficient credit|payment required/.test(m)) return "quota_exhausted";
8783
+ if (/api key|unauthorized|authentication|permission denied|invalid[_ ]api/.test(m)) return "auth";
8784
+ if (/model .*(not found|does not exist|unavailable)|no such model|unknown model|not supported for/.test(m)) {
8785
+ return "model_unavailable";
8786
+ }
8787
+ if (/context length|too many tokens|maximum context|prompt is too long|input too long/.test(m)) return "context_length";
8788
+ if (/safety|content filter|blocked|recitation|prohibited/.test(m)) return "content_filter";
8789
+ if (/econnreset|etimedout|enotfound|socket hang up|network|fetch failed|aborted/.test(m)) return "network";
8790
+ return "unknown";
8791
+ })();
8792
+ return { kind, systemic: isSystemicKind(kind), status, raw };
8793
+ }
8794
+ function describeProviderError(c, modelId) {
8795
+ const on = ` on ${modelId}` ;
8796
+ const detail = c.raw ? ` Provider said: ${c.raw}` : "";
8797
+ switch (c.kind) {
8798
+ case "rate_limit":
8799
+ 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}`;
8800
+ case "quota_exhausted":
8801
+ 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}`;
8802
+ case "auth":
8803
+ 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}`;
8804
+ case "model_unavailable":
8805
+ 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}`;
8806
+ case "context_length":
8807
+ return `The prompt was too long${on}. Reduce the attached context, or use a model with a larger context window for this tier.${detail}`;
8808
+ case "content_filter":
8809
+ return `The provider's safety filter refused this request${on}.${detail}`;
8810
+ case "network":
8811
+ return `Network error reaching the provider${on}. This is usually transient.${detail}`;
8812
+ default:
8813
+ return `The model call failed${on}.${detail}`;
8814
+ }
8815
+ }
8816
+
8817
+ // src/core/run-breaker.ts
8818
+ var DEFAULT_FAILURE_THRESHOLD = 3;
8819
+ var RunBreaker = class {
8820
+ threshold;
8821
+ /** modelId → consecutive systemic failures of the SAME kind. */
8822
+ counts = /* @__PURE__ */ new Map();
8823
+ trip = null;
8824
+ constructor(threshold = DEFAULT_FAILURE_THRESHOLD) {
8825
+ this.threshold = Math.max(1, threshold);
8826
+ }
8827
+ /**
8828
+ * Record a failed model call. Returns the trip if this failure is the one
8829
+ * that opened the breaker, otherwise null.
8830
+ *
8831
+ * A non-systemic failure RESETS the model's counter: it is evidence the model
8832
+ * is reachable and answering, which is exactly what a systemic streak claims
8833
+ * it is not.
8834
+ */
8835
+ record(err, modelId) {
8836
+ if (this.trip) return null;
8837
+ const id = modelId?.trim() || "unknown-model";
8838
+ const c = classifyProviderError(err);
8839
+ if (!c.systemic) {
8840
+ this.counts.delete(id);
8841
+ return null;
8842
+ }
8843
+ const prev = this.counts.get(id);
8844
+ const n = prev && prev.kind === c.kind ? prev.n + 1 : 1;
8845
+ this.counts.set(id, { kind: c.kind, n, last: c });
8846
+ if (n >= this.threshold) {
8847
+ this.trip = {
8848
+ modelId: id,
8849
+ kind: c.kind,
8850
+ failures: n,
8851
+ message: describeProviderError(c, id)
8852
+ };
8853
+ return this.trip;
8854
+ }
8855
+ return null;
8856
+ }
8857
+ /** True once the run should stop launching work that would fail identically. */
8858
+ isOpen() {
8859
+ return this.trip !== null;
8860
+ }
8861
+ /** Why the breaker opened, or null while it is closed. */
8862
+ reason() {
8863
+ return this.trip;
8864
+ }
8865
+ /**
8866
+ * One line for a worker/section that never ran because the breaker was
8867
+ * already open. Says the work was skipped deliberately — not that it failed
8868
+ * on its own merits, which is what an empty FAILED status would imply.
8869
+ */
8870
+ skipMessage() {
8871
+ const t = this.trip;
8872
+ if (!t) return "Skipped.";
8873
+ return `Skipped \u2014 the run stopped after ${t.failures} consecutive failures on ${t.modelId}. ${t.message}`;
8874
+ }
8875
+ /** Test/diagnostic view of the per-model streaks. */
8876
+ streaks() {
8877
+ const out = {};
8878
+ for (const [k, v] of this.counts) out[k] = { kind: v.kind, n: v.n };
8879
+ return out;
8880
+ }
8881
+ };
8882
+
6919
8883
  // src/tools/base.ts
6920
8884
  var BaseTool = class {
6921
8885
  workspaceRoot = process.cwd();
@@ -9129,7 +11093,18 @@ var ProviderConfigSchema = zod.z.object({
9129
11093
  apiVersion: zod.z.string().optional(),
9130
11094
  model: zod.z.string().optional(),
9131
11095
  authToken: zod.z.string().optional(),
9132
- credentialSource: zod.z.string().optional()
11096
+ credentialSource: zod.z.string().optional(),
11097
+ /**
11098
+ * "This endpoint is self-hosted, so inference genuinely costs $0."
11099
+ * Only meaningful for `ollama` and `openai-compatible`. Unset ⇒ inferred:
11100
+ * ollama is local, openai-compatible is local when its baseUrl is
11101
+ * loopback/LAN. Set it explicitly to override — e.g. `false` for an Ollama
11102
+ * instance you rent, `true` for a llama.cpp box on your own LAN reached by
11103
+ * hostname.
11104
+ */
11105
+ local: zod.z.boolean().optional(),
11106
+ /** Region for providers whose price varies by region (Azure: global | us | eu | …). */
11107
+ region: zod.z.string().optional()
9133
11108
  });
9134
11109
  var ModelOverridesSchema = zod.z.object({
9135
11110
  t1: zod.z.string().optional(),
@@ -9715,8 +11690,8 @@ var TaskAnalyzer = class {
9715
11690
  }
9716
11691
  costEfficiency(model, complexity) {
9717
11692
  if (this.tracker) return this.tracker.costEfficiencyScore(model, complexity);
9718
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
9719
- const normalised = Math.min(1, blended / 0.05);
11693
+ const blended = blendedCostPer1k(model);
11694
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
9720
11695
  const complexityWeight = (6 - complexity) / 5;
9721
11696
  return Math.max(0.1, 1 - normalised * complexityWeight);
9722
11697
  }
@@ -9851,8 +11826,8 @@ var ModelPerformanceTracker = class {
9851
11826
  * normalised over $0.05 blended as the "expensive" ceiling.
9852
11827
  */
9853
11828
  costEfficiencyScore(model, complexity) {
9854
- const blended = model.inputCostPer1kTokens + model.outputCostPer1kTokens * 2;
9855
- const normalised = Math.min(1, blended / 0.05);
11829
+ const blended = blendedCostPer1k(model);
11830
+ const normalised = Math.min(1, blended / BLENDED_COST_CEILING);
9856
11831
  const complexityWeight = (6 - complexity) / 5;
9857
11832
  return Math.max(0.1, 1 - normalised * complexityWeight);
9858
11833
  }
@@ -12477,6 +14452,7 @@ ${prompt}` : prompt;
12477
14452
  if (toolCreator) toolCreator.setPermissionEscalator(escalator);
12478
14453
  const rootPrompt = buildContextualPrompt(options.prompt, options.conversationHistory);
12479
14454
  let finalOutput = "";
14455
+ const runBreaker = new RunBreaker();
12480
14456
  let t2Results = [];
12481
14457
  let runError = null;
12482
14458
  let identityPrompt = "";
@@ -12545,6 +14521,7 @@ ${prompt}` : prompt;
12545
14521
  this.emit("tier:status", { tierId: "t3-root", status: "COMPLETED", role: "T3" });
12546
14522
  } else if (complexity === "Moderate") {
12547
14523
  const t2 = new T2Manager(this.router, this.toolRegistry, "root");
14524
+ t2.setRunBreaker(runBreaker);
12548
14525
  t2.setPresenter(true);
12549
14526
  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.");
12550
14527
  if (identityPrompt) {
@@ -12605,6 +14582,7 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12605
14582
  }
12606
14583
  } else {
12607
14584
  const t1 = new T1Administrator(this.router, this.toolRegistry, this.config);
14585
+ t1.setRunBreaker(runBreaker);
12608
14586
  t1.setPresenter(true);
12609
14587
  t1.setHierarchyContext("You are the top-level Administrator. You are responsible for the overall plan and supervising multiple T2 Managers.");
12610
14588
  if (identityPrompt) {
@@ -12629,6 +14607,20 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12629
14607
  finalOutput = result.output;
12630
14608
  t2Results = result.t2Results;
12631
14609
  }
14610
+ const trip = runBreaker.reason();
14611
+ if (trip) {
14612
+ finalOutput = [
14613
+ `\u26A0 **Run stopped early \u2014 ${trip.modelId} was failing every request.**`,
14614
+ "",
14615
+ trip.message,
14616
+ "",
14617
+ `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.`,
14618
+ finalOutput ? `
14619
+ ---
14620
+
14621
+ ${finalOutput}` : ""
14622
+ ].join("\n").trimEnd();
14623
+ }
12632
14624
  } catch (err) {
12633
14625
  if (err instanceof CascadeCancelledError || err instanceof Error && err.name === "AbortError" || options.signal?.aborted) {
12634
14626
  this.emit("run:cancelled", {
@@ -12695,7 +14687,11 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12695
14687
  inputTokens: 0,
12696
14688
  outputTokens: 0,
12697
14689
  totalTokens: stats.totalTokens,
12698
- estimatedCostUsd: stats.totalCostUsd
14690
+ estimatedCostUsd: stats.totalCostUsd,
14691
+ // At least one call ran on a model with no known price, so its spend is
14692
+ // missing from the total above. Say so rather than letting the caller
14693
+ // present an undercount as the final figure.
14694
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12699
14695
  },
12700
14696
  t2Results,
12701
14697
  durationMs,
@@ -12765,7 +14761,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12765
14761
  inputTokens: 0,
12766
14762
  outputTokens: 0,
12767
14763
  totalTokens: stats2.totalTokens,
12768
- estimatedCostUsd: stats2.totalCostUsd
14764
+ estimatedCostUsd: stats2.totalCostUsd,
14765
+ ...stats2.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12769
14766
  },
12770
14767
  t2Results: [],
12771
14768
  durationMs: Date.now() - startMs,
@@ -12789,7 +14786,8 @@ _(incomplete: ${reasons.join("; ")})_` : partial;
12789
14786
  inputTokens: result.usage?.inputTokens ?? 0,
12790
14787
  outputTokens: result.usage?.outputTokens ?? 0,
12791
14788
  totalTokens: stats.totalTokens,
12792
- estimatedCostUsd: stats.totalCostUsd
14789
+ estimatedCostUsd: stats.totalCostUsd,
14790
+ ...stats.untrackedCostCalls > 0 ? { costUnknown: true } : {}
12793
14791
  },
12794
14792
  t2Results: [],
12795
14793
  durationMs: Date.now() - startMs,
@@ -14573,7 +16571,10 @@ var DashboardServer = class {
14573
16571
  this.socket.broadcast("cost:update", {
14574
16572
  sessionId,
14575
16573
  totalTokens: result.usage.totalTokens,
14576
- totalCostUsd: result.usage.estimatedCostUsd
16574
+ totalCostUsd: result.usage.estimatedCostUsd,
16575
+ // True when a model with no known price was used: the total above is
16576
+ // an undercount, and the UI must not present it as the full spend.
16577
+ costUnknown: result.usage.costUnknown === true
14577
16578
  });
14578
16579
  this.throttledBroadcast("workspace");
14579
16580
  } catch (err) {
@@ -14886,7 +16887,8 @@ var DashboardServer = class {
14886
16887
  this.socket.broadcast("cost:update", {
14887
16888
  sessionId,
14888
16889
  totalTokens: result.usage.totalTokens,
14889
- totalCostUsd: result.usage.estimatedCostUsd
16890
+ totalCostUsd: result.usage.estimatedCostUsd,
16891
+ costUnknown: result.usage.costUnknown === true
14890
16892
  });
14891
16893
  this.throttledBroadcast("workspace");
14892
16894
  } catch (err) {
@@ -15428,7 +17430,8 @@ ${prompt}`;
15428
17430
  this.socket.broadcast("cost:update", {
15429
17431
  sessionId,
15430
17432
  totalTokens: result.usage.totalTokens,
15431
- totalCostUsd: result.usage.estimatedCostUsd
17433
+ totalCostUsd: result.usage.estimatedCostUsd,
17434
+ costUnknown: result.usage.costUnknown === true
15432
17435
  });
15433
17436
  this.socket.broadcastToRoom(`session:${sessionId}`, "session:complete", { sessionId, result });
15434
17437
  this.throttledBroadcast("workspace");