ResponsesCodec
ResponsesCodec is the pre-built codec for the OpenAI Responses API. It implements Codec<OpenAIInput, OpenAIOutput, OpenAIProjection, OpenAIMessage>, so a session encodes a ResponseStreamEvent stream out and decodes it back into OpenAIMessage objects without a custom implementation.
It is a single codec value, so you pass ResponsesCodec itself and never call it. It takes no type parameters, and one instance serves every session in the process.
On the agent, bind it to the session:
1
2
3
4
5
6
7
8
import { createAgentSession } from '@ably/ai-transport';
import { ResponsesCodec } from '@ably/ai-transport/openai';
const session = createAgentSession({
client: ably,
channelName: invocation.sessionName,
codec: ResponsesCodec,
});On the client, the same value goes to the React provider:
1
2
3
4
5
6
7
8
'use client';
import { ClientSessionProvider } from '@ably/ai-transport/react';
import { ResponsesCodec } from '@ably/ai-transport/openai';
<ClientSessionProvider channelName="conversations:demo" codec={ResponsesCodec}>
<Chat />
</ClientSessionProvider>You rarely call the methods below yourself. A session drives init, fold, createEncoder, createDecoder, and getMessages for you. The methods you do call are createUserMessage, to send a user turn, and the three tool factories, to answer a tool call from the client.
Properties
init() => OpenAIProjectionOpenAIProjection.fold(state, event, meta) => OpenAIProjectionOpenAIInput or OpenAIOutput into the projection.createEncoder(channel, options?) => Encoder<OpenAIInput, OpenAIOutput>createDecoder() => Decoder<OpenAIInput, OpenAIOutput>getMessages(projection) => CodecMessage<OpenAIMessage>[]{ codecMessageId, message } pairs from an OpenAIProjection.createUserMessage(message: OpenAIMessage) => OpenAIInputOpenAIMessage as the UserMessage input variant.createRegenerate(target, parent) => OpenAIInputRegenerate input targeting an assistant message.createToolResult(codecMessageId, payload) => OpenAIInputToolResult input addressed at the assistant message that holds the function_call. Payload keyed by call_id.createToolResultError(codecMessageId, payload) => OpenAIInputToolResultError input for a tool that failed. Payload keyed by call_id.createToolApprovalResponse(codecMessageId, payload) => OpenAIInputToolApprovalResponse input answering a gated call. Payload keyed by call_id.Tool payloads
The three tool factories take a payload keyed by OpenAI's snake_case call_id, taken from the function_call being answered. The Vercel codec uses toolCallId for the same purpose, so the two are not interchangeable.
call_idStringcall_id of the function_call this result answers.outputResponses.ResponseInputItem.FunctionCallOutput['output']function_call_output.output shape, so it reaches the model unchanged.call_idStringcall_id of the function_call that failed.messageStringfunction_call_output.output, so it becomes the output the model reads on the next turn.call_idStringcall_id of the gated function_call.approvedBooleanreasonStringAn approval records a decision and produces no output, so the agent runs an approved call server-side when it resumes. A denial needs no server execution, because the reducer folds a rejection function_call_output on the client. Conversation helpers covers reading that state back.
Types
role'user' | 'assistant'itemsOpenAIItem[]function_call.toolCallStatesRecord<string, OpenAIToolCallState>call_id. Present only when the message holds at least one tool call, because OpenAI's item model has no field for either.OpenAIItemResponseOutputMessage | ResponseReasoningItem | ResponseFunctionToolCall | ResponseInputItem.FunctionCallOutput | ResponseInputItem.Message/responses with no conversion.approval'pending' | 'approved' | 'denied'result'ok' | 'failed'nameStringargumentsStringreasonStringtype'tool-approval-request'call_idStringcall_id of the function_call this approval gates.nameStringfunction_call.argumentsStringfunction_call.OpenAIInputUserMessage<OpenAIMessage> | Regenerate | ToolResult<OpenAIToolResultPayload> | ToolResultError<OpenAIToolResultErrorPayload> | ToolApprovalResponse<OpenAIToolApprovalResponsePayload>ai-input wire. The SDK's well-known input variants, with the tool variants parameterised by the OpenAI payload shapes.OpenAIOutputResponseStreamEvent | { type: 'function_call_output', item } | ToolApprovalRequestEventai-output wire. The codec passes OpenAI's own ResponseStreamEvent through, and adds two events of its own: function_call_output, because a Responses stream never carries a tool's output, and tool-approval-request, because the Responses API has no equivalent.The event inventory is total, so an event outside it throws at the encoder rather than being dropped. An agent that enables a hosted tool (web or file search, code interpreter, image generation, MCP, custom tools) or audio must filter those events out of the stream before piping it, as Scope and trade-offs describes.
OpenAIProjection{ messages: CodecMessage<OpenAIMessage>[] }{ codecMessageId, message } pair list in publication order. The SDK does not inspect this shape. Use getMessages instead.The well-known input variants (UserMessage, Regenerate, ToolResult, ToolResultError, ToolApprovalResponse) are documented on the Codec reference page.
Example
Decode a single Ably message and fold the resulting events into a fresh projection. ReducerMeta.serial is required, because the reducer uses it as the high-water-mark for idempotency.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { ResponsesCodec } from '@ably/ai-transport/openai';
const decoder = ResponsesCodec.createDecoder();
let projection = ResponsesCodec.init();
channel.subscribe((message) => {
if (!message.serial) return; // live channel-subscribe messages always carry one
const { inputs, outputs } = decoder.decode(message);
for (const input of inputs) {
projection = ResponsesCodec.fold(projection, input, { serial: message.serial });
}
for (const output of outputs) {
projection = ResponsesCodec.fold(projection, output, { serial: message.serial });
}
render(ResponsesCodec.getMessages(projection).map((entry) => entry.message));
});Read next
- Conversation helpers:
toResponsesInputand the correlation readers that drive the agent loop. - OpenAI Responses: how the codec fits an agent, including the tool and approval flow.
- Codec: the generic
Codecinterface and the well-known input variants.