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