> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel UI Message Stream

> Drop-in compatible adapter for Vercel AI SDK's useChat hook

# Vercel UI Message Stream Adapter

## What it is

Vercel's [AI SDK](https://ai-sdk.dev/) has become the de-facto standard streaming protocol for AI chat UIs in React. Its `useChat`, `useAssistant`, and `useCompletion` hooks consume a specific line-delimited JSON stream over HTTP — the **UI Message Stream Protocol v1**.

Agentium ships an adapter that converts an `agent.stream()` async iterable into that exact protocol, so you can:

* Drop an Agentium agent into a Vercel AI Chatbot template with zero React changes
* Reuse any community ChatKit / shadcn AI chat component that targets the Vercel protocol
* Future-proof against the inevitable consolidation of streaming protocols

## Architecture

```
                                    ┌─────────────────────────────────────┐
                                    │   createAgentUIStreamResponse        │
                                    │                                      │
   agent.stream(input)  ──────────▶ │   1. wrap iterable in ReadableStream │
                                    │   2. convert each chunk -> UIMsg     │
                                    │   3. emit text-start / text-delta /  │
                                    │      text-end / tool-input-* /       │
                                    │      tool-output-available / finish  │
                                    │                                      │
                                    └─────────────┬───────────────────────┘
                                                  ▼
                                  ┌─────────────────────────────────┐
                                  │  Response (text/event-stream)    │
                                  │  x-vercel-ai-ui-message-stream:  │
                                  │  v1                              │
                                  └─────────────────────────────────┘
                                                  │
                                                  ▼
                                  ┌─────────────────────────────────┐
                                  │  Vercel useChat hook (React)     │
                                  └─────────────────────────────────┘
```

## Two entry points

### `createAgentUIStreamResponse(agent, input, options?)` — Web / Edge runtimes

Returns a standard fetch `Response` whose body streams UI message chunks. Use as the return value of a Next.js Route Handler, Hono handler, Cloudflare Worker, etc.

```typescript theme={null}
// app/api/chat/route.ts (Next.js App Router)
import { Agent, openai } from "@agentium/core";
import { createAgentUIStreamResponse } from "@agentium/transport";

const agent = new Agent({
  name: "assistant",
  model: openai("gpt-4o-mini"),
});

export async function POST(req: Request) {
  const { input, sessionId } = await req.json();
  return createAgentUIStreamResponse(agent, input, { sessionId });
}
```

Client side (unchanged from the Vercel example):

```tsx theme={null}
"use client";
import { useChat } from "ai/react";

export default function Chat() {
  const { messages, input, handleSubmit, handleInputChange } = useChat({ api: "/api/chat" });
  return (
    <form onSubmit={handleSubmit}>
      <input value={input} onChange={handleInputChange} />
      <button>Send</button>
      {messages.map((m) => <div key={m.id}>{m.content}</div>)}
    </form>
  );
}
```

### `pipeAgentUIStreamToResponse(agent, input, res, options?)` — Node ServerResponse / Express

For Node's classic `http.ServerResponse` shape (Express, Fastify with raw mode, etc.):

```typescript theme={null}
import { Agent, openai } from "@agentium/core";
import { pipeAgentUIStreamToResponse } from "@agentium/transport";
import express from "express";

const agent = new Agent({ name: "assistant", model: openai("gpt-4o-mini") });

const app = express();
app.use(express.json());

app.post("/api/chat", async (req, res) => {
  await pipeAgentUIStreamToResponse(agent, req.body.input, res, {
    sessionId: req.body.sessionId,
  });
});

app.listen(3000);
```

## `AgentUIStreamOptions`

Both functions accept the same options:

```typescript theme={null}
interface AgentUIStreamOptions {
  sessionId?: string;     // forwarded to agent.stream
  userId?: string;        // forwarded to agent.stream
  apiKey?: string;        // per-request model API key override
  signal?: AbortSignal;   // cancel the run mid-stream
}
```

## Lower-level: `agentUIStream(agent, input, options?)`

Returns the raw `ReadableStream<Uint8Array>` if you want to wrap the response yourself (custom headers, intermediate transforms, multiplex with another stream):

```typescript theme={null}
import { agentUIStream } from "@agentium/transport";

const stream = agentUIStream(agent, input, { sessionId });

return new Response(stream, {
  status: 200,
  headers: {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
    "x-vercel-ai-ui-message-stream": "v1",
    "X-Request-Id": crypto.randomUUID(), // your own header
  },
});
```

## Wire-level protocol

The adapter emits one SSE event per chunk in the form:

```
data: {"type":"text-delta","id":"text-1","textDelta":"Hello "}

data: {"type":"text-delta","id":"text-1","textDelta":"world!"}

data: {"type":"finish"}

data: [DONE]
```

The full chunk vocabulary:

| Type                    | When emitted                                 | Payload                           |
| ----------------------- | -------------------------------------------- | --------------------------------- |
| `start`                 | At the beginning of every run                | `{ messageId }`                   |
| `text-start`            | First text chunk in a turn                   | `{ id }`                          |
| `text-delta`            | Each text token                              | `{ id, textDelta }`               |
| `text-end`              | When tool calls interrupt text OR at the end | `{ id }`                          |
| `reasoning-delta`       | Each reasoning / thinking token (Claude, o1) | `{ id, reasoningDelta }`          |
| `tool-input-start`      | LLM starts a tool call                       | `{ toolCallId, toolName }`        |
| `tool-input-available`  | Tool input is finalized                      | `{ toolCallId, toolName, input }` |
| `tool-output-available` | Tool result is back                          | `{ toolCallId, output }`          |
| `error`                 | An error occurred mid-stream                 | `{ errorText }`                   |
| `finish-step`           | At every roundtrip end                       | `{ finishReason, usage }`         |
| `finish`                | At the very end of the whole run             | (none)                            |

The protocol matches AI SDK 5+ exactly. If you want to use it with the older AI SDK 3.x/4.x ("data stream protocol v0"), wrap the stream in your own transformer.

## What chunks does the adapter need from `agent.stream`?

The adapter is loose about the agent's internal chunk shape — it handles any of:

| Agent chunk                                                             | UI Message output                           |
| ----------------------------------------------------------------------- | ------------------------------------------- |
| `{ type: "text", text }`                                                | `text-start` then `text-delta`              |
| `{ type: "text-delta", delta }`                                         | `text-delta`                                |
| `{ type: "reasoning" \| "thinking", ... }`                              | `reasoning-delta`                           |
| `{ type: "tool.call" \| "tool-call", toolCallId, toolName, arguments }` | `tool-input-start` + `tool-input-available` |
| `{ type: "tool.result" \| "tool-result", toolCallId, output }`          | `tool-output-available`                     |
| `{ type: "error", error }`                                              | `error`                                     |
| `{ type: "finish" \| "done", finishReason, usage }`                     | `finish-step`                               |

If you build a custom Agent subclass with non-standard chunks, just teach it to emit one of the above shapes.

## Headers

The adapter sets:

```
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
x-vercel-ai-ui-message-stream: v1
```

`x-vercel-ai-ui-message-stream: v1` is what Vercel's hooks look for to identify the protocol. Don't strip it in a reverse proxy.

## Compose with [Resumable SSE](/transport/resumable-sse)

The UI Stream adapter is one-shot — if the client reconnects, they get a fresh stream. For full mobile-grade resumability (`Last-Event-ID` replay), use the lower-level `defaultEventLog` + `formatSSEEvent` instead of the UI stream adapter.

The two can coexist: serve UI-stream-protocol from `/api/chat` for desktop browsers (Vercel hooks), and a separate `/api/run` endpoint with resumable SSE for mobile clients.

## See also

* [Resumable SSE](/transport/resumable-sse) — for mobile / flaky network resilience
* [Vercel AI SDK docs](https://ai-sdk.dev/) — client-side hooks
* [@agentium/transport](/transport/overview) — full transport package overview
