fix(opencode): repair thinking blocks rejected by Anthropic API#49
fix(opencode): repair thinking blocks rejected by Anthropic API#49iceteaSA wants to merge 1 commit into
Conversation
Anthropic signs each thinking block to its original position. Two things break that: (1) opencode's AI SDK flattens interleaved thinking to the front of the turn, displacing blocks from their signed positions; (2) stale signatures on historical assistant turns replayed in a new request context. Strategy: keep a single signed thinking block only when it is the lone block in the latest assistant turn. Downgrade all others to <thinking> text. Drop redacted_thinking (no readable content). Also guard against the final block being a thinking block, which Anthropic rejects separately. Sanitize lone UTF-16 surrogates in downgraded text to avoid invalid-UTF8 400s.
d1867ab to
b6971c4
Compare
|
Closing this in favor of the upstream OpenCode fix: anomalyco/opencode#30182. This PR repairs the final Anthropic payload by downgrading/dropping The upstream PR addresses the signed-thinking reorder issue at the source by keeping OpenCode's existing ordinary-text reorder while skipping it when it would move signed or redacted Anthropic reasoning. After that lands, we should update the OpenCode dependency and re-test. If there is still a concrete residual invalid-body case, we can open a narrower plugin-side guard for that specific shape. |
Problem
Anthropic signs each thinking block against its original position. Two things break signatures and trigger
thinking blocks cannot be modified400s:Interleaved thinking flattened by the AI SDK — opencode's Vercel AI SDK regroups all thinking blocks to the front of the assistant turn instead of leaving them interleaved with the tool_use blocks they authorized. The displaced blocks no longer match their signed positions.
Stale signatures on historical assistant turns replayed in a new request context (after context compaction or provider failover).
Final block constraint — Anthropic also rejects an assistant message whose final block is a
thinkingblock.Strategy
<thinking>text — preserves reasoning content without invalid signatures.redacted_thinkingblocks (no readable text to preserve).thinkingblock being the final block in the message.Cache impact
None on the static prefix. The transform runs before cache_control anchors are applied, so anchors land on the already-normalized structure. Historical turns render deterministically once they become non-last. The only cost is the unavoidable one-time tail transition when the current turn becomes historical.
Tests
6 new test cases covering: flattened interleaving, single-block preservation, historical downgrades, redacted_thinking drops, lone-surrogate sanitization, and trailing-thinking-block downgrade.
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by cubic
Repairs Anthropic 400s by normalizing
thinkingblocks inopencoderequests. Prevents errors from flattened interleaving, stale signatures on historical turns, and finalthinkingblocks.thinkingblock only in the latest assistant turn; downgrade all others to<thinking>text.redacted_thinkingblocks unless it’s the single block in the latest assistant turn; never allow it as the final block.thinkingas the final block in an assistant message.Written for commit b6971c4. Summary will update on new commits.