lukasa1993/tanstack-skills-extracted/tanstack-ai-core-ag-ui-protocol
Claude Code and agent skill by lukasa1993/tanstack-skills-extracted
- community
Checking that the SKILL.md file is still in the repository.
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, REASONING_*, STATE_SNAPSHOT/DELTA, CUSTOM), toServerSentEventsStream() for SSE format, toHttpStream() for NDJSON format. For backends serving AG-UI events without client packages.
Install
npx vskill@latest install lukasa1993/tanstack-skills-extracted/tanstack-ai-core-ag-ui-protocolSKILL.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
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
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. The last event is RUN_FINISHED (or RUN_ERROR). There is no [DONE] line.
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):
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).
Client-side pairing: SSE endpoints are consumed by fetchServerSentEvents(). HTTP stream endpoints are consumed by fetchHttpStream(). Both are connection adapters from @tanstack/ai-react (or the framework-specific package).
3. AG-UI Event Types Reference
All events extend BaseAGUIEvent which carries type, timestamp, optional model, and optional rawEvent.
| Event Type | Description | |
|---|---|---|
RUN_STARTED | First event in a stream. Carries runId and optional threadId. | |
TEXT_MESSAGE_START | New text message begins. Carries messageId and role. | |
TEXT_MESSAGE_CONTENT | Incremental text token. Carries messageId and delta (the new text). | |
TEXT_MESSAGE_END | Text message complete. Carries messageId. | |
TOOL_CALL_START | Tool invocation begins. Carries toolCallId, toolCallName, and index. | |
TOOL_CALL_ARGS | Incremental tool arguments JSON. Carries toolCallId and delta. | |
TOOL_CALL_END | Tool call arguments complete. Carries toolCallId. | |
REASONING_START | Thinking begins. REASONING_MESSAGE_START / _CONTENT / _END stream the text (delta), then REASONING_END. | |
REASONING_ENCRYPTED_VALUE | Provider thinking signature. Kept on the thinking part and sent back on the next turn. | |
MESSAGES_SNAPSHOT | Full conversation transcript snapshot. Carries messages: Array<UIMessage>. | |
STATE_SNAPSHOT | Full application state snapshot. Carries state: Record<string, unknown>. | |
STATE_DELTA | Incremental state update. Carries delta: Record<string, unknown>. | |
CUSTOM | Extension point. Carries name (string) and optional value (unknown). | |
RUN_FINISHED | Stream complete. Carries runId and finishReason ('stop' / 'length' / 'content_filter' / 'tool_calls' / null). | |
RUN_ERROR | Error during stream. Carries message, optional code and runId; a nested error: { message, code? } copy is kept too. |
Typical event sequence for a text-only response:
Typical event sequence with tool calls:
Type aliases: StreamChunk is an alias for AGUIEvent (the discriminated union of all event interfaces). For event type names, use the EventType enum, or ` ${EventType} ` for the string union.
4. Typed CUSTOM Events — ChatStream and KnownCustomEvent
The CUSTOM row above describes the raw StreamChunk union, where the single generic CustomEvent member types value as any -- once merged into a union, that any poisons every other member too, so narrowing on name still leaves value: any. chat() doesn't return raw StreamChunk; by default (no outputSchema, stream not explicitly false) it returns ChatStream, which swaps that generic member for KnownCustomEvent -- a discriminated union of every CUSTOM event TanStack AI itself emits, each with a literal name and a concrete value. Narrow with a plain if -- no helper, no cast:
Caveat -- `.endsWith()` (or any non-literal check) does not narrow. SessionIdEvent['name'] is the template-literal type ` ${string}.session-id . TypeScript's control-flow narrowing only understands exact comparisons (===) and in/type-predicate checks against a discriminant -- a runtime chunk.name.endsWith('.session-id') check doesn't inform the type system, so chunk.value stays the union of every KnownCustomEvent's value, not { sessionId: string }. Compare against the exact literal you expect, or write a user-defined type predicate ((c): c is SessionIdEvent => c.name.endsWith('.session-id')) and call that in the if` instead.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/tanstack-ai-core-ag-ui-protocol/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/lukasa1993/tanstack-skills-extracted/tanstack-ai-core-ag-ui-protocol)