Streaming
The Agents SDK can deliver output from the model and other execution steps incrementally. Streaming keeps your UI responsive and avoids waiting for the entire final result before updating the user.
Enabling streaming
Section titled “Enabling streaming”Pass a { stream: true } option to Runner.run() to obtain a streaming object rather than a full result:
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',});
const result = await run(agent, 'Tell me a story about a cat.', { stream: true,});When streaming is enabled the returned stream implements the AsyncIterable interface. Each yielded event is an object describing what happened within the run. The stream yields one of three event types, each describing a different part of the agent’s execution. Most applications only want the model’s text though, so the stream provides helpers.
Get the text output
Section titled “Get the text output”Call stream.toTextStream() to obtain a stream of the emitted text. When compatibleWithNodeStreams is true the return value is a regular Node.js Readable. We can pipe it directly into process.stdout or another destination.
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',});
const result = await run(agent, 'Tell me a story about a cat.', { stream: true,});
result .toTextStream({ compatibleWithNodeStreams: true, }) .pipe(process.stdout);The promise stream.completed resolves once the run and all pending callbacks are completed. Always await it if you want to ensure there is no more output. This includes post-processing work such as session persistence or history compaction hooks that finish after the last text token arrives.
toTextStream() only emits assistant text. Tool calls, handoffs, approvals, and other runtime events are available from the full event stream.
Listen to all events
Section titled “Listen to all events”You can use a for await loop to inspect each event as it arrives. Useful information includes low level model events, any agent switches and SDK specific run information:
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',});
const result = await run(agent, 'Tell me a story about a cat.', { stream: true,});
for await (const event of result) { // these are the raw events from the model if (event.type === 'raw_model_stream_event') { console.log(`${event.type} %o`, event.data); } // agent updated events if (event.type === 'agent_updated_stream_event') { console.log(`${event.type} %s`, event.agent.name); } // Agent SDK specific events if (event.type === 'run_item_stream_event') { console.log(`${event.type} %o`, event.item); }}See the streamed example for a fully worked script that prints both the plain text stream and the raw event stream.
Responses WebSocket transport (optional)
Section titled “Responses WebSocket transport (optional)”The streaming APIs on this page also work with the OpenAI Responses WebSocket transport.
Enable it globally with setOpenAIResponsesTransport('websocket'), or use your own OpenAIProvider with useResponsesWebSocket: true.
You do not need withResponsesWebSocketSession(...) or a custom OpenAIProvider just to stream over WebSocket. If reconnecting between runs is acceptable, run() / Runner.run(..., { stream: true }) still works after enabling the transport.
Use withResponsesWebSocketSession(...) or a custom OpenAIProvider / Runner when you want connection reuse and more explicit provider lifecycle control.
Continuation with previousResponseId uses the same semantics as the HTTP transport. The difference is just the transport and connection lifecycle.
If you build the provider yourself, remember to call await provider.close() when shutting down. Websocket-backed model wrappers are cached for reuse by default, and closing the provider releases those connections. withResponsesWebSocketSession(...) gives you the same reuse but scopes cleanup to a single callback automatically.
See examples/basic/stream-ws.ts for a complete example with streaming, tool calls, approvals, and previousResponseId.
Event types
Section titled “Event types”The stream yields three different event types:
raw_model_stream_event
Section titled “raw_model_stream_event”import { isOpenAIChatCompletionsRawModelStreamEvent, isOpenAIResponsesRawModelStreamEvent, type RunStreamEvent,} from '@openai/agents';
export function logOpenAIRawModelEvent(event: RunStreamEvent) { if (isOpenAIResponsesRawModelStreamEvent(event)) { console.log(event.source); console.log(event.data.event.type); return; }
if (isOpenAIChatCompletionsRawModelStreamEvent(event)) { console.log(event.source); console.log(event.data.event.object); }}Example:
{ "type": "raw_model_stream_event", "data": { "type": "output_text_delta", "delta": "Hello" }}If you are using the OpenAI provider, @openai/agents-openai and @openai/agents both export helpers that narrow raw OpenAI payloads without changing the generic RunRawModelStreamEvent contract in agents-core.
import type { RunStreamEvent } from '@openai/agents';import { isOpenAIResponsesRawModelStreamEvent } from '@openai/agents';
export function isOpenAIResponsesTextDelta(event: RunStreamEvent): boolean { return ( isOpenAIResponsesRawModelStreamEvent(event) && event.data.event.type === 'response.output_text.delta' );}When you only need transport-agnostic streaming code, checking event.type === 'raw_model_stream_event' is still enough.
If you are using OpenAI models and want to inspect provider-specific payloads without manual casts, the SDK also exports narrowing helpers:
isOpenAIResponsesRawModelStreamEvent(event)for Responses raw events.isOpenAIChatCompletionsRawModelStreamEvent(event)for Chat Completions chunks.
For these OpenAI model events, RunRawModelStreamEvent.source is also populated with either 'openai-responses' or 'openai-chat-completions'.
This is especially useful when you want to inspect Responses-only events such as response.reasoning_summary_text.delta, response.output_item.done, or MCP argument deltas while keeping TypeScript aware of the underlying event shape.
See examples/basic/stream-ws.ts, examples/tools/code-interpreter.ts, and examples/connectors/index.ts for fuller OpenAI-specific streaming patterns.
run_item_stream_event
Section titled “run_item_stream_event”import type { RunItemStreamEvent, RunStreamEvent } from '@openai/agents';
export function isRunItemStreamEvent( event: RunStreamEvent,): event is RunItemStreamEvent { return event.type === 'run_item_stream_event';}name identifies which kind of item was produced:
name | Meaning |
|---|---|
message_output_created | A message output item was created. |
handoff_requested | The model requested a handoff. |
handoff_occurred | The runtime completed a handoff to another agent. |
tool_search_called | A tool_search_call item was emitted. |
tool_search_output_created | A tool_search_output item with loaded tool definitions was emitted. |
tool_called | A tool call item was emitted. |
tool_output | A tool result item was emitted. |
reasoning_item_created | A reasoning item was emitted. |
tool_approval_requested | A tool call paused for human approval. |
The tool_search_* events only appear on Responses runs that use toolSearchTool() to load deferred tools during the run.
Example handoff payload:
{ "type": "run_item_stream_event", "name": "handoff_occurred", "item": { "type": "handoff_call", "id": "h1", "status": "completed", "name": "transfer_to_refund_agent" }}agent_updated_stream_event
Section titled “agent_updated_stream_event”import type { RunAgentUpdatedStreamEvent, RunStreamEvent,} from '@openai/agents';
export function isRunAgentUpdatedStreamEvent( event: RunStreamEvent,): event is RunAgentUpdatedStreamEvent { return event.type === 'agent_updated_stream_event';}Example:
{ "type": "agent_updated_stream_event", "agent": { "name": "Refund Agent" }}Human in the loop while streaming
Section titled “Human in the loop while streaming”Streaming is compatible with handoffs that pause execution (for example when a tool requires approval). The interruptions field on the stream object exposes the pending approvals, and you can continue execution by calling state.approve() or state.reject() for each of them. After the stream pauses, stream.completed resolves and stream.interruptions contains the approvals to handle. Executing again with { stream: true } resumes streaming output.
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Storyteller', instructions: 'You are a storyteller. You will be given a topic and you will tell a story about it.',});
let stream = await run( agent, 'What is the weather in San Francisco and Oakland?', { stream: true },);stream.toTextStream({ compatibleWithNodeStreams: true }).pipe(process.stdout);await stream.completed;
while (stream.interruptions?.length) { console.log( 'Human-in-the-loop: approval required for the following tool calls:', ); const state = stream.state; for (const interruption of stream.interruptions) { const approved = confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`, ); if (approved) { state.approve(interruption); } else { state.reject(interruption); } }
// Resume execution with streaming output stream = await run(agent, state, { stream: true }); const textStream = stream.toTextStream({ compatibleWithNodeStreams: true }); textStream.pipe(process.stdout); await stream.completed;}A fuller example that interacts with the user is human-in-the-loop-stream.ts.
- Remember to wait for
stream.completedbefore exiting to ensure all output has been flushed. - The initial
{ stream: true }option only applies to the call where it is provided. If you re-run with aRunStateyou must specify the option again. - If your application only cares about the textual result prefer
toTextStream()to avoid dealing with individual event objects.
With streaming and the event system you can integrate an agent into a chat interface, terminal application or any place where users benefit from incremental updates.