Skip to content

fix(core): repair interleaved text/tool-call parts in assistant messages#14456

Open
bil9148 wants to merge 2 commits intoanomalyco:devfrom
bil9148:fix/repair-interleaved-tool-calls
Open

fix(core): repair interleaved text/tool-call parts in assistant messages#14456
bil9148 wants to merge 2 commits intoanomalyco:devfrom
bil9148:fix/repair-interleaved-tool-calls

Conversation

@bil9148
Copy link

@bil9148 bil9148 commented Feb 20, 2026

Issue for this PR

Closes #10616, #8377, #1662, #2720, #2214, #5750

Related: #4802, #8312, #10673, #7002

Type of change

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

What does this PR do?

I kept hitting the tool_use ids were found without tool_result blocks immediately after error in my sessions. Once it happens, every subsequent message fails too — the only workaround is reverting the last message or starting a new chat. Extremely disruptive.

I dug into the actual API request payload from the logs and found that the tool_use/tool_result pairing was actually correct — all IDs matched. The real problem was the ordering of content parts within assistant messages.

The AI SDK's convertToModelMessages merges multi-step conversations and can produce assistant messages like:

[text, tool_use, tool_use, text, tool_use]

That second text block sitting between tool_use blocks causes Anthropic's API to think the tool-calling turn ended early, so it sees the first two tool_use IDs as orphaned from their tool_result blocks.

The fix adds repairAssistantMessages() in ProviderTransform.message() that:

  1. Reorders assistant message content so all text/reasoning parts come before all tool-call parts
  2. As a secondary safety net, injects synthetic tool-result blocks for any genuinely missing tool-results (handles edge cases like process crashes mid-tool-execution)

It also logs a warning when it makes repairs so the issue is observable in ~/.local/share/opencode/log/.

I verified the root cause by extracting messages[29] from the error's requestBodyValues in the log file. The content parts were [text, tool_use, tool_use, text, tool_use] — the interleaving was clearly visible. After the fix, the same session works fine.

How did you verify your code works?

  • Reproduced the error in my session, applied the fix, retried the exact same failing message — it worked immediately
  • 5 new test cases covering: interleaved reordering, orphaned tool-call injection, partial patching of existing tool messages, and no-op when messages are already correct
  • 2 existing tests updated to account for the new safety net
  • All 107 tests pass, typecheck clean

Checklist

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

The AI SDK's convertToModelMessages can produce assistant messages where
text blocks are interleaved between tool-call blocks when multi-step
conversations have text in between tool calls across steps. For example:

  [text, tool_use, tool_use, text, tool_use]

Anthropic's API interprets the second text block as ending the tool-calling
turn, causing the preceding tool_use blocks to appear orphaned from their
tool_result blocks in the next message. This produces the error:

  tool_use ids were found without tool_result blocks immediately after

This adds repairAssistantMessages() as a safety net in ProviderTransform.message()
that:
1. Reorders content so all text/reasoning parts come before tool-call parts
2. Injects synthetic tool-result blocks for any genuinely orphaned tool-calls

Fixes anomalyco#10616, anomalyco#8377, anomalyco#1662, anomalyco#2720, anomalyco#2214, anomalyco#5750
@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Feb 20, 2026
@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search, I found one potentially related PR:

Related PR:

The other PRs found (#14393, #8888) appear less directly related as they focus on different aspects (thinking blocks and message conversion safeguards respectively).

@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Feb 20, 2026
@github-actions
Copy link
Contributor

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


if (next?.role === "tool" && Array.isArray(next.content)) {
next.content = [...next.content, ...patches]
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to avoid using the 'else' statements:
https://github.com/anomalyco/opencode/blob/dev/CONTRIBUTING.md#style-preferences
Could this be reworked?

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.

messages.87: tool_use ids were found without tool_result blocks immediately

2 participants