Skip to content

Commit 3997203

Browse files
committed
feat(runtime): add TanStack AI stream converter and barrel export
Add convertTanStackStream async generator that converts TanStack AI stream chunks (TEXT_MESSAGE_CONTENT, TOOL_CALL_START/ARGS/END) into AG-UI events. Create converters/index.ts barrel that re-exports both AI SDK and TanStack converters.
1 parent ddc58b1 commit 3997203

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { convertAISDKStream } from "./aisdk";
2+
export { convertTanStackStream } from "./tanstack";
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
BaseEvent,
3+
EventType,
4+
TextMessageChunkEvent,
5+
ToolCallArgsEvent,
6+
ToolCallEndEvent,
7+
ToolCallStartEvent,
8+
} from "@ag-ui/client";
9+
import { randomUUID } from "crypto";
10+
11+
/**
12+
* Converts a TanStack AI stream into AG-UI `BaseEvent` objects.
13+
*
14+
* This is a pure converter — it does NOT emit lifecycle events
15+
* (RUN_STARTED / RUN_FINISHED / RUN_ERROR). The caller (Agent class)
16+
* is responsible for those.
17+
*/
18+
export async function* convertTanStackStream(
19+
stream: AsyncIterable<unknown>,
20+
abortSignal: AbortSignal,
21+
): AsyncGenerator<BaseEvent> {
22+
const messageId = randomUUID();
23+
24+
for await (const chunk of stream) {
25+
if (abortSignal.aborted) break;
26+
27+
const raw = chunk as Record<string, unknown>;
28+
const type = raw.type as string;
29+
30+
if (type === "TEXT_MESSAGE_CONTENT" && raw.delta) {
31+
const textEvent: TextMessageChunkEvent = {
32+
type: EventType.TEXT_MESSAGE_CHUNK,
33+
role: "assistant",
34+
messageId,
35+
delta: raw.delta as string,
36+
};
37+
yield textEvent;
38+
} else if (type === "TOOL_CALL_START") {
39+
const startEvent: ToolCallStartEvent = {
40+
type: EventType.TOOL_CALL_START,
41+
parentMessageId: messageId,
42+
toolCallId: raw.toolCallId as string,
43+
toolCallName: raw.toolCallName as string,
44+
};
45+
yield startEvent;
46+
} else if (type === "TOOL_CALL_ARGS") {
47+
const argsEvent: ToolCallArgsEvent = {
48+
type: EventType.TOOL_CALL_ARGS,
49+
toolCallId: raw.toolCallId as string,
50+
delta: raw.delta as string,
51+
};
52+
yield argsEvent;
53+
} else if (type === "TOOL_CALL_END") {
54+
const endEvent: ToolCallEndEvent = {
55+
type: EventType.TOOL_CALL_END,
56+
toolCallId: raw.toolCallId as string,
57+
};
58+
yield endEvent;
59+
}
60+
// Unknown chunk types are silently ignored
61+
}
62+
}

0 commit comments

Comments
 (0)