Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .changeset/ag-ui-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@tanstack/ai': minor
'@tanstack/ai-openai': minor
'@tanstack/ai-anthropic': minor
'@tanstack/ai-gemini': minor
'@tanstack/ai-ollama': minor
---

feat: Add AG-UI protocol events to streaming system

All text adapters now emit AG-UI protocol events in addition to supporting legacy event types:

- `RUN_STARTED` / `RUN_FINISHED` - Run lifecycle events
- `TEXT_MESSAGE_START` / `TEXT_MESSAGE_CONTENT` / `TEXT_MESSAGE_END` - Text message streaming
- `TOOL_CALL_START` / `TOOL_CALL_ARGS` / `TOOL_CALL_END` - Tool call streaming

Comment on lines +14 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Document all AG-UI event types introduced in this PR.

The changeset omits several event types mentioned in the broader PR changes. Based on the AI summary, the following event types are also part of the AG-UI protocol but are not documented here:

  • RUN_ERROR — error lifecycle event
  • STEP_STARTED / STEP_FINISHED — step lifecycle events
  • STATE_SNAPSHOT / STATE_DELTA — state events
  • CUSTOM — custom events

Include all introduced event types in the changeset description to provide a complete changelog for users.

🤖 Prompt for AI Agents
In @.changeset/ag-ui-events.md around lines 14 - 19, Update the changeset text
to document all AG-UI protocol events introduced in the PR by adding the missing
event types and short descriptions: include RUN_ERROR (error lifecycle event),
STEP_STARTED and STEP_FINISHED (step lifecycle events), STATE_SNAPSHOT and
STATE_DELTA (state events), and CUSTOM (custom events), alongside the already
listed RUN_STARTED/RUN_FINISHED, TEXT_MESSAGE_*, and TOOL_CALL_* entries; ensure
each event name (e.g., RUN_ERROR, STEP_STARTED, STATE_SNAPSHOT, CUSTOM) appears
in the list with a one-line description so the changelog fully reflects the
protocol surface.

This provides a standardized event format across all adapters while maintaining backward compatibility with existing code that processes legacy `content`, `tool_call`, and `done` events.
26 changes: 19 additions & 7 deletions docs/guides/streaming.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,27 @@ messages.forEach((message) => {
});
```

## Stream Chunks
## Stream Events (AG-UI Protocol)

Stream chunks contain different types of data:
TanStack AI implements the [AG-UI Protocol](https://docs.ag-ui.com/introduction) for streaming. Stream events contain different types of data:

- **Content chunks** - Text content being generated
- **Thinking chunks** - Model's internal reasoning process (when supported)
- **Tool call chunks** - When the model calls a tool
- **Tool result chunks** - Results from tool execution
- **Done chunks** - Stream completion
### AG-UI Events

- **RUN_STARTED** - Emitted when a run begins
- **TEXT_MESSAGE_START/CONTENT/END** - Text content streaming lifecycle
- **TOOL_CALL_START/ARGS/END** - Tool invocation lifecycle
- **STEP_STARTED/STEP_FINISHED** - Thinking/reasoning steps
- **RUN_FINISHED** - Run completion with finish reason and usage
- **RUN_ERROR** - Error occurred during the run

### Legacy Event Types (Backward Compatible)

- **content** - Text content being generated (maps to TEXT_MESSAGE_CONTENT)
- **thinking** - Model's reasoning process (maps to STEP_STARTED/STEP_FINISHED)
- **tool_call** - Tool invocation (maps to TOOL_CALL_START/ARGS)
- **tool_result** - Tool execution result (maps to TOOL_CALL_END)
- **done** - Stream completion (maps to RUN_FINISHED)
- **error** - Error occurred (maps to RUN_ERROR)

### Thinking Chunks

Expand Down
286 changes: 265 additions & 21 deletions docs/protocol/chunk-definitions.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,287 @@
---
title: Chunk Definitions
title: AG-UI Event Definitions
id: chunk-definitions
---

All streaming responses in TanStack AI consist of a series of **StreamChunks** - discrete JSON objects representing different events during the conversation. These chunks enable real-time updates for content generation, tool calls, errors, and completion signals.

This document defines the data structures (chunks) that flow between the TanStack AI server and client during streaming chat operations.
TanStack AI implements the [AG-UI (Agent-User Interaction) Protocol](https://docs.ag-ui.com/introduction), an open, lightweight, event-based protocol that standardizes how AI agents connect to user-facing applications.

All streaming responses in TanStack AI consist of a series of **AG-UI Events** - discrete JSON objects representing different stages of the conversation lifecycle. These events enable real-time updates for content generation, tool calls, thinking/reasoning, and completion signals.

## Base Structure

All chunks share a common base structure:
All AG-UI events share a common base structure:

```typescript
interface BaseStreamChunk {
type: StreamChunkType;
id: string; // Unique identifier for the message/response
model: string; // Model identifier (e.g., "gpt-5.2", "claude-3-5-sonnet")
timestamp: number; // Unix timestamp in milliseconds
interface BaseAGUIEvent {
type: AGUIEventType;
timestamp: number; // Unix timestamp in milliseconds
model?: string; // Model identifier (TanStack AI addition)
rawEvent?: unknown; // Original provider event for debugging
}
```

### Chunk Types
### AG-UI Event Types

```typescript
type StreamChunkType =
| 'content' // Text content being generated
| 'thinking' // Model's reasoning process (when supported)
| 'tool_call' // Model calling a tool/function
| 'tool-input-available' // Tool inputs are ready for client execution
type AGUIEventType =
| 'RUN_STARTED' // Run lifecycle begins
| 'RUN_FINISHED' // Run completed successfully
| 'RUN_ERROR' // Error occurred
| 'TEXT_MESSAGE_START' // Text message begins
| 'TEXT_MESSAGE_CONTENT' // Text content streaming
| 'TEXT_MESSAGE_END' // Text message completes
| 'TOOL_CALL_START' // Tool invocation begins
| 'TOOL_CALL_ARGS' // Tool arguments streaming
| 'TOOL_CALL_END' // Tool call completes (with result)
| 'STEP_STARTED' // Thinking/reasoning step begins
| 'STEP_FINISHED' // Thinking/reasoning step completes
| 'STATE_SNAPSHOT' // Full state synchronization
| 'STATE_DELTA' // Incremental state update
| 'CUSTOM'; // Custom extensibility events
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### Legacy Event Types (Backward Compatibility)

For backward compatibility, TanStack AI also supports legacy event types:

```typescript
type LegacyStreamChunkType =
| 'content' // -> TEXT_MESSAGE_CONTENT
| 'thinking' // -> STEP_STARTED/STEP_FINISHED
| 'tool_call' // -> TOOL_CALL_START/TOOL_CALL_ARGS
| 'tool-input-available' // Tool inputs ready for client
| 'approval-requested' // Tool requires user approval
| 'tool_result' // Result from tool execution
| 'done' // Stream completion
| 'error'; // Error occurred
| 'tool_result' // -> TOOL_CALL_END
| 'done' // -> RUN_FINISHED
| 'error'; // -> RUN_ERROR
```

## AG-UI Event Definitions

### RUN_STARTED

Emitted when a run begins. This is the first event in any streaming response.

```typescript
interface RunStartedEvent extends BaseAGUIEvent {
type: 'RUN_STARTED';
runId: string; // Unique identifier for this run
threadId?: string; // Optional thread/conversation ID
}
```

## Chunk Definitions
**Example:**
```json
{
"type": "RUN_STARTED",
"runId": "run_abc123",
"model": "gpt-4o",
"timestamp": 1701234567890
}
```

---

### RUN_FINISHED

Emitted when a run completes successfully.

```typescript
interface RunFinishedEvent extends BaseAGUIEvent {
type: 'RUN_FINISHED';
runId: string;
finishReason: 'stop' | 'length' | 'content_filter' | 'tool_calls' | null;
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
}
```

**Example:**
```json
{
"type": "RUN_FINISHED",
"runId": "run_abc123",
"model": "gpt-4o",
"timestamp": 1701234567900,
"finishReason": "stop",
"usage": {
"promptTokens": 100,
"completionTokens": 50,
"totalTokens": 150
}
}
```

---

### RUN_ERROR

Emitted when an error occurs during a run.

```typescript
interface RunErrorEvent extends BaseAGUIEvent {
type: 'RUN_ERROR';
runId?: string;
error: {
message: string;
code?: string;
};
}
```

**Example:**
```json
{
"type": "RUN_ERROR",
"runId": "run_abc123",
"model": "gpt-4o",
"timestamp": 1701234567890,
"error": {
"message": "Rate limit exceeded",
"code": "rate_limit"
}
}
```

---

### TEXT_MESSAGE_START

Emitted when a text message starts.

```typescript
interface TextMessageStartEvent extends BaseAGUIEvent {
type: 'TEXT_MESSAGE_START';
messageId: string;
role: 'assistant';
}
```

---

### TEXT_MESSAGE_CONTENT

Emitted when text content is generated (streaming tokens).

```typescript
interface TextMessageContentEvent extends BaseAGUIEvent {
type: 'TEXT_MESSAGE_CONTENT';
messageId: string;
delta: string; // The incremental content token
content?: string; // Full accumulated content so far
}
```

**Example:**
```json
{
"type": "TEXT_MESSAGE_CONTENT",
"messageId": "msg_abc123",
"model": "gpt-4o",
"timestamp": 1701234567890,
"delta": "Hello",
"content": "Hello"
}
```

---

### TEXT_MESSAGE_END

Emitted when a text message completes.

```typescript
interface TextMessageEndEvent extends BaseAGUIEvent {
type: 'TEXT_MESSAGE_END';
messageId: string;
}
```

---

### TOOL_CALL_START

Emitted when a tool call starts.

```typescript
interface ToolCallStartEvent extends BaseAGUIEvent {
type: 'TOOL_CALL_START';
toolCallId: string;
toolName: string;
index?: number; // Index for parallel tool calls
}
```

---

### TOOL_CALL_ARGS

Emitted when tool call arguments are streaming.

```typescript
interface ToolCallArgsEvent extends BaseAGUIEvent {
type: 'TOOL_CALL_ARGS';
toolCallId: string;
delta: string; // Incremental JSON arguments delta
args?: string; // Full accumulated arguments so far
}
```

---

### TOOL_CALL_END

Emitted when a tool call completes.

```typescript
interface ToolCallEndEvent extends BaseAGUIEvent {
type: 'TOOL_CALL_END';
toolCallId: string;
toolName: string;
input?: unknown; // Final parsed input arguments
result?: string; // Tool execution result (if executed)
}
```

---

### STEP_STARTED

Emitted when a thinking/reasoning step starts.

```typescript
interface StepStartedEvent extends BaseAGUIEvent {
type: 'STEP_STARTED';
stepId: string;
stepType?: string; // e.g., 'thinking', 'planning'
}
```

---

### STEP_FINISHED

Emitted when a thinking/reasoning step finishes.

```typescript
interface StepFinishedEvent extends BaseAGUIEvent {
type: 'STEP_FINISHED';
stepId: string;
delta?: string; // Incremental thinking content
content?: string; // Full accumulated thinking content
}
```

---

## Legacy Chunk Definitions (Backward Compatibility)

The following legacy chunk types are still supported for backward compatibility. New implementations should use the AG-UI event types above.

### ContentStreamChunk
### ContentStreamChunk (Legacy)

Emitted when the model generates text content. Sent incrementally as tokens are generated.

Expand Down
17 changes: 11 additions & 6 deletions packages/php/tanstack-ai/src/SSEFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,23 @@ public static function formatDone(): string
}

/**
* Format an error as an SSE error chunk.
* Format an error as an SSE RUN_ERROR chunk (AG-UI Protocol).
*
* @param \Throwable $error Exception to format
* @return string SSE-formatted error chunk
* @param string|null $runId Optional run ID for correlation
* @param string|null $model Optional model name
* @return string SSE-formatted RUN_ERROR chunk
*/
public static function formatError(\Throwable $error): string
public static function formatError(\Throwable $error, ?string $runId = null, ?string $model = null): string
{
$errorChunk = [
'type' => 'error',
'type' => 'RUN_ERROR',
'runId' => $runId ?? ('run-' . bin2hex(random_bytes(4))),
'model' => $model,
'timestamp' => (int)(microtime(true) * 1000),
'error' => [
'type' => get_class($error),
'message' => $error->getMessage()
'message' => $error->getMessage(),
'code' => (string)$error->getCode()
]
];
return self::formatChunk($errorChunk);
Expand Down
Loading
Loading