stateDiagram-v2
[*] --> UserInput: User types/pastes
UserInput --> CliMessage: CLI processes input
CliMessage --> APIMessage: Format for LLM
APIMessage --> LLMStream: API Request
LLMStream --> StreamEvent: Server sends chunks
StreamEvent --> ContentBlockDelta: Parse deltas
ContentBlockDelta --> AccumulatedMessage: Build message
AccumulatedMessage --> ToolUseBlock: Contains tool requests?
ToolUseBlock --> ToolExecution: Execute tools
ToolExecution --> ToolProgress: Yield progress
ToolProgress --> CliMessage: Progress updates
ToolExecution --> ToolResult: Complete execution
ToolResult --> ToolResultBlock: Format result
ToolResultBlock --> CliMessage: Tool result message
AccumulatedMessage --> CliMessage: Final assistant message
CliMessage --> [*]: Display to user
CliMessage --> APIMessage: Loop continues
The most fascinating aspect of Claude Code's data architecture is how it manages the transformation of data through multiple representations while maintaining streaming performance. Let's start with the core innovation:
// The dual-representation message system (inferred from analysis)
interface MessageTransformPipeline {
// Stage 1: CLI Internal Representation
cliMessage: {
type: "user" | "assistant" | "attachment" | "progress"
uuid: string // CLI-specific tracking
timestamp: string
message?: APICompatibleMessage // Only for user/assistant
attachment?: AttachmentContent // Only for attachment
progress?: ProgressUpdate // Only for progress
}
// Stage 2: API Wire Format
apiMessage: {
role: "user" | "assistant"
content: string | ContentBlock[]
// No CLI-specific fields
}
// Stage 3: Streaming Accumulator
streamAccumulator: {
partial: Partial<APIMessage>
deltas: ContentBlockDelta[]
buffers: Map<string, string> // tool_use_id → accumulating JSON
}
}
Why This Matters: This three-stage representation allows Claude Code to maintain UI responsiveness while handling complex streaming protocols. The CLI can update progress indicators using CliMessage
metadata while the actual LLM communication uses a clean APIMessage
format.
Based on decompilation analysis, Claude Code implements a sophisticated type system for content:
// The ContentBlock discriminated union (reconstructed)
type ContentBlock =
| TextBlock
| ImageBlock
| ToolUseBlock
| ToolResultBlock
| ThinkingBlock
| DocumentBlock // Platform-specific
| VideoBlock // Platform-specific
| GuardContentBlock // Platform-specific
| ReasoningBlock // Platform-specific
| CachePointBlock // Platform-specific
// Performance annotations based on inferred usage
interface ContentBlockMetrics {
TextBlock: {
memorySize: "O(text.length)",
parseTime: "O(1)",
serializeTime: "O(n)",
streamable: true
},
ImageBlock: {
memorySize: "O(1) + external", // Reference to base64/S3
parseTime: "O(1)",
serializeTime: "O(size)" | "O(1) for S3",
streamable: false
},
ToolUseBlock: {
memorySize: "O(JSON.stringify(input).length)",
parseTime: "O(n) for JSON parse",
serializeTime: "O(n)",
streamable: true // JSON can stream
}
}
One of Claude Code's most clever innovations is handling streaming JSON for tool inputs:
// Inferred implementation of streaming JSON parser
class StreamingToolInputParser {
private buffer: string = '';
private depth: number = 0;
private inString: boolean = false;
private escape: boolean = false;
addChunk(chunk: string): ParseResult {
this.buffer += chunk;
// Track JSON structure depth
for (const char of chunk) {
if (!this.inString) {
if (char === '{' || char === '[') this.depth++;
else if (char === '}' || char === ']') this.depth--;
}
// Track string boundaries
if (char === '"' && !this.escape) {
this.inString = !this.inString;
}
this.escape = (char === '\\\\\\\\' && !this.escape);
}
// Attempt parse at depth 0
if (this.depth === 0 && this.buffer.length > 0) {
try {
return { complete: true, value: JSON.parse(this.buffer) };
} catch (e) {
// Try auto-closing unclosed strings
if (this.inString) {
try {
return {
complete: true,
value: JSON.parse(this.buffer + '"'),
repaired: true
};
} catch {}
}
return { complete: false, error: e };
}
}
return { complete: false };
}
}
This parser can handle incremental JSON chunks from the LLM, attempting to parse as soon as the structure appears complete.
graph TB
subgraph "Input Processing"
UserText[User Text Input]
SlashCmd["/command"]
BashCmd[!shell command]
MemoryCmd[#memory note]
PastedContent[Pasted Image/Text]
UserText --> NormalMessage[Create User CliMessage]
SlashCmd --> CommandProcessor[Process Command]
BashCmd --> SyntheticTool[Synthetic BashTool Message]
MemoryCmd --> MemoryUpdate[Update CLAUDE.md]
PastedContent --> ContentDetection{Detect Type}
ContentDetection -->|Image| ImageBlock[Create ImageBlock]
ContentDetection -->|Text| TextBlock[Create TextBlock]
end
subgraph "Message Transformation"
NormalMessage --> StripMetadata[Remove CLI fields]
SyntheticTool --> StripMetadata
ImageBlock --> StripMetadata
TextBlock --> StripMetadata
StripMetadata --> APIMessage[Clean API Message]
APIMessage --> TokenCount{Count Tokens}
TokenCount -->|Over Limit| Compact[Compaction Process]
TokenCount -->|Under Limit| Send[Send to LLM]
Compact --> SummaryMessage[Summary Message]
SummaryMessage --> Send
end
The CliMessage
type serves as the central nervous system of the application:
interface CliMessage {
type: "user" | "assistant" | "attachment" | "progress"
uuid: string
timestamp: string
// For user/assistant messages only
message?: {
role: "user" | "assistant"
id?: string // LLM-provided ID
model?: string // Which model responded
stop_reason?: StopReason // Why generation stopped
stop_sequence?: string // Specific stop sequence hit
usage?: TokenUsage // Detailed token counts
content: string | ContentBlock[]
}
// CLI-specific metadata
costUSD?: number // Calculated cost
durationMs?: number // API call duration
requestId?: string // For debugging
isApiErrorMessage?: boolean // Error display flag
isMeta?: boolean // System-generated message
// Type-specific fields
attachment?: AttachmentContent
progress?: {
toolUseID: string
parentToolUseID?: string // For AgentTool sub-tools
data: any // Tool-specific progress
}
}
// Performance characteristics
interface CliMessagePerformance {
creation: "O(1)",
serialization: "O(content size)",
memoryRetention: "Weak references for large content",
garbageCollection: "Eligible when removed from history array"
}
Claude Code carefully controls where data structures can be modified:
// Inferred mutation control patterns
class MessageMutationControl {
// Mutation Point 1: Stream accumulation
static accumulateStreamDelta(
message: Partial<CliMessage>,
delta: ContentBlockDelta
): void {
if (delta.type === 'text_delta') {
const lastBlock = message.content[message.content.length - 1];
if (lastBlock.type === 'text') {
lastBlock.text += delta.text; // MUTATION
}
}
}
// Mutation Point 2: Tool result injection
static injectToolResult(
history: CliMessage[],
toolResult: ToolResultBlock
): void {
const newMessage: CliMessage = {
type: 'user',
isMeta: true, // System-generated
message: {
role: 'user',
content: [toolResult]
},
// ... other fields
};
history.push(newMessage); // MUTATION
}
// Mutation Point 3: Cost calculation
static updateCostMetadata(
message: CliMessage,
usage: TokenUsage
): void {
message.costUSD = calculateCost(usage, message.model); // MUTATION
message.durationMs = Date.now() - parseISO(message.timestamp); // MUTATION
}
}