@stll/ai-catalog 0.1.6 → 0.1.7

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
@@ -466,997 +466,1076 @@ const MODEL_TEMPERATURE_POLICIES = {
466
466
  "pixtral-large-latest": "emit"
467
467
  };
468
468
  //#endregion
469
- //#region src/capabilities-overrides.ts
470
- const CAPABILITY_OVERRIDES = {
471
- "us.amazon.nova-pro-v1:0": {
472
- documentInput: true,
473
- documentInputReason: "2026-08-20: AWS documents PDF input for Nova Pro through Bedrock Converse",
474
- reasoningEfforts: null,
475
- temperatureSupported: true,
476
- reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
477
- },
478
- "us.amazon.nova-lite-v1:0": {
479
- documentInput: true,
480
- documentInputReason: "2026-08-20: AWS documents PDF input for Nova Lite through Bedrock Converse",
481
- reasoningEfforts: null,
482
- temperatureSupported: true,
483
- reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
484
- },
485
- "us.amazon.nova-micro-v1:0": {
486
- documentInput: false,
487
- documentInputReason: "2026-08-20: AWS documents Nova Micro as text-only",
488
- reasoningEfforts: null,
489
- temperatureSupported: true,
490
- reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
491
- }
492
- };
493
- //#endregion
494
- //#region src/document-input-overrides.ts
495
- /**
496
- * Reviewed document-input corrections for models.dev records that exist but
497
- * under-report the provider API's file-input support. The generator rejects an
498
- * override once upstream agrees, so sourced data takes ownership automatically.
499
- */
500
- const DOCUMENT_INPUT_OVERRIDES = {
501
- "gpt-5.4-mini": {
502
- supported: true,
503
- reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
504
- },
505
- "gpt-5.4-nano": {
506
- supported: true,
507
- reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
508
- },
509
- "gpt-5.2": {
510
- supported: true,
511
- reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
512
- }
513
- };
514
- //#endregion
515
- //#region src/index.ts
516
- /**
517
- * Canonical AI provider and model catalog.
518
- *
519
- * Single source of truth for every provider/model identifier stella
520
- * offers. Imported by the API runtime
521
- * (`apps/api/src/lib/tanstack-ai-models.ts`)
522
- * and the BYOK settings UI (`apps/web`), so the picker can never offer
523
- * a model the backend rejects, and vice versa.
524
- *
525
- * Pure data: no provider SDKs, no env access, no side effects. This is
526
- * deliberate so scripts (the nightly upstream-validation check) and
527
- * both apps can import it cheaply. Keep it that way. The only runtime
528
- * dependency is valibot, used solely to construct branded types the
529
- * same way the rest of the repo does.
530
- *
531
- * Model IDs go stale when providers rename or retire models. The
532
- * nightly `model-catalog-upstream` check
533
- * (`packages/scripts/src/model-catalog-upstream.ts`) validates every
534
- * ID here against live provider/aggregator listings so a retired model
535
- * fails CI instead of 400-ing a user at runtime.
536
- */
537
- /**
538
- * Logical model roles. Call sites declare *what* they need, not
539
- * *which* model to use.
540
- *
541
- * - fast: classification, extraction, short generation
542
- * - chat: conversational with tool use and streaming
543
- * - reasoning: complex multi-step legal analysis
544
- * - pdf: native PDF/image understanding
545
- */
546
- const MODEL_ROLES = [
547
- "fast",
548
- "chat",
549
- "reasoning",
550
- "pdf"
551
- ];
552
- const TEMPERATURE_POLICIES = ["emit", "omit"];
553
- const AI_PROVIDERS = [
554
- "google",
555
- "openrouter",
556
- "openai",
557
- "azure_foundry",
558
- "anthropic",
559
- "bedrock",
560
- "mistral",
561
- "openai_compatible",
562
- "huggingface"
563
- ];
564
- const TANSTACK_AI_PROVIDERS = [
565
- "google",
566
- "openrouter",
567
- "openai",
568
- "anthropic",
569
- "bedrock",
570
- "mistral"
571
- ];
572
- const MODEL_CATALOG_PROVIDER_KIND = {
573
- google: "first-party",
574
- openrouter: "aggregator",
575
- openai: "first-party",
576
- anthropic: "first-party",
577
- bedrock: "platform",
578
- mistral: "first-party"
579
- };
580
- /**
581
- * Runtime form of the first-party provider union. The conditional annotation
582
- * makes adding a first-party policy without adding its discovery implementation
583
- * a compile error.
584
- */
585
- const FIRST_PARTY_MODEL_PROVIDERS = [
586
- "google",
587
- "openai",
588
- "anthropic",
589
- "mistral"
590
- ];
591
- /**
592
- * Per-role default model IDs for the BYOK-capable cloud providers.
593
- * Shared between the instance default table (`DEFAULT_MODELS`) and the
594
- * settings-UI default selection, so a default is defined exactly once.
595
- */
596
- const BYOK_DEFAULT_MODELS = {
597
- google: {
598
- fast: "gemini-3.7-flash",
599
- chat: "gemini-3.7-flash",
600
- reasoning: "gemini-3.7-flash",
601
- pdf: "gemini-3.7-flash"
602
- },
603
- openrouter: {
604
- fast: "openai/gpt-5.6-luna",
605
- chat: "openai/gpt-5.6-terra",
606
- reasoning: "openai/gpt-5.6-terra",
607
- pdf: "openai/gpt-5.6-terra"
608
- },
609
- openai: {
610
- fast: "gpt-5.4-nano",
611
- chat: "gpt-5.4-mini",
612
- reasoning: "gpt-5.4",
613
- pdf: "gpt-5.4"
614
- },
615
- anthropic: {
616
- fast: "claude-opus-5",
617
- chat: "claude-opus-5",
618
- reasoning: "claude-opus-5",
619
- pdf: "claude-opus-5"
620
- },
621
- bedrock: {
622
- fast: "us.amazon.nova-micro-v1:0",
623
- chat: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
624
- reasoning: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
625
- pdf: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
626
- },
627
- mistral: {
628
- fast: "mistral-small-latest",
629
- chat: "mistral-medium-latest",
630
- reasoning: "magistral-medium-latest",
631
- pdf: "mistral-large-latest"
632
- }
633
- };
634
- /**
635
- * Instance-level default model IDs per provider. Extends the BYOK
636
- * defaults with the providers that only the instance path uses
637
- * (custom deployments and OpenAI-compatible endpoints).
638
- */
639
- const DEFAULT_MODELS = {
640
- ...BYOK_DEFAULT_MODELS,
641
- azure_foundry: {
642
- fast: "gpt-5.4-nano",
643
- chat: "gpt-5.4-mini",
644
- reasoning: "gpt-5.4",
645
- pdf: "gpt-5.4"
646
- },
647
- openai_compatible: {
648
- fast: "default",
649
- chat: "default",
650
- reasoning: "default",
651
- pdf: "default"
652
- },
653
- huggingface: {
654
- fast: "speakleash/Bielik-1.5B-v3.0-Instruct",
655
- chat: "speakleash/Bielik-11B-v2.3-Instruct",
656
- reasoning: "speakleash/Bielik-11B-v2.3-Instruct",
657
- pdf: "speakleash/Bielik-11B-v2.3-Instruct"
658
- }
659
- };
660
- /**
661
- * BYOK-offered model IDs per provider — the curated catalog users pick
662
- * from in org settings, and the server-side allowlist the API enforces.
663
- * The frontend list is not a security boundary; this is what the API
664
- * accepts.
665
- *
666
- * Limited to providers the TanStack AI integration supports for BYOK.
667
- * Providers without a first-class TanStack adapter path are intentionally
668
- * not accepted at this boundary.
669
- */
670
- const BYOK_MODEL_OPTIONS = {
671
- google: [
672
- "gemini-3.8-flash",
673
- "gemini-3.7-flash",
674
- "gemini-3.6-flash",
675
- "gemini-3.5-flash-lite",
676
- "gemini-3.1-pro-preview",
677
- "gemini-3.5-flash",
678
- "gemini-3.1-flash-lite"
679
- ],
680
- anthropic: [
681
- "claude-sonnet-5",
682
- "claude-fable-5-1",
683
- "claude-fable-5",
684
- "claude-opus-5",
685
- "claude-opus-4-8",
686
- "claude-opus-4-7",
687
- "claude-sonnet-4-6",
688
- "claude-opus-4-6",
689
- "claude-haiku-4-5-20251001"
690
- ],
691
- openai: [
692
- "gpt-5.6",
693
- "gpt-5.6-terra",
694
- "gpt-5.6-luna",
695
- "gpt-5.5",
696
- "gpt-5.4",
697
- "gpt-5.4-mini",
698
- "gpt-5.4-nano",
699
- "gpt-5.2"
700
- ],
701
- openrouter: [
702
- "openai/gpt-5.6-luna",
703
- "openai/gpt-5.6-terra",
704
- "google/gemini-3.8-flash",
705
- "google/gemini-3.7-flash",
706
- "google/gemini-3.6-flash",
707
- "google/gemini-3.5-flash-lite",
708
- "google/gemini-3.1-pro-preview",
709
- "google/gemini-3.5-flash",
710
- "google/gemini-3.1-flash-lite",
711
- "anthropic/claude-sonnet-5",
712
- "anthropic/claude-opus-5",
713
- "anthropic/claude-opus-4.8",
714
- "anthropic/claude-sonnet-4.6",
715
- "openai/gpt-5.5",
716
- "openai/gpt-5.4-mini"
717
- ],
718
- bedrock: [
719
- "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
720
- "us.anthropic.claude-haiku-4-5-20251001-v1:0",
721
- "us.amazon.nova-pro-v1:0",
722
- "us.amazon.nova-lite-v1:0",
723
- "us.amazon.nova-micro-v1:0",
724
- "openai.gpt-oss-120b-1:0",
725
- "openai.gpt-oss-20b-1:0",
726
- "us.deepseek.r1-v1:0"
727
- ],
728
- mistral: [
729
- "mistral-large-latest",
730
- "mistral-medium-latest",
731
- "mistral-small-latest",
732
- "magistral-medium-latest",
733
- "magistral-small",
734
- "pixtral-large-latest"
735
- ]
736
- };
737
- /**
738
- * Presentation metadata for every offered model. Keeping this exhaustive beside
739
- * the allowlist prevents a newly offered model from leaking a raw provider ID
740
- * into user-facing pickers. OpenRouter entries deliberately carry their model
741
- * creator's icon while their routing provider remains available separately.
742
- */
743
- const MODEL_DISPLAY_METADATA = {
469
+ //#region src/model-rates.gen.ts
470
+ const MODEL_RATES = {
744
471
  "gemini-3.8-flash": {
745
- displayName: "Gemini 3.8 Flash",
746
- iconProvider: "google"
472
+ kind: "flat",
473
+ inputPerMTok: 75e3,
474
+ outputPerMTok: 375e3,
475
+ cachedInputPerMTok: 7500
747
476
  },
748
477
  "gemini-3.7-flash": {
749
- displayName: "Gemini 3.7 Flash",
750
- iconProvider: "google"
478
+ kind: "flat",
479
+ inputPerMTok: 75e3,
480
+ outputPerMTok: 375e3,
481
+ cachedInputPerMTok: 7500
751
482
  },
752
483
  "gemini-3.6-flash": {
753
- displayName: "Gemini 3.6 Flash",
754
- iconProvider: "google"
484
+ kind: "flat",
485
+ inputPerMTok: 75e3,
486
+ outputPerMTok: 375e3,
487
+ cachedInputPerMTok: 7500
755
488
  },
756
489
  "gemini-3.5-flash-lite": {
757
- displayName: "Gemini 3.5 Flash Lite",
758
- iconProvider: "google"
490
+ kind: "flat",
491
+ inputPerMTok: 3e4,
492
+ outputPerMTok: 25e4,
493
+ cachedInputPerMTok: 3e3
759
494
  },
760
495
  "gemini-3.1-pro-preview": {
761
- displayName: "Gemini 3.1 Pro Preview",
762
- iconProvider: "google"
496
+ kind: "input-token-tiered",
497
+ inputTokenThreshold: 2e5,
498
+ standard: {
499
+ inputPerMTok: 2e5,
500
+ outputPerMTok: 12e5,
501
+ cachedInputPerMTok: 2e4
502
+ },
503
+ aboveThreshold: {
504
+ inputPerMTok: 4e5,
505
+ outputPerMTok: 18e5,
506
+ cachedInputPerMTok: 4e4
507
+ }
763
508
  },
764
509
  "gemini-3.5-flash": {
765
- displayName: "Gemini 3.5 Flash",
766
- iconProvider: "google"
510
+ kind: "flat",
511
+ inputPerMTok: 15e4,
512
+ outputPerMTok: 9e5,
513
+ cachedInputPerMTok: 15e3
767
514
  },
768
515
  "gemini-3.1-flash-lite": {
769
- displayName: "Gemini 3.1 Flash Lite",
770
- iconProvider: "google"
771
- },
772
- "claude-sonnet-5": {
773
- displayName: "Claude Sonnet 5",
774
- iconProvider: "anthropic"
775
- },
776
- "claude-fable-5-1": {
777
- displayName: "Claude Fable 5.1",
778
- iconProvider: "anthropic"
779
- },
780
- "claude-fable-5": {
781
- displayName: "Claude Fable 5",
782
- iconProvider: "anthropic"
783
- },
784
- "claude-opus-5": {
785
- displayName: "Claude Opus 5",
786
- iconProvider: "anthropic"
787
- },
788
- "claude-opus-4-8": {
789
- displayName: "Claude Opus 4.8",
790
- iconProvider: "anthropic"
791
- },
792
- "claude-opus-4-7": {
793
- displayName: "Claude Opus 4.7",
794
- iconProvider: "anthropic"
795
- },
796
- "claude-sonnet-4-6": {
797
- displayName: "Claude Sonnet 4.6",
798
- iconProvider: "anthropic"
799
- },
800
- "claude-opus-4-6": {
801
- displayName: "Claude Opus 4.6",
802
- iconProvider: "anthropic"
803
- },
804
- "claude-haiku-4-5-20251001": {
805
- displayName: "Claude Haiku 4.5",
806
- iconProvider: "anthropic"
516
+ kind: "flat",
517
+ inputPerMTok: 25e3,
518
+ outputPerMTok: 15e4,
519
+ cachedInputPerMTok: 2500
807
520
  },
808
521
  "gpt-5.6": {
809
- displayName: "GPT-5.6 Sol",
810
- iconProvider: "openai"
522
+ kind: "input-token-tiered",
523
+ inputTokenThreshold: 272e3,
524
+ standard: {
525
+ inputPerMTok: 4e5,
526
+ outputPerMTok: 2e6,
527
+ cachedInputPerMTok: 4e4,
528
+ cachedWriteInputPerMTok: 5e5
529
+ },
530
+ aboveThreshold: {
531
+ inputPerMTok: 8e5,
532
+ outputPerMTok: 3e6,
533
+ cachedInputPerMTok: 8e4,
534
+ cachedWriteInputPerMTok: 1e6
535
+ }
811
536
  },
812
537
  "gpt-5.6-terra": {
813
- displayName: "GPT-5.6 Terra",
814
- iconProvider: "openai"
538
+ kind: "input-token-tiered",
539
+ inputTokenThreshold: 272e3,
540
+ standard: {
541
+ inputPerMTok: 2e5,
542
+ outputPerMTok: 12e5,
543
+ cachedInputPerMTok: 2e4,
544
+ cachedWriteInputPerMTok: 25e4
545
+ },
546
+ aboveThreshold: {
547
+ inputPerMTok: 4e5,
548
+ outputPerMTok: 18e5,
549
+ cachedInputPerMTok: 4e4,
550
+ cachedWriteInputPerMTok: 5e5
551
+ }
815
552
  },
816
553
  "gpt-5.6-luna": {
817
- displayName: "GPT-5.6 Luna",
818
- iconProvider: "openai"
554
+ kind: "input-token-tiered",
555
+ inputTokenThreshold: 272e3,
556
+ standard: {
557
+ inputPerMTok: 2e4,
558
+ outputPerMTok: 12e4,
559
+ cachedInputPerMTok: 2e3,
560
+ cachedWriteInputPerMTok: 25e3
561
+ },
562
+ aboveThreshold: {
563
+ inputPerMTok: 4e4,
564
+ outputPerMTok: 18e4,
565
+ cachedInputPerMTok: 4e3,
566
+ cachedWriteInputPerMTok: 5e4
567
+ }
819
568
  },
820
569
  "gpt-5.5": {
821
- displayName: "GPT-5.5",
822
- iconProvider: "openai"
570
+ kind: "input-token-tiered",
571
+ inputTokenThreshold: 272e3,
572
+ standard: {
573
+ inputPerMTok: 5e5,
574
+ outputPerMTok: 3e6,
575
+ cachedInputPerMTok: 5e4
576
+ },
577
+ aboveThreshold: {
578
+ inputPerMTok: 1e6,
579
+ outputPerMTok: 45e5,
580
+ cachedInputPerMTok: 1e5
581
+ }
823
582
  },
824
583
  "gpt-5.4": {
825
- displayName: "GPT-5.4",
826
- iconProvider: "openai"
584
+ kind: "input-token-tiered",
585
+ inputTokenThreshold: 272e3,
586
+ standard: {
587
+ inputPerMTok: 25e4,
588
+ outputPerMTok: 15e5,
589
+ cachedInputPerMTok: 25e3
590
+ },
591
+ aboveThreshold: {
592
+ inputPerMTok: 5e5,
593
+ outputPerMTok: 225e4,
594
+ cachedInputPerMTok: 5e4
595
+ }
827
596
  },
828
597
  "gpt-5.4-mini": {
829
- displayName: "GPT-5.4 Mini",
830
- iconProvider: "openai"
598
+ kind: "flat",
599
+ inputPerMTok: 75e3,
600
+ outputPerMTok: 45e4,
601
+ cachedInputPerMTok: 7500
831
602
  },
832
603
  "gpt-5.4-nano": {
833
- displayName: "GPT-5.4 Nano",
834
- iconProvider: "openai"
604
+ kind: "flat",
605
+ inputPerMTok: 2e4,
606
+ outputPerMTok: 125e3,
607
+ cachedInputPerMTok: 2e3
835
608
  },
836
609
  "gpt-5.2": {
837
- displayName: "GPT-5.2",
838
- iconProvider: "openai"
839
- },
840
- "openai/gpt-5.6-luna": {
841
- displayName: "GPT-5.6 Luna",
842
- iconProvider: "openai"
843
- },
844
- "openai/gpt-5.6-terra": {
845
- displayName: "GPT-5.6 Terra",
846
- iconProvider: "openai"
847
- },
848
- "google/gemini-3.8-flash": {
849
- displayName: "Gemini 3.8 Flash",
850
- iconProvider: "google"
851
- },
852
- "google/gemini-3.7-flash": {
853
- displayName: "Gemini 3.7 Flash",
854
- iconProvider: "google"
855
- },
856
- "google/gemini-3.6-flash": {
857
- displayName: "Gemini 3.6 Flash",
858
- iconProvider: "google"
859
- },
860
- "google/gemini-3.5-flash-lite": {
861
- displayName: "Gemini 3.5 Flash Lite",
862
- iconProvider: "google"
610
+ kind: "flat",
611
+ inputPerMTok: 175e3,
612
+ outputPerMTok: 14e5,
613
+ cachedInputPerMTok: 17500
863
614
  },
864
- "google/gemini-3.1-pro-preview": {
865
- displayName: "Gemini 3.1 Pro Preview",
866
- iconProvider: "google"
615
+ "claude-sonnet-5": {
616
+ kind: "flat",
617
+ inputPerMTok: 2e5,
618
+ outputPerMTok: 1e6,
619
+ cachedInputPerMTok: 2e4,
620
+ cachedWriteInputPerMTok: 25e4
867
621
  },
868
- "google/gemini-3.5-flash": {
869
- displayName: "Gemini 3.5 Flash",
870
- iconProvider: "google"
622
+ "claude-fable-5-1": {
623
+ kind: "flat",
624
+ inputPerMTok: 1e6,
625
+ outputPerMTok: 5e6,
626
+ cachedInputPerMTok: 25e3,
627
+ cachedWriteInputPerMTok: 125e4
871
628
  },
872
- "google/gemini-3.1-flash-lite": {
873
- displayName: "Gemini 3.1 Flash Lite",
874
- iconProvider: "google"
629
+ "claude-fable-5": {
630
+ kind: "flat",
631
+ inputPerMTok: 1e6,
632
+ outputPerMTok: 5e6,
633
+ cachedInputPerMTok: 1e5,
634
+ cachedWriteInputPerMTok: 125e4
875
635
  },
876
- "anthropic/claude-sonnet-5": {
877
- displayName: "Claude Sonnet 5",
878
- iconProvider: "anthropic"
636
+ "claude-opus-5": {
637
+ kind: "flat",
638
+ inputPerMTok: 5e5,
639
+ outputPerMTok: 25e5,
640
+ cachedInputPerMTok: 5e4,
641
+ cachedWriteInputPerMTok: 625e3
879
642
  },
880
- "anthropic/claude-opus-5": {
881
- displayName: "Claude Opus 5",
882
- iconProvider: "anthropic"
643
+ "claude-opus-4-8": {
644
+ kind: "flat",
645
+ inputPerMTok: 5e5,
646
+ outputPerMTok: 25e5,
647
+ cachedInputPerMTok: 5e4,
648
+ cachedWriteInputPerMTok: 625e3
883
649
  },
884
- "anthropic/claude-opus-4.8": {
885
- displayName: "Claude Opus 4.8",
886
- iconProvider: "anthropic"
650
+ "claude-opus-4-7": {
651
+ kind: "flat",
652
+ inputPerMTok: 5e5,
653
+ outputPerMTok: 25e5,
654
+ cachedInputPerMTok: 5e4,
655
+ cachedWriteInputPerMTok: 625e3
887
656
  },
888
- "anthropic/claude-sonnet-4.6": {
889
- displayName: "Claude Sonnet 4.6",
890
- iconProvider: "anthropic"
657
+ "claude-sonnet-4-6": {
658
+ kind: "flat",
659
+ inputPerMTok: 3e5,
660
+ outputPerMTok: 15e5,
661
+ cachedInputPerMTok: 3e4,
662
+ cachedWriteInputPerMTok: 375e3
891
663
  },
892
- "openai/gpt-5.5": {
893
- displayName: "GPT-5.5",
894
- iconProvider: "openai"
664
+ "claude-opus-4-6": {
665
+ kind: "flat",
666
+ inputPerMTok: 5e5,
667
+ outputPerMTok: 25e5,
668
+ cachedInputPerMTok: 5e4,
669
+ cachedWriteInputPerMTok: 625e3
895
670
  },
896
- "openai/gpt-5.4-mini": {
897
- displayName: "GPT-5.4 Mini",
898
- iconProvider: "openai"
671
+ "claude-haiku-4-5-20251001": {
672
+ kind: "flat",
673
+ inputPerMTok: 1e5,
674
+ outputPerMTok: 5e5,
675
+ cachedInputPerMTok: 1e4,
676
+ cachedWriteInputPerMTok: 125e3
899
677
  },
900
678
  "us.anthropic.claude-sonnet-4-5-20250929-v1:0": {
901
- displayName: "Claude Sonnet 4.5",
902
- iconProvider: "anthropic"
679
+ kind: "flat",
680
+ inputPerMTok: 3e5,
681
+ outputPerMTok: 15e5,
682
+ cachedInputPerMTok: 3e4,
683
+ cachedWriteInputPerMTok: 375e3
903
684
  },
904
685
  "us.anthropic.claude-haiku-4-5-20251001-v1:0": {
905
- displayName: "Claude Haiku 4.5",
906
- iconProvider: "anthropic"
686
+ kind: "flat",
687
+ inputPerMTok: 1e5,
688
+ outputPerMTok: 5e5,
689
+ cachedInputPerMTok: 1e4,
690
+ cachedWriteInputPerMTok: 125e3
907
691
  },
908
692
  "us.amazon.nova-pro-v1:0": {
909
- displayName: "Amazon Nova Pro",
910
- iconProvider: "bedrock"
693
+ kind: "flat",
694
+ inputPerMTok: 8e4,
695
+ outputPerMTok: 32e4,
696
+ cachedInputPerMTok: 2e4
911
697
  },
912
698
  "us.amazon.nova-lite-v1:0": {
913
- displayName: "Amazon Nova Lite",
914
- iconProvider: "bedrock"
699
+ kind: "flat",
700
+ inputPerMTok: 6e3,
701
+ outputPerMTok: 24e3,
702
+ cachedInputPerMTok: 1500
915
703
  },
916
704
  "us.amazon.nova-micro-v1:0": {
917
- displayName: "Amazon Nova Micro",
918
- iconProvider: "bedrock"
705
+ kind: "flat",
706
+ inputPerMTok: 3500,
707
+ outputPerMTok: 14e3,
708
+ cachedInputPerMTok: 875
919
709
  },
920
710
  "openai.gpt-oss-120b-1:0": {
921
- displayName: "GPT OSS 120B",
922
- iconProvider: "openai"
711
+ kind: "flat",
712
+ inputPerMTok: 15e3,
713
+ outputPerMTok: 6e4
923
714
  },
924
715
  "openai.gpt-oss-20b-1:0": {
925
- displayName: "GPT OSS 20B",
926
- iconProvider: "openai"
716
+ kind: "flat",
717
+ inputPerMTok: 7e3,
718
+ outputPerMTok: 3e4
927
719
  },
928
720
  "us.deepseek.r1-v1:0": {
929
- displayName: "DeepSeek R1",
930
- iconProvider: "bedrock"
721
+ kind: "flat",
722
+ inputPerMTok: 135e3,
723
+ outputPerMTok: 54e4
931
724
  },
932
725
  "mistral-large-latest": {
933
- displayName: "Mistral Large",
934
- iconProvider: "mistral"
726
+ kind: "flat",
727
+ inputPerMTok: 5e4,
728
+ outputPerMTok: 15e4
935
729
  },
936
730
  "mistral-medium-latest": {
937
- displayName: "Mistral Medium",
938
- iconProvider: "mistral"
731
+ kind: "flat",
732
+ inputPerMTok: 15e4,
733
+ outputPerMTok: 75e4
939
734
  },
940
735
  "mistral-small-latest": {
941
- displayName: "Mistral Small",
942
- iconProvider: "mistral"
736
+ kind: "flat",
737
+ inputPerMTok: 15e3,
738
+ outputPerMTok: 6e4
943
739
  },
944
740
  "magistral-medium-latest": {
945
- displayName: "Magistral Medium",
946
- iconProvider: "mistral"
741
+ kind: "flat",
742
+ inputPerMTok: 2e5,
743
+ outputPerMTok: 5e5
947
744
  },
948
745
  "magistral-small": {
949
- displayName: "Magistral Small",
950
- iconProvider: "mistral"
746
+ kind: "flat",
747
+ inputPerMTok: 5e4,
748
+ outputPerMTok: 15e4
951
749
  },
952
750
  "pixtral-large-latest": {
953
- displayName: "Pixtral Large",
954
- iconProvider: "mistral"
955
- }
956
- };
957
- const MODEL_DISPLAY_METADATA_BY_ID = MODEL_DISPLAY_METADATA;
958
- const getModelDisplayMetadata = (modelId) => MODEL_DISPLAY_METADATA_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
959
- /**
960
- * Offered models whose provider API accepts PDF/file content. Generated from
961
- * live models.dev input modalities plus dated, reviewed source corrections;
962
- * the nightly upstream check rejects drift.
963
- */
964
- const BYOK_DOCUMENT_INPUT_MODEL_OPTIONS = MODEL_DOCUMENT_INPUT_OPTIONS;
965
- const isBYOKProviderRoleSupported = ({ provider, role }) => role !== "pdf" || BYOK_DOCUMENT_INPUT_MODEL_OPTIONS[provider].length > 0;
966
- const isBYOKModelRoleSupported = ({ provider, modelId, role }) => {
967
- if (role !== "pdf") return true;
968
- return BYOK_DOCUMENT_INPUT_MODEL_OPTIONS[provider].includes(modelId);
751
+ kind: "flat",
752
+ inputPerMTok: 2e5,
753
+ outputPerMTok: 6e5
754
+ },
755
+ "gemini-2.5-flash": {
756
+ kind: "flat",
757
+ inputPerMTok: 3e4,
758
+ outputPerMTok: 25e4,
759
+ cachedInputPerMTok: 3e3
760
+ },
761
+ "gemini-2.5-pro": {
762
+ kind: "input-token-tiered",
763
+ inputTokenThreshold: 2e5,
764
+ standard: {
765
+ inputPerMTok: 125e3,
766
+ outputPerMTok: 1e6,
767
+ cachedInputPerMTok: 12500
768
+ },
769
+ aboveThreshold: {
770
+ inputPerMTok: 25e4,
771
+ outputPerMTok: 15e5,
772
+ cachedInputPerMTok: 25e3
773
+ }
774
+ },
775
+ "gpt-4o-mini": {
776
+ kind: "flat",
777
+ inputPerMTok: 15e3,
778
+ outputPerMTok: 6e4,
779
+ cachedInputPerMTok: 7500
780
+ },
781
+ "gpt-4o": {
782
+ kind: "flat",
783
+ inputPerMTok: 25e4,
784
+ outputPerMTok: 1e6,
785
+ cachedInputPerMTok: 125e3
786
+ }
969
787
  };
788
+ //#endregion
789
+ //#region src/model-rate.ts
970
790
  /**
971
- * Models whose provider adapter can receive a PDF `document` content part in a
972
- * chat turn without throwing. This is a narrower, runtime question than the
973
- * pdf ROLE (`BYOK_DOCUMENT_INPUT_MODEL_OPTIONS`, which curates model selection
974
- * for PDF-processing flows): it only asks "will the chat stream survive this
975
- * attachment." The Mistral text adapter maps PDF `document` parts to
976
- * `document_url`, so its
977
- * vision models accept PDF attachments even though Mistral is deliberately not
978
- * offered as a pdf-role provider. Mistral's `document_url` takes PDF but NOT
979
- * textual documents (docx/txt/csv/md), so those still gate on
980
- * `BYOK_DOCUMENT_INPUT_MODEL_OPTIONS` (which excludes Mistral). Superset
981
- * invariant: every pdf-role document model also accepts a PDF chat attachment.
791
+ * models.dev publishes USD prices per one million tokens. Stella stores the
792
+ * same rates as integer ledger units so usage arithmetic never depends on
793
+ * floating point values.
982
794
  */
983
- const CHAT_PDF_ATTACHMENT_MODEL_OPTIONS = {
984
- ...BYOK_DOCUMENT_INPUT_MODEL_OPTIONS,
985
- mistral: [
986
- "mistral-medium-latest",
987
- "mistral-small-latest",
988
- "pixtral-large-latest"
989
- ]
990
- };
991
- const isChatPdfAttachmentModelSupported = ({ provider, modelId }) => {
992
- return CHAT_PDF_ATTACHMENT_MODEL_OPTIONS[provider].includes(modelId);
795
+ const MODEL_RATE_UNITS_PER_USD = 1e5;
796
+ const getStandardModelRate = (rate) => rate.kind === "flat" ? rate : rate.standard;
797
+ const resolveModelRate = (rate, inputTokens) => rate.kind === "input-token-tiered" && inputTokens > rate.inputTokenThreshold ? rate.aboveThreshold : getStandardModelRate(rate);
798
+ //#endregion
799
+ //#region src/model-rate-policy.ts
800
+ const MODELS_DEV_RATE_PROVIDER_BY_CATALOG_PROVIDER = {
801
+ google: "google",
802
+ openai: "openai",
803
+ anthropic: "anthropic",
804
+ bedrock: "amazon-bedrock",
805
+ mistral: "mistral"
993
806
  };
807
+ const defineRateSourceAliases = (aliases) => aliases;
994
808
  /**
995
- * Whether a model id is currently offered for this provider+role: it
996
- * must be in the curated catalog for the provider AND satisfy the
997
- * role's input-modality constraint (PDF needs a document-capable
998
- * model). This is the runtime allowlist, so a model dropped by a
999
- * catalog bump stops being valid here even though it is still a
1000
- * non-empty string in a stored org config.
809
+ * Explicit source coordinates for non-offered model IDs that remain valid in
810
+ * deployment overrides and usage attribution. Their numeric rates still come
811
+ * exclusively from models.dev.
1001
812
  */
1002
- const isOfferedBYOKModelForRole = ({ provider, modelId, role }) => {
1003
- return BYOK_MODEL_OPTIONS[provider].includes(modelId) && isBYOKModelRoleSupported({
1004
- provider,
1005
- modelId,
1006
- role
1007
- });
813
+ const RETAINED_MODELS_DEV_RATE_ENTRIES = {
814
+ "gemini-2.5-flash": {
815
+ modelId: "gemini-2.5-flash",
816
+ provider: "google"
817
+ },
818
+ "gemini-2.5-pro": {
819
+ modelId: "gemini-2.5-pro",
820
+ provider: "google"
821
+ },
822
+ "gpt-4o-mini": {
823
+ modelId: "gpt-4o-mini",
824
+ provider: "openai"
825
+ },
826
+ "gpt-4o": {
827
+ modelId: "gpt-4o",
828
+ provider: "openai"
829
+ }
1008
830
  };
1009
831
  /**
1010
- * Resolve a model id that will actually work for this provider+role,
1011
- * keeping the SAME provider. Returns the caller's model unchanged when
1012
- * it is still offered; otherwise falls back to the provider's per-role
1013
- * default (`BYOK_DEFAULT_MODELS`). Returns `null` only when the
1014
- * provider has no valid model for the role at all — the sole case
1015
- * today is `mistral` + `pdf`, because the TanStack Mistral adapter
1016
- * exposes no `document` input modality, so no Mistral model (not even
1017
- * the default) can serve the PDF role.
1018
- *
1019
- * Used to auto-heal org AI configs whose pinned model was removed by a
1020
- * catalog bump, so generation resolves to a supported model instead of
1021
- * 400-ing (or forwarding a retired id to the provider).
832
+ * Exact mappings where Stella uses a provider routing ID while models.dev
833
+ * publishes pricing under the corresponding base model ID. Generation rejects
834
+ * a mapping once models.dev begins publishing the Stella ID directly.
1022
835
  */
1023
- const resolveWorkingBYOKModelForRole = ({ provider, modelId, role }) => {
1024
- if (isOfferedBYOKModelForRole({
1025
- provider,
1026
- modelId,
1027
- role
1028
- })) return modelId;
1029
- const fallback = BYOK_DEFAULT_MODELS[provider][role];
1030
- return isOfferedBYOKModelForRole({
1031
- provider,
1032
- modelId: fallback,
1033
- role
1034
- }) ? fallback : null;
836
+ const MODELS_DEV_RATE_SOURCE_ALIASES = defineRateSourceAliases({
837
+ "us.amazon.nova-pro-v1:0": {
838
+ modelId: "amazon.nova-pro-v1:0",
839
+ provider: "amazon-bedrock",
840
+ reason: "2026-09-03: stella uses the Bedrock US inference-profile ID; models.dev prices its corresponding base model ID",
841
+ sourceUrl: "https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html"
842
+ },
843
+ "us.amazon.nova-lite-v1:0": {
844
+ modelId: "amazon.nova-lite-v1:0",
845
+ provider: "amazon-bedrock",
846
+ reason: "2026-09-03: stella uses the Bedrock US inference-profile ID; models.dev prices its corresponding base model ID",
847
+ sourceUrl: "https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html"
848
+ },
849
+ "us.amazon.nova-micro-v1:0": {
850
+ modelId: "amazon.nova-micro-v1:0",
851
+ provider: "amazon-bedrock",
852
+ reason: "2026-09-03: stella uses the Bedrock US inference-profile ID; models.dev prices its corresponding base model ID",
853
+ sourceUrl: "https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html"
854
+ }
855
+ });
856
+ //#endregion
857
+ //#region src/capabilities-overrides.ts
858
+ const CAPABILITY_OVERRIDES = {
859
+ "us.amazon.nova-pro-v1:0": {
860
+ documentInput: true,
861
+ documentInputReason: "2026-08-20: AWS documents PDF input for Nova Pro through Bedrock Converse",
862
+ reasoningEfforts: null,
863
+ temperatureSupported: true,
864
+ reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
865
+ },
866
+ "us.amazon.nova-lite-v1:0": {
867
+ documentInput: true,
868
+ documentInputReason: "2026-08-20: AWS documents PDF input for Nova Lite through Bedrock Converse",
869
+ reasoningEfforts: null,
870
+ temperatureSupported: true,
871
+ reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
872
+ },
873
+ "us.amazon.nova-micro-v1:0": {
874
+ documentInput: false,
875
+ documentInputReason: "2026-08-20: AWS documents Nova Micro as text-only",
876
+ reasoningEfforts: null,
877
+ temperatureSupported: true,
878
+ reason: "2026-07-20: absent from the models.dev amazon-bedrock catalog; Bedrock Converse accepts temperature and Nova has no reasoning dial"
879
+ }
1035
880
  };
881
+ //#endregion
882
+ //#region src/document-input-overrides.ts
1036
883
  /**
1037
- * Anthropic models that use the adaptive-thinking request shape
1038
- * (`thinking: { type: "adaptive" }`). Newer Claude models reject the
1039
- * legacy budget-based form, so every Opus 4.6+/Sonnet 4.6/Fable entry
1040
- * offered above must appear here or it will 400 on the reasoning role.
1041
- */
1042
- const ANTHROPIC_ADAPTIVE_THINKING_MODELS = [
1043
- "claude-sonnet-5",
1044
- "claude-opus-5",
1045
- "claude-sonnet-4-6",
1046
- "claude-opus-4-6",
1047
- "claude-opus-4-7",
1048
- "claude-opus-4-8",
1049
- "claude-fable-5",
1050
- "claude-fable-5-1"
1051
- ];
1052
- /**
1053
- * Canonical reasoning-effort ladder, ordered weakest to strongest.
1054
- * Union of every effort keyword the offered providers accept; no
1055
- * single model accepts all of them, which is exactly why the
1056
- * generated per-model `MODEL_REASONING_EFFORTS` exists.
1057
- */
1058
- const REASONING_EFFORTS = [
1059
- "none",
1060
- "minimal",
1061
- "low",
1062
- "medium",
1063
- "high",
1064
- "xhigh",
1065
- "max"
1066
- ];
1067
- const resolvedReasoningEffortSchema = v.pipe(v.picklist(REASONING_EFFORTS), v.brand("ResolvedReasoningEffort"));
1068
- /**
1069
- * Provider-native IDs that are exact aliases of an offered catalog ID.
1070
- * Every metadata lookup normalizes here, so instance/dev overrides cannot
1071
- * bypass rates or capabilities and duplicated alias rows cannot drift apart.
1072
- */
1073
- /**
1074
- * Per-provider model served when a user's included budget is exhausted
1075
- * and no explicit selection overrides it. Falls back to each
1076
- * provider's fast-role default; providers without a first-class
1077
- * adapter path cannot serve the lane. Total over providers so a new
1078
- * provider lands with an explicit decision.
884
+ * Reviewed document-input corrections for models.dev records that exist but
885
+ * under-report the provider API's file-input support. The generator rejects an
886
+ * override once upstream agrees, so sourced data takes ownership automatically.
1079
887
  */
1080
- const FALLBACK_CHAT_MODEL_BY_PROVIDER = {
1081
- google: DEFAULT_MODELS.google.fast,
1082
- openrouter: "openai/gpt-5.6-luna",
1083
- openai: "gpt-5.6-luna",
1084
- anthropic: DEFAULT_MODELS.anthropic.fast,
1085
- bedrock: DEFAULT_MODELS.bedrock.fast,
1086
- mistral: DEFAULT_MODELS.mistral.fast,
1087
- azure_foundry: null,
1088
- openai_compatible: null,
1089
- huggingface: null
1090
- };
1091
- const MODEL_CATALOG_ID_ALIASES = {
1092
- "gpt-5.6-sol": "gpt-5.6",
1093
- "claude-opus-4.8": "claude-opus-4-8",
1094
- "claude-sonnet-4.6": "claude-sonnet-4-6"
888
+ const DOCUMENT_INPUT_OVERRIDES = {
889
+ "gpt-5.4-mini": {
890
+ supported: true,
891
+ reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
892
+ },
893
+ "gpt-5.4-nano": {
894
+ supported: true,
895
+ reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
896
+ },
897
+ "gpt-5.2": {
898
+ supported: true,
899
+ reason: "2026-08-20: OpenAI Responses accepts input_file PDF content for this vision-capable model; models.dev currently omits the pdf modality"
900
+ }
1095
901
  };
1096
- const MODEL_CATALOG_ID_ALIAS_TARGET_BY_ID = MODEL_CATALOG_ID_ALIASES;
1097
- const normalizeModelCatalogId = (modelId) => MODEL_CATALOG_ID_ALIAS_TARGET_BY_ID[modelId] ?? modelId;
1098
- const MODEL_TEMPERATURE_POLICY_BY_ID = MODEL_TEMPERATURE_POLICIES;
902
+ //#endregion
903
+ //#region src/index.ts
1099
904
  /**
1100
- * Whether stella should emit a `temperature` override for this model.
1101
- * This is deliberately stricter than provider acceptance: deprecated
1102
- * parameters that are accepted but ignored resolve to `false`. Unknown ids
1103
- * also resolve to `false` (send nothing; provider default). Callers must
1104
- * never index `MODEL_TEMPERATURE_POLICIES` directly with
1105
- * a runtime string.
905
+ * Canonical AI provider and model catalog.
906
+ *
907
+ * Single source of truth for every provider/model identifier stella
908
+ * offers. Imported by the API runtime
909
+ * (`apps/api/src/lib/tanstack-ai-models.ts`)
910
+ * and the BYOK settings UI (`apps/web`), so the picker can never offer
911
+ * a model the backend rejects, and vice versa.
912
+ *
913
+ * Pure data: no provider SDKs, no env access, no side effects. This is
914
+ * deliberate so scripts (the nightly upstream-validation check) and
915
+ * both apps can import it cheaply. Keep it that way. The only runtime
916
+ * dependency is valibot, used solely to construct branded types the
917
+ * same way the rest of the repo does.
918
+ *
919
+ * Model IDs go stale when providers rename or retire models. The
920
+ * nightly `model-catalog-upstream` check
921
+ * (`packages/scripts/src/model-catalog-upstream.ts`) validates every
922
+ * ID here against live provider/aggregator listings so a retired model
923
+ * fails CI instead of 400-ing a user at runtime.
1106
924
  */
1107
- const shouldEmitTemperature = (modelId) => MODEL_TEMPERATURE_POLICY_BY_ID[normalizeModelCatalogId(modelId)] === "emit";
1108
- const MODEL_REASONING_EFFORTS_BY_ID = MODEL_REASONING_EFFORTS;
1109
925
  /**
1110
- * Declared reasoning-effort values for a model ID, or `null` when the
1111
- * model has no effort control or is not catalogued (custom deployments,
1112
- * env overrides). Callers must never index `MODEL_REASONING_EFFORTS`
1113
- * directly with a runtime string.
926
+ * Logical model roles. Call sites declare *what* they need, not
927
+ * *which* model to use.
928
+ *
929
+ * - fast: classification, extraction, short generation
930
+ * - chat: conversational with tool use and streaming
931
+ * - reasoning: complex multi-step legal analysis
932
+ * - pdf: native PDF/image understanding
1114
933
  */
1115
- const getModelReasoningEfforts = (modelId) => MODEL_REASONING_EFFORTS_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
1116
- const MODEL_DEFAULT_REASONING_EFFORTS_BY_ID = MODEL_DEFAULT_REASONING_EFFORTS;
934
+ const MODEL_ROLES = [
935
+ "fast",
936
+ "chat",
937
+ "reasoning",
938
+ "pdf"
939
+ ];
940
+ const TEMPERATURE_POLICIES = ["emit", "omit"];
941
+ const AI_PROVIDERS = [
942
+ "google",
943
+ "openrouter",
944
+ "openai",
945
+ "azure_foundry",
946
+ "anthropic",
947
+ "bedrock",
948
+ "mistral",
949
+ "openai_compatible",
950
+ "huggingface"
951
+ ];
952
+ const TANSTACK_AI_PROVIDERS = [
953
+ "google",
954
+ "openrouter",
955
+ "openai",
956
+ "anthropic",
957
+ "bedrock",
958
+ "mistral"
959
+ ];
960
+ const MODEL_CATALOG_PROVIDER_KIND = {
961
+ google: "first-party",
962
+ openrouter: "aggregator",
963
+ openai: "first-party",
964
+ anthropic: "first-party",
965
+ bedrock: "platform",
966
+ mistral: "first-party"
967
+ };
1117
968
  /**
1118
- * Concrete provider-default effort published for a model, or null when the
1119
- * provider exposes no named default. Unknown ids also resolve to null.
969
+ * Runtime form of the first-party provider union. The conditional annotation
970
+ * makes adding a first-party policy without adding its discovery implementation
971
+ * a compile error.
1120
972
  */
1121
- const getModelDefaultReasoningEffort = (modelId) => MODEL_DEFAULT_REASONING_EFFORTS_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
1122
- const asResolvedReasoningEffort = (effort) => v.parse(resolvedReasoningEffortSchema, effort);
973
+ const FIRST_PARTY_MODEL_PROVIDERS = [
974
+ "google",
975
+ "openai",
976
+ "anthropic",
977
+ "mistral"
978
+ ];
1123
979
  /**
1124
- * Clamp a requested reasoning effort into the target model's declared
1125
- * capability.
1126
- *
1127
- * - Unknown model or no effort control → `null` (send no effort at
1128
- * all; the provider default is the only universally safe choice).
1129
- * - Requested value supported → returned unchanged.
1130
- * - Otherwise → the supported value nearest on `REASONING_EFFORTS`,
1131
- * preferring the weaker side on ties, so a "none" request against a
1132
- * reasoning-mandatory model degrades to its weakest tier instead of
1133
- * erroring, and requests above the model's ceiling clamp down to it.
980
+ * Per-role default model IDs for the BYOK-capable cloud providers.
981
+ * Shared between the instance default table (`DEFAULT_MODELS`) and the
982
+ * settings-UI default selection, so a default is defined exactly once.
1134
983
  */
1135
- const resolveReasoningEffort = ({ modelId, requested }) => {
1136
- const efforts = getModelReasoningEfforts(modelId);
1137
- const weakest = efforts?.at(0);
1138
- if (efforts === null || weakest === void 0) return null;
1139
- if (efforts.includes(requested)) return asResolvedReasoningEffort(requested);
1140
- const requestedRank = REASONING_EFFORTS.indexOf(requested);
1141
- let nearest = weakest;
1142
- let nearestDistance = Number.POSITIVE_INFINITY;
1143
- for (const effort of efforts) {
1144
- const distance = Math.abs(REASONING_EFFORTS.indexOf(effort) - requestedRank);
1145
- if (distance < nearestDistance || distance === nearestDistance && REASONING_EFFORTS.indexOf(effort) < REASONING_EFFORTS.indexOf(nearest)) {
1146
- nearest = effort;
1147
- nearestDistance = distance;
1148
- }
984
+ const BYOK_DEFAULT_MODELS = {
985
+ google: {
986
+ fast: "gemini-3.7-flash",
987
+ chat: "gemini-3.7-flash",
988
+ reasoning: "gemini-3.7-flash",
989
+ pdf: "gemini-3.7-flash"
990
+ },
991
+ openrouter: {
992
+ fast: "openai/gpt-5.6-luna",
993
+ chat: "openai/gpt-5.6-terra",
994
+ reasoning: "openai/gpt-5.6-terra",
995
+ pdf: "openai/gpt-5.6-terra"
996
+ },
997
+ openai: {
998
+ fast: "gpt-5.4-nano",
999
+ chat: "gpt-5.4-mini",
1000
+ reasoning: "gpt-5.4",
1001
+ pdf: "gpt-5.4"
1002
+ },
1003
+ anthropic: {
1004
+ fast: "claude-opus-5",
1005
+ chat: "claude-opus-5",
1006
+ reasoning: "claude-opus-5",
1007
+ pdf: "claude-opus-5"
1008
+ },
1009
+ bedrock: {
1010
+ fast: "us.amazon.nova-micro-v1:0",
1011
+ chat: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
1012
+ reasoning: "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
1013
+ pdf: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
1014
+ },
1015
+ mistral: {
1016
+ fast: "mistral-small-latest",
1017
+ chat: "mistral-medium-latest",
1018
+ reasoning: "magistral-medium-latest",
1019
+ pdf: "mistral-large-latest"
1149
1020
  }
1150
- return asResolvedReasoningEffort(nearest);
1151
1021
  };
1152
- const getStandardModelRate = (rate) => rate.kind === "flat" ? rate : rate.standard;
1153
- const resolveModelRate = (rate, inputTokens) => rate.kind === "input-token-tiered" && inputTokens > rate.inputTokenThreshold ? rate.aboveThreshold : getStandardModelRate(rate);
1154
1022
  /**
1155
- * Providers whose catalog entries are first-party API model IDs and so
1156
- * must carry an explicit rate. Mirrors `MODELS_DEV_PROVIDER` in the
1157
- * nightly check; `openrouter` (provider-prefixed slugs) and the
1158
- * legacy/custom-deployment providers are metered by their underlying
1159
- * model IDs or the fallback rate.
1023
+ * Instance-level default model IDs per provider. Extends the BYOK
1024
+ * defaults with the providers that only the instance path uses
1025
+ * (custom deployments and OpenAI-compatible endpoints).
1160
1026
  */
1161
- const MODEL_RATES = {
1162
- "gemini-2.5-flash": {
1163
- kind: "flat",
1164
- inputPerMTok: 3e4,
1165
- outputPerMTok: 25e4,
1166
- cachedInputPerMTok: 7500
1167
- },
1168
- "gemini-2.5-pro": {
1169
- kind: "flat",
1170
- inputPerMTok: 125e3,
1171
- outputPerMTok: 1e6,
1172
- cachedInputPerMTok: 31250
1027
+ const DEFAULT_MODELS = {
1028
+ ...BYOK_DEFAULT_MODELS,
1029
+ azure_foundry: {
1030
+ fast: "gpt-5.4-nano",
1031
+ chat: "gpt-5.4-mini",
1032
+ reasoning: "gpt-5.4",
1033
+ pdf: "gpt-5.4"
1173
1034
  },
1174
- "gemini-3.1-flash-lite": {
1175
- kind: "flat",
1176
- inputPerMTok: 25e3,
1177
- outputPerMTok: 15e4,
1178
- cachedInputPerMTok: 2500
1035
+ openai_compatible: {
1036
+ fast: "default",
1037
+ chat: "default",
1038
+ reasoning: "default",
1039
+ pdf: "default"
1179
1040
  },
1180
- "gemini-3.5-flash": {
1181
- kind: "flat",
1182
- inputPerMTok: 15e4,
1183
- outputPerMTok: 9e5,
1184
- cachedInputPerMTok: 15e3
1041
+ huggingface: {
1042
+ fast: "speakleash/Bielik-1.5B-v3.0-Instruct",
1043
+ chat: "speakleash/Bielik-11B-v2.3-Instruct",
1044
+ reasoning: "speakleash/Bielik-11B-v2.3-Instruct",
1045
+ pdf: "speakleash/Bielik-11B-v2.3-Instruct"
1046
+ }
1047
+ };
1048
+ /**
1049
+ * BYOK-offered model IDs per provider — the curated catalog users pick
1050
+ * from in org settings, and the server-side allowlist the API enforces.
1051
+ * The frontend list is not a security boundary; this is what the API
1052
+ * accepts.
1053
+ *
1054
+ * Limited to providers the TanStack AI integration supports for BYOK.
1055
+ * Providers without a first-class TanStack adapter path are intentionally
1056
+ * not accepted at this boundary.
1057
+ */
1058
+ const BYOK_MODEL_OPTIONS = {
1059
+ google: [
1060
+ "gemini-3.8-flash",
1061
+ "gemini-3.7-flash",
1062
+ "gemini-3.6-flash",
1063
+ "gemini-3.5-flash-lite",
1064
+ "gemini-3.1-pro-preview",
1065
+ "gemini-3.5-flash",
1066
+ "gemini-3.1-flash-lite"
1067
+ ],
1068
+ anthropic: [
1069
+ "claude-sonnet-5",
1070
+ "claude-fable-5-1",
1071
+ "claude-fable-5",
1072
+ "claude-opus-5",
1073
+ "claude-opus-4-8",
1074
+ "claude-opus-4-7",
1075
+ "claude-sonnet-4-6",
1076
+ "claude-opus-4-6",
1077
+ "claude-haiku-4-5-20251001"
1078
+ ],
1079
+ openai: [
1080
+ "gpt-5.6",
1081
+ "gpt-5.6-terra",
1082
+ "gpt-5.6-luna",
1083
+ "gpt-5.5",
1084
+ "gpt-5.4",
1085
+ "gpt-5.4-mini",
1086
+ "gpt-5.4-nano",
1087
+ "gpt-5.2"
1088
+ ],
1089
+ openrouter: [
1090
+ "openai/gpt-5.6-luna",
1091
+ "openai/gpt-5.6-terra",
1092
+ "google/gemini-3.8-flash",
1093
+ "google/gemini-3.7-flash",
1094
+ "google/gemini-3.6-flash",
1095
+ "google/gemini-3.5-flash-lite",
1096
+ "google/gemini-3.1-pro-preview",
1097
+ "google/gemini-3.5-flash",
1098
+ "google/gemini-3.1-flash-lite",
1099
+ "anthropic/claude-sonnet-5",
1100
+ "anthropic/claude-opus-5",
1101
+ "anthropic/claude-opus-4.8",
1102
+ "anthropic/claude-sonnet-4.6",
1103
+ "openai/gpt-5.5",
1104
+ "openai/gpt-5.4-mini"
1105
+ ],
1106
+ bedrock: [
1107
+ "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
1108
+ "us.anthropic.claude-haiku-4-5-20251001-v1:0",
1109
+ "us.amazon.nova-pro-v1:0",
1110
+ "us.amazon.nova-lite-v1:0",
1111
+ "us.amazon.nova-micro-v1:0",
1112
+ "openai.gpt-oss-120b-1:0",
1113
+ "openai.gpt-oss-20b-1:0",
1114
+ "us.deepseek.r1-v1:0"
1115
+ ],
1116
+ mistral: [
1117
+ "mistral-large-latest",
1118
+ "mistral-medium-latest",
1119
+ "mistral-small-latest",
1120
+ "magistral-medium-latest",
1121
+ "magistral-small",
1122
+ "pixtral-large-latest"
1123
+ ]
1124
+ };
1125
+ /**
1126
+ * Presentation metadata for every offered model. Keeping this exhaustive beside
1127
+ * the allowlist prevents a newly offered model from leaking a raw provider ID
1128
+ * into user-facing pickers. OpenRouter entries deliberately carry their model
1129
+ * creator's icon while their routing provider remains available separately.
1130
+ */
1131
+ const MODEL_DISPLAY_METADATA = {
1132
+ "gemini-3.8-flash": {
1133
+ displayName: "Gemini 3.8 Flash",
1134
+ iconProvider: "google"
1185
1135
  },
1186
- "gemini-3.5-flash-lite": {
1187
- kind: "flat",
1188
- inputPerMTok: 3e4,
1189
- outputPerMTok: 25e4,
1190
- cachedInputPerMTok: 3e3
1136
+ "gemini-3.7-flash": {
1137
+ displayName: "Gemini 3.7 Flash",
1138
+ iconProvider: "google"
1191
1139
  },
1192
1140
  "gemini-3.6-flash": {
1193
- kind: "flat",
1194
- inputPerMTok: 75e3,
1195
- outputPerMTok: 375e3,
1196
- cachedInputPerMTok: 7500
1197
- },
1198
- "gemini-3.7-flash": {
1199
- kind: "flat",
1200
- inputPerMTok: 75e3,
1201
- outputPerMTok: 375e3,
1202
- cachedInputPerMTok: 7500
1141
+ displayName: "Gemini 3.6 Flash",
1142
+ iconProvider: "google"
1203
1143
  },
1204
- "gemini-3.8-flash": {
1205
- kind: "flat",
1206
- inputPerMTok: 75e3,
1207
- outputPerMTok: 375e3,
1208
- cachedInputPerMTok: 7500
1144
+ "gemini-3.5-flash-lite": {
1145
+ displayName: "Gemini 3.5 Flash Lite",
1146
+ iconProvider: "google"
1209
1147
  },
1210
1148
  "gemini-3.1-pro-preview": {
1211
- kind: "input-token-tiered",
1212
- inputTokenThreshold: 2e5,
1213
- standard: {
1214
- inputPerMTok: 2e5,
1215
- outputPerMTok: 12e5,
1216
- cachedInputPerMTok: 2e4
1217
- },
1218
- aboveThreshold: {
1219
- inputPerMTok: 4e5,
1220
- outputPerMTok: 18e5,
1221
- cachedInputPerMTok: 4e4
1222
- }
1149
+ displayName: "Gemini 3.1 Pro Preview",
1150
+ iconProvider: "google"
1151
+ },
1152
+ "gemini-3.5-flash": {
1153
+ displayName: "Gemini 3.5 Flash",
1154
+ iconProvider: "google"
1223
1155
  },
1224
- "gpt-4o-mini": {
1225
- kind: "flat",
1226
- inputPerMTok: 15e3,
1227
- outputPerMTok: 6e4,
1228
- cachedInputPerMTok: 7500
1156
+ "gemini-3.1-flash-lite": {
1157
+ displayName: "Gemini 3.1 Flash Lite",
1158
+ iconProvider: "google"
1229
1159
  },
1230
- "gpt-4o": {
1231
- kind: "flat",
1232
- inputPerMTok: 25e4,
1233
- outputPerMTok: 1e6,
1234
- cachedInputPerMTok: 125e3
1160
+ "claude-sonnet-5": {
1161
+ displayName: "Claude Sonnet 5",
1162
+ iconProvider: "anthropic"
1235
1163
  },
1236
- "gpt-5.2": {
1237
- kind: "flat",
1238
- inputPerMTok: 175e3,
1239
- outputPerMTok: 14e5,
1240
- cachedInputPerMTok: 17500
1164
+ "claude-fable-5-1": {
1165
+ displayName: "Claude Fable 5.1",
1166
+ iconProvider: "anthropic"
1241
1167
  },
1242
- "gpt-5.4-nano": {
1243
- kind: "flat",
1244
- inputPerMTok: 2e4,
1245
- outputPerMTok: 125e3,
1246
- cachedInputPerMTok: 2e3
1168
+ "claude-fable-5": {
1169
+ displayName: "Claude Fable 5",
1170
+ iconProvider: "anthropic"
1247
1171
  },
1248
- "gpt-5.4-mini": {
1249
- kind: "flat",
1250
- inputPerMTok: 75e3,
1251
- outputPerMTok: 45e4,
1252
- cachedInputPerMTok: 7500
1172
+ "claude-opus-5": {
1173
+ displayName: "Claude Opus 5",
1174
+ iconProvider: "anthropic"
1253
1175
  },
1254
- "gpt-5.4": {
1255
- kind: "input-token-tiered",
1256
- inputTokenThreshold: 272e3,
1257
- standard: {
1258
- inputPerMTok: 25e4,
1259
- outputPerMTok: 15e5,
1260
- cachedInputPerMTok: 25e3
1261
- },
1262
- aboveThreshold: {
1263
- inputPerMTok: 5e5,
1264
- outputPerMTok: 225e4,
1265
- cachedInputPerMTok: 5e4
1266
- }
1176
+ "claude-opus-4-8": {
1177
+ displayName: "Claude Opus 4.8",
1178
+ iconProvider: "anthropic"
1267
1179
  },
1268
- "gpt-5.5": {
1269
- kind: "input-token-tiered",
1270
- inputTokenThreshold: 272e3,
1271
- standard: {
1272
- inputPerMTok: 5e5,
1273
- outputPerMTok: 3e6,
1274
- cachedInputPerMTok: 5e4
1275
- },
1276
- aboveThreshold: {
1277
- inputPerMTok: 1e6,
1278
- outputPerMTok: 45e5,
1279
- cachedInputPerMTok: 1e5
1280
- }
1180
+ "claude-opus-4-7": {
1181
+ displayName: "Claude Opus 4.7",
1182
+ iconProvider: "anthropic"
1183
+ },
1184
+ "claude-sonnet-4-6": {
1185
+ displayName: "Claude Sonnet 4.6",
1186
+ iconProvider: "anthropic"
1187
+ },
1188
+ "claude-opus-4-6": {
1189
+ displayName: "Claude Opus 4.6",
1190
+ iconProvider: "anthropic"
1191
+ },
1192
+ "claude-haiku-4-5-20251001": {
1193
+ displayName: "Claude Haiku 4.5",
1194
+ iconProvider: "anthropic"
1281
1195
  },
1282
1196
  "gpt-5.6": {
1283
- kind: "input-token-tiered",
1284
- inputTokenThreshold: 272e3,
1285
- standard: {
1286
- inputPerMTok: 4e5,
1287
- outputPerMTok: 2e6,
1288
- cachedInputPerMTok: 4e4
1289
- },
1290
- aboveThreshold: {
1291
- inputPerMTok: 8e5,
1292
- outputPerMTok: 3e6,
1293
- cachedInputPerMTok: 8e4
1294
- }
1197
+ displayName: "GPT-5.6 Sol",
1198
+ iconProvider: "openai"
1199
+ },
1200
+ "gpt-5.6-terra": {
1201
+ displayName: "GPT-5.6 Terra",
1202
+ iconProvider: "openai"
1295
1203
  },
1296
1204
  "gpt-5.6-luna": {
1297
- kind: "input-token-tiered",
1298
- inputTokenThreshold: 272e3,
1299
- standard: {
1300
- inputPerMTok: 2e4,
1301
- outputPerMTok: 12e4,
1302
- cachedInputPerMTok: 2e3
1303
- },
1304
- aboveThreshold: {
1305
- inputPerMTok: 4e4,
1306
- outputPerMTok: 18e4,
1307
- cachedInputPerMTok: 4e3
1308
- }
1205
+ displayName: "GPT-5.6 Luna",
1206
+ iconProvider: "openai"
1309
1207
  },
1310
- "gpt-5.6-terra": {
1311
- kind: "input-token-tiered",
1312
- inputTokenThreshold: 272e3,
1313
- standard: {
1314
- inputPerMTok: 2e5,
1315
- outputPerMTok: 12e5,
1316
- cachedInputPerMTok: 2e4
1317
- },
1318
- aboveThreshold: {
1319
- inputPerMTok: 4e5,
1320
- outputPerMTok: 18e5,
1321
- cachedInputPerMTok: 4e4
1322
- }
1208
+ "gpt-5.5": {
1209
+ displayName: "GPT-5.5",
1210
+ iconProvider: "openai"
1323
1211
  },
1324
- "claude-haiku-4-5-20251001": {
1325
- kind: "flat",
1326
- inputPerMTok: 1e5,
1327
- outputPerMTok: 5e5,
1328
- cachedInputPerMTok: 1e4
1212
+ "gpt-5.4": {
1213
+ displayName: "GPT-5.4",
1214
+ iconProvider: "openai"
1329
1215
  },
1330
- "claude-sonnet-4-6": {
1331
- kind: "flat",
1332
- inputPerMTok: 3e5,
1333
- outputPerMTok: 15e5,
1334
- cachedInputPerMTok: 3e4
1216
+ "gpt-5.4-mini": {
1217
+ displayName: "GPT-5.4 Mini",
1218
+ iconProvider: "openai"
1335
1219
  },
1336
- "claude-sonnet-5": {
1337
- kind: "flat",
1338
- inputPerMTok: 2e5,
1339
- outputPerMTok: 1e6,
1340
- cachedInputPerMTok: 2e4
1220
+ "gpt-5.4-nano": {
1221
+ displayName: "GPT-5.4 Nano",
1222
+ iconProvider: "openai"
1341
1223
  },
1342
- "claude-opus-4-6": {
1343
- kind: "flat",
1344
- inputPerMTok: 5e5,
1345
- outputPerMTok: 25e5,
1346
- cachedInputPerMTok: 5e4
1224
+ "gpt-5.2": {
1225
+ displayName: "GPT-5.2",
1226
+ iconProvider: "openai"
1347
1227
  },
1348
- "claude-opus-4-7": {
1349
- kind: "flat",
1350
- inputPerMTok: 5e5,
1351
- outputPerMTok: 25e5,
1352
- cachedInputPerMTok: 5e4
1228
+ "openai/gpt-5.6-luna": {
1229
+ displayName: "GPT-5.6 Luna",
1230
+ iconProvider: "openai"
1353
1231
  },
1354
- "claude-opus-4-8": {
1355
- kind: "flat",
1356
- inputPerMTok: 5e5,
1357
- outputPerMTok: 25e5,
1358
- cachedInputPerMTok: 5e4
1232
+ "openai/gpt-5.6-terra": {
1233
+ displayName: "GPT-5.6 Terra",
1234
+ iconProvider: "openai"
1359
1235
  },
1360
- "claude-opus-5": {
1361
- kind: "flat",
1362
- inputPerMTok: 5e5,
1363
- outputPerMTok: 25e5,
1364
- cachedInputPerMTok: 5e4
1236
+ "google/gemini-3.8-flash": {
1237
+ displayName: "Gemini 3.8 Flash",
1238
+ iconProvider: "google"
1365
1239
  },
1366
- "claude-fable-5": {
1367
- kind: "flat",
1368
- inputPerMTok: 1e6,
1369
- outputPerMTok: 5e6,
1370
- cachedInputPerMTok: 1e5
1240
+ "google/gemini-3.7-flash": {
1241
+ displayName: "Gemini 3.7 Flash",
1242
+ iconProvider: "google"
1371
1243
  },
1372
- "claude-fable-5-1": {
1373
- kind: "flat",
1374
- inputPerMTok: 1e6,
1375
- outputPerMTok: 5e6,
1376
- cachedInputPerMTok: 25e3
1244
+ "google/gemini-3.6-flash": {
1245
+ displayName: "Gemini 3.6 Flash",
1246
+ iconProvider: "google"
1377
1247
  },
1378
- "mistral-small-latest": {
1379
- kind: "flat",
1380
- inputPerMTok: 15e3,
1381
- outputPerMTok: 6e4
1248
+ "google/gemini-3.5-flash-lite": {
1249
+ displayName: "Gemini 3.5 Flash Lite",
1250
+ iconProvider: "google"
1382
1251
  },
1383
- "mistral-large-latest": {
1384
- kind: "flat",
1385
- inputPerMTok: 5e4,
1386
- outputPerMTok: 15e4
1252
+ "google/gemini-3.1-pro-preview": {
1253
+ displayName: "Gemini 3.1 Pro Preview",
1254
+ iconProvider: "google"
1387
1255
  },
1388
- "mistral-medium-latest": {
1389
- kind: "flat",
1390
- inputPerMTok: 15e4,
1391
- outputPerMTok: 75e4
1256
+ "google/gemini-3.5-flash": {
1257
+ displayName: "Gemini 3.5 Flash",
1258
+ iconProvider: "google"
1392
1259
  },
1393
- "mistral-medium-3-5": {
1394
- kind: "flat",
1395
- inputPerMTok: 15e4,
1396
- outputPerMTok: 75e4
1260
+ "google/gemini-3.1-flash-lite": {
1261
+ displayName: "Gemini 3.1 Flash Lite",
1262
+ iconProvider: "google"
1397
1263
  },
1398
- "magistral-medium-latest": {
1399
- kind: "flat",
1400
- inputPerMTok: 2e5,
1401
- outputPerMTok: 5e5
1264
+ "anthropic/claude-sonnet-5": {
1265
+ displayName: "Claude Sonnet 5",
1266
+ iconProvider: "anthropic"
1402
1267
  },
1403
- "magistral-small": {
1404
- kind: "flat",
1405
- inputPerMTok: 5e4,
1406
- outputPerMTok: 15e4
1268
+ "anthropic/claude-opus-5": {
1269
+ displayName: "Claude Opus 5",
1270
+ iconProvider: "anthropic"
1407
1271
  },
1408
- "magistral-small-latest": {
1409
- kind: "flat",
1410
- inputPerMTok: 5e4,
1411
- outputPerMTok: 15e4
1272
+ "anthropic/claude-opus-4.8": {
1273
+ displayName: "Claude Opus 4.8",
1274
+ iconProvider: "anthropic"
1412
1275
  },
1413
- "pixtral-large-latest": {
1414
- kind: "flat",
1415
- inputPerMTok: 2e5,
1416
- outputPerMTok: 6e5
1276
+ "anthropic/claude-sonnet-4.6": {
1277
+ displayName: "Claude Sonnet 4.6",
1278
+ iconProvider: "anthropic"
1279
+ },
1280
+ "openai/gpt-5.5": {
1281
+ displayName: "GPT-5.5",
1282
+ iconProvider: "openai"
1283
+ },
1284
+ "openai/gpt-5.4-mini": {
1285
+ displayName: "GPT-5.4 Mini",
1286
+ iconProvider: "openai"
1417
1287
  },
1418
1288
  "us.anthropic.claude-sonnet-4-5-20250929-v1:0": {
1419
- kind: "flat",
1420
- inputPerMTok: 3e5,
1421
- outputPerMTok: 15e5,
1422
- cachedInputPerMTok: 3e4
1289
+ displayName: "Claude Sonnet 4.5",
1290
+ iconProvider: "anthropic"
1423
1291
  },
1424
1292
  "us.anthropic.claude-haiku-4-5-20251001-v1:0": {
1425
- kind: "flat",
1426
- inputPerMTok: 1e5,
1427
- outputPerMTok: 5e5,
1428
- cachedInputPerMTok: 1e4
1293
+ displayName: "Claude Haiku 4.5",
1294
+ iconProvider: "anthropic"
1429
1295
  },
1430
1296
  "us.amazon.nova-pro-v1:0": {
1431
- kind: "flat",
1432
- inputPerMTok: 8e4,
1433
- outputPerMTok: 32e4
1297
+ displayName: "Amazon Nova Pro",
1298
+ iconProvider: "bedrock"
1434
1299
  },
1435
1300
  "us.amazon.nova-lite-v1:0": {
1436
- kind: "flat",
1437
- inputPerMTok: 6e3,
1438
- outputPerMTok: 24e3
1301
+ displayName: "Amazon Nova Lite",
1302
+ iconProvider: "bedrock"
1439
1303
  },
1440
1304
  "us.amazon.nova-micro-v1:0": {
1441
- kind: "flat",
1442
- inputPerMTok: 3500,
1443
- outputPerMTok: 14e3
1305
+ displayName: "Amazon Nova Micro",
1306
+ iconProvider: "bedrock"
1444
1307
  },
1445
1308
  "openai.gpt-oss-120b-1:0": {
1446
- kind: "flat",
1447
- inputPerMTok: 15e3,
1448
- outputPerMTok: 6e4
1309
+ displayName: "GPT OSS 120B",
1310
+ iconProvider: "openai"
1449
1311
  },
1450
1312
  "openai.gpt-oss-20b-1:0": {
1451
- kind: "flat",
1452
- inputPerMTok: 7e3,
1453
- outputPerMTok: 3e4
1313
+ displayName: "GPT OSS 20B",
1314
+ iconProvider: "openai"
1454
1315
  },
1455
1316
  "us.deepseek.r1-v1:0": {
1456
- kind: "flat",
1457
- inputPerMTok: 135e3,
1458
- outputPerMTok: 54e4
1317
+ displayName: "DeepSeek R1",
1318
+ iconProvider: "bedrock"
1319
+ },
1320
+ "mistral-large-latest": {
1321
+ displayName: "Mistral Large",
1322
+ iconProvider: "mistral"
1323
+ },
1324
+ "mistral-medium-latest": {
1325
+ displayName: "Mistral Medium",
1326
+ iconProvider: "mistral"
1327
+ },
1328
+ "mistral-small-latest": {
1329
+ displayName: "Mistral Small",
1330
+ iconProvider: "mistral"
1331
+ },
1332
+ "magistral-medium-latest": {
1333
+ displayName: "Magistral Medium",
1334
+ iconProvider: "mistral"
1335
+ },
1336
+ "magistral-small": {
1337
+ displayName: "Magistral Small",
1338
+ iconProvider: "mistral"
1339
+ },
1340
+ "pixtral-large-latest": {
1341
+ displayName: "Pixtral Large",
1342
+ iconProvider: "mistral"
1343
+ }
1344
+ };
1345
+ const MODEL_DISPLAY_METADATA_BY_ID = MODEL_DISPLAY_METADATA;
1346
+ const getModelDisplayMetadata = (modelId) => MODEL_DISPLAY_METADATA_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
1347
+ /**
1348
+ * Offered models whose provider API accepts PDF/file content. Generated from
1349
+ * live models.dev input modalities plus dated, reviewed source corrections;
1350
+ * the nightly upstream check rejects drift.
1351
+ */
1352
+ const BYOK_DOCUMENT_INPUT_MODEL_OPTIONS = MODEL_DOCUMENT_INPUT_OPTIONS;
1353
+ const isBYOKProviderRoleSupported = ({ provider, role }) => role !== "pdf" || BYOK_DOCUMENT_INPUT_MODEL_OPTIONS[provider].length > 0;
1354
+ const isBYOKModelRoleSupported = ({ provider, modelId, role }) => {
1355
+ if (role !== "pdf") return true;
1356
+ return BYOK_DOCUMENT_INPUT_MODEL_OPTIONS[provider].includes(modelId);
1357
+ };
1358
+ /**
1359
+ * Models whose provider adapter can receive a PDF `document` content part in a
1360
+ * chat turn without throwing. This is a narrower, runtime question than the
1361
+ * pdf ROLE (`BYOK_DOCUMENT_INPUT_MODEL_OPTIONS`, which curates model selection
1362
+ * for PDF-processing flows): it only asks "will the chat stream survive this
1363
+ * attachment." The Mistral text adapter maps PDF `document` parts to
1364
+ * `document_url`, so its
1365
+ * vision models accept PDF attachments even though Mistral is deliberately not
1366
+ * offered as a pdf-role provider. Mistral's `document_url` takes PDF but NOT
1367
+ * textual documents (docx/txt/csv/md), so those still gate on
1368
+ * `BYOK_DOCUMENT_INPUT_MODEL_OPTIONS` (which excludes Mistral). Superset
1369
+ * invariant: every pdf-role document model also accepts a PDF chat attachment.
1370
+ */
1371
+ const CHAT_PDF_ATTACHMENT_MODEL_OPTIONS = {
1372
+ ...BYOK_DOCUMENT_INPUT_MODEL_OPTIONS,
1373
+ mistral: [
1374
+ "mistral-medium-latest",
1375
+ "mistral-small-latest",
1376
+ "pixtral-large-latest"
1377
+ ]
1378
+ };
1379
+ const isChatPdfAttachmentModelSupported = ({ provider, modelId }) => {
1380
+ return CHAT_PDF_ATTACHMENT_MODEL_OPTIONS[provider].includes(modelId);
1381
+ };
1382
+ /**
1383
+ * Whether a model id is currently offered for this provider+role: it
1384
+ * must be in the curated catalog for the provider AND satisfy the
1385
+ * role's input-modality constraint (PDF needs a document-capable
1386
+ * model). This is the runtime allowlist, so a model dropped by a
1387
+ * catalog bump stops being valid here even though it is still a
1388
+ * non-empty string in a stored org config.
1389
+ */
1390
+ const isOfferedBYOKModelForRole = ({ provider, modelId, role }) => {
1391
+ return BYOK_MODEL_OPTIONS[provider].includes(modelId) && isBYOKModelRoleSupported({
1392
+ provider,
1393
+ modelId,
1394
+ role
1395
+ });
1396
+ };
1397
+ /**
1398
+ * Resolve a model id that will actually work for this provider+role,
1399
+ * keeping the SAME provider. Returns the caller's model unchanged when
1400
+ * it is still offered; otherwise falls back to the provider's per-role
1401
+ * default (`BYOK_DEFAULT_MODELS`). Returns `null` only when the
1402
+ * provider has no valid model for the role at all — the sole case
1403
+ * today is `mistral` + `pdf`, because the TanStack Mistral adapter
1404
+ * exposes no `document` input modality, so no Mistral model (not even
1405
+ * the default) can serve the PDF role.
1406
+ *
1407
+ * Used to auto-heal org AI configs whose pinned model was removed by a
1408
+ * catalog bump, so generation resolves to a supported model instead of
1409
+ * 400-ing (or forwarding a retired id to the provider).
1410
+ */
1411
+ const resolveWorkingBYOKModelForRole = ({ provider, modelId, role }) => {
1412
+ if (isOfferedBYOKModelForRole({
1413
+ provider,
1414
+ modelId,
1415
+ role
1416
+ })) return modelId;
1417
+ const fallback = BYOK_DEFAULT_MODELS[provider][role];
1418
+ return isOfferedBYOKModelForRole({
1419
+ provider,
1420
+ modelId: fallback,
1421
+ role
1422
+ }) ? fallback : null;
1423
+ };
1424
+ /**
1425
+ * Anthropic models that use the adaptive-thinking request shape
1426
+ * (`thinking: { type: "adaptive" }`). Newer Claude models reject the
1427
+ * legacy budget-based form, so every Opus 4.6+/Sonnet 4.6/Fable entry
1428
+ * offered above must appear here or it will 400 on the reasoning role.
1429
+ */
1430
+ const ANTHROPIC_ADAPTIVE_THINKING_MODELS = [
1431
+ "claude-sonnet-5",
1432
+ "claude-opus-5",
1433
+ "claude-sonnet-4-6",
1434
+ "claude-opus-4-6",
1435
+ "claude-opus-4-7",
1436
+ "claude-opus-4-8",
1437
+ "claude-fable-5",
1438
+ "claude-fable-5-1"
1439
+ ];
1440
+ /**
1441
+ * Canonical reasoning-effort ladder, ordered weakest to strongest.
1442
+ * Union of every effort keyword the offered providers accept; no
1443
+ * single model accepts all of them, which is exactly why the
1444
+ * generated per-model `MODEL_REASONING_EFFORTS` exists.
1445
+ */
1446
+ const REASONING_EFFORTS = [
1447
+ "none",
1448
+ "minimal",
1449
+ "low",
1450
+ "medium",
1451
+ "high",
1452
+ "xhigh",
1453
+ "max"
1454
+ ];
1455
+ const resolvedReasoningEffortSchema = v.pipe(v.picklist(REASONING_EFFORTS), v.brand("ResolvedReasoningEffort"));
1456
+ /**
1457
+ * Per-provider model served when a user's included budget is exhausted
1458
+ * and no explicit selection overrides it. Falls back to each
1459
+ * provider's fast-role default; providers without a first-class
1460
+ * adapter path cannot serve the lane. Total over providers so a new
1461
+ * provider lands with an explicit decision.
1462
+ */
1463
+ const FALLBACK_CHAT_MODEL_BY_PROVIDER = {
1464
+ google: DEFAULT_MODELS.google.fast,
1465
+ openrouter: "openai/gpt-5.6-luna",
1466
+ openai: "gpt-5.6-luna",
1467
+ anthropic: DEFAULT_MODELS.anthropic.fast,
1468
+ bedrock: DEFAULT_MODELS.bedrock.fast,
1469
+ mistral: DEFAULT_MODELS.mistral.fast,
1470
+ azure_foundry: null,
1471
+ openai_compatible: null,
1472
+ huggingface: null
1473
+ };
1474
+ /**
1475
+ * Provider-native IDs that are exact aliases of an offered catalog ID.
1476
+ * Every metadata lookup normalizes here, so instance/dev overrides cannot
1477
+ * bypass rates or capabilities and duplicated alias rows cannot drift apart.
1478
+ */
1479
+ const MODEL_CATALOG_ID_ALIASES = {
1480
+ "gpt-5.6-sol": "gpt-5.6",
1481
+ "claude-opus-4.8": "claude-opus-4-8",
1482
+ "claude-sonnet-4.6": "claude-sonnet-4-6"
1483
+ };
1484
+ const MODEL_CATALOG_ID_ALIAS_TARGET_BY_ID = MODEL_CATALOG_ID_ALIASES;
1485
+ const normalizeModelCatalogId = (modelId) => MODEL_CATALOG_ID_ALIAS_TARGET_BY_ID[modelId] ?? modelId;
1486
+ const MODEL_TEMPERATURE_POLICY_BY_ID = MODEL_TEMPERATURE_POLICIES;
1487
+ /**
1488
+ * Whether stella should emit a `temperature` override for this model.
1489
+ * This is deliberately stricter than provider acceptance: deprecated
1490
+ * parameters that are accepted but ignored resolve to `false`. Unknown ids
1491
+ * also resolve to `false` (send nothing; provider default). Callers must
1492
+ * never index `MODEL_TEMPERATURE_POLICIES` directly with
1493
+ * a runtime string.
1494
+ */
1495
+ const shouldEmitTemperature = (modelId) => MODEL_TEMPERATURE_POLICY_BY_ID[normalizeModelCatalogId(modelId)] === "emit";
1496
+ const MODEL_REASONING_EFFORTS_BY_ID = MODEL_REASONING_EFFORTS;
1497
+ /**
1498
+ * Declared reasoning-effort values for a model ID, or `null` when the
1499
+ * model has no effort control or is not catalogued (custom deployments,
1500
+ * env overrides). Callers must never index `MODEL_REASONING_EFFORTS`
1501
+ * directly with a runtime string.
1502
+ */
1503
+ const getModelReasoningEfforts = (modelId) => MODEL_REASONING_EFFORTS_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
1504
+ const MODEL_DEFAULT_REASONING_EFFORTS_BY_ID = MODEL_DEFAULT_REASONING_EFFORTS;
1505
+ /**
1506
+ * Concrete provider-default effort published for a model, or null when the
1507
+ * provider exposes no named default. Unknown ids also resolve to null.
1508
+ */
1509
+ const getModelDefaultReasoningEffort = (modelId) => MODEL_DEFAULT_REASONING_EFFORTS_BY_ID[normalizeModelCatalogId(modelId)] ?? null;
1510
+ const asResolvedReasoningEffort = (effort) => v.parse(resolvedReasoningEffortSchema, effort);
1511
+ /**
1512
+ * Clamp a requested reasoning effort into the target model's declared
1513
+ * capability.
1514
+ *
1515
+ * - Unknown model or no effort control → `null` (send no effort at
1516
+ * all; the provider default is the only universally safe choice).
1517
+ * - Requested value supported → returned unchanged.
1518
+ * - Otherwise → the supported value nearest on `REASONING_EFFORTS`,
1519
+ * preferring the weaker side on ties, so a "none" request against a
1520
+ * reasoning-mandatory model degrades to its weakest tier instead of
1521
+ * erroring, and requests above the model's ceiling clamp down to it.
1522
+ */
1523
+ const resolveReasoningEffort = ({ modelId, requested }) => {
1524
+ const efforts = getModelReasoningEfforts(modelId);
1525
+ const weakest = efforts?.at(0);
1526
+ if (efforts === null || weakest === void 0) return null;
1527
+ if (efforts.includes(requested)) return asResolvedReasoningEffort(requested);
1528
+ const requestedRank = REASONING_EFFORTS.indexOf(requested);
1529
+ let nearest = weakest;
1530
+ let nearestDistance = Number.POSITIVE_INFINITY;
1531
+ for (const effort of efforts) {
1532
+ const distance = Math.abs(REASONING_EFFORTS.indexOf(effort) - requestedRank);
1533
+ if (distance < nearestDistance || distance === nearestDistance && REASONING_EFFORTS.indexOf(effort) < REASONING_EFFORTS.indexOf(nearest)) {
1534
+ nearest = effort;
1535
+ nearestDistance = distance;
1536
+ }
1459
1537
  }
1538
+ return asResolvedReasoningEffort(nearest);
1460
1539
  };
1461
1540
  const MODEL_RATES_BY_ID = MODEL_RATES;
1462
1541
  const OPENROUTER_UNDERLYING_MODEL_ID_BY_ID = Object.fromEntries(BYOK_MODEL_OPTIONS.openrouter.map((modelId) => [modelId, modelId.slice(modelId.indexOf("/") + 1)]));
@@ -1559,4 +1638,4 @@ const DEFAULT_CONTEXT_WINDOW_TOKENS = 128e3;
1559
1638
  */
1560
1639
  const getContextWindowTokens = (modelId) => CONTEXT_WINDOW_TOKENS_BY_ID[normalizeModelCatalogId(modelId)] ?? 128e3;
1561
1640
  //#endregion
1562
- export { AI_PROVIDERS, ANTHROPIC_ADAPTIVE_THINKING_MODELS, BYOK_DEFAULT_MODELS, BYOK_DOCUMENT_INPUT_MODEL_OPTIONS, BYOK_MODEL_OPTIONS, CAPABILITY_OVERRIDES, CHAT_PDF_ATTACHMENT_MODEL_OPTIONS, CONTEXT_WINDOW_TOKENS, DEFAULT_CONTEXT_WINDOW_TOKENS, DEFAULT_MODELS, DOCUMENT_INPUT_OVERRIDES, FALLBACK_CHAT_MODEL_BY_PROVIDER, FIRST_PARTY_MODEL_PROVIDERS, MODEL_CATALOG_ID_ALIASES, MODEL_CATALOG_PROVIDER_KIND, MODEL_DEFAULT_REASONING_EFFORTS, MODEL_DISPLAY_METADATA, MODEL_RATES, MODEL_REASONING_EFFORTS, MODEL_ROLES, MODEL_TEMPERATURE_POLICIES, REASONING_EFFORTS, TANSTACK_AI_PROVIDERS, TEMPERATURE_POLICIES, getContextWindowTokens, getModelDefaultReasoningEffort, getModelDisplayMetadata, getModelRate, getModelReasoningEfforts, getStandardModelRate, isBYOKModelRoleSupported, isBYOKProviderRoleSupported, isChatPdfAttachmentModelSupported, normalizeModelCatalogId, resolveModelRate, resolveReasoningEffort, resolveWorkingBYOKModelForRole, shouldEmitTemperature };
1641
+ export { AI_PROVIDERS, ANTHROPIC_ADAPTIVE_THINKING_MODELS, BYOK_DEFAULT_MODELS, BYOK_DOCUMENT_INPUT_MODEL_OPTIONS, BYOK_MODEL_OPTIONS, CAPABILITY_OVERRIDES, CHAT_PDF_ATTACHMENT_MODEL_OPTIONS, CONTEXT_WINDOW_TOKENS, DEFAULT_CONTEXT_WINDOW_TOKENS, DEFAULT_MODELS, DOCUMENT_INPUT_OVERRIDES, FALLBACK_CHAT_MODEL_BY_PROVIDER, FIRST_PARTY_MODEL_PROVIDERS, MODELS_DEV_RATE_PROVIDER_BY_CATALOG_PROVIDER, MODELS_DEV_RATE_SOURCE_ALIASES, MODEL_CATALOG_ID_ALIASES, MODEL_CATALOG_PROVIDER_KIND, MODEL_DEFAULT_REASONING_EFFORTS, MODEL_DISPLAY_METADATA, MODEL_RATES, MODEL_RATE_UNITS_PER_USD, MODEL_REASONING_EFFORTS, MODEL_ROLES, MODEL_TEMPERATURE_POLICIES, REASONING_EFFORTS, RETAINED_MODELS_DEV_RATE_ENTRIES, TANSTACK_AI_PROVIDERS, TEMPERATURE_POLICIES, getContextWindowTokens, getModelDefaultReasoningEffort, getModelDisplayMetadata, getModelRate, getModelReasoningEfforts, getStandardModelRate, isBYOKModelRoleSupported, isBYOKProviderRoleSupported, isChatPdfAttachmentModelSupported, normalizeModelCatalogId, resolveModelRate, resolveReasoningEffort, resolveWorkingBYOKModelForRole, shouldEmitTemperature };