@stll/ai-catalog 0.1.6 → 0.1.8

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