Claude Extended Thinking: assistant message content order causes API error #6766

Closed
opened 2026-02-16 18:05:12 -05:00 by yindo · 3 comments
Owner

Originally created by @Soein on GitHub (Jan 19, 2026).

Originally assigned to: @rekram1-node on GitHub.

Bug Description

When using Claude models with Extended Thinking enabled through a proxy (e.g., via @ai-sdk/anthropic), multi-turn conversations with tool use fail with the following error:

messages.45.content.0.type: Expected 'thinking' or 'redacted_thinking', but found 'tool_use'

Root Cause

Claude Extended Thinking requires that assistant messages must start with a thinking or redacted_thinking block when thinking is enabled. However, OpenCode's message reconstruction does not guarantee this ordering.

Code Analysis

In packages/opencode/src/session/message-v2.ts, the toModelMessage function builds message parts in iteration order:

for (const part of msg.parts) {
  if (part.type === "text") /* add text */
  if (part.type === "tool") /* add tool */  
  if (part.type === "reasoning") /* add reasoning */
}

The parts are sorted by their generated ID:

result.sort((a, b) => (a.id > b.id ? 1 : -1))

In packages/opencode/src/provider/transform.ts, the normalizeMessages function for Anthropic only filters empty content but does not reorder parts to ensure reasoning blocks come first:

if (model.api.npm === "@ai-sdk/anthropic") {
  msgs = msgs
    .map((msg) => {
      // ... only filters empty text/reasoning parts
      const filtered = msg.content.filter((part) => {
        if (part.type === "text" || part.type === "reasoning") {
          return part.text !== ""
        }
        return true
      })
      // ...
    })
}

Reproduction Steps

  1. Configure a Claude model with Extended Thinking enabled (e.g., claude-sonnet-4-5 with thinking.budgetTokens > 0)
  2. Start a conversation that triggers tool use
  3. Continue the conversation for multiple turns
  4. Eventually, the API returns the error about unexpected tool_use at position 0

Proposed Fix

Add reordering logic in normalizeMessages for Anthropic models to ensure reasoning parts always come before other content types:

if (model.api.npm === "@ai-sdk/anthropic") {
  msgs = msgs.map((msg) => {
    if (msg.role === "assistant" && Array.isArray(msg.content)) {
      // Ensure reasoning blocks come first (Claude Extended Thinking requirement)
      const reasoningParts = msg.content.filter(p => p.type === "reasoning")
      const otherParts = msg.content.filter(p => p.type !== "reasoning")
      return { ...msg, content: [...reasoningParts, ...otherParts] }
    }
    return msg
  })
  // ... existing empty content filtering
}

Environment

  • OpenCode version: latest
  • Provider: Custom proxy using Anthropic-compatible API
  • Model: Claude models with Extended Thinking enabled

Related

Originally created by @Soein on GitHub (Jan 19, 2026). Originally assigned to: @rekram1-node on GitHub. ## Bug Description When using Claude models with Extended Thinking enabled through a proxy (e.g., via `@ai-sdk/anthropic`), multi-turn conversations with tool use fail with the following error: ``` messages.45.content.0.type: Expected 'thinking' or 'redacted_thinking', but found 'tool_use' ``` ## Root Cause Claude Extended Thinking requires that assistant messages **must start with a `thinking` or `redacted_thinking` block** when thinking is enabled. However, OpenCode's message reconstruction does not guarantee this ordering. ### Code Analysis In `packages/opencode/src/session/message-v2.ts`, the `toModelMessage` function builds message parts in iteration order: ```typescript for (const part of msg.parts) { if (part.type === "text") /* add text */ if (part.type === "tool") /* add tool */ if (part.type === "reasoning") /* add reasoning */ } ``` The `parts` are sorted by their generated ID: ```typescript result.sort((a, b) => (a.id > b.id ? 1 : -1)) ``` In `packages/opencode/src/provider/transform.ts`, the `normalizeMessages` function for Anthropic only filters empty content but **does not reorder parts** to ensure `reasoning` blocks come first: ```typescript if (model.api.npm === "@ai-sdk/anthropic") { msgs = msgs .map((msg) => { // ... only filters empty text/reasoning parts const filtered = msg.content.filter((part) => { if (part.type === "text" || part.type === "reasoning") { return part.text !== "" } return true }) // ... }) } ``` ## Reproduction Steps 1. Configure a Claude model with Extended Thinking enabled (e.g., `claude-sonnet-4-5` with `thinking.budgetTokens > 0`) 2. Start a conversation that triggers tool use 3. Continue the conversation for multiple turns 4. Eventually, the API returns the error about unexpected `tool_use` at position 0 ## Proposed Fix Add reordering logic in `normalizeMessages` for Anthropic models to ensure `reasoning` parts always come before other content types: ```typescript if (model.api.npm === "@ai-sdk/anthropic") { msgs = msgs.map((msg) => { if (msg.role === "assistant" && Array.isArray(msg.content)) { // Ensure reasoning blocks come first (Claude Extended Thinking requirement) const reasoningParts = msg.content.filter(p => p.type === "reasoning") const otherParts = msg.content.filter(p => p.type !== "reasoning") return { ...msg, content: [...reasoningParts, ...otherParts] } } return msg }) // ... existing empty content filtering } ``` ## Environment - OpenCode version: latest - Provider: Custom proxy using Anthropic-compatible API - Model: Claude models with Extended Thinking enabled ## Related - [Anthropic Extended Thinking Documentation](https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking)
yindo closed this issue 2026-02-16 18:05:12 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Jan 19, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #6176: Claude extended thinking + tool use: signature not preserved in message history
  • #3077: Expected thinking or redacted_thinking, but found tool_use (same error)
  • #2599: v0.8.0 Introduced thinking block bug with Claude Sonnet 4 (same root cause)
  • #8010: Expected thinking or redacted_thinking but found tool_use (AWS Bedrock variant)
  • #8377: Most sessions eventually gets a tool_use error (related message ordering)
  • #6346: Devstral via LiteLLM OpenAI-compatible fails with invalid_request_message_order (systemic issue)
  • #8675: Claude with Antigravity shows message structure validation failure (proxy variant)

The most relevant duplicate appears to be #6176, which was closed on Dec 26, 2025 but the issue persists. The underlying cause seems to be message content ordering not being enforced properly across different providers and model configurations.

Feel free to ignore if your specific case differs from these.

@github-actions[bot] commented on GitHub (Jan 19, 2026): This issue might be a duplicate of existing issues. Please check: - #6176: Claude extended thinking + tool use: signature not preserved in message history - #3077: Expected `thinking` or `redacted_thinking`, but found `tool_use` (same error) - #2599: v0.8.0 Introduced thinking block bug with Claude Sonnet 4 (same root cause) - #8010: Expected `thinking` or `redacted_thinking` but found `tool_use` (AWS Bedrock variant) - #8377: Most sessions eventually gets a `tool_use` error (related message ordering) - #6346: Devstral via LiteLLM OpenAI-compatible fails with invalid_request_message_order (systemic issue) - #8675: Claude with Antigravity shows message structure validation failure (proxy variant) The most relevant duplicate appears to be #6176, which was closed on Dec 26, 2025 but the issue persists. The underlying cause seems to be message content ordering not being enforced properly across different providers and model configurations. Feel free to ignore if your specific case differs from these.
Author
Owner

@Soein commented on GitHub (Jan 19, 2026):

Update: Related to #6176

After reviewing #6176, it appears this may be the same root cause - the Antigravity adapter not correctly converting thoughtSignature to the providerMetadata.anthropic.signature format that AI SDK expects.

My Setup

  • Plugin: opencode-antigravity-auth@1.3.0
  • Proxy: CLIProxyAPI (custom proxy)
  • Provider configured as @ai-sdk/anthropic compatible

Possible Causes

  1. Signature format mismatch (per #6176): The adapter may not wrap signatures in the correct format
  2. Message ordering (my original analysis): transform.ts doesn't reorder reasoning parts to be first

Questions

  1. Is opencode-antigravity-auth based on oh-my-opencode's Antigravity adapter?
  2. If so, does it include the fixes from oh-my-opencode PR #256?

If this is confirmed as a plugin issue rather than OpenCode core, please feel free to close as duplicate of #6176.

@Soein commented on GitHub (Jan 19, 2026): ## Update: Related to #6176 After reviewing #6176, it appears this may be the same root cause - the Antigravity adapter not correctly converting `thoughtSignature` to the `providerMetadata.anthropic.signature` format that AI SDK expects. ### My Setup - Plugin: `opencode-antigravity-auth@1.3.0` - Proxy: CLIProxyAPI (custom proxy) - Provider configured as `@ai-sdk/anthropic` compatible ### Possible Causes 1. **Signature format mismatch** (per #6176): The adapter may not wrap signatures in the correct format 2. **Message ordering** (my original analysis): `transform.ts` doesn't reorder reasoning parts to be first ### Questions 1. Is `opencode-antigravity-auth` based on oh-my-opencode's Antigravity adapter? 2. If so, does it include the fixes from oh-my-opencode PR #256? If this is confirmed as a plugin issue rather than OpenCode core, please feel free to close as duplicate of #6176.
Author
Owner

@rekram1-node commented on GitHub (Jan 21, 2026):

Oh if you are using a plugin and getting an issue I highly recommend opening issue w/ them

@rekram1-node commented on GitHub (Jan 21, 2026): Oh if you are using a plugin and getting an issue I highly recommend opening issue w/ them
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#6766