Skip to content

fix: reasoning text trimEnd() in processor.ts invalidates Bedrock extended thinking signature #18078

@wonjun3991

Description

@wonjun3991

Bug Report

Error

undefined: The model returned the following errors:
messages.3.content.6: `thinking` or `redacted_thinking` blocks in the latest assistant message 
cannot be modified. These blocks must remain as they were in the original response.

Root Cause

In packages/opencode/src/session/processor.ts, the reasoning-end handler trims the reasoning text before storing it in SQLite:

case "reasoning-end":
  if (value.id in reasoningMap) {
    const part = reasoningMap[value.id]
    part.text = part.text.trimEnd()  // ← BUG: trims text permanently
    // ...
    if (value.providerMetadata) part.metadata = value.providerMetadata  // signature is for ORIGINAL text
    await Session.updatePart(part)
  }

The Amazon Bedrock API computes the signature cryptographically over the exact original bytes of the reasoning text (including trailing whitespace). When opencode calls trimEnd():

  • Stored text: "Let me think..." (trimmed)
  • Stored signature: computed over "Let me think...\n\n" (original)

On the next request, Bedrock receives text ≠ signature → ValidationException: "cannot be modified".

Data Flow

Bedrock response: reasoning.text = "thinking...\n\n", signature = "sig123"
    ↓
processor.ts reasoning-end: part.text = "thinking..." (trimEnd applied)
    ↓
SQLite: { text: "thinking...", metadata: { bedrock: { signature: "sig123" } } }
    ↓
Next request: text = "thinking...", signature = "sig123"
    ↓
Bedrock validates: sig123 was for "thinking...\n\n" ≠ "thinking..." → ERROR ❌

Fix

In packages/opencode/src/session/processor.ts, remove trimEnd() from the reasoning-end case:

case "reasoning-end":
  if (value.id in reasoningMap) {
    const part = reasoningMap[value.id]
    // REMOVED: part.text = part.text.trimEnd()
    // Reasoning text must be preserved byte-for-byte — the API-provided signature
    // validates the exact content. Trimming invalidates the signature and causes
    // "thinking blocks cannot be modified" on the next Bedrock request.
    part.time = { ...part.time, end: Date.now() }
    if (value.providerMetadata) part.metadata = value.providerMetadata
    await Session.updatePart(part)
    delete reasoningMap[value.id]
  }

Related Bug in @ai-sdk/amazon-bedrock

A secondary bug exists in packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts where trimIfLast() is applied to reasoning blocks that have a signature:

// BUGGY (line 298):
text: trimIfLast(isLastBlock, isLastMessage, isLastContentPart, part.text),
signature: reasoningMetadata.signature,

// FIX: never trim when signature is present
text: reasoningMetadata.signature != null
  ? part.text
  : trimIfLast(isLastBlock, isLastMessage, isLastContentPart, part.text),
signature: reasoningMetadata.signature,

This should be reported separately to vercel/ai.

Reproduction

  1. Configure OpenCode with Claude Opus 4 via Amazon Bedrock (extended thinking enabled)
  2. Have a multi-turn conversation where extended thinking is used
  3. Error appears on the turn after extended thinking was used (typically turn 2-3)

Environment

  • OpenCode: v1.2.27
  • Provider: amazon-bedrock
  • Model: global.anthropic.claude-opus-4-6-v1 (variant: max)
  • Confirmed in oh-my-opencode logs: ValidationException on converse-stream

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions