Skip to content

Research note

Streaming protocols: from tokens to UI messages

How text streams, SSE data streams, and UIMessage parts carry LLM output to the frontend — and what frontend engineers need from each.

Series · Part 3 of 6

LLM Interface Protocols

A factual map of the protocols, specs, and transport layers teams use to turn LLM responses into structured, trusted product interfaces — from output contracts and streaming to AG-UI, A2UI, and MCP Apps.

  1. 1 The protocol landscape for LLM interfaces
  2. 2 Output contracts: structured outputs and tool calling
  3. 3 Streaming protocols: from tokens to UI messages
  4. 4 AG-UI explained for frontend engineers
  5. 5 A2UI explained for frontend engineers
  6. 6 MCP Apps explained for frontend engineers

A typed output contract does not help users if the frontend receives it all at once, thirty seconds after they sent a prompt.

Streaming is the transport layer between model and UI. It is also where teams silently invent incompatible protocols — custom SSE events, ad-hoc JSON lines, websocket frames — that break the moment you swap backends or add a second client.

Key idea

Modern AI frontends need more than token streaming. Tool calls, reasoning blocks, files, custom data parts, and step boundaries each need a defined place on the wire. The Vercel AI SDK stream protocol is the most documented de facto standard for full-stack TypeScript apps; AG-UI standardizes a wider agent event bus for agentic hosts.

The problem: chat streaming is not UI streaming

Early integrations treated streaming as “append characters to a div.” That is fine for prose. It fails when the same response also contains:

Without a stream protocol, each feature becomes a one-off parser on the frontend. That is fragile, untestable, and expensive to onboard into.

Layer 1: Plain text streams

The simplest contract: the backend sends chunks of plain text; the client concatenates them.

The Vercel AI SDK text stream protocol documents this explicitly. streamText().toTextStreamResponse() on the backend; TextStreamChatTransport with streamProtocol: 'text' on the frontend.

Use when: the UI is prose-only — no tools, no structured parts, no multi-step agent loop.

Limit: the moment you add tool rendering or streamObject, text-only transport is insufficient.

Layer 2: SSE data streams and UIMessage

The AI SDK’s data stream protocol uses Server-Sent Events (SSE) with typed JSON parts. Custom backends must set the header x-vercel-ai-ui-message-stream: v1.

Core part types

The spec defines a growing vocabulary of parts. The ones that matter most for interface work:

Part typeRole
start / finishMessage lifecycle
text-start / text-delta / text-endStreaming prose blocks with IDs
reasoning-start / reasoning-delta / reasoning-endSeparated reasoning UI
tool-input-start / tool-input-delta / tool-input-availableTool call formation
tool-output-availableTool result for render
source-url / source-documentCitations
fileMedia attachments
data-*Custom structured payloads (e.g. data-weather)
start-step / finish-stepMulti-step agent loops
error / abortFailure and cancellation

Stream termination uses a literal [DONE] marker after SSE data: lines.

sequenceDiagram
  participant U as User
  participant F as Frontend
  participant B as Backend
  U->>F: send message
  F->>B: POST messages
  B-->>F: SSE start
  B-->>F: text-delta chunks
  B-->>F: tool-input-available
  B-->>F: tool-output-available
  B-->>F: finish
  B-->>F: DONE
  F->>U: render parts

UIMessage: the client-side model

On the client, parts accumulate into a UIMessage with a parts array. React hooks like useChat map part.type to UI:

message.parts.map((part, i) => {
  switch (part.type) {
    case "text":
      return <TextBlock key={i} text={part.text} />;
    case "tool-showComparison":
      return <ComparisonCard key={i} data={part.output} />;
    default:
      return null;
  }
});

This is protocol-driven UI at the transport layer: the wire format names renderable units. It pairs naturally with structured outputs when tool outputs match component props.

The SDK documents reading UIMessage streams and streaming custom data for cases where data-* parts carry app-specific state alongside the model stream.

Layer 3: Agent event buses (AG-UI)

When the backend is a long-running agent — LangGraph, CrewAI, Google ADK, Microsoft Agent Framework — a chat-oriented SSE schema may not cover shared state, interrupts, sub-agents, or frontend-executed tools.

AG-UI defines an event-based protocol for agent ↔ frontend communication: streaming chat, multimodal attachments, generative UI hooks, shared state diffs, human-in-the-loop interrupts, and tool output streaming. CopilotKit ships integrations with LangGraph, Google ADK, Mastra, and others.

Think of the relationship this way:

They can coexist. AG-UI can carry A2UI payloads as generative UI content inside its event model.

flowchart TB
  T["Text stream"] -->|"prose only"| C1["Chat bubble"]
  D["AI SDK data stream"] -->|"tools + parts"| C2["Part-based UI"]
  A["AG-UI events"] -->|"agent lifecycle"| C3["Full agentic UI"]

Other transports worth knowing

TransportTypical useUI relevance
OpenAI Realtime APIVoice + low-latency duplexAudio UI, live transcripts, tool calls over WebSocket
WebSockets (generic)Custom agents, binary dataYou own the schema — document it
HTTP pollingLegacy enterprise constraintsPoor UX for generation; avoid for primary chat
A2A message envelopesRemote agent resultsCarries payloads (e.g. A2UI); not a stream protocol itself

Frontend engineering implications

1. Render from parts, not from string length. If your UI logic is content.length > 0, tool and structured parts will break it.

2. Design loading states per part type. streamObject and text-delta both stream, but components should skeleton differently.

3. Treat custom data-* as a versioned contract. The suffix is free-form; document data-comparison-v1 like a public API.

4. Make cancellation first-class. The data stream includes abort parts; wire them to UI that restores editability.

5. Log raw streams in development. Protocol mismatches (missing finish-step, wrong header) are easier to diagnose from SSE logs than from a blank chat pane.

Product implication

Streaming protocol choice affects perceived speed and trust. Users forgive latency when partial structure appears early — a card skeleton beats a blinking cursor.

For agentic products, missing step boundaries means users cannot tell whether the system is thinking, calling tools, or stuck. Explicit start-step / finish-step (AI SDK) or AG-UI thinking events turn that into legible progress — without exposing raw chain-of-thought.

What to watch next

Transport gets bits to the client. Agent ↔ frontend semantics — shared state, interrupts, frontend tools — are where AG-UI explained for frontend engineers picks up.

References:

Previous
AG-UI explained for frontend engineers
Next
Output contracts: structured outputs and tool calling