A streamed Claude response arrives as a sequence of deltas: fragments of text, boundaries between content blocks, and a closing envelope of metadata. Rendering those fragments live is the easy half of the job. The harder half is that almost everything after the render needs the message as a single object again — the conversation history you send on the next turn, the audit log, the usage record, the branch in your code that checks why generation stopped. Concatenating text chunks by hand looks like it works, until it doesn't. The official SDKs therefore ship streaming helpers that do the reassembly for you, and using them is one of the cheapest reliability wins available.
Accumulators: the running state of the message
An accumulator is the piece of the streaming helper that folds every incoming event into a growing in-memory copy of the message. As deltas arrive, it appends text to the right content block, tracks block boundaries, and merges the metadata events into the message envelope. Your code can keep reacting to individual fragments — updating a UI, relaying over Server-Sent Events — while the accumulator quietly maintains the canonical whole.
Why not just join the text yourself? Because a Claude message is not necessarily one text block. Responses can contain multiple content blocks of different types — for example tool_use blocks when the model decides to call one of your tools. A naive string-concatenation approach silently drops everything that is not text, which is precisely the content your agentic code needs. The accumulator preserves block structure, so the reassembled message is faithful to what a non-streaming call would have returned.
Finalizers: getting the completed message out
The finalizer is the other end of the same helper: once the stream closes, it hands you the fully assembled message object. Three fields on that final object matter far more than they first appear:
The stop reason. A response can end for very different reasons, and production code must branch on them. Documented stop reasons include, for example, "refusal" (with a stop_details.category field on Claude Fable 5's safety classifier refusals) and "model_context_window_exceeded", which newer models return when input plus max_tokens would overflow the context window and generation is cut short. Code that never inspects the stop reason will happily treat a truncated or refused answer as a complete one.
Usage. The final message carries the token accounting for the request. With prompt caching in play there are several counters — input_tokens, cache_read_input_tokens, and cache_creation_input_tokens all count toward the context window — and the final envelope is where you read them for cost tracking and capacity planning.
Complete content. Including any tool-use blocks, ready to be appended to the conversation history for the next turn.
Type safety across the SDKs
Streaming events are heterogeneous — text deltas, block starts, metadata — and the statically typed SDKs model them as discriminated types, so the compiler forces you to handle each event kind deliberately rather than optimistically reading a text field that may not exist. This is most visible in TypeScript (see streaming in TypeScript), and the typed helper surfaces exist across the official SDK family: Python, TypeScript, Java, Go, Ruby, C#, and PHP. The exact helper names and event type definitions vary by language and evolve with SDK releases, so treat each repository's README as the authoritative reference for signatures.
One portability note: because Claude Platform on AWS streams the same SSE format as the first-party Claude API, the accumulate-then-finalize pattern — and the code built on it — carries across without structural change; the client class is the main thing that swaps.
Where to go next
Start at streaming basics if the event model is new, or jump to building a tool-calling loop to see why preserving tool_use blocks in the final message matters so much.