OpenAI Responses
The OpenAI Responses API streams model output as typed events over HTTP. AI Transport's OpenAI codec carries that stream over an Ably channel, so the same server code reaches every subscribed client instead of a single HTTP response.
The OpenAI Responses API is OpenAI's interface for calling a model and getting back a stream of typed events: output text, reasoning, refusals, function-call arguments, and lifecycle events. AI Transport writes that stream to an Ably channel, so the channel gives you reconnects, multi-device delivery, and replay from your own store.
The OpenAI codec, openai from @ably/ai-transport/openai, carries the Responses API's own stream events, plus an input event for the input items a client sends. Pass it to createTransport, on the agent and on the client.
What the OpenAI Responses API brings
The openai npm package owns the model call and the typed event model:
| Feature | Description |
|---|---|
| Responses streaming | client.responses.create({ stream: true }) returns a stream of ResponseStreamEvents: output-text deltas, reasoning, refusals, function-call arguments, and item lifecycle events. |
| Function tools | You advertise function tools on the request. The model emits function calls, you run them, and you send the outputs back on the next Responses API call. |
| Reasoning models | Reasoning items carry the model's summarised thinking, and encrypted_content for requests that do not store responses. |
| Round-trippable items | Each output item is also valid Responses API input, so a stored conversation feeds the next turn without conversion. |
| Typed SDK | The official SDK owns auth, the HTTP call, streaming, and the Responses type definitions. |
What AI Transport adds
Everything below comes from AI Transport rather than the framework.
| Feature | Description |
|---|---|
| Streams that outlive a connection | The response flows through a channel rather than one HTTP response, so a client can reconnect and receive what it missed. |
| Multi-device delivery | Every device subscribed to the conversation receives the same messages in realtime. |
| History and replay | A client that loads the conversation from your store replays the channel from the stored serial, then receives live messages. |
| Token compaction | A client that joins mid-response receives the text so far in one delivery, rather than a replay of every token. |
Where they connect
On the server, AI Transport replaces the HTTP response with pipe(). To forward a Responses stream over HTTP yourself, you serialise each event into a Server-Sent Events response by hand:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Agent code, without AI Transport.
const encoder = new TextEncoder();
return new Response(
new ReadableStream({
async start(controller) {
for await (const event of stream) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
}
controller.close();
},
}),
{ headers: { 'Content-Type': 'text/event-stream' } },
);With AI Transport, a ResponseStreamEvent is already an event the codec knows, and pipe() takes the SDK's async iterable directly:
1
2
3
// Agent code.
const stream = await client.responses.create({ model: 'gpt-5.5', input, stream: true }, { signal: req.signal });
const { serial } = await transport.pipe(stream, { signal: req.signal });Every subscribed client decodes the same events. A client sends the user's input as an input event, and merges the output text deltas into the response it renders:
1
2
3
4
5
6
7
8
9
// Client code.
const requestId = crypto.randomUUID();
await transport.send({ type: 'input', items: [{ role: 'user', content: 'What is the weather?' }] }, { headers: { requestId } });
// Wake the agent. It reads the prompt from the channel.
await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ channelName: 'conversations:42', requestId }) });
transport.subscribe(({ event }) => {
if (event?.type === 'response.output_text.delta') appendText(event.item_id, event.delta);
});The codec appends each streamed part of a response, such as output text or a function call's arguments, to one message, and the matching *.done event closes it. Text goes on the channel once: the codec sends the text fields that the Responses API repeats in its *.done events and finished items as empty strings, and the final response's output as an empty array, so a client builds the text from the deltas.
To store the conversation for the next turn, keep the output items from the stream on the agent before the codec encodes them. The quickstart passes each event to the pipe through a small generator that keeps the response.completed event's output.
Run tools
The agent runs its tool loop as it would without AI Transport: call the Responses API, run the function calls the model emits, add the model's output items and the tool outputs to the input, and call it again. Pipe each call's stream to the transport, so every subscribed client sees each model turn as it streams. For reasoning models, keep the reasoning items that came before a function call in the input with the call.
A client that runs a tool itself, in the browser, sends the result as a function_call_output item in an input event, so the agent and every other client receive it.
Scope and trade-offs
The codec carries the raw Responses events, so the channel uses OpenAI's own event model. Stored items stay round-trippable to the Responses API, and a conversation is tied to the Responses shape. To work with several model providers behind one interface, use the Vercel AI SDK.
Read next
- Get started with OpenAI: build a working chat app.
- OpenAI codec API reference: the events the codec encodes and decodes, and how it writes them.
- Codec architecture: how a codec translates a framework's events into Ably messages.