diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 86a00ef43..2290e013e 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixed - Fixed raw `COPILOT_GITHUB_TOKEN` credentials skipping plan-specific endpoint discovery, which routed GitHub Copilot Business model requests to the personal endpoint and returned HTTP 403. The GitHub Copilot model cache is now scoped per credential, so switching the token no longer serves another account's stale endpoint for the cache TTL ([#8507](https://github.com/can1357/oh-my-pi/issues/8507)). +- Fixed the OpenRouter `deepseek/deepseek-v4-pro-0813` route silently clamping the reasoning effort to `high`: the dated SKU advertises (and accepts) the wire-exact `low`/`high`/`max` ladder, so its effort override no longer collapses to `high`-only. The undated `deepseek/deepseek-v4-pro` OpenRouter route stays `high`-only. ([#8517](https://github.com/can1357/oh-my-pi/issues/8517)) ## [17.3.2] - 2026-08-13 diff --git a/packages/catalog/src/model-thinking.ts b/packages/catalog/src/model-thinking.ts index f83237574..be0ba1151 100644 --- a/packages/catalog/src/model-thinking.ts +++ b/packages/catalog/src/model-thinking.ts @@ -374,13 +374,20 @@ function getModelDefinedEfforts( // on every first-party/aggregator host — the direct API, aggregators, and // Ollama Cloud alike (medium/xhigh fold into high, max is a real wire // tier). See https://api-docs.deepseek.com/api/create-chat-completion. - // OpenRouter's non-Flash V4 route still exposes only high; the older - // reasoners (V3.x, R1, deepseek-reasoner) top out at high/max. + // OpenRouter's non-Flash V4 route exposes only high, except the dated + // `deepseek-v4-pro-0813` SKU: its /models metadata advertises (and the + // route accepts) the full low/high/max ladder like every other host. + // The older reasoners (V3.x, R1, deepseek-reasoner) top out at high/max. if (isDeepseekV4FlashModelId(spec.id)) { return LOW_HIGH_MAX_REASONING_EFFORTS; } if (bareModelId(spec.id).toLowerCase().includes("deepseek-v4")) { - return isOpenRouterThinkingFormat(compat) ? HIGH_ONLY_REASONING_EFFORTS : LOW_HIGH_MAX_REASONING_EFFORTS; + if (!isOpenRouterThinkingFormat(compat)) { + return LOW_HIGH_MAX_REASONING_EFFORTS; + } + return bareModelId(spec.id).toLowerCase() === "deepseek-v4-pro-0813" + ? LOW_HIGH_MAX_REASONING_EFFORTS + : HIGH_ONLY_REASONING_EFFORTS; } return isOpenRouterThinkingFormat(compat) ? HIGH_ONLY_REASONING_EFFORTS : HIGH_MAX_REASONING_EFFORTS; } diff --git a/packages/catalog/test/model-thinking.test.ts b/packages/catalog/test/model-thinking.test.ts index 908bd2c36..e05ec9865 100644 --- a/packages/catalog/test/model-thinking.test.ts +++ b/packages/catalog/test/model-thinking.test.ts @@ -314,6 +314,34 @@ describe("model thinking derivation", () => { expect(getSupportedEfforts(v32)).toEqual([Effort.High, Effort.Max]); }); + it("grants the low/high/max ladder to OpenRouter deepseek-v4-pro-0813 but not the undated route (issue #8517)", () => { + // OpenRouter's /models advertises reasoning.supported_efforts + // [low, high, max] for the dated SKU; the discovered ladder is baked + // into thinking.efforts. + const discovered = { mode: "effort" as const, efforts: [Effort.Low, Effort.High, Effort.Max] }; + const dated = createModel({ + id: "deepseek/deepseek-v4-pro-0813", + api: "openrouter", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + thinking: discovered, + }); + const bare = createModel({ + id: "deepseek/deepseek-v4-pro", + api: "openrouter", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + thinking: discovered, + }); + + // The dated SKU keeps its advertised ladder; :max no longer clamps. + expect(getSupportedEfforts(dated)).toEqual([Effort.Low, Effort.High, Effort.Max]); + expect(clampThinkingLevelForModel(dated, Effort.Max)).toBe(Effort.Max); + // The undated OpenRouter route stays high-only. + expect(getSupportedEfforts(bare)).toEqual([Effort.High]); + expect(clampThinkingLevelForModel(bare, Effort.Max)).toBe(Effort.High); + }); + it("encodes the Gemini 3 Pro effort gap and mandatory reasoning in metadata", () => { const model = createModel({ id: "gemini-3-pro-preview",