Assemble a Reply From a Stream
With streaming on, a reply arrives as a few dozen small events instead of one object. Rebuild it.
Implement assemble(events). Each event is a dict with a "type":
message_start: carriesmessage.usage.input_tokens.content_block_start: opens the block atindex;content_blockis{"type": "text"}or{"type": "tool_use", "id": ..., "name": ...}.content_block_delta: more of the block atindex. A text block getsdelta.text; a tool-use block getsdelta.partial_json, a fragment of the JSON text of its arguments, which can split anywhere.content_block_stop: the block atindexis finished.message_delta: carriesdelta.stop_reasonandusage.output_tokens. These arrive last.message_stop: the stream ended cleanly.ping, and any type you do not recognise: ignore.error: raiseStreamError(provided) with the event'serror.message.
Return a dict:
"text": all text blocks joined, in block order."tool_calls": a list of{"id", "name", "input"}in block order,inputbeing the parsed arguments ({}if no fragments arrived)."stop_reason","input_tokens","output_tokens": from the events above.Noneand0if they never arrived."complete":Trueonly ifmessage_stopwas seen.
The catch: deltas for different blocks can interleave, so text goes to the block its index names, not the block that opened last. And a stream can die at any point: return what arrived, with complete false, and leave out any tool call whose arguments do not parse. Half a tool call must never reach a tool.