Content Blocks
The content taxonomy and opaque-payload passthrough
Block types
ContentBlock =
| { type: "text"; text }
| { type: "thinking"; text; signature? } // resume-critical
| { type: "tool-call"; toolCallId; name; arguments }
| { type: "tool-result"; toolCallId; toolResultId; content; isError }
| { type: "image"; mimeType; data?; uri?; alt? }
| { type: "reference"; uri; title?; mediaType? }
| { type: "error"; message; code?; detail? }
| { type: "unknown"; nativeType; value } // opaque-payload containerOpaque passthrough
Agent ecosystems contain bytes that cannot be parsed but must round-trip verbatim:
- Claude's thinking
signature(required by the Anthropic API on resume) - Codex's reasoning
encrypted_content(server-side verification chain)
ASP carries them in a typed unknown block — never normalized away. The rule is strict: any canonical→native→canonical re-encoding path must leave unknown blocks idempotent (no double-wrapping), or the nativeType is clobbered and the payload lost.
Tool results
Tool-result content is authoritative on the tool entity's result, not on the message blocks — harnesses disagree on the block shape (pi uses [text], Codex uses [tool-result]), so exporters must read from the tool entity.
Full detail: asp/spec/content-blocks.md.