From 58ced35dc06d97a7016f53c07dce12d55c620b68 Mon Sep 17 00:00:00 2001 From: roboomp Date: Mon, 3 Aug 2026 21:48:22 +0000 Subject: [PATCH] fix(catalog): honored disabled DeepSeek thinking Moved the direct DeepSeek enabled toggle into the thinking-only compat variant and normalized stale cached compat metadata before request encoding. Fixes #7559 --- packages/ai/test/issue-1207-repro.test.ts | 35 ++++++++++++++++++----- packages/catalog/CHANGELOG.md | 4 +++ packages/catalog/src/compat/openai.ts | 25 ++++++++++++++-- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/packages/ai/test/issue-1207-repro.test.ts b/packages/ai/test/issue-1207-repro.test.ts index d2f0c4541..096398e4a 100644 --- a/packages/ai/test/issue-1207-repro.test.ts +++ b/packages/ai/test/issue-1207-repro.test.ts @@ -25,16 +25,22 @@ function abortedSignal(): AbortSignal { return controller.signal; } +interface CaptureOptions { + tools?: Tool[]; + reasoning?: Effort; + disableReasoning?: boolean; +} + async function capturePayload( model: Model<"openai-completions">, - tools?: Tool[], - reasoning: "high" | "max" = "high", + options: CaptureOptions = {}, ): Promise> { const { promise, resolve } = Promise.withResolvers(); - streamOpenAICompletions(model, contextWithTools(tools), { + streamOpenAICompletions(model, contextWithTools(options.tools), { apiKey: "test-key", signal: abortedSignal(), - reasoning, + reasoning: options.disableReasoning ? undefined : (options.reasoning ?? Effort.High), + disableReasoning: options.disableReasoning, toolChoice: "auto", maxTokens: 123, onPayload: payload => resolve(payload), @@ -42,7 +48,7 @@ async function capturePayload( return (await promise) as Record; } -function customDeepseekFlash(): Model<"openai-completions"> { +function customDeepseekFlash(legacyThinkingExtraBody = false): Model<"openai-completions"> { return buildModel({ ...getBundledModel("openai", "gpt-4o-mini"), api: "openai-completions", @@ -54,6 +60,7 @@ function customDeepseekFlash(): Model<"openai-completions"> { compat: { supportsReasoningEffort: true, reasoningEffortMap: { xhigh: "max" }, + ...(legacyThinkingExtraBody ? { extraBody: { thinking: { type: "enabled" } } } : {}), }, } as ModelSpec<"openai-completions">); } @@ -65,7 +72,9 @@ describe("issue #1207 — DeepSeek V4 keeps reasoning with tools", () => { expect(compat.supportsToolChoice).toBe(false); expect(compat.maxTokensField).toBe("max_tokens"); - expect(compat.extraBody).toEqual({ thinking: { type: "enabled" } }); + expect(compat.extraBody).toBeUndefined(); + expect(compat.reasoningDisableMode).toBe("zai-thinking-disabled"); + expect(compat.whenThinking?.extraBody).toEqual({ thinking: { type: "enabled" } }); // DeepSeek's reasoning_effort is the honest wire-exact high/max pair; // no synthetic lower tiers, no alias map. expect(model.thinking?.efforts).toEqual([Effort.High, Effort.Max]); @@ -93,6 +102,18 @@ describe("issue #1207 — DeepSeek V4 keeps reasoning with tools", () => { expect(body.max_completion_tokens).toBeUndefined(); }); + it("disables thinking for bundled and legacy cached model definitions", async () => { + const bundled = getBundledModel("deepseek", "deepseek-v4-flash") as Model<"openai-completions">; + const legacyCached = customDeepseekFlash(true); + + for (const model of [bundled, legacyCached]) { + const body = await capturePayload(model, { disableReasoning: true }); + expect(model.compat.extraBody).toBeUndefined(); + expect(body.reasoning_effort).toBeUndefined(); + expect(body.thinking).toEqual({ type: "disabled" }); + } + }); + it("does not mix Fireworks DeepSeek effort with the native thinking toggle", async () => { const model = getBundledModel("fireworks", "deepseek-v4-pro") as Model<"openai-completions">; const compat = model.compat; @@ -127,7 +148,7 @@ describe("issue #1207 — DeepSeek V4 keeps reasoning with tools", () => { paths: "(string | string[])?", }), }; - const body = await capturePayload(model, [unionTool]); + const body = await capturePayload(model, { tools: [unionTool] }); const tools = body.tools as Array<{ function: { parameters: Record } }>; const properties = tools[0].function.parameters.properties as Record>; const branches = properties.paths.anyOf as Array>; diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 804fbf1ec..074b16d65 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -6,6 +6,10 @@ - Replaced arktype with `@oh-my-pi/omptype` for discovery payload schemas (same definition DSL, lazy JIT validation). +### Fixed + +- Fixed `thinking-level: off` still enabling reasoning on direct DeepSeek V4 requests by emitting the provider's disabled thinking toggle and migrating stale cached model metadata ([#7559](https://github.com/can1357/oh-my-pi/issues/7559)). + ## [17.2.6] - 2026-08-03 ### Added diff --git a/packages/catalog/src/compat/openai.ts b/packages/catalog/src/compat/openai.ts index dc29945f7..c7f97b4e1 100644 --- a/packages/catalog/src/compat/openai.ts +++ b/packages/catalog/src/compat/openai.ts @@ -583,7 +583,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv wireModelIdMode, isVercelGatewayHost: isVercelGateway, supportsStrictMode: detectStrictModeSupport(provider, baseUrl), - extraBody: isDirectDeepseekReasoning ? { thinking: { type: "enabled" } } : undefined, + extraBody: undefined, toolStrictMode: isCerebras ? "all_strict" : "mixed", // Kimi-family ids trigger MFJS on any host, not just native base URLs: // proxies (OpenRouter, custom gateways) forward `tools.function.parameters` @@ -604,10 +604,24 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv }; applyCompatOverrides(compat, spec.compat); + const deepseekThinking = compat.extraBody?.thinking; + if ( + isDirectDeepseekReasoning && + typeof deepseekThinking === "object" && + deepseekThinking !== null && + "type" in deepseekThinking && + deepseekThinking.type === "enabled" + ) { + const extraBody = { ...compat.extraBody }; + delete extraBody.thinking; + compat.extraBody = Object.keys(extraBody).length > 0 ? extraBody : undefined; + } if (spec.compat?.reasoningDisableMode === undefined) { compat.reasoningDisableMode = requiresEnabledThinking ? "omit" - : resolveReasoningDisableMode(compat.thinkingFormat); + : isDirectDeepseekReasoning + ? "zai-thinking-disabled" + : resolveReasoningDisableMode(compat.thinkingFormat); } if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) { compat.omitReasoningEffort = true; @@ -615,7 +629,12 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv mergeModelReasoningEffortMap(compat, spec.id, isMimoReasoningEffortModel); const whenThinkingPolicy = - spec.compat?.whenThinking ?? (isOpenCodeProvider && spec.reasoning ? OPENCODE_WHEN_THINKING : undefined); + spec.compat?.whenThinking ?? + (isDirectDeepseekReasoning + ? { extraBody: { ...compat.extraBody, thinking: { type: "enabled" } } } + : isOpenCodeProvider && spec.reasoning + ? OPENCODE_WHEN_THINKING + : undefined); if (whenThinkingPolicy) { const variant: ResolvedOpenAICompat = { ...compat }; applyCompatOverrides(variant, whenThinkingPolicy);