Streaming and events
Purpose and Scope
Streaming lets an application begin processing model output before the full response is finished. In the OpenAI platform documentation this is described as HTTP streaming over server-sent events, enabled with a streaming flag on request APIs such as Responses. In this SDK, the same idea appears in two complementary forms: low-level async iteration over streamed events or chunks, and higher-level helpers that turn those events into snapshots, callbacks, or final accumulated objects. The developer problem is not only how to print partial text, but how to preserve ordering, observe lifecycle events, and recover a complete response when generation ends.
Sources: README.md, src/lib/ChatCompletionStream.ts, tests/streaming.test.ts, tests/lib/ResponseAccumulator.test.ts
The README establishes the Responses API as the primary model interaction surface and shows the normal non-streaming shape first: construct OpenAI, call client.responses.create, then read response.output_text. Streaming keeps the same client-centered programming model, but changes the response handling phase. Instead of waiting for one final object, application code receives a sequence of typed events or chunks and can update a terminal, UI, log, or tool dispatcher incrementally. For older chat workflows, ChatCompletionStream provides a chat-specific event emitter and async iterable around streamed chat completion chunks.
Sources: README.md, src/lib/ChatCompletionStream.ts
Relevant Source Files
README.md— Introduces the SDK, theOpenAIclient, the Responses API as the primary model surface, and the baseline non-streaming output pattern that streaming builds on.src/lib/ChatCompletionStream.ts— Defines the chat streaming helper, its event interfaces, stream parameter type, async iteration contract, and specialized events for content, refusals, tool-call arguments, and log probabilities.tests/streaming.test.ts— Exercises server-sent event decoding behavior, including events with and without data, multiple events, multiple data lines, and async iteration over parsed SSE messages.tests/lib/ResponseAccumulator.test.ts— Verifies that Responses stream events can be accumulated into a final response snapshot, replayed without mutating raw events, and replaced by authoritative terminal responses.
Core Streaming Primitives
The most general primitive is an async stream of events. The SSE tests import _iterSSEMessages from openai/core/streaming and iterate it with Symbol.asyncIterator, which shows the SDK’s internal contract for turning an HTTP response body into discrete message records. The tests cover event: fields, data: fields, missing event names, empty data, multiple events, and multi-line data. This matters for application code because platform streaming is event-oriented: a stream is not just a string transport, it is a sequence of named events whose payloads may arrive across several network chunks.
Sources: tests/streaming.test.ts
For chat completions, ChatCompletionStream is the higher-level primitive. It extends AbstractChatCompletionRunner and implements AsyncIterable<ChatCompletionChunk>, so callers can consume chunks with for await while also subscribing to named events. The class exposes event payload interfaces such as ContentDeltaEvent, ContentDoneEvent, RefusalDeltaEvent, FunctionToolCallArgumentsDeltaEvent, and logprob delta and done events. That design separates raw chunk receipt from semantic milestones: partial text, final parsed content, refusal text, tool-call argument assembly, and token log probability updates are all represented as distinct event channels.
Sources: src/lib/ChatCompletionStream.ts
Event Iteration and Semantic Events
A streaming event should be handled according to its type. The official streaming guide names Responses lifecycle and content events such as response.created, response.output_text.delta, and response.completed; the accumulator tests demonstrate the same lifecycle in SDK terms. A typical response starts with response.created, adds an output item with response.output_item.added, adds a content part with response.content_part.added, appends text with response.output_text.delta, and ends with a terminal event such as response.completed. Applications that only need text can listen for deltas, while applications that need full state should preserve the full event order.
Sources: tests/lib/ResponseAccumulator.test.ts
A minimal Responses streaming loop is conceptually shaped like this:
const stream = await client.responses.create({
model: 'gpt-5.5',
input: 'Write a short deployment checklist.',
stream: true,
});
for await (const event of stream) {
if (event.type === 'response.output_text.delta') {
process.stdout.write(event.delta);
}
}This pattern uses the SDK client introduced in the README and the typed event model reflected in the accumulator tests. In production, do not assume every event is text. Tool calls, annotations, refusals, failures, and completion events are all part of the same stream model. Filtering too aggressively can make later continuation or auditing harder, especially when a workflow needs complete output items rather than only human-readable text.
Sources: README.md, tests/lib/ResponseAccumulator.test.ts
Response Accumulation and Final Snapshots
Incremental display and final state are different tasks. tests/lib/ResponseAccumulator.test.ts verifies accumulateResponse, a helper that applies ResponseStreamEvent objects to build a response snapshot. The test starts from a created response, adds an assistant message item, adds an output text content part, appends the Hello world delta, and then processes response.completed. The expected snapshot has output_text set to Hello world and an output message whose content includes an output_text part with the accumulated text.
Sources: tests/lib/ResponseAccumulator.test.ts
The accumulator tests also document two important correctness constraints. First, accumulation should not mutate the raw event objects, because callers may store, replay, or inspect the original stream later. The replay test accumulates the same event array twice and expects identical results while confirming the original event payloads remain unchanged. Second, terminal events such as response.completed, response.failed, and response.incomplete can carry an authoritative response object. When such an event arrives, consumers should treat that terminal response as the final source of truth instead of relying only on locally accumulated deltas.
Sources: tests/lib/ResponseAccumulator.test.ts
ChatCompletionStream Reference
ChatCompletionStream is the compatibility-oriented helper for Chat Completions streaming. Its parameter type is ChatCompletionStreamParams, which is based on chat completion create parameters while omitting stream and allowing stream?: true. The class implements AsyncIterable<ChatCompletionChunk>, so it can be used in iteration-oriented code, and it emits semantic events through ChatCompletionStreamEvents. The event names include content, chunk, content.delta, content.done, refusal.delta, refusal.done, tool_calls.function.arguments.delta, tool_calls.function.arguments.done, logprobs.content.delta, logprobs.content.done, logprobs.refusal.delta, and logprobs.refusal.done.
Sources: src/lib/ChatCompletionStream.ts
Use chat stream events when you need chat-specific conveniences: a running content snapshot, parsed content for auto-parseable response formats, incremental function-call arguments, or logprob snapshots. Use raw async iteration when you need maximum control over chunk handling. The source imports parser helpers such as partialParse, maybeParseChatCompletion, and tool parsing predicates, which indicates that the stream helper does more than forward bytes; it maintains per-choice state and emits richer semantic events as the chat completion evolves.
Sources: src/lib/ChatCompletionStream.ts
Testing Signals and Operational Guidance
The streaming tests are useful design signals for application authors. They show that SSE messages may contain an event name without data, data without an event name, several events in one stream, or data split over multiple data: lines before the blank line that terminates an SSE message. Robust consumers should therefore iterate the SDK stream rather than parsing raw response bytes themselves. The SDK test suite verifies these decoding cases against ReadableStreamFrom and Response, which also reflects the cross-runtime web-stream style used by modern JavaScript environments.
Sources: tests/streaming.test.ts
When building a streaming feature, decide first whether the user experience needs live deltas, final structured state, or both. For live text, handle delta events and write or render only the delta. For complete state, accumulate events and wait for terminal events before persisting the final object. For Chat Completions, prefer ChatCompletionStream events when you need chat-specific snapshots, parsed tool-call arguments, refusals, or logprobs. Next, read the Responses API concepts page for the modern event model, and the Chat Completions page if you maintain older message-based streaming workflows.
Sources: README.md, src/lib/ChatCompletionStream.ts, tests/lib/ResponseAccumulator.test.ts