Skip to content

Commit 7dc3633

Browse files
committed
fix: disable tools for deepseek-reasoner and strip <think> tags from text parts
DeepSeek R1 (deepseek-reasoner) does not honour the tools parameter on the standard api.deepseek.com endpoint despite models.dev reporting tool_call: true. When tools are sent, R1 ignores them and writes the invocation as markdown text, causing tool calls to appear as plain text instead of being executed. - llm.ts: disable canTool for deepseek-reasoner so tools are never sent to it, avoiding the markdown-text fallback entirely - processor.ts: strip <think>...</think> blocks from text parts for reasoning models that emit chain-of-thought inline rather than as dedicated reasoning events, preventing raw thinking tokens from appearing in the UI
1 parent 4bf976e commit 7dc3633

2 files changed

Lines changed: 16 additions & 1 deletion

File tree

packages/opencode/src/session/llm.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,15 @@ const live: Layer.Layer<
193193
},
194194
)
195195

196-
const canTool = input.model.capabilities.toolcall
196+
// DeepSeek R1 (deepseek-reasoner) does not honour the `tools` parameter on the
197+
// standard api.deepseek.com endpoint despite models.dev reporting tool_call: true.
198+
// When tools are sent, R1 ignores the definitions and writes the invocation as
199+
// markdown text inside its response — exactly the wrong behaviour. Disable tools
200+
// for it so the model falls back to conversational output instead.
201+
const isDeepSeekR1 =
202+
input.model.providerID === "deepseek" &&
203+
input.model.api.id.toLowerCase().includes("reasoner")
204+
const canTool = input.model.capabilities.toolcall && !isDeepSeekR1
197205
const tools = canTool ? resolveTools(input) : {}
198206

199207
// LiteLLM and some Anthropic proxies require the tools parameter to be present

packages/opencode/src/session/processor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,13 @@ export const layer: Layer.Layer<
442442
},
443443
{ text: ctx.currentText.text },
444444
)).text
445+
// Some reasoning models (e.g. DeepSeek R1) emit chain-of-thought inside
446+
// <think>…</think> tags in the text stream rather than as dedicated
447+
// reasoning events. Strip those blocks so they don't appear as visible
448+
// text in the UI — they are stored separately via reasoning-start/delta.
449+
if (ctx.model.capabilities.reasoning) {
450+
ctx.currentText.text = ctx.currentText.text.replace(/<think>[\s\S]*?<\/think>\s*/g, "").trimStart()
451+
}
445452
{
446453
const end = Date.now()
447454
ctx.currentText.time = { start: ctx.currentText.time?.start ?? end, end }

0 commit comments

Comments
 (0)