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.

JavaScript

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() => VercelProjection
Build an empty VercelProjection.
fold(state, event, meta) => VercelProjection
Fold a VercelInput or VercelOutput into the projection.
createEncoder(channel, options?) => Encoder<VercelInput, VercelOutput>
Create a Vercel encoder bound to the supplied channel writer.
createDecoder() => Decoder<VercelInput, VercelOutput>
Create a Vercel decoder for the channel.
getMessages(projection) => CodecMessage<UIMessage>[]
Extract { 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) => VercelInput
Wrap a UIMessage as the UserMessage input variant.
createRegenerate(target, parent) => VercelInput
Build a Regenerate input targeting an assistant UIMessage.
createToolResult(codecMessageId, { toolCallId, output }) => VercelInput
Build a ToolResult input addressed at the assistant codec-message that contains the tool call.
createToolResultError(codecMessageId, { toolCallId, message }) => VercelInput
Build a ToolResultError input.
createToolApprovalResponse(codecMessageId, { toolCallId, approved, reason? }) => VercelInput
Build a ToolApprovalResponse 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>
Discriminated union of every record-shape a client publishes on the 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.UIMessageChunk
Every record-shape the agent publishes on the ai-output wire. The codec passes Vercel's UIMessageChunk through unchanged.
TProjection{ messages: CodecMessage<UIMessage>[], ... }
Per-run projection. Carries the { 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.

JavaScript

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));
});