@tanstack/ai-client 0.11.8 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/chat-client.d.ts +2 -1
- package/dist/esm/chat-client.js +27 -7
- package/dist/esm/chat-client.js.map +1 -1
- package/dist/esm/connection-adapters.d.ts +20 -2
- package/dist/esm/connection-adapters.js +154 -48
- package/dist/esm/connection-adapters.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -1
- package/dist/esm/types.d.ts +58 -8
- package/dist/esm/types.js.map +1 -1
- package/package.json +2 -2
- package/src/chat-client.ts +37 -8
- package/src/connection-adapters.ts +226 -61
- package/src/index.ts +6 -1
- package/src/types.ts +69 -10
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { uiMessagesToWire, EventType } from "@tanstack/ai";
|
|
2
|
+
class StreamTruncatedError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super(
|
|
5
|
+
"Stream ended with unterminated trailing data — connection was likely cut short."
|
|
6
|
+
);
|
|
7
|
+
this.name = "StreamTruncatedError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
function generateRunId(prefix) {
|
|
3
11
|
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4
12
|
}
|
|
13
|
+
function requireSyntheticId(value, field) {
|
|
14
|
+
if (!value) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Cannot synthesize terminal event: ${field} not supplied via runContext and not observed in the upstream stream.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
5
21
|
function mergeHeaders(customHeaders) {
|
|
6
22
|
if (!customHeaders) {
|
|
7
23
|
return {};
|
|
@@ -31,13 +47,56 @@ async function* readStreamLines(reader, abortSignal) {
|
|
|
31
47
|
}
|
|
32
48
|
}
|
|
33
49
|
}
|
|
34
|
-
if (buffer.trim()) {
|
|
35
|
-
|
|
50
|
+
if (buffer.trim() && !abortSignal?.aborted) {
|
|
51
|
+
throw new StreamTruncatedError();
|
|
36
52
|
}
|
|
37
53
|
} finally {
|
|
38
54
|
reader.releaseLock();
|
|
39
55
|
}
|
|
40
56
|
}
|
|
57
|
+
async function* responseToSSEChunks(response, abortSignal) {
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`HTTP error! status: ${response.status} ${response.statusText}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const reader = response.body?.getReader();
|
|
64
|
+
if (!reader) {
|
|
65
|
+
throw new Error("Response body is not readable");
|
|
66
|
+
}
|
|
67
|
+
let lastThreadId;
|
|
68
|
+
let lastRunId;
|
|
69
|
+
let lastModel;
|
|
70
|
+
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
71
|
+
if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const data = line.startsWith("data: ") ? line.slice(6) : line;
|
|
75
|
+
if (data === "[DONE]") {
|
|
76
|
+
const synthetic = {
|
|
77
|
+
type: EventType.RUN_FINISHED,
|
|
78
|
+
threadId: lastThreadId ?? "",
|
|
79
|
+
runId: lastRunId ?? "",
|
|
80
|
+
model: lastModel ?? "",
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
finishReason: "stop"
|
|
83
|
+
};
|
|
84
|
+
yield synthetic;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const chunk = JSON.parse(data);
|
|
88
|
+
if ("threadId" in chunk && typeof chunk.threadId === "string") {
|
|
89
|
+
lastThreadId = chunk.threadId;
|
|
90
|
+
}
|
|
91
|
+
if ("runId" in chunk && typeof chunk.runId === "string") {
|
|
92
|
+
lastRunId = chunk.runId;
|
|
93
|
+
}
|
|
94
|
+
if ("model" in chunk && typeof chunk.model === "string") {
|
|
95
|
+
lastModel = chunk.model;
|
|
96
|
+
}
|
|
97
|
+
yield chunk;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
41
100
|
function normalizeConnectionAdapter(connection) {
|
|
42
101
|
if (!connection) {
|
|
43
102
|
throw new Error("Connection adapter is required");
|
|
@@ -99,6 +158,8 @@ function normalizeConnectionAdapter(connection) {
|
|
|
99
158
|
},
|
|
100
159
|
async send(messages, data, abortSignal, runContext) {
|
|
101
160
|
let hasTerminalEvent = false;
|
|
161
|
+
let upstreamThreadId;
|
|
162
|
+
let upstreamRunId;
|
|
102
163
|
try {
|
|
103
164
|
const stream2 = connection.connect(
|
|
104
165
|
messages,
|
|
@@ -107,33 +168,51 @@ function normalizeConnectionAdapter(connection) {
|
|
|
107
168
|
runContext
|
|
108
169
|
);
|
|
109
170
|
for await (const chunk of stream2) {
|
|
171
|
+
if ("threadId" in chunk && typeof chunk.threadId === "string") {
|
|
172
|
+
upstreamThreadId = chunk.threadId;
|
|
173
|
+
}
|
|
174
|
+
if ("runId" in chunk && typeof chunk.runId === "string") {
|
|
175
|
+
upstreamRunId = chunk.runId;
|
|
176
|
+
}
|
|
110
177
|
if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
|
|
111
178
|
hasTerminalEvent = true;
|
|
112
179
|
}
|
|
113
180
|
push(chunk);
|
|
114
181
|
}
|
|
115
182
|
if (!abortSignal?.aborted && !hasTerminalEvent) {
|
|
116
|
-
|
|
183
|
+
const synthetic = {
|
|
117
184
|
type: EventType.RUN_FINISHED,
|
|
118
|
-
threadId:
|
|
119
|
-
|
|
185
|
+
threadId: requireSyntheticId(
|
|
186
|
+
upstreamThreadId ?? runContext?.threadId,
|
|
187
|
+
"threadId"
|
|
188
|
+
),
|
|
189
|
+
runId: requireSyntheticId(
|
|
190
|
+
upstreamRunId ?? runContext?.runId,
|
|
191
|
+
"runId"
|
|
192
|
+
),
|
|
120
193
|
model: "connect-wrapper",
|
|
121
194
|
timestamp: Date.now(),
|
|
122
195
|
finishReason: "stop"
|
|
123
|
-
}
|
|
196
|
+
};
|
|
197
|
+
push(synthetic);
|
|
124
198
|
}
|
|
125
199
|
} catch (err) {
|
|
126
200
|
if (!abortSignal?.aborted && !hasTerminalEvent) {
|
|
127
|
-
|
|
201
|
+
const message = err instanceof Error ? err.message : "Unknown error in connect()";
|
|
202
|
+
const synthetic = {
|
|
128
203
|
type: EventType.RUN_ERROR,
|
|
129
|
-
threadId:
|
|
130
|
-
|
|
204
|
+
threadId: requireSyntheticId(
|
|
205
|
+
upstreamThreadId ?? runContext?.threadId,
|
|
206
|
+
"threadId"
|
|
207
|
+
),
|
|
208
|
+
runId: requireSyntheticId(
|
|
209
|
+
upstreamRunId ?? runContext?.runId,
|
|
210
|
+
"runId"
|
|
211
|
+
),
|
|
131
212
|
timestamp: Date.now(),
|
|
132
|
-
message
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
});
|
|
213
|
+
message
|
|
214
|
+
};
|
|
215
|
+
push(synthetic);
|
|
137
216
|
}
|
|
138
217
|
throw err;
|
|
139
218
|
}
|
|
@@ -186,30 +265,7 @@ function fetchServerSentEvents(url, options = {}) {
|
|
|
186
265
|
credentials: resolvedOptions.credentials || "same-origin",
|
|
187
266
|
...signal ? { signal } : {}
|
|
188
267
|
});
|
|
189
|
-
|
|
190
|
-
throw new Error(
|
|
191
|
-
`HTTP error! status: ${response.status} ${response.statusText}`
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
const reader = response.body?.getReader();
|
|
195
|
-
if (!reader) {
|
|
196
|
-
throw new Error("Response body is not readable");
|
|
197
|
-
}
|
|
198
|
-
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
199
|
-
const data2 = line.startsWith("data: ") ? line.slice(6) : line;
|
|
200
|
-
if (data2 === "[DONE]") {
|
|
201
|
-
console.warn(
|
|
202
|
-
"[@tanstack/ai-client] Received [DONE] sentinel. This is deprecated — upgrade your @tanstack/ai server package. RUN_FINISHED is the stream terminator."
|
|
203
|
-
);
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
const parsed = JSON.parse(data2);
|
|
208
|
-
yield parsed;
|
|
209
|
-
} catch (parseError) {
|
|
210
|
-
console.warn("Failed to parse SSE chunk:", data2);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
268
|
+
yield* responseToSSEChunks(response, abortSignal);
|
|
213
269
|
}
|
|
214
270
|
};
|
|
215
271
|
}
|
|
@@ -269,33 +325,83 @@ function fetchHttpStream(url, options = {}) {
|
|
|
269
325
|
throw new Error("Response body is not readable");
|
|
270
326
|
}
|
|
271
327
|
for await (const line of readStreamLines(reader, abortSignal)) {
|
|
272
|
-
|
|
273
|
-
const parsed = JSON.parse(line);
|
|
274
|
-
yield parsed;
|
|
275
|
-
} catch (parseError) {
|
|
276
|
-
console.warn("Failed to parse HTTP stream chunk:", line);
|
|
277
|
-
}
|
|
328
|
+
yield JSON.parse(line);
|
|
278
329
|
}
|
|
279
330
|
}
|
|
280
331
|
};
|
|
281
332
|
}
|
|
282
333
|
function stream(streamFactory) {
|
|
283
334
|
return {
|
|
284
|
-
async *connect(messages, data,
|
|
285
|
-
yield* streamFactory(messages, data);
|
|
335
|
+
async *connect(messages, data, abortSignal) {
|
|
336
|
+
yield* streamFactory(messages, data, abortSignal);
|
|
286
337
|
}
|
|
287
338
|
};
|
|
288
339
|
}
|
|
340
|
+
function fetcherToConnectionAdapter(fetcher) {
|
|
341
|
+
return {
|
|
342
|
+
async *connect(messages, data, abortSignal, runContext) {
|
|
343
|
+
if (!abortSignal) {
|
|
344
|
+
throw new Error(
|
|
345
|
+
"fetcherToConnectionAdapter requires an AbortSignal — the chat client always supplies one."
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (!runContext) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"fetcherToConnectionAdapter requires a RunAgentInputContext — the chat client always supplies one."
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
const uiMessages = messages;
|
|
354
|
+
const result = await fetcher(
|
|
355
|
+
{
|
|
356
|
+
messages: uiMessages,
|
|
357
|
+
data,
|
|
358
|
+
threadId: runContext.threadId,
|
|
359
|
+
runId: runContext.runId
|
|
360
|
+
},
|
|
361
|
+
{ signal: abortSignal }
|
|
362
|
+
);
|
|
363
|
+
if (result instanceof Response) {
|
|
364
|
+
yield* responseToSSEChunks(result, abortSignal);
|
|
365
|
+
} else {
|
|
366
|
+
yield* abortableIterable(result, abortSignal);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async function* abortableIterable(iterable, signal) {
|
|
372
|
+
if (signal.aborted) return;
|
|
373
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
374
|
+
const abortPromise = new Promise(
|
|
375
|
+
(resolve) => {
|
|
376
|
+
signal.addEventListener(
|
|
377
|
+
"abort",
|
|
378
|
+
() => resolve({ done: true, value: void 0 }),
|
|
379
|
+
{ once: true }
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
try {
|
|
384
|
+
while (true) {
|
|
385
|
+
const result = await Promise.race([iterator.next(), abortPromise]);
|
|
386
|
+
if (result.done) return;
|
|
387
|
+
yield result.value;
|
|
388
|
+
}
|
|
389
|
+
} finally {
|
|
390
|
+
await iterator.return?.();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
289
393
|
function rpcStream(rpcCall) {
|
|
290
394
|
return {
|
|
291
|
-
async *connect(messages, data,
|
|
292
|
-
yield* rpcCall(messages, data);
|
|
395
|
+
async *connect(messages, data, abortSignal) {
|
|
396
|
+
yield* rpcCall(messages, data, abortSignal);
|
|
293
397
|
}
|
|
294
398
|
};
|
|
295
399
|
}
|
|
296
400
|
export {
|
|
401
|
+
StreamTruncatedError,
|
|
297
402
|
fetchHttpStream,
|
|
298
403
|
fetchServerSentEvents,
|
|
404
|
+
fetcherToConnectionAdapter,
|
|
299
405
|
normalizeConnectionAdapter,
|
|
300
406
|
rpcStream,
|
|
301
407
|
stream
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-adapters.js","sources":["../../src/connection-adapters.ts"],"sourcesContent":["import { EventType, uiMessagesToWire } from '@tanstack/ai'\nimport type { ModelMessage, StreamChunk, UIMessage } from '@tanstack/ai'\n\nfunction generateRunId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * Merge custom headers into request headers\n */\nfunction mergeHeaders(\n customHeaders?: Record<string, string> | Headers,\n): Record<string, string> {\n if (!customHeaders) {\n return {}\n }\n if (customHeaders instanceof Headers) {\n const result: Record<string, string> = {}\n customHeaders.forEach((value, key) => {\n result[key] = value\n })\n return result\n }\n return customHeaders\n}\n\n/**\n * Read lines from a stream (newline-delimited)\n */\nasync function* readStreamLines(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n try {\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (!abortSignal?.aborted) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n if (line.trim()) {\n yield line\n }\n }\n }\n\n // Process any remaining data in the buffer\n if (buffer.trim()) {\n yield buffer\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Per-send context provided by the chat client to the connection adapter.\n * The adapter combines this with serialized messages to build a full\n * AG-UI `RunAgentInput` payload.\n */\nexport interface RunAgentInputContext {\n threadId: string\n runId: string\n parentRunId?: string\n /** Client-declared tools to advertise in the request payload. */\n clientTools?: Array<{\n name: string\n description: string\n parameters: unknown\n }>\n /** Arbitrary user-controlled passthrough data. */\n forwardedProps?: Record<string, unknown>\n}\n\nexport interface ConnectConnectionAdapter {\n /**\n * Connect and return an async iterable of StreamChunks.\n */\n connect: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => AsyncIterable<StreamChunk>\n}\n\nexport interface SubscribeConnectionAdapter {\n /**\n * Subscribe to stream chunks.\n */\n subscribe: (abortSignal?: AbortSignal) => AsyncIterable<StreamChunk>\n /**\n * Send a request; chunks arrive through subscribe().\n */\n send: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => Promise<void>\n}\n\n/**\n * Connection adapter union.\n * Provide either `connect`, or `subscribe` + `send`.\n */\nexport type ConnectionAdapter =\n | ConnectConnectionAdapter\n | SubscribeConnectionAdapter\n\n/**\n * Normalize a ConnectionAdapter to subscribe/send operations.\n *\n * If a connection provides native subscribe/send, that mode is used.\n * Otherwise, connect() is wrapped using an async queue.\n */\nexport function normalizeConnectionAdapter(\n connection: ConnectionAdapter | undefined,\n): SubscribeConnectionAdapter {\n if (!connection) {\n throw new Error('Connection adapter is required')\n }\n\n const hasConnect = 'connect' in connection\n const hasSubscribe = 'subscribe' in connection\n const hasSend = 'send' in connection\n\n if (hasConnect && (hasSubscribe || hasSend)) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send, not both modes',\n )\n }\n\n if (hasSubscribe && hasSend) {\n return {\n subscribe: connection.subscribe.bind(connection),\n send: connection.send.bind(connection),\n }\n }\n\n if (!hasConnect) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send',\n )\n }\n\n // Legacy connect() wrapper\n let activeBuffer: Array<StreamChunk> = []\n let activeWaiters: Array<(chunk: StreamChunk | null) => void> = []\n\n function push(chunk: StreamChunk): void {\n const waiter = activeWaiters.shift()\n if (waiter) {\n waiter(chunk)\n } else {\n activeBuffer.push(chunk)\n }\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n // Transfer ownership to the latest subscriber so only one active\n // subscribe() call receives chunks from the shared connect-wrapper queue.\n const myBuffer: Array<StreamChunk> = activeBuffer.splice(0)\n const myWaiters: Array<(chunk: StreamChunk | null) => void> = []\n activeBuffer = myBuffer\n activeWaiters = myWaiters\n\n return (async function* () {\n while (!abortSignal?.aborted) {\n let chunk: StreamChunk | null\n const buffered = myBuffer.shift()\n if (buffered !== undefined) {\n chunk = buffered\n } else {\n chunk = await new Promise<StreamChunk | null>((resolve) => {\n const onAbort = () => resolve(null)\n myWaiters.push((c) => {\n abortSignal?.removeEventListener('abort', onAbort)\n resolve(c)\n })\n abortSignal?.addEventListener('abort', onAbort, { once: true })\n })\n }\n if (chunk !== null) yield chunk\n }\n })()\n },\n async send(messages, data, abortSignal, runContext) {\n let hasTerminalEvent = false\n try {\n const stream = connection.connect(\n messages,\n data,\n abortSignal,\n runContext,\n )\n for await (const chunk of stream) {\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n hasTerminalEvent = true\n }\n push(chunk)\n }\n\n // If the connect stream ended cleanly without a terminal event,\n // synthesize RUN_FINISHED so request-scoped consumers can complete.\n // Reuse the caller's threadId/runId so client-side activeRunIds tracking matches.\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n push({\n type: EventType.RUN_FINISHED,\n threadId: runContext?.threadId ?? `thread-${Date.now()}`,\n runId: runContext?.runId ?? `run-${Date.now()}`,\n model: 'connect-wrapper',\n timestamp: Date.now(),\n finishReason: 'stop',\n })\n }\n } catch (err) {\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n push({\n type: EventType.RUN_ERROR,\n threadId: runContext?.threadId ?? `thread-${Date.now()}`,\n runId: runContext?.runId ?? `run-${Date.now()}`,\n timestamp: Date.now(),\n message:\n err instanceof Error ? err.message : 'Unknown error in connect()',\n error: {\n message:\n err instanceof Error\n ? err.message\n : 'Unknown error in connect()',\n },\n })\n }\n throw err\n }\n },\n }\n}\n\n/**\n * Options for fetch-based connection adapters\n */\nexport interface FetchConnectionOptions {\n headers?: Record<string, string> | Headers\n credentials?: RequestCredentials\n signal?: AbortSignal\n body?: Record<string, any>\n fetchClient?: typeof globalThis.fetch\n}\n\n/**\n * Create a Server-Sent Events connection adapter\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for SSE streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchServerSentEvents('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchServerSentEvents(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchServerSentEvents('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchServerSentEvents('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchServerSentEvents('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-4o',\n * }\n * }));\n * ```\n */\nexport function fetchServerSentEvents(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)\n const forwardedProps = {\n ...resolvedOptions.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n const requestBody = {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy\n // field name `data`. Server endpoints that have not migrated\n // off the pre-AG-UI shape (`{ messages, data }`) keep working.\n // AG-UI strict consumers strip this via `RunAgentInputSchema`\n // (see `chatParamsFromRequestBody`). Will be removed when the\n // legacy `body` client option is dropped.\n // Shallow-cloned so that downstream mutation of `data` (e.g.\n // by a logging interceptor or fetch wrapper) cannot corrupt\n // `forwardedProps` and vice versa.\n data: { ...forwardedProps },\n }\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n const response = await fetchClient(resolvedUrl, {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n ...(signal ? { signal } : {}),\n })\n\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n }\n\n // Parse Server-Sent Events format\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n\n for await (const line of readStreamLines(reader, abortSignal)) {\n // Handle Server-Sent Events format\n const data = line.startsWith('data: ') ? line.slice(6) : line\n\n if (data === '[DONE]') {\n console.warn(\n '[@tanstack/ai-client] Received [DONE] sentinel. This is deprecated — upgrade your @tanstack/ai server package. RUN_FINISHED is the stream terminator.',\n )\n continue\n }\n\n try {\n const parsed: StreamChunk = JSON.parse(data)\n yield parsed\n } catch (parseError) {\n // Skip non-JSON lines or malformed chunks\n console.warn('Failed to parse SSE chunk:', data)\n }\n }\n },\n }\n}\n\n/**\n * Create an HTTP streaming connection adapter (for raw streaming without SSE format)\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for HTTP streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchHttpStream('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchHttpStream(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchHttpStream('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchHttpStream('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchHttpStream('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-4o',\n * }\n * }));\n * ```\n */\nexport function fetchHttpStream(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)\n const forwardedProps = {\n ...resolvedOptions.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n const requestBody = {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy\n // field name `data`. Server endpoints that have not migrated\n // off the pre-AG-UI shape (`{ messages, data }`) keep working.\n // AG-UI strict consumers strip this via `RunAgentInputSchema`\n // (see `chatParamsFromRequestBody`). Will be removed when the\n // legacy `body` client option is dropped.\n // Shallow-cloned so that downstream mutation of `data` (e.g.\n // by a logging interceptor or fetch wrapper) cannot corrupt\n // `forwardedProps` and vice versa.\n data: { ...forwardedProps },\n }\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n const response = await fetchClient(resolvedUrl, {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n ...(signal ? { signal } : {}),\n })\n\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n }\n\n // Parse raw HTTP stream (newline-delimited JSON)\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n\n for await (const line of readStreamLines(reader, abortSignal)) {\n try {\n const parsed: StreamChunk = JSON.parse(line)\n yield parsed\n } catch (parseError) {\n console.warn('Failed to parse HTTP stream chunk:', line)\n }\n }\n },\n }\n}\n\n/**\n * Create a direct stream connection adapter (for server functions or direct streams)\n *\n * @param streamFactory - A function that returns an async iterable of StreamChunks\n * @returns A connection adapter for direct streams\n *\n * @example\n * ```typescript\n * // With TanStack Start server function\n * const connection = stream(() => serverFunction({ messages }));\n *\n * const client = new ChatClient({ connection });\n * ```\n */\nexport function stream(\n streamFactory: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n ) => AsyncIterable<StreamChunk>,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, _abortSignal, _runContext) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* streamFactory(messages, data)\n },\n }\n}\n\n/**\n * Create an RPC stream connection adapter (for RPC-based streaming like Cap'n Web RPC)\n *\n * @param rpcCall - A function that accepts messages and returns an async iterable of StreamChunks\n * @returns A connection adapter for RPC streams\n *\n * @example\n * ```typescript\n * // With Cap'n Web RPC\n * const connection = rpcStream((messages, data) =>\n * api.streamMurfResponse(messages, data)\n * );\n *\n * const client = new ChatClient({ connection });\n * ```\n */\nexport function rpcStream(\n rpcCall: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n ) => AsyncIterable<StreamChunk>,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, _abortSignal, _runContext) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* rpcCall(messages, data)\n },\n }\n}\n"],"names":["stream","data"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection-adapters.js","sources":["../../src/connection-adapters.ts"],"sourcesContent":["import { EventType, uiMessagesToWire } from '@tanstack/ai'\nimport type {\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n StreamChunk,\n UIMessage,\n} from '@tanstack/ai'\nimport type { ChatFetcher } from './types'\n\n/**\n * Thrown when an SSE/HTTP stream ends with a non-empty unterminated buffer.\n * Indicates the connection was cut mid-line (server crash, dropped TCP, proxy\n * timeout) so the partial content cannot be safely parsed.\n */\nexport class StreamTruncatedError extends Error {\n constructor() {\n super(\n 'Stream ended with unterminated trailing data — connection was likely cut short.',\n )\n this.name = 'StreamTruncatedError'\n }\n}\n\nfunction generateRunId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * Asserts an id is present when synthesizing a terminal event. The chat\n * client always supplies `runContext.threadId` / `runContext.runId`, so an\n * absent id at this layer indicates the adapter was wired up by a caller\n * that bypassed that contract — surface it rather than fabricating one.\n */\nfunction requireSyntheticId(\n value: string | undefined,\n field: 'threadId' | 'runId',\n): string {\n if (!value) {\n throw new Error(\n `Cannot synthesize terminal event: ${field} not supplied via runContext and not observed in the upstream stream.`,\n )\n }\n return value\n}\n\n/**\n * Merge custom headers into request headers\n */\nfunction mergeHeaders(\n customHeaders?: Record<string, string> | Headers,\n): Record<string, string> {\n if (!customHeaders) {\n return {}\n }\n if (customHeaders instanceof Headers) {\n const result: Record<string, string> = {}\n customHeaders.forEach((value, key) => {\n result[key] = value\n })\n return result\n }\n return customHeaders\n}\n\n/**\n * Read lines from a stream (newline-delimited)\n */\nasync function* readStreamLines(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n try {\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (!abortSignal?.aborted) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n if (line.trim()) {\n yield line\n }\n }\n }\n\n // A non-empty trailing buffer means the connection was cut mid-line.\n // Surface this as an error so the chat client transitions to 'error'\n // state instead of silently presenting a partial stream as success.\n // Skip when the consumer aborted — a user-initiated stop() interrupting\n // mid-line is expected, not a truncation bug.\n if (buffer.trim() && !abortSignal?.aborted) {\n throw new StreamTruncatedError()\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Yield StreamChunks parsed from an SSE Response body.\n *\n * Accepts either `data: {...}` lines or bare JSON lines. Skips comments\n * starting with `:` (proxies and CDNs inject these as keepalives) and the\n * `event:` / `id:` / `retry:` SSE control fields. A `[DONE]` sentinel is\n * treated as a terminal event: a synthesized RUN_FINISHED is yielded using\n * the most recent upstream `threadId` / `runId`, ensuring the consumer sees\n * a clean terminal event with real correlation ids.\n *\n * A JSON parse failure throws — the consumer surfaces it as an error.\n */\nasync function* responseToSSEChunks(\n response: Response,\n abortSignal?: AbortSignal,\n): AsyncGenerator<StreamChunk> {\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n }\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n let lastThreadId: string | undefined\n let lastRunId: string | undefined\n let lastModel: string | undefined\n for await (const line of readStreamLines(reader, abortSignal)) {\n if (\n line.startsWith(':') ||\n line.startsWith('event:') ||\n line.startsWith('id:') ||\n line.startsWith('retry:')\n ) {\n continue\n }\n const data = line.startsWith('data: ') ? line.slice(6) : line\n if (data === '[DONE]') {\n const synthetic: RunFinishedEvent = {\n type: EventType.RUN_FINISHED,\n threadId: lastThreadId ?? '',\n runId: lastRunId ?? '',\n model: lastModel ?? '',\n timestamp: Date.now(),\n finishReason: 'stop',\n }\n yield synthetic\n return\n }\n const chunk = JSON.parse(data) as StreamChunk\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n lastThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n lastRunId = chunk.runId\n }\n if ('model' in chunk && typeof chunk.model === 'string') {\n lastModel = chunk.model\n }\n yield chunk\n }\n}\n\n/**\n * Per-send context provided by the chat client to the connection adapter.\n * The adapter combines this with serialized messages to build a full\n * AG-UI `RunAgentInput` payload.\n */\nexport interface RunAgentInputContext {\n threadId: string\n runId: string\n parentRunId?: string\n /** Client-declared tools to advertise in the request payload. */\n clientTools?: Array<{\n name: string\n description: string\n parameters: unknown\n }>\n /** Arbitrary user-controlled passthrough data. */\n forwardedProps?: Record<string, unknown>\n}\n\nexport interface ConnectConnectionAdapter {\n /**\n * Connect and return an async iterable of StreamChunks.\n */\n connect: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => AsyncIterable<StreamChunk>\n}\n\nexport interface SubscribeConnectionAdapter {\n /**\n * Subscribe to stream chunks.\n */\n subscribe: (abortSignal?: AbortSignal) => AsyncIterable<StreamChunk>\n /**\n * Send a request; chunks arrive through subscribe().\n */\n send: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n runContext?: RunAgentInputContext,\n ) => Promise<void>\n}\n\n/**\n * Connection adapter union.\n * Provide either `connect`, or `subscribe` + `send`.\n */\nexport type ConnectionAdapter =\n | ConnectConnectionAdapter\n | SubscribeConnectionAdapter\n\n/**\n * Normalize a ConnectionAdapter to subscribe/send operations.\n *\n * If a connection provides native subscribe/send, that mode is used.\n * Otherwise, connect() is wrapped using an async queue.\n */\nexport function normalizeConnectionAdapter(\n connection: ConnectionAdapter | undefined,\n): SubscribeConnectionAdapter {\n if (!connection) {\n throw new Error('Connection adapter is required')\n }\n\n const hasConnect = 'connect' in connection\n const hasSubscribe = 'subscribe' in connection\n const hasSend = 'send' in connection\n\n if (hasConnect && (hasSubscribe || hasSend)) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send, not both modes',\n )\n }\n\n if (hasSubscribe && hasSend) {\n return {\n subscribe: connection.subscribe.bind(connection),\n send: connection.send.bind(connection),\n }\n }\n\n if (!hasConnect) {\n throw new Error(\n 'Connection adapter must provide either connect or both subscribe and send',\n )\n }\n\n // Legacy connect() wrapper\n let activeBuffer: Array<StreamChunk> = []\n let activeWaiters: Array<(chunk: StreamChunk | null) => void> = []\n\n function push(chunk: StreamChunk): void {\n const waiter = activeWaiters.shift()\n if (waiter) {\n waiter(chunk)\n } else {\n activeBuffer.push(chunk)\n }\n }\n\n return {\n subscribe(abortSignal?: AbortSignal): AsyncIterable<StreamChunk> {\n // Transfer ownership to the latest subscriber so only one active\n // subscribe() call receives chunks from the shared connect-wrapper queue.\n const myBuffer: Array<StreamChunk> = activeBuffer.splice(0)\n const myWaiters: Array<(chunk: StreamChunk | null) => void> = []\n activeBuffer = myBuffer\n activeWaiters = myWaiters\n\n return (async function* () {\n while (!abortSignal?.aborted) {\n let chunk: StreamChunk | null\n const buffered = myBuffer.shift()\n if (buffered !== undefined) {\n chunk = buffered\n } else {\n chunk = await new Promise<StreamChunk | null>((resolve) => {\n const onAbort = () => resolve(null)\n myWaiters.push((c) => {\n abortSignal?.removeEventListener('abort', onAbort)\n resolve(c)\n })\n abortSignal?.addEventListener('abort', onAbort, { once: true })\n })\n }\n if (chunk !== null) yield chunk\n }\n })()\n },\n async send(messages, data, abortSignal, runContext) {\n let hasTerminalEvent = false\n let upstreamThreadId: string | undefined\n let upstreamRunId: string | undefined\n try {\n const stream = connection.connect(\n messages,\n data,\n abortSignal,\n runContext,\n )\n for await (const chunk of stream) {\n if ('threadId' in chunk && typeof chunk.threadId === 'string') {\n upstreamThreadId = chunk.threadId\n }\n if ('runId' in chunk && typeof chunk.runId === 'string') {\n upstreamRunId = chunk.runId\n }\n if (chunk.type === 'RUN_FINISHED' || chunk.type === 'RUN_ERROR') {\n hasTerminalEvent = true\n }\n push(chunk)\n }\n\n // If the connect stream ended cleanly without a terminal event,\n // synthesize RUN_FINISHED so request-scoped consumers can complete.\n // Reuse the caller's threadId/runId so client-side activeRunIds tracking matches.\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n const synthetic: RunFinishedEvent = {\n type: EventType.RUN_FINISHED,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n model: 'connect-wrapper',\n timestamp: Date.now(),\n finishReason: 'stop',\n }\n push(synthetic)\n }\n } catch (err) {\n if (!abortSignal?.aborted && !hasTerminalEvent) {\n const message =\n err instanceof Error ? err.message : 'Unknown error in connect()'\n const synthetic: RunErrorEvent = {\n type: EventType.RUN_ERROR,\n threadId: requireSyntheticId(\n upstreamThreadId ?? runContext?.threadId,\n 'threadId',\n ),\n runId: requireSyntheticId(\n upstreamRunId ?? runContext?.runId,\n 'runId',\n ),\n timestamp: Date.now(),\n message,\n }\n push(synthetic)\n }\n throw err\n }\n },\n }\n}\n\n/**\n * Options for fetch-based connection adapters\n */\nexport interface FetchConnectionOptions {\n headers?: Record<string, string> | Headers\n credentials?: RequestCredentials\n signal?: AbortSignal\n body?: Record<string, any>\n fetchClient?: typeof globalThis.fetch\n}\n\n/**\n * Create a Server-Sent Events connection adapter\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for SSE streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchServerSentEvents('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchServerSentEvents(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchServerSentEvents('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchServerSentEvents('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchServerSentEvents('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-4o',\n * }\n * }));\n * ```\n */\nexport function fetchServerSentEvents(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)\n const forwardedProps = {\n ...resolvedOptions.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n const requestBody = {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy\n // field name `data`. Server endpoints that have not migrated\n // off the pre-AG-UI shape (`{ messages, data }`) keep working.\n // AG-UI strict consumers strip this via `RunAgentInputSchema`\n // (see `chatParamsFromRequestBody`). Will be removed when the\n // legacy `body` client option is dropped.\n // Shallow-cloned so that downstream mutation of `data` (e.g.\n // by a logging interceptor or fetch wrapper) cannot corrupt\n // `forwardedProps` and vice versa.\n data: { ...forwardedProps },\n }\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n const response = await fetchClient(resolvedUrl, {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n ...(signal ? { signal } : {}),\n })\n\n yield* responseToSSEChunks(response, abortSignal)\n },\n }\n}\n\n/**\n * Create an HTTP streaming connection adapter (for raw streaming without SSE format)\n *\n * @param url - The API endpoint URL (or a function that returns the URL)\n * @param options - Fetch options (headers, credentials, body, etc.) or a function that returns options (can be async)\n * @returns A connection adapter for HTTP streams\n *\n * @example\n * ```typescript\n * // Static URL\n * const connection = fetchHttpStream('/api/chat');\n *\n * // Dynamic URL\n * const connection = fetchHttpStream(() => `/api/chat?user=${userId}`);\n *\n * // With options\n * const connection = fetchHttpStream('/api/chat', {\n * headers: { 'Authorization': 'Bearer token' }\n * });\n *\n * // With dynamic options\n * const connection = fetchHttpStream('/api/chat', () => ({\n * headers: { 'Authorization': `Bearer ${getToken()}` }\n * }));\n *\n * // With additional body data\n * const connection = fetchHttpStream('/api/chat', async () => ({\n * body: {\n * provider: 'openai',\n * model: 'gpt-4o',\n * }\n * }));\n * ```\n */\nexport function fetchHttpStream(\n url: string | (() => string),\n options:\n | FetchConnectionOptions\n | (() => FetchConnectionOptions | Promise<FetchConnectionOptions>) = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n // Resolve URL and options if they are functions\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions =\n typeof options === 'function' ? await options() : options\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(resolvedOptions.headers),\n }\n\n // Build AG-UI RunAgentInput payload.\n //\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps` (constructor body /\n // forwardedProps options), overridden by per-message `data` passed\n // to `connection.send`. Runtime values win over static config —\n // this matches the documented \"forwardedProps wins\" semantic.\n const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)\n const forwardedProps = {\n ...resolvedOptions.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n const requestBody = {\n threadId: runContext?.threadId ?? generateRunId('thread'),\n runId: runContext?.runId ?? generateRunId('run'),\n ...(runContext?.parentRunId !== undefined && {\n parentRunId: runContext.parentRunId,\n }),\n state: {},\n messages: wireMessages,\n tools: runContext?.clientTools ?? [],\n context: [],\n forwardedProps,\n // Backward-compat mirror of `forwardedProps` under the legacy\n // field name `data`. Server endpoints that have not migrated\n // off the pre-AG-UI shape (`{ messages, data }`) keep working.\n // AG-UI strict consumers strip this via `RunAgentInputSchema`\n // (see `chatParamsFromRequestBody`). Will be removed when the\n // legacy `body` client option is dropped.\n // Shallow-cloned so that downstream mutation of `data` (e.g.\n // by a logging interceptor or fetch wrapper) cannot corrupt\n // `forwardedProps` and vice versa.\n data: { ...forwardedProps },\n }\n\n const fetchClient = resolvedOptions.fetchClient ?? fetch\n // `RequestInit.signal` is typed `AbortSignal | null` (no `undefined`\n // under `exactOptionalPropertyTypes`), so spread it conditionally\n // rather than passing `undefined` explicitly.\n const signal = abortSignal || resolvedOptions.signal\n const response = await fetchClient(resolvedUrl, {\n method: 'POST',\n headers: requestHeaders,\n body: JSON.stringify(requestBody),\n credentials: resolvedOptions.credentials || 'same-origin',\n ...(signal ? { signal } : {}),\n })\n\n if (!response.ok) {\n throw new Error(\n `HTTP error! status: ${response.status} ${response.statusText}`,\n )\n }\n\n // Parse raw HTTP stream (newline-delimited JSON)\n const reader = response.body?.getReader()\n if (!reader) {\n throw new Error('Response body is not readable')\n }\n\n for await (const line of readStreamLines(reader, abortSignal)) {\n yield JSON.parse(line) as StreamChunk\n }\n },\n }\n}\n\n/**\n * Create a direct stream connection adapter (for server functions or direct streams)\n *\n * @param streamFactory - A function that returns an async iterable of StreamChunks\n * @returns A connection adapter for direct streams\n *\n * @example\n * ```typescript\n * // With TanStack Start server function\n * const connection = stream(() => serverFunction({ messages }));\n *\n * const client = new ChatClient({ connection });\n * ```\n */\nexport function stream(\n streamFactory: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* streamFactory(messages, data, abortSignal)\n },\n }\n}\n\n/**\n * Wrap a `ChatFetcher` as a `ConnectConnectionAdapter` so the chat client can\n * consume it through the same `subscribe`/`send` plumbing used for SSE /\n * HTTP-stream / RPC connections. May return either a `Response` (parsed as\n * SSE) or an `AsyncIterable<StreamChunk>` (yielded directly).\n *\n * @internal\n */\nexport function fetcherToConnectionAdapter(\n fetcher: ChatFetcher,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n if (!abortSignal) {\n throw new Error(\n 'fetcherToConnectionAdapter requires an AbortSignal — the chat client always supplies one.',\n )\n }\n if (!runContext) {\n throw new Error(\n 'fetcherToConnectionAdapter requires a RunAgentInputContext — the chat client always supplies one.',\n )\n }\n const uiMessages = messages as Array<UIMessage>\n const result = await fetcher(\n {\n messages: uiMessages,\n data,\n threadId: runContext.threadId,\n runId: runContext.runId,\n },\n { signal: abortSignal },\n )\n if (result instanceof Response) {\n yield* responseToSSEChunks(result, abortSignal)\n } else {\n yield* abortableIterable(result, abortSignal)\n }\n },\n }\n}\n\n/**\n * Wrap an AsyncIterable so iteration aborts when `signal` fires. Without\n * this, a fetcher that returns a generator ignoring its signal would leave\n * the for-await loop hanging until the iterable naturally ends.\n */\nasync function* abortableIterable<T>(\n iterable: AsyncIterable<T>,\n signal: AbortSignal,\n): AsyncGenerator<T> {\n if (signal.aborted) return\n const iterator = iterable[Symbol.asyncIterator]()\n const abortPromise = new Promise<{ done: true; value: undefined }>(\n (resolve) => {\n signal.addEventListener(\n 'abort',\n () => resolve({ done: true, value: undefined }),\n { once: true },\n )\n },\n )\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const result = await Promise.race([iterator.next(), abortPromise])\n if (result.done) return\n yield result.value\n }\n } finally {\n await iterator.return?.()\n }\n}\n\n/**\n * Create an RPC stream connection adapter (for RPC-based streaming like Cap'n Web RPC)\n *\n * @param rpcCall - A function that accepts messages and returns an async iterable of StreamChunks\n * @returns A connection adapter for RPC streams\n *\n * @example\n * ```typescript\n * // With Cap'n Web RPC\n * const connection = rpcStream((messages, data) =>\n * api.streamMurfResponse(messages, data)\n * );\n *\n * const client = new ChatClient({ connection });\n * ```\n */\nexport function rpcStream(\n rpcCall: (\n messages: Array<UIMessage> | Array<ModelMessage>,\n data?: Record<string, any>,\n abortSignal?: AbortSignal,\n ) => AsyncIterable<StreamChunk>,\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal) {\n // Pass messages as-is (UIMessages with parts preserved)\n // Server-side chat() handles conversion to ModelMessages\n yield* rpcCall(messages, data, abortSignal)\n },\n }\n}\n"],"names":["stream"],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ export { ChatClient } from './chat-client.js';
|
|
|
2
2
|
export { RealtimeClient } from './realtime-client.js';
|
|
3
3
|
export { GenerationClient } from './generation-client.js';
|
|
4
4
|
export { VideoGenerationClient } from './video-generation-client.js';
|
|
5
|
-
export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientOptions, ChatRequestBody, InferChatMessages, ChatClientState, ConnectionStatus, MultimodalContent, } from './types.js';
|
|
5
|
+
export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientOptions, ChatRequestBody, InferChatMessages, ChatClientState, ConnectionStatus, ChatFetcher, ChatFetcherInput, ChatFetcherOptions, ChatTransport, DistributedOmit, MultimodalContent, } from './types.js';
|
|
6
6
|
export type { InferGenerationOutput, GenerationClientState, GenerationClientOptions, GenerationFetcher, GenerationFetcherOptions, GenerationTransport, VideoGenerationClientOptions, VideoStatusInfo, VideoGenerateResult, ImageGenerateInput, AudioGenerateInput, SpeechGenerateInput, TranscriptionGenerateInput, SummarizeGenerateInput, VideoGenerateInput, } from './generation-types.js';
|
|
7
7
|
export { GENERATION_EVENTS } from './generation-types.js';
|
|
8
8
|
export { clientTools, createChatClientOptions } from './types.js';
|
|
9
9
|
export type { ExtractToolNames, ExtractToolInput, ExtractToolOutput, } from './tool-types.js';
|
|
10
10
|
export type { AnyClientTool } from '@tanstack/ai';
|
|
11
11
|
export type { RealtimeAdapter, RealtimeConnection, RealtimeClientOptions, RealtimeClientState, RealtimeStateChangeCallback, } from './realtime-types.js';
|
|
12
|
-
export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, type ConnectConnectionAdapter, type ConnectionAdapter, type FetchConnectionOptions, type RunAgentInputContext, type SubscribeConnectionAdapter, } from './connection-adapters.js';
|
|
12
|
+
export { fetchServerSentEvents, fetchHttpStream, stream, rpcStream, StreamTruncatedError, type ConnectConnectionAdapter, type ConnectionAdapter, type FetchConnectionOptions, type RunAgentInputContext, type SubscribeConnectionAdapter, } from './connection-adapters.js';
|
|
13
13
|
export { uiMessageToModelMessages, modelMessageToUIMessage, modelMessagesToUIMessages, convertMessagesToModelMessages, normalizeToUIMessage, generateMessageId, } from '@tanstack/ai';
|
|
14
14
|
export { StreamProcessor, ImmediateStrategy, PunctuationStrategy, BatchStrategy, WordBoundaryStrategy, CompositeStrategy, parsePartialJSON, PartialJSONParser, defaultJSONParser, type ChunkStrategy, type StreamProcessorOptions, type StreamProcessorEvents, type InternalToolCallState, type ToolCallState, type ToolResultState, type JSONParser, type ChunkRecording, type ProcessorResult, type ProcessorState, } from '@tanstack/ai';
|
package/dist/esm/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { GenerationClient } from "./generation-client.js";
|
|
|
4
4
|
import { VideoGenerationClient } from "./video-generation-client.js";
|
|
5
5
|
import { GENERATION_EVENTS } from "./generation-types.js";
|
|
6
6
|
import { clientTools, createChatClientOptions } from "./types.js";
|
|
7
|
-
import { fetchHttpStream, fetchServerSentEvents, rpcStream, stream } from "./connection-adapters.js";
|
|
7
|
+
import { StreamTruncatedError, fetchHttpStream, fetchServerSentEvents, rpcStream, stream } from "./connection-adapters.js";
|
|
8
8
|
import { BatchStrategy, CompositeStrategy, ImmediateStrategy, PartialJSONParser, PunctuationStrategy, StreamProcessor, WordBoundaryStrategy, convertMessagesToModelMessages, defaultJSONParser, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, parsePartialJSON, uiMessageToModelMessages } from "@tanstack/ai";
|
|
9
9
|
export {
|
|
10
10
|
BatchStrategy,
|
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
PunctuationStrategy,
|
|
18
18
|
RealtimeClient,
|
|
19
19
|
StreamProcessor,
|
|
20
|
+
StreamTruncatedError,
|
|
20
21
|
VideoGenerationClient,
|
|
21
22
|
WordBoundaryStrategy,
|
|
22
23
|
clientTools,
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
import { AnyClientTool, AudioPart, ChunkStrategy, ContentPart, DocumentPart, ImagePart, InferToolInput, InferToolOutput, ModelMessage, StreamChunk, StructuredOutputPart, VideoPart } from '@tanstack/ai';
|
|
2
2
|
import { ConnectionAdapter } from './connection-adapters.js';
|
|
3
3
|
export type { StructuredOutputPart } from '@tanstack/ai';
|
|
4
|
+
/**
|
|
5
|
+
* `messages` is the full UIMessage history (not a delta). `data` is the
|
|
6
|
+
* merged body — `ChatClientOptions.body` plus any per-call data passed to
|
|
7
|
+
* `sendMessage(...)`. `threadId` / `runId` are the AG-UI correlation ids
|
|
8
|
+
* the chat client uses to track this turn — forward them to your server
|
|
9
|
+
* if it needs to correlate requests.
|
|
10
|
+
*/
|
|
11
|
+
export interface ChatFetcherInput {
|
|
12
|
+
messages: Array<UIMessage>;
|
|
13
|
+
data?: Record<string, unknown>;
|
|
14
|
+
threadId: string;
|
|
15
|
+
runId: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ChatFetcherOptions {
|
|
18
|
+
/** Fires when `stop()` is called or the request is superseded. */
|
|
19
|
+
signal: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Direct function that performs a chat request. Mirrors
|
|
23
|
+
* `GenerationFetcher`. Returns either a `Response` (SSE body parsed by the
|
|
24
|
+
* chat client) or an `AsyncIterable<StreamChunk>` (yielded directly). May
|
|
25
|
+
* return the value synchronously, as a `Promise`, or as an async generator
|
|
26
|
+
* (`async function*`) — the chat client awaits whichever shape is returned.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* useChat({
|
|
31
|
+
* fetcher: ({ messages }, { signal }) =>
|
|
32
|
+
* chatFn({ data: { messages }, signal }),
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type ChatFetcher = (input: ChatFetcherInput, options: ChatFetcherOptions) => Response | AsyncIterable<StreamChunk> | Promise<Response | AsyncIterable<StreamChunk>>;
|
|
37
|
+
/**
|
|
38
|
+
* Distributive `Omit` — applies `Omit<O, K>` per branch of a union so
|
|
39
|
+
* discriminated unions survive omission. Plain `Omit` collapses unions
|
|
40
|
+
* into a single object shape, which would erase the `ChatTransport` XOR
|
|
41
|
+
* when framework hooks omit React-managed callbacks from
|
|
42
|
+
* `ChatClientOptions`.
|
|
43
|
+
*/
|
|
44
|
+
export type DistributedOmit<TObject, TKeys extends keyof any> = TObject extends unknown ? Omit<TObject, TKeys> : never;
|
|
45
|
+
/**
|
|
46
|
+
* Discriminated union enforcing that exactly one of `connection` or
|
|
47
|
+
* `fetcher` is provided. Mirrors `GenerationTransport`.
|
|
48
|
+
*/
|
|
49
|
+
export type ChatTransport = {
|
|
50
|
+
connection: ConnectionAdapter;
|
|
51
|
+
fetcher?: never;
|
|
52
|
+
} | {
|
|
53
|
+
fetcher: ChatFetcher;
|
|
54
|
+
connection?: never;
|
|
55
|
+
};
|
|
4
56
|
/**
|
|
5
57
|
* Tool call states - track the lifecycle of a tool call
|
|
6
58
|
*/
|
|
@@ -136,13 +188,11 @@ export interface UIMessage<TTools extends ReadonlyArray<AnyClientTool> = any, TD
|
|
|
136
188
|
parts: Array<MessagePart<TTools, TData>>;
|
|
137
189
|
createdAt?: Date;
|
|
138
190
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*/
|
|
145
|
-
connection: ConnectionAdapter;
|
|
191
|
+
/**
|
|
192
|
+
* Options for `ChatClient`. Exactly one of `connection` or `fetcher` must be
|
|
193
|
+
* provided — the type-level XOR is enforced via `ChatTransport`.
|
|
194
|
+
*/
|
|
195
|
+
export type ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> = any> = {
|
|
146
196
|
/**
|
|
147
197
|
* Initial messages to populate the chat
|
|
148
198
|
*/
|
|
@@ -250,7 +300,7 @@ export interface ChatClientOptions<TTools extends ReadonlyArray<AnyClientTool> =
|
|
|
250
300
|
*/
|
|
251
301
|
chunkStrategy?: ChunkStrategy;
|
|
252
302
|
};
|
|
253
|
-
}
|
|
303
|
+
} & ChatTransport;
|
|
254
304
|
export interface ChatRequestBody {
|
|
255
305
|
messages: Array<ModelMessage>;
|
|
256
306
|
data?: Record<string, any>;
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n AnyClientTool,\n AudioPart,\n ChunkStrategy,\n ContentPart,\n DocumentPart,\n ImagePart,\n InferToolInput,\n InferToolOutput,\n ModelMessage,\n StreamChunk,\n StructuredOutputPart,\n VideoPart,\n} from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\n\nexport type { StructuredOutputPart } from '@tanstack/ai'\n\n/**\n * Tool call states - track the lifecycle of a tool call\n */\nexport type ToolCallState =\n | 'awaiting-input' // Received start but no arguments yet\n | 'input-streaming' // Partial arguments received\n | 'input-complete' // All arguments received\n | 'approval-requested' // Waiting for user approval\n | 'approval-responded' // User has approved/denied\n | 'complete' // Result is complete\n\n/**\n * Tool result states - track the lifecycle of a tool result\n */\nexport type ToolResultState =\n | 'streaming' // Placeholder for future streamed output\n | 'complete' // Result is complete\n | 'error' // Error occurred\n\n/**\n * ChatClient state - track the lifecycle of a chat\n */\nexport type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error'\n\n/**\n * Connection lifecycle state for the subscription loop.\n */\nexport type ConnectionStatus =\n | 'disconnected'\n | 'connecting'\n | 'connected'\n | 'error'\n\n/**\n * Multimodal content input for sending messages with rich media.\n * Allows sending text, images, audio, video, and documents to the LLM.\n *\n * @example\n * ```ts\n * // Send an image with a question\n * client.sendMessage({\n * content: [\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ],\n * id: 'custom-message-id' // optional\n * })\n * ```\n */\nexport interface MultimodalContent {\n /**\n * The content of the message.\n * Can be a simple string or an array of content parts for multimodal messages.\n */\n content: string | Array<ContentPart>\n /**\n * Optional custom ID for the message.\n * If not provided, a unique ID will be generated.\n */\n id?: string\n}\n\n/**\n * Message parts - building blocks of UIMessage\n */\nexport interface TextPart {\n type: 'text'\n content: string\n}\n\n/**\n * Helper type that creates a tool-call part for a specific tool.\n * This is a conditional type to enable proper distribution over union types,\n * creating a discriminated union where `name` is the discriminant.\n */\ntype ToolCallPartForTool<T> = T extends AnyClientTool\n ? {\n type: 'tool-call'\n id: string\n name: T['name']\n arguments: string // JSON string (may be incomplete)\n /** Parsed tool input (typed from inputSchema) */\n input?: InferToolInput<T>\n state: ToolCallState\n /** Approval metadata if tool requires user approval */\n approval?: {\n id: string // Unique approval ID\n needsApproval: boolean // Always true if present\n approved?: boolean // User's decision (undefined until responded)\n }\n /** Tool execution output (for client tools or after approval) */\n output?: InferToolOutput<T>\n }\n : never\n\n/**\n * Fallback tool-call part type when tools are not typed\n */\ntype UntypedToolCallPart = {\n type: 'tool-call'\n id: string\n name: string\n arguments: string\n input?: any\n state: ToolCallState\n approval?: {\n id: string\n needsApproval: boolean\n approved?: boolean\n }\n output?: any\n}\n\n/**\n * Tool call part that creates a proper discriminated union.\n * When TTools is typed, checking `part.name === 'toolName'` will narrow\n * `part.output` to the correct type for that tool.\n *\n * The discriminant is `name`, so code like:\n * ```ts\n * if (part.name === 'recommendGuitar') {\n * // part.output is now typed to the recommendGuitar tool's output\n * }\n * ```\n */\nexport type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n // Check if we have a concrete tools array (not 'any' or 'never')\n [TTools] extends [never]\n ? UntypedToolCallPart\n : unknown extends TTools\n ? UntypedToolCallPart\n : TTools extends ReadonlyArray<infer Tool>\n ? Tool extends AnyClientTool\n ? ToolCallPartForTool<Tool>\n : UntypedToolCallPart\n : UntypedToolCallPart\n\nexport interface ToolResultPart {\n type: 'tool-result'\n toolCallId: string\n content: string\n state: ToolResultState\n error?: string // Error message if state is \"error\"\n}\n\nexport interface ThinkingPart {\n type: 'thinking'\n content: string\n}\n\nexport type MessagePart<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> =\n | TextPart\n | ImagePart\n | AudioPart\n | VideoPart\n | DocumentPart\n | ToolCallPart<TTools>\n | ToolResultPart\n | ThinkingPart\n | StructuredOutputPart<TData>\n\n/**\n * UIMessage - Domain-specific message format optimized for building chat UIs\n * Contains parts that can be text, tool calls, or tool results.\n *\n * `TTools` narrows the tool-call/result part types based on the registered\n * tools. `TData` is the schema-inferred type for any `structured-output` part\n * on the message — defaulted to `unknown` so untyped consumers (the core\n * stream processor, the wire converter) don't need to thread a schema generic\n * everywhere; the hook layer (`useChat({ outputSchema })`) substitutes it on\n * the public return so `m.parts.find(p => p.type === 'structured-output').data`\n * is typed without manual casts.\n */\nexport interface UIMessage<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> {\n id: string\n role: 'system' | 'user' | 'assistant'\n parts: Array<MessagePart<TTools, TData>>\n createdAt?: Date\n}\n\nexport interface ChatClientOptions<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n> {\n /**\n * Connection adapter for streaming.\n * Supports mutually exclusive modes: request-response via `connect()`, or\n * subscribe/send mode via `subscribe()` + `send()`.\n */\n connection: ConnectionAdapter\n\n /**\n * Initial messages to populate the chat\n */\n initialMessages?: Array<UIMessage<TTools>>\n\n /**\n * Unique identifier for this chat instance\n * Used for managing multiple chats\n */\n id?: string\n\n /**\n * Thread ID to use for this chat session. Persists across sends within\n * the session. If omitted, a unique thread ID is generated.\n */\n threadId?: string\n\n /**\n * Arbitrary client-controlled JSON forwarded to the server in the\n * AG-UI `RunAgentInput.forwardedProps` field. Use this for per-session\n * options like provider/model selection or feature flags that the\n * server endpoint should read.\n *\n * Replaces the legacy `body` option. If both are provided,\n * `forwardedProps` wins on key collision.\n */\n forwardedProps?: Record<string, any>\n\n /**\n * @deprecated Use `forwardedProps` instead. `body` continues to work\n * unchanged — its values are merged into the AG-UI\n * `RunAgentInput.forwardedProps` field on the wire and are also\n * mirrored under the legacy `data` field for servers that have not\n * migrated yet. Will be removed in a future major release.\n */\n body?: Record<string, any>\n\n /**\n * Callback when a response is received\n */\n onResponse?: (response?: Response) => void | Promise<void>\n\n /**\n * Callback when a stream chunk is received\n */\n onChunk?: (chunk: StreamChunk) => void\n\n /**\n * Callback when the response is finished\n */\n onFinish?: (message: UIMessage<TTools>) => void\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void\n\n /**\n * Callback when messages change\n */\n onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void\n\n /**\n * Callback when loading state changes\n */\n onLoadingChange?: (isLoading: boolean) => void\n\n /**\n * Callback when error state changes\n */\n onErrorChange?: (error: Error | undefined) => void\n\n /**\n * Callback when chat status changes\n */\n onStatusChange?: (status: ChatClientState) => void\n\n /**\n * Callback when subscription lifecycle changes.\n * This is independent from request lifecycle (`isLoading`, `status`).\n */\n onSubscriptionChange?: (isSubscribed: boolean) => void\n\n /**\n * Callback when connection lifecycle changes.\n */\n onConnectionStatusChange?: (status: ConnectionStatus) => void\n\n /**\n * Callback when session generation activity changes.\n * Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).\n * Unlike `onLoadingChange` (request-local), this reflects shared generation\n * activity visible to all subscribers (e.g. across tabs/devices).\n */\n onSessionGeneratingChange?: (isGenerating: boolean) => void\n\n /**\n * Callback when a custom event is received from a server-side tool.\n * Custom events are emitted by tools using `context.emitCustomEvent()` during execution.\n *\n * @param eventType - The name of the custom event\n * @param data - The event payload data\n * @param context - Additional context including the toolCallId that emitted the event\n */\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n /**\n * Client-side tools with execution logic\n * When provided, tools with execute functions will be called automatically\n */\n tools?: TTools\n\n /**\n * Stream processing options (optional)\n * Configure chunking strategy\n */\n streamProcessor?: {\n /**\n * Strategy for when to emit text updates\n * Defaults to ImmediateStrategy (every chunk)\n */\n chunkStrategy?: ChunkStrategy\n }\n}\n\nexport interface ChatRequestBody {\n messages: Array<ModelMessage>\n data?: Record<string, any>\n}\n\n/**\n * Create a typed array of client tools with proper type inference.\n * This eliminates the need for `as const` when defining tool arrays.\n *\n * @example\n * ```ts\n * const tools = clientTools(\n * myTool1.client(() => result1),\n * myTool2.client(() => result2),\n * )\n *\n * // tools is now properly typed as a tuple with literal tool names\n * // This enables type narrowing when checking part.name === 'toolName'\n * ```\n */\nexport function clientTools<const T extends Array<AnyClientTool>>(\n ...tools: T\n): T {\n return tools\n}\n\n/**\n * Helper to create typed chat client options\n * Use this to get proper type inference for messages\n *\n * @example\n * ```ts\n * const tools = clientTools(myTool1, myTool2)\n *\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools,\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * ```\n */\nexport function createChatClientOptions<\n const TTools extends ReadonlyArray<AnyClientTool>,\n>(options: ChatClientOptions<TTools>): ChatClientOptions<TTools> {\n return options\n}\n\n/**\n * Extract the message type from chat options\n *\n * @example\n * ```ts\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools: [myTool1, myTool2],\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>\n * ```\n */\nexport type InferChatMessages<T> =\n T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never\n"],"names":[],"mappings":"AA2WO,SAAS,eACX,OACA;AACH,SAAO;AACT;AAkBO,SAAS,wBAEd,SAA+D;AAC/D,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type {\n AnyClientTool,\n AudioPart,\n ChunkStrategy,\n ContentPart,\n DocumentPart,\n ImagePart,\n InferToolInput,\n InferToolOutput,\n ModelMessage,\n StreamChunk,\n StructuredOutputPart,\n VideoPart,\n} from '@tanstack/ai'\nimport type { ConnectionAdapter } from './connection-adapters'\n\nexport type { StructuredOutputPart } from '@tanstack/ai'\n\n/**\n * `messages` is the full UIMessage history (not a delta). `data` is the\n * merged body — `ChatClientOptions.body` plus any per-call data passed to\n * `sendMessage(...)`. `threadId` / `runId` are the AG-UI correlation ids\n * the chat client uses to track this turn — forward them to your server\n * if it needs to correlate requests.\n */\nexport interface ChatFetcherInput {\n messages: Array<UIMessage>\n data?: Record<string, unknown>\n threadId: string\n runId: string\n}\n\nexport interface ChatFetcherOptions {\n /** Fires when `stop()` is called or the request is superseded. */\n signal: AbortSignal\n}\n\n/**\n * Direct function that performs a chat request. Mirrors\n * `GenerationFetcher`. Returns either a `Response` (SSE body parsed by the\n * chat client) or an `AsyncIterable<StreamChunk>` (yielded directly). May\n * return the value synchronously, as a `Promise`, or as an async generator\n * (`async function*`) — the chat client awaits whichever shape is returned.\n *\n * @example\n * ```ts\n * useChat({\n * fetcher: ({ messages }, { signal }) =>\n * chatFn({ data: { messages }, signal }),\n * })\n * ```\n */\nexport type ChatFetcher = (\n input: ChatFetcherInput,\n options: ChatFetcherOptions,\n) =>\n | Response\n | AsyncIterable<StreamChunk>\n | Promise<Response | AsyncIterable<StreamChunk>>\n\n/**\n * Distributive `Omit` — applies `Omit<O, K>` per branch of a union so\n * discriminated unions survive omission. Plain `Omit` collapses unions\n * into a single object shape, which would erase the `ChatTransport` XOR\n * when framework hooks omit React-managed callbacks from\n * `ChatClientOptions`.\n */\nexport type DistributedOmit<\n TObject,\n TKeys extends keyof any,\n> = TObject extends unknown ? Omit<TObject, TKeys> : never\n\n/**\n * Discriminated union enforcing that exactly one of `connection` or\n * `fetcher` is provided. Mirrors `GenerationTransport`.\n */\nexport type ChatTransport =\n | { connection: ConnectionAdapter; fetcher?: never }\n | { fetcher: ChatFetcher; connection?: never }\n\n/**\n * Tool call states - track the lifecycle of a tool call\n */\nexport type ToolCallState =\n | 'awaiting-input' // Received start but no arguments yet\n | 'input-streaming' // Partial arguments received\n | 'input-complete' // All arguments received\n | 'approval-requested' // Waiting for user approval\n | 'approval-responded' // User has approved/denied\n | 'complete' // Result is complete\n\n/**\n * Tool result states - track the lifecycle of a tool result\n */\nexport type ToolResultState =\n | 'streaming' // Placeholder for future streamed output\n | 'complete' // Result is complete\n | 'error' // Error occurred\n\n/**\n * ChatClient state - track the lifecycle of a chat\n */\nexport type ChatClientState = 'ready' | 'submitted' | 'streaming' | 'error'\n\n/**\n * Connection lifecycle state for the subscription loop.\n */\nexport type ConnectionStatus =\n | 'disconnected'\n | 'connecting'\n | 'connected'\n | 'error'\n\n/**\n * Multimodal content input for sending messages with rich media.\n * Allows sending text, images, audio, video, and documents to the LLM.\n *\n * @example\n * ```ts\n * // Send an image with a question\n * client.sendMessage({\n * content: [\n * { type: 'text', content: 'What is in this image?' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/photo.jpg' } }\n * ],\n * id: 'custom-message-id' // optional\n * })\n * ```\n */\nexport interface MultimodalContent {\n /**\n * The content of the message.\n * Can be a simple string or an array of content parts for multimodal messages.\n */\n content: string | Array<ContentPart>\n /**\n * Optional custom ID for the message.\n * If not provided, a unique ID will be generated.\n */\n id?: string\n}\n\n/**\n * Message parts - building blocks of UIMessage\n */\nexport interface TextPart {\n type: 'text'\n content: string\n}\n\n/**\n * Helper type that creates a tool-call part for a specific tool.\n * This is a conditional type to enable proper distribution over union types,\n * creating a discriminated union where `name` is the discriminant.\n */\ntype ToolCallPartForTool<T> = T extends AnyClientTool\n ? {\n type: 'tool-call'\n id: string\n name: T['name']\n arguments: string // JSON string (may be incomplete)\n /** Parsed tool input (typed from inputSchema) */\n input?: InferToolInput<T>\n state: ToolCallState\n /** Approval metadata if tool requires user approval */\n approval?: {\n id: string // Unique approval ID\n needsApproval: boolean // Always true if present\n approved?: boolean // User's decision (undefined until responded)\n }\n /** Tool execution output (for client tools or after approval) */\n output?: InferToolOutput<T>\n }\n : never\n\n/**\n * Fallback tool-call part type when tools are not typed\n */\ntype UntypedToolCallPart = {\n type: 'tool-call'\n id: string\n name: string\n arguments: string\n input?: any\n state: ToolCallState\n approval?: {\n id: string\n needsApproval: boolean\n approved?: boolean\n }\n output?: any\n}\n\n/**\n * Tool call part that creates a proper discriminated union.\n * When TTools is typed, checking `part.name === 'toolName'` will narrow\n * `part.output` to the correct type for that tool.\n *\n * The discriminant is `name`, so code like:\n * ```ts\n * if (part.name === 'recommendGuitar') {\n * // part.output is now typed to the recommendGuitar tool's output\n * }\n * ```\n */\nexport type ToolCallPart<TTools extends ReadonlyArray<AnyClientTool> = any> =\n // Check if we have a concrete tools array (not 'any' or 'never')\n [TTools] extends [never]\n ? UntypedToolCallPart\n : unknown extends TTools\n ? UntypedToolCallPart\n : TTools extends ReadonlyArray<infer Tool>\n ? Tool extends AnyClientTool\n ? ToolCallPartForTool<Tool>\n : UntypedToolCallPart\n : UntypedToolCallPart\n\nexport interface ToolResultPart {\n type: 'tool-result'\n toolCallId: string\n content: string\n state: ToolResultState\n error?: string // Error message if state is \"error\"\n}\n\nexport interface ThinkingPart {\n type: 'thinking'\n content: string\n}\n\nexport type MessagePart<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> =\n | TextPart\n | ImagePart\n | AudioPart\n | VideoPart\n | DocumentPart\n | ToolCallPart<TTools>\n | ToolResultPart\n | ThinkingPart\n | StructuredOutputPart<TData>\n\n/**\n * UIMessage - Domain-specific message format optimized for building chat UIs\n * Contains parts that can be text, tool calls, or tool results.\n *\n * `TTools` narrows the tool-call/result part types based on the registered\n * tools. `TData` is the schema-inferred type for any `structured-output` part\n * on the message — defaulted to `unknown` so untyped consumers (the core\n * stream processor, the wire converter) don't need to thread a schema generic\n * everywhere; the hook layer (`useChat({ outputSchema })`) substitutes it on\n * the public return so `m.parts.find(p => p.type === 'structured-output').data`\n * is typed without manual casts.\n */\nexport interface UIMessage<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n TData = unknown,\n> {\n id: string\n role: 'system' | 'user' | 'assistant'\n parts: Array<MessagePart<TTools, TData>>\n createdAt?: Date\n}\n\n/**\n * Options for `ChatClient`. Exactly one of `connection` or `fetcher` must be\n * provided — the type-level XOR is enforced via `ChatTransport`.\n */\nexport type ChatClientOptions<\n TTools extends ReadonlyArray<AnyClientTool> = any,\n> = {\n /**\n * Initial messages to populate the chat\n */\n initialMessages?: Array<UIMessage<TTools>>\n\n /**\n * Unique identifier for this chat instance\n * Used for managing multiple chats\n */\n id?: string\n\n /**\n * Thread ID to use for this chat session. Persists across sends within\n * the session. If omitted, a unique thread ID is generated.\n */\n threadId?: string\n\n /**\n * Arbitrary client-controlled JSON forwarded to the server in the\n * AG-UI `RunAgentInput.forwardedProps` field. Use this for per-session\n * options like provider/model selection or feature flags that the\n * server endpoint should read.\n *\n * Replaces the legacy `body` option. If both are provided,\n * `forwardedProps` wins on key collision.\n */\n forwardedProps?: Record<string, any>\n\n /**\n * @deprecated Use `forwardedProps` instead. `body` continues to work\n * unchanged — its values are merged into the AG-UI\n * `RunAgentInput.forwardedProps` field on the wire and are also\n * mirrored under the legacy `data` field for servers that have not\n * migrated yet. Will be removed in a future major release.\n */\n body?: Record<string, any>\n\n /**\n * Callback when a response is received\n */\n onResponse?: (response?: Response) => void | Promise<void>\n\n /**\n * Callback when a stream chunk is received\n */\n onChunk?: (chunk: StreamChunk) => void\n\n /**\n * Callback when the response is finished\n */\n onFinish?: (message: UIMessage<TTools>) => void\n\n /**\n * Callback when an error occurs\n */\n onError?: (error: Error) => void\n\n /**\n * Callback when messages change\n */\n onMessagesChange?: (messages: Array<UIMessage<TTools>>) => void\n\n /**\n * Callback when loading state changes\n */\n onLoadingChange?: (isLoading: boolean) => void\n\n /**\n * Callback when error state changes\n */\n onErrorChange?: (error: Error | undefined) => void\n\n /**\n * Callback when chat status changes\n */\n onStatusChange?: (status: ChatClientState) => void\n\n /**\n * Callback when subscription lifecycle changes.\n * This is independent from request lifecycle (`isLoading`, `status`).\n */\n onSubscriptionChange?: (isSubscribed: boolean) => void\n\n /**\n * Callback when connection lifecycle changes.\n */\n onConnectionStatusChange?: (status: ConnectionStatus) => void\n\n /**\n * Callback when session generation activity changes.\n * Derived from stream run events (RUN_STARTED / RUN_FINISHED / RUN_ERROR).\n * Unlike `onLoadingChange` (request-local), this reflects shared generation\n * activity visible to all subscribers (e.g. across tabs/devices).\n */\n onSessionGeneratingChange?: (isGenerating: boolean) => void\n\n /**\n * Callback when a custom event is received from a server-side tool.\n * Custom events are emitted by tools using `context.emitCustomEvent()` during execution.\n *\n * @param eventType - The name of the custom event\n * @param data - The event payload data\n * @param context - Additional context including the toolCallId that emitted the event\n */\n onCustomEvent?: (\n eventType: string,\n data: unknown,\n context: { toolCallId?: string },\n ) => void\n\n /**\n * Client-side tools with execution logic\n * When provided, tools with execute functions will be called automatically\n */\n tools?: TTools\n\n /**\n * Stream processing options (optional)\n * Configure chunking strategy\n */\n streamProcessor?: {\n /**\n * Strategy for when to emit text updates\n * Defaults to ImmediateStrategy (every chunk)\n */\n chunkStrategy?: ChunkStrategy\n }\n} & ChatTransport\n\nexport interface ChatRequestBody {\n messages: Array<ModelMessage>\n data?: Record<string, any>\n}\n\n/**\n * Create a typed array of client tools with proper type inference.\n * This eliminates the need for `as const` when defining tool arrays.\n *\n * @example\n * ```ts\n * const tools = clientTools(\n * myTool1.client(() => result1),\n * myTool2.client(() => result2),\n * )\n *\n * // tools is now properly typed as a tuple with literal tool names\n * // This enables type narrowing when checking part.name === 'toolName'\n * ```\n */\nexport function clientTools<const T extends Array<AnyClientTool>>(\n ...tools: T\n): T {\n return tools\n}\n\n/**\n * Helper to create typed chat client options\n * Use this to get proper type inference for messages\n *\n * @example\n * ```ts\n * const tools = clientTools(myTool1, myTool2)\n *\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools,\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * ```\n */\nexport function createChatClientOptions<\n const TTools extends ReadonlyArray<AnyClientTool>,\n>(options: ChatClientOptions<TTools>): ChatClientOptions<TTools> {\n return options\n}\n\n/**\n * Extract the message type from chat options\n *\n * @example\n * ```ts\n * const chatOptions = createChatClientOptions({\n * connection: fetchServerSentEvents('/api/chat'),\n * tools: [myTool1, myTool2],\n * })\n *\n * type MyMessages = InferChatMessages<typeof chatOptions>\n * // MyMessages is now Array<UIMessage<[typeof myTool1, typeof myTool2]>>\n * ```\n */\nexport type InferChatMessages<T> =\n T extends ChatClientOptions<infer TTools> ? Array<UIMessage<TTools>> : never\n"],"names":[],"mappings":"AAsaO,SAAS,eACX,OACA;AACH,SAAO;AACT;AAkBO,SAAS,wBAEd,SAA+D;AAC/D,SAAO;AACT;"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Framework-agnostic headless client for TanStack AI chat, realtime sessions, streaming transports, and media generations.",
|
|
5
5
|
"author": "",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/TanStack/ai.git",
|
|
10
|
-
"directory": "packages/
|
|
10
|
+
"directory": "packages/ai-client"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"ai",
|