/** * ContentBlock — the discriminated union of blocks inside a Message (PROTOCOL §11). * * `type` is the discriminator. `tool_result` blocks are nested by `tool_call_id` to * pair with their originating call. Stays wire-safe: no functions, no class instances. */ import { Type, type Static } from '@sinclair/typebox'; import { ToolResultContentSchema } from '@/types/tool-result'; export const TextBlockSchema = Type.Object({ type: Type.Literal('text'), text: Type.String(), }); export const ToolCallBlockSchema = Type.Object({ type: Type.Literal('tool_call'), id: Type.String(), name: Type.String(), args: Type.Record(Type.String(), Type.Unknown()), }); export const ToolResultBlockSchema = Type.Object({ type: Type.Literal('tool_result'), tool_call_id: Type.String(), content: ToolResultContentSchema, is_error: Type.Optional(Type.Boolean()), }); export const ThinkingBlockSchema = Type.Object({ type: Type.Literal('thinking'), text: Type.String(), // Opaque provider token for reasoning replay (Anthropic wire `signature`, OpenAI // `reasoning_signature`, Google `thoughtSignature`). Maps to/from the wire field per // provider in the outbound transform. Absent for providers that emit only readable text. thinkingSignature: Type.Optional(Type.String()), // Marks safety-filtered / encrypted reasoning the model returned but the user can't read. // Redacted blocks carry a signature (same-model replay) but no usable text; cross-model // they are dropped entirely rather than downgraded. redacted: Type.Optional(Type.Boolean()), }); export const ContentBlockSchema = Type.Union([ TextBlockSchema, ToolCallBlockSchema, ToolResultBlockSchema, ThinkingBlockSchema, ]); export type TextBlock = Static; export type ToolCallBlock = Static; export type ToolResultBlock = Static; export type ThinkingBlock = Static; export type ContentBlock = Static;