From 6cdad6906b4619844c1829d8bae1fa443052f6bf Mon Sep 17 00:00:00 2001 From: dixoxib Date: Sun, 26 Apr 2026 18:14:37 +0200 Subject: [PATCH] fix(opencode): ensure reasoning_content for DeepSeek models, add thinking: enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DeepSeek requires reasoning_content in providerOptions.openaiCompatible for all assistant messages, even when empty (the API rejects requests without it). The generic interleaved handling only runs when capabilities.interleaved is set to an object with a field, which may not always be the case for DeepSeek models depending on models.dev data. - Merge the reasoning part padding into a single DeepSeek‑specific block that extracts reasoning parts to providerOptions.openaiCompatible.reasoning_content and strips them from content (with return, skipping generic interleaved) - Add result["thinking"] = { type: "enabled" } in options() so DeepSeek reasoning models return reasoning_content in the first place --- packages/opencode/src/provider/transform.ts | 38 +++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index b2e9b59a3ac6..9ede41c6aacf 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -175,20 +175,29 @@ function normalizeMessages( return result } - // Deepseek requires all assistant messages to have reasoning on them + // DeepSeek requires reasoning_content in providerOptions for all assistant messages. + // Always include it even when empty to satisfy the API requirement. if (model.api.id.includes("deepseek")) { - msgs = msgs.map((msg) => { + return msgs.map((msg) => { if (msg.role !== "assistant") return msg - if (Array.isArray(msg.content)) { - if (msg.content.some((part) => part.type === "reasoning")) return msg - return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] } - } + const parts = Array.isArray(msg.content) + ? msg.content + : msg.content + ? [{ type: "text" as const, text: msg.content as string }] + : [] + const reasoningParts = parts.filter((part: any) => part.type === "reasoning") + const reasoningText = reasoningParts.map((part: any) => part.text).join("") + const filteredContent = parts.filter((part: any) => part.type !== "reasoning") return { ...msg, - content: [ - ...(msg.content ? [{ type: "text" as const, text: msg.content }] : []), - { type: "reasoning" as const, text: "" }, - ], + content: filteredContent, + providerOptions: { + ...msg.providerOptions, + openaiCompatible: { + ...msg.providerOptions?.openaiCompatible, + reasoning_content: reasoningText, + }, + }, } }) } @@ -907,6 +916,15 @@ export function options(input: { result["enable_thinking"] = true } + // Enable thinking for DeepSeek reasoning models. + // DeepSeek API requires thinking to be enabled to return reasoning_content. + if ( + (input.model.api.id.includes("deepseek") || input.model.providerID.includes("deepseek")) && + input.model.capabilities.reasoning + ) { + result["thinking"] = { type: "enabled" } + } + if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) { if (!input.model.api.id.includes("gpt-5-pro")) { result["reasoningEffort"] = "medium"