can1357/oh-my-pi · error · ValidationError
Unknown assistant content type
Error message
Unknown assistant content type
What it means
When converting an AssistantMessage into Bedrock's wire format, the switch over assistant content block types only supports text, toolCall, and (demoted) thinking blocks. Any other assistant-side block type falls through to the default branch and throws a ValidationError before any request reaches AWS. Notably, this converter deliberately demotes replayed thinking blocks to text so they can be safely replayed, but it has no mapping for other block kinds.
Source
Thrown at packages/ai/src/providers/amazon-bedrock.ts:915
reasoningContent: {
reasoningText: { text: c.thinking.toWellFormed(), signature: c.thinkingSignature },
},
});
} else {
// No signature was captured. Do NOT fall back to unsigned reasoningContent here:
// a model streaming reasoningContent does not imply it accepts reasoningContent
// echoed back in a request. Amazon Nova streams unsigned reasoning just fine but
// rejects it on replay with HTTP 400 "User messages cannot contain reasoning
// content. Please remove the reasoning content and try again.", which wedges the
// agent loop on every turn after the first. Demote to plain text instead — the
// content survives, just no longer typed as a reasoning block. This matches how
// every other provider (Anthropic, Google, OpenAI-completions) handles thinking
// blocks it can't safely replay.
contentBlocks.push({ text: renderDemotedThinking(model.id, c.thinking) });
}
break;
default:
throw new AIError.ValidationError("Unknown assistant content type");
}
}
// Skip if all content blocks were filtered out
if (contentBlocks.length === 0) continue;
result.push({ role: "assistant", content: contentBlocks });
break;
}
case "toolResult": {
// Collect all consecutive toolResult messages into a single user message —
// Bedrock requires all tool results to be in one message.
const toolResults: ToolResultBlockWire[] = [];
toolResults.push({
toolResult: {
toolUseId: normalizeToolCallId(m.toolCallId),
content: m.content.map(c =>
c.type === "image"
? { image: createImageBlock(c.mimeType, c.data) }
: { text: c.text.toWellFormed() },View on GitHub (pinned to 9690622007)
Solutions
- Sanitize assistant messages before replay: keep only text, toolCall, and thinking blocks
- Strip or re-emit non-text assistant blocks as text summaries when migrating history from another provider
- Check that transcript persistence isn't corrupting block type discriminators (e.g. saving/loading changed the shape)
- If a legitimate Bedrock-supported assistant block is missing, extend the conversion switch in amazon-bedrock.ts
Example fix
// before: replaying foreign assistant blocks verbatim
history.push(foreignAssistantMessage); // contains { type: "image", ... }
// after: convert to text before sending
history.push({
role: "assistant",
content: foreignAssistantMessage.content
.filter(c => c.type === "text" || c.type === "toolCall" || c.type === "thinking"),
}); Defensive patterns
Strategy: validation
Validate before calling
const ASSISTANT_BLOCK_TYPES = new Set(["text", "toolCall", "thinking"]);
function sanitizeAssistant(m: Context): Context {
if (m.role !== "assistant") return m;
return { ...m, content: m.content.filter(c => ASSISTANT_BLOCK_TYPES.has(c.type)) };
}
const safeContext = context.map(sanitizeAssistant); Type guard
function isSupportedAssistantBlock(c: ContentBlock): boolean {
return c.type === "text" || c.type === "toolCall" || c.type === "thinking";
} Try / catch
try {
return await provider.complete(context);
} catch (err) {
if (err instanceof AIError.ValidationError && err.message === "Unknown assistant content type") {
logger.warn("dropping unsupported assistant blocks for bedrock replay");
return provider.complete(context.map(sanitizeAssistant));
}
throw err;
} Prevention
- When migrating history between providers, run a per-provider block sanitizer first
- Keep thinking blocks as-is — this converter demotes them automatically; other block types are not handled
- Persist transcripts with their provider ID so replay paths know which conversion to apply
When it happens
Trigger: Replaying a conversation to Bedrock where an assistant message contains a content block type the Bedrock converter does not handle (e.g. image in assistant role, or an exotic custom block) — typically when reconstructing history that was generated by a different provider.
Common situations: Switching a session from another provider to Bedrock mid-conversation; resuming stored transcripts that contain assistant blocks not produced by this converter; programmatic construction of assistant messages with wrong block types.
Related errors
- Unknown user content type
- Unknown message role
- Unknown image type: ${mimeType}
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/9f1f4936491e3689.
Report an issue: GitHub.