{"record":{"id":"9f1f4936491e3689","repo":"can1357/oh-my-pi","slug":"unknown-assistant-content-type","errorCode":null,"errorMessage":"Unknown assistant content type","messagePattern":"Unknown assistant content type","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/amazon-bedrock.ts","lineNumber":915,"sourceCode":"\t\t\t\t\t\t\t\t\treasoningContent: {\n\t\t\t\t\t\t\t\t\t\treasoningText: { text: c.thinking.toWellFormed(), signature: c.thinkingSignature },\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// No signature was captured. Do NOT fall back to unsigned reasoningContent here:\n\t\t\t\t\t\t\t\t// a model streaming reasoningContent does not imply it accepts reasoningContent\n\t\t\t\t\t\t\t\t// echoed back in a request. Amazon Nova streams unsigned reasoning just fine but\n\t\t\t\t\t\t\t\t// rejects it on replay with HTTP 400 \"User messages cannot contain reasoning\n\t\t\t\t\t\t\t\t// content. Please remove the reasoning content and try again.\", which wedges the\n\t\t\t\t\t\t\t\t// agent loop on every turn after the first. Demote to plain text instead — the\n\t\t\t\t\t\t\t\t// content survives, just no longer typed as a reasoning block. This matches how\n\t\t\t\t\t\t\t\t// every other provider (Anthropic, Google, OpenAI-completions) handles thinking\n\t\t\t\t\t\t\t\t// blocks it can't safely replay.\n\t\t\t\t\t\t\t\tcontentBlocks.push({ text: renderDemotedThinking(model.id, c.thinking) });\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tthrow new AIError.ValidationError(\"Unknown assistant content type\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Skip if all content blocks were filtered out\n\t\t\t\tif (contentBlocks.length === 0) continue;\n\t\t\t\tresult.push({ role: \"assistant\", content: contentBlocks });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"toolResult\": {\n\t\t\t\t// Collect all consecutive toolResult messages into a single user message —\n\t\t\t\t// Bedrock requires all tool results to be in one message.\n\t\t\t\tconst toolResults: ToolResultBlockWire[] = [];\n\t\t\t\ttoolResults.push({\n\t\t\t\t\ttoolResult: {\n\t\t\t\t\t\ttoolUseId: normalizeToolCallId(m.toolCallId),\n\t\t\t\t\t\tcontent: m.content.map(c =>\n\t\t\t\t\t\t\tc.type === \"image\"\n\t\t\t\t\t\t\t\t? { image: createImageBlock(c.mimeType, c.data) }\n\t\t\t\t\t\t\t\t: { text: c.text.toWellFormed() },","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/amazon-bedrock.ts#L897-L933","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: replaying foreign assistant blocks verbatim\nhistory.push(foreignAssistantMessage); // contains { type: \"image\", ... }\n// after: convert to text before sending\nhistory.push({\n  role: \"assistant\",\n  content: foreignAssistantMessage.content\n    .filter(c => c.type === \"text\" || c.type === \"toolCall\" || c.type === \"thinking\"),\n});","handlingStrategy":"validation","validationCode":"const ASSISTANT_BLOCK_TYPES = new Set([\"text\", \"toolCall\", \"thinking\"]);\nfunction sanitizeAssistant(m: Context): Context {\n  if (m.role !== \"assistant\") return m;\n  return { ...m, content: m.content.filter(c => ASSISTANT_BLOCK_TYPES.has(c.type)) };\n}\nconst safeContext = context.map(sanitizeAssistant);","typeGuard":"function isSupportedAssistantBlock(c: ContentBlock): boolean {\n  return c.type === \"text\" || c.type === \"toolCall\" || c.type === \"thinking\";\n}","tryCatchPattern":"try {\n  return await provider.complete(context);\n} catch (err) {\n  if (err instanceof AIError.ValidationError && err.message === \"Unknown assistant content type\") {\n    logger.warn(\"dropping unsupported assistant blocks for bedrock replay\");\n    return provider.complete(context.map(sanitizeAssistant));\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["bedrock","validation","content-blocks"],"backgroundTag":"unsupported-content-block-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}