Vercel
The Vercel codec carries the Vercel AI SDK's UIMessageChunk events over an Ably channel, unchanged, plus a user-message event for the message a client sends. The agent pipes the stream that toUIMessageStream returns, and the client merges the chunks it receives with the AI SDK's own readUIMessageStream.
Import vercelAdaptor and vercelDecoder from @ably/ai-transport/vercel. The agent publishes with an agent adaptor, and the client reads with a decoder. The codec works with the AI SDK's ai package at version 6 or 7.
1
2
3
4
5
6
7
8
import { vercelAdaptor, vercelDecoder } from '@ably/ai-transport/vercel';
// Agent code. channel is a channel from your Ably Pub/Sub client.
const adaptor = vercelAdaptor(channel);
// Client code.
const decoder = vercelDecoder();
await channel.subscribe(decoder.receive);The codec writes each event type in one of three ways:
- Text, reasoning and tool-input streams:
text-delta,reasoning-deltaandtool-input-deltaeach append their text to one message for the stream, keyed by the stream id or the tool call id. The first delta publishes the message. The chunk that starts the stream and the chunk that ends it are plain publishes of their own, and the end chunk, ortool-input-availablefor a tool input, closes the stream. - Transient data parts: a
data-*chunk markedtransientis published as an ephemeral message, so it reaches subscribed clients and stays out of history. - Every other chunk is a plain publish that carries the whole event as the message data. The
user-messageevent is a plain publish that carries its message.
Create a Vercel adaptor
vercelAdaptor<TMetadata, TDataParts, TTools>(channel: ChannelWriter, options?: AgentAdaptorOptions): AgentAdaptor<VercelEvent<TMetadata, TDataParts, TTools>>Create an agent adaptor that publishes Vercel events on channel, typed for your own UIMessage type parameters.
1
2
3
import { vercelAdaptor } from '@ably/ai-transport/vercel';
const adaptor = vercelAdaptor(channel);Parameters
channelrequiredChannelWriterRealtimeChannel satisfies it. See createAgentAdaptor.optionsoptionalAgentAdaptorOptionscreateAgentAdaptor.In TypeScript, pass the type parameters of your UIMessage:
TMetadataoptionalTypeunknown.TDataPartsoptionalTypeUIDataTypes.TToolsoptionalTypeUITools.Returns
AgentAdaptor<VercelEvent>. The adaptor, for events of the following type:
UIMessageChunkUIMessageChunktoUIMessageStream produces, such as start, text-delta, tool-input-available and finish, unchanged.user-messageVercelUserMessageCreate a Vercel decoder
vercelDecoder<TMetadata, TDataParts, TTools>(options?: DecoderOptions): Decoder<VercelEvent<TMetadata, TDataParts, TTools>>Create a decoder that decodes what a Vercel adaptor publishes, typed for your own UIMessage type parameters.
1
2
3
4
import { vercelDecoder } from '@ably/ai-transport/vercel';
const decoder = vercelDecoder();
await channel.subscribe(decoder.receive);Parameters
vercelDecoder takes the same type parameters as vercelAdaptor.
optionsoptionalDecoderOptionscreateDecoder.Returns
Decoder<VercelEvent>. The decoder, for the events the adaptor publishes.
Example
The agent publishes the user's message and pipes the AI SDK's chunk stream. The client POSTs the user's message to the agent, and merges each response from its start chunk to its finish chunk with readUIMessageStream.
1
2
3
4
5
6
7
// Agent code.
import { streamText, toUIMessageStream } from 'ai';
const headers = { requestId: message.id };
await adaptor.send({ type: 'user-message', message }, { headers });
const result = streamText({ model, messages });
await adaptor.pipe(toUIMessageStream({ stream: result.fullStream }), { signal: req.signal, headers });1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Client code.
import { readUIMessageStream } from 'ai';
let response;
decoder.onEvent(({ event }) => {
if (event === undefined || event.type === 'user-message') return;
if (event.type === 'start') {
const stream = new ReadableStream({ start: (controller) => (response = controller) });
(async () => {
for await (const message of readUIMessageStream({ stream })) render(message);
})();
}
response?.enqueue(event);
if (event.type === 'finish') response?.close();
});
await channel.subscribe(decoder.receive);
const message = { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: 'What is the weather?' }] };
// Wake the agent. It publishes the prompt to the channel.
await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ channelName: 'conversations:42', message }) });