@oh-my-pi/pi-catalog 17.3.5 → 17.3.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.
@@ -73,8 +73,10 @@ export function resolveModelCacheProviderId(providerId: string, options: ModelCa
73
73
  case "openrouter":
74
74
  return "openrouter:pseudo-api";
75
75
  case "vllm": {
76
+ // v2: qwen3.8 rows cached before the reasoning/template-effort upgrade
77
+ // carry `reasoning: false` and must be refetched.
76
78
  const baseUrl = options.baseUrl ?? getDefaultModelDiscoveryBaseUrl(providerId)!;
77
- return `vllm:${Bun.hash(baseUrl).toString(36)}`;
79
+ return `vllm:models-v2:${Bun.hash(baseUrl).toString(36)}`;
78
80
  }
79
81
  default:
80
82
  return providerId;
@@ -474,17 +474,18 @@ export const CATALOG_PROVIDERS = [
474
474
  defaultModel: "openai/gpt-oss-120b",
475
475
  envVars: ["COREWEAVE_API_KEY", "WANDB_API_KEY"],
476
476
  createModelManagerOptions: (config: ModelManagerConfig) => coreWeaveModelManagerOptions(config),
477
+ dynamicModelsAuthoritative: true,
477
478
  catalogDiscovery: { label: "CoreWeave Serverless Inference" },
478
479
  },
479
480
  {
480
481
  id: "xai",
481
- defaultModel: "grok-4.5",
482
+ defaultModel: "grok-4.6",
482
483
  envVars: ["XAI_API_KEY"],
483
484
  createModelManagerOptions: (config: ModelManagerConfig) => xaiModelManagerOptions(config),
484
485
  },
485
486
  {
486
487
  id: "xai-oauth",
487
- defaultModel: "grok-4.5",
488
+ defaultModel: "grok-4.6",
488
489
  envVars: ["XAI_OAUTH_TOKEN", "XAI_API_KEY"],
489
490
  createModelManagerOptions: (config: ModelManagerConfig) => xaiOAuthModelManagerOptions(config),
490
491
  catalogDiscovery: {
@@ -16,6 +16,7 @@ import {
16
16
  isGrokReasoningEffortCapable,
17
17
  isKimiK3ModelId,
18
18
  isKimiModelId,
19
+ isQwen38PlusTemplateEffortModelId,
19
20
  isReasoningGlmModelId,
20
21
  } from "../identity/family";
21
22
  import { resolveModelReference } from "../identity/reference";
@@ -1073,10 +1074,61 @@ export interface GmiCloudModelManagerConfig {
1073
1074
  fetch?: FetchImpl;
1074
1075
  }
1075
1076
 
1077
+ /**
1078
+ * Map a discovered GMI Cloud model to a full spec.
1079
+ *
1080
+ * GMI's `/v1/models` returns only bare `{id}` rows, so discovery defaults carry
1081
+ * no limits, reasoning, or thinking metadata. When a gmi-cloud bundled
1082
+ * reference exists (the seeded default) it supplies GMI's published tariff and
1083
+ * limits directly. Every other id is an open-weight model GMI resells under its
1084
+ * canonical id (`deepseek-ai/…`, `moonshotai/…`, `zai-org/…`, `Qwen/…`), so its
1085
+ * intrinsic capabilities — context window, output limit, reasoning, thinking
1086
+ * ladder — are recovered from any bundled upstream entry via the canonical
1087
+ * reference index. Pricing is deliberately never borrowed across providers:
1088
+ * GMI's per-model tariff is unknown for these ids, so cost stays zeroed rather
1089
+ * than inheriting another provider's rate.
1090
+ */
1091
+ function mapGmiCloudModel(
1092
+ entry: OpenAICompatibleModelRecord,
1093
+ defaults: ModelSpec<"openai-completions">,
1094
+ reference: ModelSpec<"openai-completions"> | undefined,
1095
+ ): ModelSpec<"openai-completions"> {
1096
+ if (reference) {
1097
+ return mapWithBundledReference(entry, defaults, reference);
1098
+ }
1099
+ const canonical = resolveModelReference(defaults.id, getBundledModelReferenceIndex()) as
1100
+ | ModelSpec<"openai-completions">
1101
+ | undefined;
1102
+ if (!canonical) {
1103
+ return { ...defaults, name: toModelName(entry.name, defaults.name) };
1104
+ }
1105
+ const contextWindow = canonical.contextWindow ?? defaults.contextWindow;
1106
+ const maxTokens =
1107
+ canonical.maxTokens != null && contextWindow != null
1108
+ ? Math.min(canonical.maxTokens, contextWindow)
1109
+ : (canonical.maxTokens ?? defaults.maxTokens);
1110
+ return {
1111
+ ...defaults,
1112
+ name: toModelName(entry.name, canonical.name ?? defaults.name),
1113
+ reasoning: canonical.reasoning,
1114
+ input: canonical.input,
1115
+ ...(canonical.thinking && { thinking: canonical.thinking }),
1116
+ contextWindow,
1117
+ maxTokens,
1118
+ };
1119
+ }
1120
+
1076
1121
  export function gmiCloudModelManagerOptions(
1077
1122
  config?: GmiCloudModelManagerConfig,
1078
1123
  ): ModelManagerOptions<"openai-completions"> {
1079
- return createSimpleOpenAICompletionsOptions("gmi-cloud", GMI_CLOUD_BASE_URL, config);
1124
+ return createOpenAICompatibleModelManagerOptions({
1125
+ api: "openai-completions",
1126
+ providerId: "gmi-cloud",
1127
+ defaultBaseUrl: GMI_CLOUD_BASE_URL,
1128
+ config,
1129
+ requireApiKey: true,
1130
+ mapModel: mapGmiCloudModel,
1131
+ });
1080
1132
  }
1081
1133
 
1082
1134
  // ---------------------------------------------------------------------------
@@ -2980,6 +3032,10 @@ export const ALIBABA_TOKEN_PLAN_DISCOVERED_MODEL_LIMITS: Readonly<Record<string,
2980
3032
  contextWindow: 1_000_000,
2981
3033
  maxTokens: 384_000,
2982
3034
  },
3035
+ "deepseek-v4-pro-0813": {
3036
+ contextWindow: 1_000_000,
3037
+ maxTokens: 384_000,
3038
+ },
2983
3039
  "deepseek-v3.2": {
2984
3040
  contextWindow: 131_072,
2985
3041
  maxTokens: 65_536,
@@ -4994,6 +5050,11 @@ export function vllmModelManagerOptions(config?: VllmModelManagerConfig): ModelM
4994
5050
  return {
4995
5051
  ...model,
4996
5052
  contextWindow: toPositiveNumber(entry.max_model_len, model.contextWindow),
5053
+ // vLLM's /v1/models reports no reasoning capability. Qwen 3.8+
5054
+ // open weights always think (the template cannot disable it), so
5055
+ // light up the effort dial; buildModel derives the template
5056
+ // ladder from the id + local-backend compat.
5057
+ reasoning: model.reasoning || isQwen38PlusTemplateEffortModelId(model.id),
4997
5058
  };
4998
5059
  },
4999
5060
  fetch: config?.fetch,
@@ -5071,8 +5132,18 @@ export interface GithubCopilotModelManagerConfig {
5071
5132
 
5072
5133
  const COPILOT_ANTHROPIC_MODEL_PATTERN = /^claude-(haiku|sonnet|opus|fable|mythos)-\d/;
5073
5134
  const isCopilotResponsesModelId = (modelId: string): boolean =>
5074
- modelId === "grok-4.5" || modelId.startsWith("gpt-5") || modelId.startsWith("oswe") || modelId.startsWith("mai-");
5075
- const COPILOT_CACHE_INVALIDATED_MODEL_IDS = ["grok-4.5", "grok-4.5-1m", "mai-code-1-flash-picker"];
5135
+ modelId === "grok-4.5" ||
5136
+ modelId === "grok-4.6" ||
5137
+ modelId.startsWith("gpt-5") ||
5138
+ modelId.startsWith("oswe") ||
5139
+ modelId.startsWith("mai-");
5140
+ const COPILOT_CACHE_INVALIDATED_MODEL_IDS = [
5141
+ "grok-4.5",
5142
+ "grok-4.5-1m",
5143
+ "grok-4.6",
5144
+ "grok-4.6-1m",
5145
+ "mai-code-1-flash-picker",
5146
+ ];
5076
5147
 
5077
5148
  function inferCopilotApi(modelId: string): Api {
5078
5149
  if (COPILOT_ANTHROPIC_MODEL_PATTERN.test(modelId)) {
@@ -5705,8 +5776,17 @@ const OPENCODE_ZEN_API_RESOLUTION = createOpenCodeApiResolution("https://opencod
5705
5776
  // /zen/go/v1/chat/completions route does not work for this model while
5706
5777
  // /zen/go/v1/responses does (user-verified against the live gateway,
5707
5778
  // 2026-08-08; Flash only — deepseek-v4-pro serves fine on chat completions).
5779
+ //
5780
+ // muse-spark-1.2 / muse-spark-1.2-contributor are the same inverse case: the
5781
+ // Go gateway's /zen/go/v1/models discovery drops the `provider.npm` hint, so
5782
+ // without an override they fall through to openai-completions even though the
5783
+ // gateway only serves them at /zen/go/v1/responses (@ai-sdk/openai per
5784
+ // https://opencode.ai/docs/go/#endpoints). The completions parser then closes
5785
+ // the stream with no finish_reason on every tool-call turn (#8957).
5708
5786
  const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen/go", {
5709
5787
  "deepseek-v4-flash": "openai-responses",
5788
+ "muse-spark-1.2": "openai-responses",
5789
+ "muse-spark-1.2-contributor": "openai-responses",
5710
5790
  "minimax-m2.7": "openai-completions",
5711
5791
  "minimax-m3": "openai-completions",
5712
5792
  "minimax-m3-free": "openai-completions",
package/src/types.ts CHANGED
@@ -259,6 +259,16 @@ export interface OpenAICompat {
259
259
  * Non-Qwen templates ignore the flag, so the auto-detection is safe.
260
260
  */
261
261
  qwenPreserveThinking?: boolean;
262
+ /**
263
+ * Route the requested thinking effort onto the Qwen 3.8+ chat template's
264
+ * `reasoning_effort` kwarg (`low`/`medium`/`xhigh`; template default
265
+ * `xhigh`). Emitted inside `chat_template_kwargs` for both Qwen dialects
266
+ * (plus the top-level field on the `qwen` dialect, which newer llama.cpp
267
+ * builds map natively). Without it the qwen dialects only toggle
268
+ * `enable_thinking` and the template always thinks at its `xhigh` default.
269
+ * Default: auto-detected (Qwen 3.8+ id on a local llama.cpp-style backend).
270
+ */
271
+ qwenTemplateReasoningEffort?: boolean;
262
272
  /** Whether assistant tool-call messages must include non-empty content. Default: false. */
263
273
  requiresAssistantContentForToolCalls?: boolean;
264
274
  /** Whether the provider supports the `tool_choice` parameter. Default: true. */
@@ -604,6 +614,7 @@ export interface ResolvedOpenAISharedCompat {
604
614
  allowsSyntheticReasoningContentForToolCalls: boolean;
605
615
  replayReasoningContent: boolean;
606
616
  qwenPreserveThinking: boolean;
617
+ qwenTemplateReasoningEffort: boolean;
607
618
  requiresThinkingAsText: boolean;
608
619
  requiresMistralToolIds: boolean;
609
620
  requiresToolResultName: boolean;
@@ -669,6 +680,7 @@ export type ResolvedOpenAICompat = ResolvedOpenAISharedCompat &
669
680
  | "allowsSyntheticReasoningContentForToolCalls"
670
681
  | "replayReasoningContent"
671
682
  | "qwenPreserveThinking"
683
+ | "qwenTemplateReasoningEffort"
672
684
  | "requiresThinkingAsText"
673
685
  | "requiresMistralToolIds"
674
686
  | "requiresToolResultName"
@@ -111,19 +111,20 @@ function thinkingPair(baseId: string, name: string): EffortVariantFamily {
111
111
  };
112
112
  }
113
113
 
114
- type DevinTierRoutes = Partial<Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", string>>;
114
+ type TierRoutes = Partial<Record<"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", string>>;
115
115
 
116
116
  /** Devin families with a `-max` sibling: five wire tiers, `low` floor. */
117
117
  const DEVIN_FIVE_TIER_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
118
118
  /** Pre-5.6 Devin GPT families top out at `-xhigh`: four wire tiers, `low` floor. */
119
119
  const DEVIN_FOUR_TIER_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
120
120
 
121
- function devinTierFamily(
122
- id: string,
123
- name: string,
124
- routes: DevinTierRoutes,
125
- efforts: readonly Effort[],
126
- ): EffortVariantFamily {
121
+ /**
122
+ * Build one effort-tier family from a tier→wire-id map: routing keeps only the
123
+ * listed efforts, `members` dedupes the targets in tier order, and thinking is
124
+ * `mode: "effort"` (mandatory when the family has no `off` route). Shared by the
125
+ * Devin and Cursor tables, whose per-effort siblings follow the same shape.
126
+ */
127
+ function tierFamily(id: string, name: string, routes: TierRoutes, efforts: readonly Effort[]): EffortVariantFamily {
127
128
  const routing: Partial<Record<Effort | "off", string>> = {};
128
129
  if (routes.off) routing.off = routes.off;
129
130
  for (const effort of efforts) {
@@ -177,7 +178,7 @@ function devinTierFamily(
177
178
  function devinGpt56Families(variant: "luna" | "sol" | "terra", name: string): readonly EffortVariantFamily[] {
178
179
  const base = `gpt-5-6-${variant}`;
179
180
  return [
180
- devinTierFamily(
181
+ tierFamily(
181
182
  base,
182
183
  name,
183
184
  {
@@ -190,7 +191,7 @@ function devinGpt56Families(variant: "luna" | "sol" | "terra", name: string): re
190
191
  },
191
192
  DEVIN_FIVE_TIER_EFFORTS,
192
193
  ),
193
- devinTierFamily(
194
+ tierFamily(
194
195
  `${base}-fast`,
195
196
  `${name} Fast`,
196
197
  {
@@ -395,7 +396,7 @@ export const GEMINI_CLI_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
395
396
  };
396
397
  export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
397
398
  families: [
398
- devinTierFamily(
399
+ tierFamily(
399
400
  "claude-opus-5",
400
401
  "Claude Opus 5",
401
402
  {
@@ -407,7 +408,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
407
408
  },
408
409
  DEVIN_FIVE_TIER_EFFORTS,
409
410
  ),
410
- devinTierFamily(
411
+ tierFamily(
411
412
  "claude-opus-5-fast",
412
413
  "Claude Opus 5 Fast",
413
414
  {
@@ -419,7 +420,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
419
420
  },
420
421
  DEVIN_FIVE_TIER_EFFORTS,
421
422
  ),
422
- devinTierFamily(
423
+ tierFamily(
423
424
  "claude-fable-5",
424
425
  "Claude Fable 5",
425
426
  {
@@ -431,7 +432,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
431
432
  },
432
433
  DEVIN_FIVE_TIER_EFFORTS,
433
434
  ),
434
- devinTierFamily(
435
+ tierFamily(
435
436
  "claude-sonnet-5",
436
437
  "Claude Sonnet 5",
437
438
  {
@@ -443,7 +444,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
443
444
  },
444
445
  DEVIN_FIVE_TIER_EFFORTS,
445
446
  ),
446
- devinTierFamily(
447
+ tierFamily(
447
448
  "claude-opus-4-7",
448
449
  "Claude Opus 4.7",
449
450
  {
@@ -455,7 +456,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
455
456
  },
456
457
  DEVIN_FIVE_TIER_EFFORTS,
457
458
  ),
458
- devinTierFamily(
459
+ tierFamily(
459
460
  "claude-opus-4-7-fast",
460
461
  "Claude Opus 4.7 Fast",
461
462
  {
@@ -467,7 +468,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
467
468
  },
468
469
  DEVIN_FIVE_TIER_EFFORTS,
469
470
  ),
470
- devinTierFamily(
471
+ tierFamily(
471
472
  "claude-opus-4-8",
472
473
  "Claude Opus 4.8",
473
474
  {
@@ -479,7 +480,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
479
480
  },
480
481
  DEVIN_FIVE_TIER_EFFORTS,
481
482
  ),
482
- devinTierFamily(
483
+ tierFamily(
483
484
  "claude-opus-4-8-fast",
484
485
  "Claude Opus 4.8 Fast",
485
486
  {
@@ -491,7 +492,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
491
492
  },
492
493
  DEVIN_FIVE_TIER_EFFORTS,
493
494
  ),
494
- devinTierFamily(
495
+ tierFamily(
495
496
  "gpt-5-2",
496
497
  "GPT-5.2",
497
498
  {
@@ -503,7 +504,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
503
504
  },
504
505
  DEVIN_FOUR_TIER_EFFORTS,
505
506
  ),
506
- devinTierFamily(
507
+ tierFamily(
507
508
  "gpt-5-3-codex",
508
509
  "GPT-5.3 Codex",
509
510
  {
@@ -514,7 +515,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
514
515
  },
515
516
  DEVIN_FOUR_TIER_EFFORTS,
516
517
  ),
517
- devinTierFamily(
518
+ tierFamily(
518
519
  "gpt-5-3-codex-fast",
519
520
  "GPT-5.3 Codex Fast",
520
521
  {
@@ -525,7 +526,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
525
526
  },
526
527
  DEVIN_FOUR_TIER_EFFORTS,
527
528
  ),
528
- devinTierFamily(
529
+ tierFamily(
529
530
  "gpt-5-4",
530
531
  "GPT-5.4",
531
532
  {
@@ -537,7 +538,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
537
538
  },
538
539
  DEVIN_FOUR_TIER_EFFORTS,
539
540
  ),
540
- devinTierFamily(
541
+ tierFamily(
541
542
  "gpt-5-4-fast",
542
543
  "GPT-5.4 Fast",
543
544
  {
@@ -549,7 +550,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
549
550
  },
550
551
  DEVIN_FOUR_TIER_EFFORTS,
551
552
  ),
552
- devinTierFamily(
553
+ tierFamily(
553
554
  "gpt-5-4-mini",
554
555
  "GPT-5.4 Mini",
555
556
  {
@@ -560,7 +561,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
560
561
  },
561
562
  DEVIN_FOUR_TIER_EFFORTS,
562
563
  ),
563
- devinTierFamily(
564
+ tierFamily(
564
565
  "gpt-5-5",
565
566
  "GPT-5.5",
566
567
  {
@@ -572,7 +573,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
572
573
  },
573
574
  DEVIN_FOUR_TIER_EFFORTS,
574
575
  ),
575
- devinTierFamily(
576
+ tierFamily(
576
577
  "gpt-5-5-fast",
577
578
  "GPT-5.5 Fast",
578
579
  {
@@ -587,7 +588,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
587
588
  ...devinGpt56Families("luna", "GPT-5.6 Luna"),
588
589
  ...devinGpt56Families("sol", "GPT-5.6 Sol"),
589
590
  ...devinGpt56Families("terra", "GPT-5.6 Terra"),
590
- devinTierFamily(
591
+ tierFamily(
591
592
  "kimi-k3",
592
593
  "Kimi K3",
593
594
  {
@@ -597,7 +598,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
597
598
  },
598
599
  [Effort.Low, Effort.High, Effort.Max],
599
600
  ),
600
- devinTierFamily(
601
+ tierFamily(
601
602
  "swe-1-7",
602
603
  "SWE-1.7",
603
604
  {
@@ -606,7 +607,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
606
607
  },
607
608
  [Effort.Medium, Effort.Max],
608
609
  ),
609
- devinTierFamily(
610
+ tierFamily(
610
611
  "grok-4-5",
611
612
  "Grok 4.5",
612
613
  {
@@ -616,7 +617,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
616
617
  },
617
618
  [Effort.Low, Effort.Medium, Effort.High],
618
619
  ),
619
- devinTierFamily(
620
+ tierFamily(
620
621
  "inkling",
621
622
  "Inkling",
622
623
  {
@@ -629,7 +630,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
629
630
  },
630
631
  DEVIN_FIVE_TIER_EFFORTS,
631
632
  ),
632
- devinTierFamily(
633
+ tierFamily(
633
634
  "gemini-3-1-pro",
634
635
  "Gemini 3.1 Pro",
635
636
  {
@@ -638,7 +639,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
638
639
  },
639
640
  [Effort.Low, Effort.High],
640
641
  ),
641
- devinTierFamily(
642
+ tierFamily(
642
643
  "gemini-3-5-flash",
643
644
  "Gemini 3.5 Flash",
644
645
  {
@@ -649,7 +650,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
649
650
  },
650
651
  [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High],
651
652
  ),
652
- devinTierFamily(
653
+ tierFamily(
653
654
  "gemini-3-6-flash",
654
655
  "Gemini 3.6 Flash",
655
656
  {
@@ -660,7 +661,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
660
661
  },
661
662
  [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High],
662
663
  ),
663
- devinTierFamily(
664
+ tierFamily(
664
665
  "gemini-3-flash",
665
666
  "Gemini 3 Flash",
666
667
  {
@@ -694,7 +695,7 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
694
695
  },
695
696
  // GLM-5.2 1M — paid variants that consume weekly quota. Collapse the
696
697
  // three 1M-context variants into one entry with proper effort routing.
697
- devinTierFamily(
698
+ tierFamily(
698
699
  "glm-5-2-1m",
699
700
  "GLM-5.2 1M",
700
701
  {
@@ -707,11 +708,43 @@ export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
707
708
  ],
708
709
  };
709
710
 
711
+ /** Cursor's Grok tier tokens equal the effort id, so routing is a direct map. */
712
+ const CURSOR_GROK_45_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
713
+ const CURSOR_GROK_46_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
714
+
715
+ /**
716
+ * Cursor serves Grok 4.5/4.6 as per-effort sibling ids
717
+ * (`cursor-grok-4.6-low|-medium|-high|-xhigh`) alongside a parallel `-fast`
718
+ * service-tier lane (`-low-fast`, …). Each lane collapses into its own logical
719
+ * model — the `-fast` axis is a sibling family, never a second routing
720
+ * dimension — with effort routing onto the live sibling wire ids.
721
+ */
722
+ function cursorGrokFamilies(version: "4.5" | "4.6", efforts: readonly Effort[]): readonly EffortVariantFamily[] {
723
+ const build = (fast: boolean): EffortVariantFamily => {
724
+ const suffix = fast ? "-fast" : "";
725
+ const routes: TierRoutes = {};
726
+ for (const effort of efforts) {
727
+ routes[effort] = `cursor-grok-${version}-${effort}${suffix}`;
728
+ }
729
+ return tierFamily(`cursor-grok-${version}${suffix}`, `Grok ${version}${fast ? " Fast" : ""}`, routes, efforts);
730
+ };
731
+ return [build(false), build(true)];
732
+ }
733
+
734
+ /** `cursor` Grok families: per-effort siblings collapsed per service-tier lane. */
735
+ export const CURSOR_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
736
+ families: [
737
+ ...cursorGrokFamilies("4.5", CURSOR_GROK_45_EFFORTS),
738
+ ...cursorGrokFamilies("4.6", CURSOR_GROK_46_EFFORTS),
739
+ ],
740
+ };
741
+
710
742
  /** Provider id → hand collapse table. The CCA providers diverge on thinking transport. */
711
743
  export const VARIANT_COLLAPSE_TABLES: Readonly<Record<string, VariantCollapseTable>> = {
712
744
  "google-antigravity": ANTIGRAVITY_VARIANT_COLLAPSE_TABLE,
713
745
  "google-gemini-cli": GEMINI_CLI_VARIANT_COLLAPSE_TABLE,
714
746
  devin: DEVIN_VARIANT_COLLAPSE_TABLE,
747
+ cursor: CURSOR_VARIANT_COLLAPSE_TABLE,
715
748
  };
716
749
 
717
750
  /**