Skip to content

fix(opencode): ignore tool calls emitted inside reasoning blocks#30277

Open
danmaxis wants to merge 1 commit into
anomalyco:devfrom
danmaxis:fix/reasoning-tool-call-guard
Open

fix(opencode): ignore tool calls emitted inside reasoning blocks#30277
danmaxis wants to merge 1 commit into
anomalyco:devfrom
danmaxis:fix/reasoning-tool-call-guard

Conversation

@danmaxis
Copy link
Copy Markdown

@danmaxis danmaxis commented Jun 1, 2026

Issue for this PR

Closes #6708

(Same underlying behavior was also reported in #8851 and #10996, now closed.)

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Reasoning models (Qwen, Kimi K2, GLM, ...) sometimes emit tool-call markup inside their <think> reasoning while still thinking. The inference server promotes that to a structured tool call, and since opencode's tools carry an execute, streamText runs it — firing a side effect and ending the turn before the model produces its real answer.

By the time the assistant message exists, the tool call is already a structured tool_calls entry with no trace of where it came from. The ordering is still visible at the provider stream level, so the fix is a small LanguageModelV3 wrapStream middleware (reasoning-tool-guard.ts) added next to the existing transformParams middleware in session/llm.ts. It runs over the stream before streamText interprets tool calls and:

  • drops any tool call that begins while a reasoning block is still open (plus that call's tool-input-* / tool-result parts), and
  • downgrades a resulting tool-calls finish reason to stop, so the session loop doesn't wait on a tool that never runs.

It only touches calls that begin before reasoning-end. Calls emitted after reasoning closes pass through, a mixed turn keeps its tool-calls finish reason, and it's a no-op for any stream without reasoning parts. Opt out per model with options.suppressToolCallsInReasoning: false.

How did you verify your code works?

Added test/session/reasoning-tool-guard.test.ts (5 cases: in-reasoning call suppressed + finish downgraded; post-reasoning call preserved; mixed turn keeps tool-calls; plain text passes through; suppressed even when the model stops mid-<think>). bun test, oxlint, prettier --check, and tsgo --noEmit pass; the existing llm.test.ts is unchanged.

Screenshots / recordings

N/A — not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Thanks for taking a look — happy to adjust the gating, naming, or placement if you'd prefer it done differently.

Some reasoning models (Qwen, Kimi K2, GLM, ...) occasionally emit tool-call
markup inside their <think> reasoning block while still thinking. The inference
server promotes that to a structured tool call, which streamText then executes
prematurely — running a side effect and ending the turn before the model
produces its real answer.

Add a language-model middleware that runs over the provider stream before tool
calls are interpreted. It drops any tool call that begins while a reasoning
block is still open (and that call's input/result parts) and downgrades a
resulting tool-calls finish reason to stop. Tool calls emitted after
reasoning-end pass through untouched, and the transform is a no-op for streams
that never emit reasoning parts. Opt out per model with
options.suppressToolCallsInReasoning: false.

Refs: anomalyco#8851, anomalyco#6708, anomalyco#10996
@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GLM 4.7 on Zai coding plan puts tool calls inside the thinking/reasoning tag.

1 participant