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.

JavaScript

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-delta and tool-input-delta each 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, or tool-input-available for a tool input, closes the stream.
  • Transient data parts: a data-* chunk marked transient is 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-message event 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.

JavaScript

1

2

3

import { vercelAdaptor } from '@ably/ai-transport/vercel';

const adaptor = vercelAdaptor(channel);

Parameters

channelrequiredChannelWriter
The channel to publish on. A RealtimeChannel satisfies it. See createAgentAdaptor.
optionsoptionalAgentAdaptorOptions
Default headers and a logger. See createAgentAdaptor.

In TypeScript, pass the type parameters of your UIMessage:

TMetadataoptionalType
The type of each message's metadata. Defaults to unknown.
TDataPartsoptionalType
The custom data part types. Defaults to UIDataTypes.
TToolsoptionalType
The tool set that types the tool parts of the user message. Defaults to UITools.

Returns

AgentAdaptor<VercelEvent>. The adaptor, for events of the following type:

UIMessageChunkUIMessageChunk
Every chunk the AI SDK's toUIMessageStream produces, such as start, text-delta, tool-input-available and finish, unchanged.
user-messageVercelUserMessage
The message a client sends to start a turn.

Create 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.

JavaScript

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.

optionsoptionalDecoderOptions
A logger. See createDecoder.

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.

JavaScript

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

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