Skip to content

_sanitizeMessageForPersistence strips Anthropic redacted_thinking blocks #978

Description

@ferdousbhai

Bug

_sanitizeMessageForPersistence in AIChatAgent filters out all reasoning parts with empty text. This inadvertently removes Anthropic's redacted_thinking blocks, which are stored by the AI SDK as reasoning parts with empty text + providerMetadata.anthropic.redactedData.

On the next turn, when persisted messages are sent back to the Anthropic API, the latest assistant message is missing its redacted_thinking blocks. The API rejects the request with:

messages.N.content.M: '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/ai-chat/src/ai-chat-agent.ts, the sanitizer:

if (part.type === "reasoning") {
  const reasoningPart = part;
  if (!reasoningPart.text || reasoningPart.text.trim() === "") return false;
}

This was designed for OpenAI's empty reasoning parts, but it also matches Anthropic's redacted_thinking:

// How @ai-sdk/anthropic stores redacted_thinking:
{
  type: "reasoning",
  text: "",                                          // ← empty!
  providerMetadata: { anthropic: { redactedData: "..." } }
}

Suggested Fix

Check for Anthropic metadata before filtering:

if (part.type === "reasoning") {
  const reasoningPart = part;
  if (!reasoningPart.text || reasoningPart.text.trim() === "") {
    // Preserve Anthropic redacted_thinking (has providerMetadata.anthropic)
    if (reasoningPart.providerMetadata?.anthropic) return true;
    return false;
  }
}

Workaround

Subclasses can patch the instance method in the constructor:

constructor(ctx: DurableObjectState, env: Env) {
  super(ctx, env);
  (this as any)._sanitizeMessageForPersistence = (message: UIMessage): UIMessage => {
    const sanitizedParts = message.parts.filter((part) => {
      if (part.type === "reasoning") {
        const rp = part as { text?: string; providerMetadata?: { anthropic?: unknown } };
        if ((!rp.text || rp.text.trim() === "") && !rp.providerMetadata?.anthropic) {
          return false;
        }
      }
      return true;
    });
    return { ...message, parts: sanitizedParts };
  };
}

Environment

  • @cloudflare/ai-chat: 0.1.3
  • ai (Vercel AI SDK): v6
  • @ai-sdk/anthropic: 3.0.37+
  • Model: Claude Opus 4.6 with thinking: { type: "adaptive" }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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