Skip to main content

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 Adapter

What it is

Vercel’s AI SDK 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.
// 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):
"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.):
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:
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):
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:
TypeWhen emittedPayload
startAt the beginning of every run{ messageId }
text-startFirst text chunk in a turn{ id }
text-deltaEach text token{ id, textDelta }
text-endWhen tool calls interrupt text OR at the end{ id }
reasoning-deltaEach reasoning / thinking token (Claude, o1){ id, reasoningDelta }
tool-input-startLLM starts a tool call{ toolCallId, toolName }
tool-input-availableTool input is finalized{ toolCallId, toolName, input }
tool-output-availableTool result is back{ toolCallId, output }
errorAn error occurred mid-stream{ errorText }
finish-stepAt every roundtrip end{ finishReason, usage }
finishAt 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 chunkUI 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

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