createUIMessageCodec
createUIMessageCodec() returns the pre-built codec for the Vercel AI SDK. The returned codec implements Codec<VercelInput, VercelOutput, VercelProjection, UIMessage> so a session can encode UIMessageChunk events out and decode them back into UIMessage objects without a custom implementation.
You rarely call createUIMessageCodec() yourself. The Vercel-pre-bound createClientSession and createAgentSession already supply the codec. Call it explicitly when you build a codec wrapper, run the encoder or decoder outside of a session, or compose the codec into a different codec.
1
2
3
4
5
import { createUIMessageCodec } from '@ably/ai-transport/vercel';
const codec = createUIMessageCodec();
const decoder = codec.createDecoder();
const projection = codec.init();Properties
createUIMessageCodec() is a factory rather than a type. Each call returns a fresh, stateless codec value that satisfies the full Codec interface for the Vercel TInput, TOutput, TProjection, and TMessage parameters listed below. Because the codec is stateless, hold a single instance at module scope (or, in React, useMemo(() => createUIMessageCodec(), [])) and reuse it rather than calling the factory inline on every render.
init() => VercelProjectionVercelProjection.fold(state, event, meta) => VercelProjectionVercelInput or VercelOutput into the projection.createEncoder(channel, options?) => Encoder<VercelInput, VercelOutput>createDecoder() => Decoder<VercelInput, VercelOutput>getMessages(projection) => CodecMessage<UIMessage>[]{ codecMessageId, message } pairs from a VercelProjection. The domain UIMessage.id is preserved verbatim from the source stream; the SDK keys correlation off the paired codecMessageId.createUserMessage(message: UIMessage) => VercelInputUIMessage as the UserMessage input variant.createRegenerate(target, parent) => VercelInputRegenerate input targeting an assistant UIMessage.createToolResult(codecMessageId, { toolCallId, output }) => VercelInputToolResult input addressed at the assistant codec-message that contains the tool call.createToolResultError(codecMessageId, { toolCallId, message }) => VercelInputToolResultError input.createToolApprovalResponse(codecMessageId, { toolCallId, approved, reason? }) => VercelInputToolApprovalResponse input.Type parameters
createUIMessageCodec is generic over the AI SDK's three UIMessage type parameters, as createUIMessageCodec<TMetadata, TDataParts, TTools>(). Supplying concrete types specialises the codec so getMessages (and a session's view.getMessages()) return messages whose metadata, data parts, and tool parts carry your types instead of the SDK defaults; omitting them preserves the default inference. The same parameters thread through createClientSession, createAgentSession, and createChatTransport, and typed messages shows the end-to-end pattern.
VercelInput, VercelOutput, and VercelProjection are likewise generic over the same three parameters, each defaulting to the SDK default. The tables below show the default instantiation.
TInputUserMessage<UIMessage> | Regenerate | ToolResult<VercelToolResultPayload> | ToolResultError<VercelToolResultErrorPayload> | ToolApprovalResponse<VercelToolApprovalResponsePayload>ai-input wire. Composed from the SDK's well-known input variants, with the tool variants parameterised by the Vercel domain payload shapes (VercelToolResultPayload, VercelToolResultErrorPayload, VercelToolApprovalResponsePayload).TOutputAI.UIMessageChunkai-output wire. The codec passes Vercel's UIMessageChunk through unchanged.TProjection{ messages: CodecMessage<UIMessage>[], ... }{ codecMessageId, message } pair list plus internal stream-tracker state and a high-water-mark serial for idempotency. 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. The Vercel codec layers no extra input variants.
Example
Decode a single Ably message and fold the resulting events into a fresh projection. ReducerMeta.serial is required (the reducer uses it as the high-water-mark for idempotency); the messageId field is optional and only needed when the codec routes events to an existing message.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createUIMessageCodec } from '@ably/ai-transport/vercel';
const codec = createUIMessageCodec();
const decoder = codec.createDecoder();
let projection = codec.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 = codec.fold(projection, input, { serial: message.serial });
}
for (const output of outputs) {
projection = codec.fold(projection, output, { serial: message.serial });
}
render(codec.getMessages(projection).map((entry) => entry.message));
});