Skip to content

Commit 6cdad69

Browse files
committed
fix(opencode): ensure reasoning_content for DeepSeek models, add thinking: enabled
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
1 parent d03e6ce commit 6cdad69

1 file changed

Lines changed: 28 additions & 10 deletions

File tree

packages/opencode/src/provider/transform.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,20 +175,29 @@ function normalizeMessages(
175175
return result
176176
}
177177

178-
// Deepseek requires all assistant messages to have reasoning on them
178+
// DeepSeek requires reasoning_content in providerOptions for all assistant messages.
179+
// Always include it even when empty to satisfy the API requirement.
179180
if (model.api.id.includes("deepseek")) {
180-
msgs = msgs.map((msg) => {
181+
return msgs.map((msg) => {
181182
if (msg.role !== "assistant") return msg
182-
if (Array.isArray(msg.content)) {
183-
if (msg.content.some((part) => part.type === "reasoning")) return msg
184-
return { ...msg, content: [...msg.content, { type: "reasoning", text: "" }] }
185-
}
183+
const parts = Array.isArray(msg.content)
184+
? msg.content
185+
: msg.content
186+
? [{ type: "text" as const, text: msg.content as string }]
187+
: []
188+
const reasoningParts = parts.filter((part: any) => part.type === "reasoning")
189+
const reasoningText = reasoningParts.map((part: any) => part.text).join("")
190+
const filteredContent = parts.filter((part: any) => part.type !== "reasoning")
186191
return {
187192
...msg,
188-
content: [
189-
...(msg.content ? [{ type: "text" as const, text: msg.content }] : []),
190-
{ type: "reasoning" as const, text: "" },
191-
],
193+
content: filteredContent,
194+
providerOptions: {
195+
...msg.providerOptions,
196+
openaiCompatible: {
197+
...msg.providerOptions?.openaiCompatible,
198+
reasoning_content: reasoningText,
199+
},
200+
},
192201
}
193202
})
194203
}
@@ -907,6 +916,15 @@ export function options(input: {
907916
result["enable_thinking"] = true
908917
}
909918

919+
// Enable thinking for DeepSeek reasoning models.
920+
// DeepSeek API requires thinking to be enabled to return reasoning_content.
921+
if (
922+
(input.model.api.id.includes("deepseek") || input.model.providerID.includes("deepseek")) &&
923+
input.model.capabilities.reasoning
924+
) {
925+
result["thinking"] = { type: "enabled" }
926+
}
927+
910928
if (input.model.api.id.includes("gpt-5") && !input.model.api.id.includes("gpt-5-chat")) {
911929
if (!input.model.api.id.includes("gpt-5-pro")) {
912930
result["reasoningEffort"] = "medium"

0 commit comments

Comments
 (0)