Streaming
Set "stream": true to receive
Server-Sent Events
instead of one buffered object. Stream a turn when you want to show progress, or
when the agent runs enough tools to make a long wait uncomfortable.
const stream = await client.responses.create({ model: "agent", input: "Where is order 4821?", stream: true,});
for await (const event of stream) { if (event.type === "response.output_text.delta") { process.stdout.write(event.delta); }}stream = client.responses.create( model="agent", input="Where is order 4821?", stream=True,)
for event in stream: if event.type == "response.output_text.delta": print(event.delta, end="")The x-thread-id header arrives with the stream headers, before the first
event.
The shape of an event
Section titled “The shape of an event”Each frame carries the event name on the event: line and a JSON object on the
data: line. The object repeats the name as type, and it carries a
sequence_number.
event: response.output_text.deltadata: {"type":"response.output_text.delta","sequence_number":9,"item_id":"msg_9f2c...","output_index":2,"content_index":0,"delta":"Order 4821 "}Sequence numbers start at 0 and count up by one, with no gaps.
The stream has no [DONE] frame. It ends after response.completed or
response.failed.
The order of events
Section titled “The order of events”A turn where the agent thinks, calls one tool, and then answers produces this order:
response.createdresponse.in_progressresponse.output_item.added reasoningresponse.reasoning_summary_text.deltaresponse.output_item.added function_callresponse.function_call_arguments.deltaresponse.function_call_arguments.doneresponse.output_item.done the tool finishedresponse.output_item.added messageresponse.content_part.addedresponse.output_text.deltaresponse.output_text.deltaresponse.reasoning_summary_text.doneresponse.output_item.done reasoningresponse.output_text.doneresponse.content_part.doneresponse.output_item.done messageresponse.completedItems open in the order they occur, and each one keeps its output_index for
the whole stream.
Two points differ from what you may expect:
Reasoning closes at the end of the turn, not before the answer. Providers do not agree on where reasoning sits. Gemini writes a first text chunk before its first reasoning chunk. Urai stores one reasoning summary for a turn, so the reasoning item stays open and collects text until the turn ends.
The output array of response.completed is the truth. It is built from
the stored rows, and its order is the order of the work. The SDKs replace the
items they collected with it, so you end with the correct answer either way.
The events
Section titled “The events”| Event | Meaning |
|---|---|
response.created |
The turn started. Carries the response object with status: "in_progress". |
response.in_progress |
The same object again, as OpenAI sends it. |
response.output_item.added |
An item opened. item holds its id and type. |
response.output_item.done |
An item closed. item restates it in full. |
response.reasoning_summary_text.delta |
More reasoning text. |
response.reasoning_summary_text.done |
The whole reasoning summary, as text. |
response.function_call_arguments.delta |
The arguments of a tool call. |
response.function_call_arguments.done |
The same arguments, complete. |
response.content_part.added |
The text part of the message opened. |
response.output_text.delta |
More answer text, as delta. |
response.output_text.done |
The whole answer, as text. |
response.content_part.done |
The text part closed. |
response.completed |
The turn finished. Carries the final response object. |
response.failed |
The turn failed. Carries the response object with status: "failed" and an error. |
response.completed and response.failed are terminal, and a stream sends one
of them, never both.
Tool calls in the stream
Section titled “Tool calls in the stream”A tool call appears as a function_call item.
event: response.output_item.addeddata: {"type":"response.output_item.added","sequence_number":4,"output_index":1,"item":{"id":"fc_5b3e...","type":"function_call","call_id":"call_1","name":"orders_lookup","arguments":"","status":"in_progress"}}
event: response.function_call_arguments.deltadata: {"type":"response.function_call_arguments.delta","sequence_number":5,"item_id":"fc_5b3e...","output_index":1,"delta":"{\"order\":\"4821\"}"}
event: response.function_call_arguments.donedata: {"type":"response.function_call_arguments.done","sequence_number":6,"item_id":"fc_5b3e...","output_index":1,"arguments":"{\"order\":\"4821\"}"}The arguments arrive in one delta and not as a trickle of tokens. Urai records the call after the model finishes writing it, so the whole string is ready at that moment.
The closing response.output_item.done reports the result:
"status": "completed"when the tool succeeded."status": "incomplete"when the tool returned an error. The agent reads that error and keeps working, so the turn continues.
The value the tool returned is not on the wire. The agent runs tools on the server, and it tells you what it found in its answer.
Nothing else about the run reaches the stream. Progress notes, tool summaries, and widget commands are for the chat interface, and they have no Responses equivalent.
Handle a failure
Section titled “Handle a failure”A turn that fails sends response.failed and closes.
event: response.faileddata: {"type":"response.failed","sequence_number":17,"response":{"id":"resp_9f2c...","status":"failed","error":{"code":"agent_error","message":"the tool sandbox stopped"}, ...}}The response object in that event holds whatever the turn produced before it failed, so you can show the partial answer.
The non-streaming path reports the same failure. The HTTP status stays 200,
and the body carries "status": "failed" with the error object.
A non-streaming request that runs past 10 minutes fails with the message the agent did not finish in time. A streamed request has no such limit.