Ag Ui ProtocolSAFE
🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.
Overview
🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.
dafe8ab3bd88OBSERVED · 2026-10-08What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: ai-core/ag-ui-protocol
description: >
Server-side AG-UI streaming protocol implementation: StreamChunk event
types (RUN_STARTED, TEXT_MESSAGE_START/CONTENT/END, TOOL_CALL_START/ARGS/END,
RUN_FINISHED, RUN_ERROR, STEP_STARTED/STEP_FINISHED, STATE_SNAPSHOT/DELTA,
CUSTOM), toServerSentEventsStream() for SSE format, toHttpStream() for
NDJSON format. For backends serving AG-UI events without client packages.
type: sub-skill
library: tanstack-ai
library_version: '0.42.0'
sources:
- 'TanStack/ai:docs/protocol/chunk-definitions.md'
- 'TanStack/ai:docs/protocol/sse-protocol.md'
- 'TanStack/ai:docs/protocol/http-stream-protocol.md'
- 'TanStack/ai:docs/protocol/custom-events.md'
---
# AG-UI Protocol
This skill builds on ai-core. Read it first for critical rules.
## Setup — Server Endpoint Producing AG-UI Events via SSE
```typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { messages } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.6'),
messages,
})
return toServerSentEventsResponse(stream)
}
```
`chat()` returns an `AsyncIterable<StreamChunk>`. Each `StreamChunk` is a
typed AG-UI event (discriminated union on `type`). The `toServerSentEventsResponse()`
helper encodes that iterable into an SSE-formatted `Response` with correct headers.
## Setup — Receiving AG-UI RunAgentInput on the Server
```typescript
import {
chat,
chatParamsFromRequestBody,
mergeAgentTools,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { serverTools } from './tools'
export async function POST(req: Request) {
let params
try {
params = await chatParamsFromRequestBody(await req.json())
} catch (error) {
return new Response(
error instanceof Error ? error.message : 'Bad request',
{ status: 400 },
)
}
const stream = chat({
adapter: openaiText('gpt-5.6'),
messages: params.messages,
tools: mergeAgentTools(serverTools, params.tools),
})
return toServerSentEventsResponse(stream)
}
```
`chatParamsFromRequestBody` validates the body against `RunAgentInputSchema` from `@ag-ui/core`. `mergeAgentTools` merges the server's tool registry with client-declared tools (server wins on collision; client-only tools become no-execute stubs that flow through the runtime's `ClientToolRequest` path).
`params.messages` is a mixed array of TanStack `UIMessage` anchors (with `parts`) and AG-UI fan-out duplicates (`{role:'tool',...}`, `{role:'reasoning',...}`). The existing `convertMessagesToModelMessages` (called inside `chat()`) handles dedup automatically.
**Wire shape (POST body):** AG-UI `RunAgentInput` — `{threadId, runId, parentRunId?, state, messages, tools, context, forwardedProps}`. The `messages` array carries TanStack `UIMessage` anchors with their canonical `parts` plus AG-UI mirror fields (`content`, `toolCalls`) inline; tool results and thinking parts are additionally emitted as fan-out `{role:'tool',...}` and `{role:'reasoning',...}` entries.
**`forwardedProps` security:** Don't spread it directly into `chat()` — clients could override `adapter`, `model`, `tools`, etc. Always allowlist specific fields.
## Core Patterns
### 1. SSE Format — toServerSentEventsStream / toServerSentEventsResponse
**Wire format:** Each event is `data: <JSON>\n\n`. Stream ends with `data: [DONE]\n\n`.
```typescript group=sse-response
import {
chat,
toServerSentEventsStream,
toServerSentEventsResponse,
} from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const messages = [{ role: 'user' as const, content: 'Hello' }]
// Option A: Get a ReadableStream (manual Response construction)
const abortController = new AbortController()
const stream = chat({
adapter: openaiText('gpt-5.6'),
messages,
abortController,
})
const sseStream = toServerSentEventsStream(stream, abortController)
const response = new Response(sseStream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
// Option B: Use the helper (sets headers automatically)
const response2 = toServerSentEventsResponse(stream, { abortController })
// Default headers: Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive
```
**Default response headers set by `toServerSentEventsResponse()`:**
| Header | Value |
| --------------- | ------------------- |
| `Content-Type` | `text/event-stream` |
| `Cache-Control` | `no-cache` |
| `Connection` | `keep-alive` |
Custom headers merge on top (user headers override defaults):
```typescript group=sse-response
toServerSentEventsResponse(stream, {
headers: {
'X-Accel-Buffering': 'no', // Disable nginx buffering
'Cache-Control': 'no-store', // Override default
},
abortController,
})
```
**Error handling:** If the stream throws, a `RUN_ERROR` event is emitted
automatically before the stream closes. If the `abortController` is already
aborted, the error event is suppressed and the stream closes silently.
### 2. HTTP Stream (NDJSON) — toHttpStream / toHttpResponse
**Wire format:** Each event is `<JSON>\n` (newline-delimited JSON, no SSE prefix, no `[DONE]` marker).
```typescript
import { chat, toHttpStream, toHttpResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
const messages = [{ role: 'user' as const, content: 'Hello' }]
// Option A: Get a ReadableStream
const abortController = new AbortController()
const stream = chat({
adapter: openaiText('gpt-5.6'),
messages,
abortController,
})
const ndjsonStream = toHttpStream(stream, abortController)
const response = new Response(ndjsonStream, {
headers: {
'Content-Type': 'application/x-ndjson',
},
})
// Option B: Use the helper (does NOT set headers automatically)
const responTrust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
dafe8ab3bd88full audit observations/trust-audit/skill/tanstack__ag-ui-protocol.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-08 | dafe8ab3bd88 | SAFE | B | 89 | first audit |
Questions
What does the Ag Ui Protocol skill do?
🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.
Is Ag Ui Protocol safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Ag Ui Protocol access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
How current is this page?
The grade is for one exact copy of the source (dafe8ab3bd88), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.