@tanstack/ai-client 0.13.0 → 0.14.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.
Files changed (53) hide show
  1. package/dist/esm/chat-client.d.ts +1 -1
  2. package/dist/esm/chat-client.js +1 -1
  3. package/dist/esm/chat-client.js.map +1 -1
  4. package/dist/esm/connection-adapters.d.ts +21 -1
  5. package/dist/esm/connection-adapters.js +262 -68
  6. package/dist/esm/connection-adapters.js.map +1 -1
  7. package/dist/esm/devtools-noop.d.ts +1 -1
  8. package/dist/esm/devtools-noop.js.map +1 -1
  9. package/dist/esm/devtools.d.ts +1 -1
  10. package/dist/esm/devtools.js +1 -1
  11. package/dist/esm/devtools.js.map +1 -1
  12. package/dist/esm/events.d.ts +1 -1
  13. package/dist/esm/events.js.map +1 -1
  14. package/dist/esm/generation-client.js.map +1 -1
  15. package/dist/esm/generation-types.d.ts +1 -1
  16. package/dist/esm/generation-types.js.map +1 -1
  17. package/dist/esm/index.d.ts +5 -4
  18. package/dist/esm/index.js +7 -3
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/realtime-client.d.ts +1 -1
  21. package/dist/esm/realtime-client.js +1 -1
  22. package/dist/esm/realtime-client.js.map +1 -1
  23. package/dist/esm/realtime-types.d.ts +1 -1
  24. package/dist/esm/response-stream.d.ts +7 -0
  25. package/dist/esm/response-stream.js +32 -0
  26. package/dist/esm/response-stream.js.map +1 -0
  27. package/dist/esm/sse-parser.d.ts +1 -1
  28. package/dist/esm/sse-parser.js +5 -6
  29. package/dist/esm/sse-parser.js.map +1 -1
  30. package/dist/esm/sse-utils.d.ts +1 -0
  31. package/dist/esm/sse-utils.js +11 -0
  32. package/dist/esm/sse-utils.js.map +1 -0
  33. package/dist/esm/tool-types.d.ts +1 -1
  34. package/dist/esm/types.d.ts +2 -2
  35. package/dist/esm/types.js.map +1 -1
  36. package/dist/esm/video-generation-client.js.map +1 -1
  37. package/package.json +3 -3
  38. package/src/chat-client.ts +2 -2
  39. package/src/connection-adapters.ts +360 -68
  40. package/src/devtools-noop.ts +1 -1
  41. package/src/devtools.ts +2 -2
  42. package/src/events.ts +1 -1
  43. package/src/generation-client.ts +1 -1
  44. package/src/generation-types.ts +1 -1
  45. package/src/index.ts +7 -3
  46. package/src/realtime-client.ts +2 -2
  47. package/src/realtime-types.ts +1 -1
  48. package/src/response-stream.ts +42 -0
  49. package/src/sse-parser.ts +9 -7
  50. package/src/sse-utils.ts +8 -0
  51. package/src/tool-types.ts +1 -1
  52. package/src/types.ts +2 -2
  53. package/src/video-generation-client.ts +1 -1
@@ -1,4 +1,6 @@
1
- import { uiMessagesToWire, EventType } from "@tanstack/ai";
1
+ import { EventType, uiMessagesToWire } from "@tanstack/ai/client";
2
+ import { getResponseStreamReader, createResponseStreamTextDecoder } from "./response-stream.js";
3
+ import { parseSseDataLine } from "./sse-utils.js";
2
4
  class StreamTruncatedError extends Error {
3
5
  constructor() {
4
6
  super(
@@ -33,7 +35,7 @@ function mergeHeaders(customHeaders) {
33
35
  }
34
36
  async function* readStreamLines(reader, abortSignal) {
35
37
  try {
36
- const decoder = new TextDecoder();
38
+ const decoder = createResponseStreamTextDecoder();
37
39
  let buffer = "";
38
40
  while (!abortSignal?.aborted) {
39
41
  const { done, value } = await reader.read();
@@ -60,10 +62,7 @@ async function* responseToSSEChunks(response, abortSignal) {
60
62
  `HTTP error! status: ${response.status} ${response.statusText}`
61
63
  );
62
64
  }
63
- const reader = response.body?.getReader();
64
- if (!reader) {
65
- throw new Error("Response body is not readable");
66
- }
65
+ const reader = getResponseStreamReader(response);
67
66
  let lastThreadId;
68
67
  let lastRunId;
69
68
  let lastModel;
@@ -71,7 +70,7 @@ async function* responseToSSEChunks(response, abortSignal) {
71
70
  if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
72
71
  continue;
73
72
  }
74
- const data = line.startsWith("data: ") ? line.slice(6) : line;
73
+ const data = parseSseDataLine(line);
75
74
  if (data === "[DONE]") {
76
75
  const synthetic = {
77
76
  type: EventType.RUN_FINISHED,
@@ -219,6 +218,28 @@ function normalizeConnectionAdapter(connection) {
219
218
  }
220
219
  };
221
220
  }
221
+ function buildRunAgentInputBody(messages, data, runContext, options) {
222
+ const wireMessages = uiMessagesToWire(messages);
223
+ const forwardedProps = {
224
+ ...options.body,
225
+ ...runContext?.forwardedProps ?? {},
226
+ ...data
227
+ };
228
+ return {
229
+ threadId: runContext?.threadId ?? generateRunId("thread"),
230
+ runId: runContext?.runId ?? generateRunId("run"),
231
+ ...runContext?.parentRunId !== void 0 && {
232
+ parentRunId: runContext.parentRunId
233
+ },
234
+ state: {},
235
+ messages: wireMessages,
236
+ tools: runContext?.clientTools ?? [],
237
+ context: [],
238
+ forwardedProps,
239
+ // Backward-compat mirror of `forwardedProps` under the legacy field name.
240
+ data: { ...forwardedProps }
241
+ };
242
+ }
222
243
  function fetchServerSentEvents(url, options = {}) {
223
244
  return {
224
245
  async *connect(messages, data, abortSignal, runContext) {
@@ -228,34 +249,12 @@ function fetchServerSentEvents(url, options = {}) {
228
249
  "Content-Type": "application/json",
229
250
  ...mergeHeaders(resolvedOptions.headers)
230
251
  };
231
- const wireMessages = uiMessagesToWire(messages);
232
- const forwardedProps = {
233
- ...resolvedOptions.body,
234
- ...runContext?.forwardedProps ?? {},
235
- ...data
236
- };
237
- const requestBody = {
238
- threadId: runContext?.threadId ?? generateRunId("thread"),
239
- runId: runContext?.runId ?? generateRunId("run"),
240
- ...runContext?.parentRunId !== void 0 && {
241
- parentRunId: runContext.parentRunId
242
- },
243
- state: {},
244
- messages: wireMessages,
245
- tools: runContext?.clientTools ?? [],
246
- context: [],
247
- forwardedProps,
248
- // Backward-compat mirror of `forwardedProps` under the legacy
249
- // field name `data`. Server endpoints that have not migrated
250
- // off the pre-AG-UI shape (`{ messages, data }`) keep working.
251
- // AG-UI strict consumers strip this via `RunAgentInputSchema`
252
- // (see `chatParamsFromRequestBody`). Will be removed when the
253
- // legacy `body` client option is dropped.
254
- // Shallow-cloned so that downstream mutation of `data` (e.g.
255
- // by a logging interceptor or fetch wrapper) cannot corrupt
256
- // `forwardedProps` and vice versa.
257
- data: { ...forwardedProps }
258
- };
252
+ const requestBody = buildRunAgentInputBody(
253
+ messages,
254
+ data,
255
+ runContext,
256
+ resolvedOptions
257
+ );
259
258
  const fetchClient = resolvedOptions.fetchClient ?? fetch;
260
259
  const signal = abortSignal || resolvedOptions.signal;
261
260
  const response = await fetchClient(resolvedUrl, {
@@ -278,34 +277,12 @@ function fetchHttpStream(url, options = {}) {
278
277
  "Content-Type": "application/json",
279
278
  ...mergeHeaders(resolvedOptions.headers)
280
279
  };
281
- const wireMessages = uiMessagesToWire(messages);
282
- const forwardedProps = {
283
- ...resolvedOptions.body,
284
- ...runContext?.forwardedProps ?? {},
285
- ...data
286
- };
287
- const requestBody = {
288
- threadId: runContext?.threadId ?? generateRunId("thread"),
289
- runId: runContext?.runId ?? generateRunId("run"),
290
- ...runContext?.parentRunId !== void 0 && {
291
- parentRunId: runContext.parentRunId
292
- },
293
- state: {},
294
- messages: wireMessages,
295
- tools: runContext?.clientTools ?? [],
296
- context: [],
297
- forwardedProps,
298
- // Backward-compat mirror of `forwardedProps` under the legacy
299
- // field name `data`. Server endpoints that have not migrated
300
- // off the pre-AG-UI shape (`{ messages, data }`) keep working.
301
- // AG-UI strict consumers strip this via `RunAgentInputSchema`
302
- // (see `chatParamsFromRequestBody`). Will be removed when the
303
- // legacy `body` client option is dropped.
304
- // Shallow-cloned so that downstream mutation of `data` (e.g.
305
- // by a logging interceptor or fetch wrapper) cannot corrupt
306
- // `forwardedProps` and vice versa.
307
- data: { ...forwardedProps }
308
- };
280
+ const requestBody = buildRunAgentInputBody(
281
+ messages,
282
+ data,
283
+ runContext,
284
+ resolvedOptions
285
+ );
309
286
  const fetchClient = resolvedOptions.fetchClient ?? fetch;
310
287
  const signal = abortSignal || resolvedOptions.signal;
311
288
  const response = await fetchClient(resolvedUrl, {
@@ -320,16 +297,231 @@ function fetchHttpStream(url, options = {}) {
320
297
  `HTTP error! status: ${response.status} ${response.statusText}`
321
298
  );
322
299
  }
323
- const reader = response.body?.getReader();
324
- if (!reader) {
325
- throw new Error("Response body is not readable");
326
- }
300
+ const reader = getResponseStreamReader(response);
327
301
  for await (const line of readStreamLines(reader, abortSignal)) {
328
302
  yield JSON.parse(line);
329
303
  }
330
304
  }
331
305
  };
332
306
  }
307
+ function createDefaultXMLHttpRequest() {
308
+ if (typeof globalThis.XMLHttpRequest !== "function") {
309
+ throw new Error("XMLHttpRequest is not available in this runtime");
310
+ }
311
+ return new globalThis.XMLHttpRequest();
312
+ }
313
+ function cleanupXhr(xhr, abortSignal, onAbort) {
314
+ xhr.onprogress = null;
315
+ xhr.onload = null;
316
+ xhr.onerror = null;
317
+ xhr.onabort = null;
318
+ xhr.onloadend = null;
319
+ if (abortSignal && onAbort) {
320
+ abortSignal.removeEventListener("abort", onAbort);
321
+ }
322
+ }
323
+ function readXhrLines(xhr, abortSignal) {
324
+ let offset = 0;
325
+ let buffer = "";
326
+ const lines = [];
327
+ const waiters = [];
328
+ let done = false;
329
+ let aborted = false;
330
+ let error;
331
+ let onAbort;
332
+ const wake = () => {
333
+ const waiter = waiters.shift();
334
+ waiter?.();
335
+ };
336
+ const enqueueDelta = () => {
337
+ if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {
338
+ error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
339
+ done = true;
340
+ return;
341
+ }
342
+ const responseText = xhr.responseText;
343
+ if (responseText.length <= offset) {
344
+ return;
345
+ }
346
+ buffer += responseText.slice(offset);
347
+ offset = responseText.length;
348
+ const splitLines = buffer.split("\n");
349
+ buffer = splitLines.pop() ?? "";
350
+ for (const line of splitLines) {
351
+ const normalized = line.endsWith("\r") ? line.slice(0, -1) : line;
352
+ if (normalized.trim()) {
353
+ lines.push(normalized);
354
+ }
355
+ }
356
+ };
357
+ const finish = () => {
358
+ enqueueDelta();
359
+ if (xhr.status < 200 || xhr.status >= 300) {
360
+ error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`);
361
+ } else if (buffer.trim() && !aborted) {
362
+ error = new StreamTruncatedError();
363
+ }
364
+ done = true;
365
+ wake();
366
+ };
367
+ xhr.onprogress = () => {
368
+ enqueueDelta();
369
+ wake();
370
+ };
371
+ xhr.onload = finish;
372
+ xhr.onerror = () => {
373
+ error = new Error("XHR request failed");
374
+ done = true;
375
+ wake();
376
+ };
377
+ xhr.onabort = () => {
378
+ aborted = true;
379
+ done = true;
380
+ wake();
381
+ };
382
+ xhr.onloadend = () => {
383
+ if (!done) {
384
+ finish();
385
+ }
386
+ };
387
+ if (abortSignal) {
388
+ onAbort = () => {
389
+ aborted = true;
390
+ xhr.abort();
391
+ };
392
+ if (abortSignal.aborted) {
393
+ onAbort();
394
+ } else {
395
+ abortSignal.addEventListener("abort", onAbort, { once: true });
396
+ }
397
+ }
398
+ return (async function* () {
399
+ try {
400
+ while (true) {
401
+ const line = lines.shift();
402
+ if (line !== void 0) {
403
+ yield line;
404
+ continue;
405
+ }
406
+ if (error) {
407
+ throw error;
408
+ }
409
+ if (done || abortSignal?.aborted) {
410
+ return;
411
+ }
412
+ await new Promise((resolve) => {
413
+ waiters.push(resolve);
414
+ });
415
+ }
416
+ } finally {
417
+ cleanupXhr(xhr, abortSignal, onAbort);
418
+ }
419
+ })();
420
+ }
421
+ function createConfiguredXhrRequest(url, options, messages, data, runContext) {
422
+ const xhr = options.xhrFactory?.() ?? createDefaultXMLHttpRequest();
423
+ xhr.open("POST", url);
424
+ if (options.withCredentials !== void 0) {
425
+ xhr.withCredentials = options.withCredentials;
426
+ }
427
+ const requestHeaders = {
428
+ "Content-Type": "application/json",
429
+ ...mergeHeaders(options.headers)
430
+ };
431
+ for (const [name, value] of Object.entries(requestHeaders)) {
432
+ xhr.setRequestHeader(name, value);
433
+ }
434
+ const requestBody = buildRunAgentInputBody(
435
+ messages,
436
+ data,
437
+ runContext,
438
+ options
439
+ );
440
+ return { xhr, body: JSON.stringify(requestBody) };
441
+ }
442
+ async function resolveXhrConnectionOptions(options) {
443
+ return typeof options === "function" ? await options() : options;
444
+ }
445
+ function xhrServerSentEvents(url, options = {}) {
446
+ return {
447
+ async *connect(messages, data, abortSignal, runContext) {
448
+ const resolvedUrl = typeof url === "function" ? url() : url;
449
+ const resolvedOptions = await resolveXhrConnectionOptions(options);
450
+ const signal = abortSignal || resolvedOptions.signal;
451
+ const request = createConfiguredXhrRequest(
452
+ resolvedUrl,
453
+ resolvedOptions,
454
+ messages,
455
+ data,
456
+ runContext
457
+ );
458
+ const lines = readXhrLines(request.xhr, signal);
459
+ if (signal?.aborted) {
460
+ await lines.next();
461
+ return;
462
+ }
463
+ request.xhr.send(request.body);
464
+ let lastThreadId;
465
+ let lastRunId;
466
+ let lastModel;
467
+ for await (const line of lines) {
468
+ if (line.startsWith(":") || line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
469
+ continue;
470
+ }
471
+ const chunkData = parseSseDataLine(line);
472
+ if (chunkData === "[DONE]") {
473
+ const synthetic = {
474
+ type: EventType.RUN_FINISHED,
475
+ threadId: lastThreadId ?? runContext?.threadId ?? "",
476
+ runId: lastRunId ?? runContext?.runId ?? "",
477
+ model: lastModel ?? "",
478
+ timestamp: Date.now(),
479
+ finishReason: "stop"
480
+ };
481
+ request.xhr.abort();
482
+ yield synthetic;
483
+ return;
484
+ }
485
+ const chunk = JSON.parse(chunkData);
486
+ if ("threadId" in chunk && typeof chunk.threadId === "string") {
487
+ lastThreadId = chunk.threadId;
488
+ }
489
+ if ("runId" in chunk && typeof chunk.runId === "string") {
490
+ lastRunId = chunk.runId;
491
+ }
492
+ if ("model" in chunk && typeof chunk.model === "string") {
493
+ lastModel = chunk.model;
494
+ }
495
+ yield chunk;
496
+ }
497
+ }
498
+ };
499
+ }
500
+ function xhrHttpStream(url, options = {}) {
501
+ return {
502
+ async *connect(messages, data, abortSignal, runContext) {
503
+ const resolvedUrl = typeof url === "function" ? url() : url;
504
+ const resolvedOptions = await resolveXhrConnectionOptions(options);
505
+ const signal = abortSignal || resolvedOptions.signal;
506
+ const request = createConfiguredXhrRequest(
507
+ resolvedUrl,
508
+ resolvedOptions,
509
+ messages,
510
+ data,
511
+ runContext
512
+ );
513
+ const lines = readXhrLines(request.xhr, signal);
514
+ if (signal?.aborted) {
515
+ await lines.next();
516
+ return;
517
+ }
518
+ request.xhr.send(request.body);
519
+ for await (const line of lines) {
520
+ yield JSON.parse(line);
521
+ }
522
+ }
523
+ };
524
+ }
333
525
  function stream(streamFactory) {
334
526
  return {
335
527
  async *connect(messages, data, abortSignal) {
@@ -404,6 +596,8 @@ export {
404
596
  fetcherToConnectionAdapter,
405
597
  normalizeConnectionAdapter,
406
598
  rpcStream,
407
- stream
599
+ stream,
600
+ xhrHttpStream,
601
+ xhrServerSentEvents
408
602
  };
409
603
  //# sourceMappingURL=connection-adapters.js.map
@@ -1 +1 @@
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":";AAeO,MAAM,6BAA6B,MAAM;AAAA,EAC9C,cAAc;AACZ;AAAA,MACE;AAAA,IAAA;AAEF,SAAK,OAAO;AAAA,EACd;AACF;AAEA,SAAS,cAAc,QAAwB;AAC7C,SAAO,GAAG,MAAM,IAAI,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC1E;AAQA,SAAS,mBACP,OACA,OACQ;AACR,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,qCAAqC,KAAK;AAAA,IAAA;AAAA,EAE9C;AACA,SAAO;AACT;AAKA,SAAS,aACP,eACwB;AACxB,MAAI,CAAC,eAAe;AAClB,WAAO,CAAA;AAAA,EACT;AACA,MAAI,yBAAyB,SAAS;AACpC,UAAM,SAAiC,CAAA;AACvC,kBAAc,QAAQ,CAAC,OAAO,QAAQ;AACpC,aAAO,GAAG,IAAI;AAAA,IAChB,CAAC;AACD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKA,gBAAgB,gBACd,QACA,aACwB;AACxB,MAAI;AACF,UAAM,UAAU,IAAI,YAAA;AACpB,QAAI,SAAS;AAEb,WAAO,CAAC,aAAa,SAAS;AAC5B,YAAM,EAAE,MAAM,MAAA,IAAU,MAAM,OAAO,KAAA;AACrC,UAAI,KAAM;AAEV,gBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM;AAChD,YAAM,QAAQ,OAAO,MAAM,IAAI;AAG/B,eAAS,MAAM,SAAS;AAExB,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,QAAQ;AACf,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAOA,QAAI,OAAO,KAAA,KAAU,CAAC,aAAa,SAAS;AAC1C,YAAM,IAAI,qBAAA;AAAA,IACZ;AAAA,EACF,UAAA;AACE,WAAO,YAAA;AAAA,EACT;AACF;AAcA,gBAAgB,oBACd,UACA,aAC6B;AAC7B,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,IAAA;AAAA,EAEjE;AACA,QAAM,SAAS,SAAS,MAAM,UAAA;AAC9B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,mBAAiB,QAAQ,gBAAgB,QAAQ,WAAW,GAAG;AAC7D,QACE,KAAK,WAAW,GAAG,KACnB,KAAK,WAAW,QAAQ,KACxB,KAAK,WAAW,KAAK,KACrB,KAAK,WAAW,QAAQ,GACxB;AACA;AAAA,IACF;AACA,UAAM,OAAO,KAAK,WAAW,QAAQ,IAAI,KAAK,MAAM,CAAC,IAAI;AACzD,QAAI,SAAS,UAAU;AACrB,YAAM,YAA8B;AAAA,QAClC,MAAM,UAAU;AAAA,QAChB,UAAU,gBAAgB;AAAA,QAC1B,OAAO,aAAa;AAAA,QACpB,OAAO,aAAa;AAAA,QACpB,WAAW,KAAK,IAAA;AAAA,QAChB,cAAc;AAAA,MAAA;AAEhB,YAAM;AACN;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAI,cAAc,SAAS,OAAO,MAAM,aAAa,UAAU;AAC7D,qBAAe,MAAM;AAAA,IACvB;AACA,QAAI,WAAW,SAAS,OAAO,MAAM,UAAU,UAAU;AACvD,kBAAY,MAAM;AAAA,IACpB;AACA,QAAI,WAAW,SAAS,OAAO,MAAM,UAAU,UAAU;AACvD,kBAAY,MAAM;AAAA,IACpB;AACA,UAAM;AAAA,EACR;AACF;AA+DO,SAAS,2BACd,YAC4B;AAC5B,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,gCAAgC;AAAA,EAClD;AAEA,QAAM,aAAa,aAAa;AAChC,QAAM,eAAe,eAAe;AACpC,QAAM,UAAU,UAAU;AAE1B,MAAI,eAAe,gBAAgB,UAAU;AAC3C,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAEA,MAAI,gBAAgB,SAAS;AAC3B,WAAO;AAAA,MACL,WAAW,WAAW,UAAU,KAAK,UAAU;AAAA,MAC/C,MAAM,WAAW,KAAK,KAAK,UAAU;AAAA,IAAA;AAAA,EAEzC;AAEA,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AAGA,MAAI,eAAmC,CAAA;AACvC,MAAI,gBAA4D,CAAA;AAEhE,WAAS,KAAK,OAA0B;AACtC,UAAM,SAAS,cAAc,MAAA;AAC7B,QAAI,QAAQ;AACV,aAAO,KAAK;AAAA,IACd,OAAO;AACL,mBAAa,KAAK,KAAK;AAAA,IACzB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAU,aAAuD;AAG/D,YAAM,WAA+B,aAAa,OAAO,CAAC;AAC1D,YAAM,YAAwD,CAAA;AAC9D,qBAAe;AACf,sBAAgB;AAEhB,cAAQ,mBAAmB;AACzB,eAAO,CAAC,aAAa,SAAS;AAC5B,cAAI;AACJ,gBAAM,WAAW,SAAS,MAAA;AAC1B,cAAI,aAAa,QAAW;AAC1B,oBAAQ;AAAA,UACV,OAAO;AACL,oBAAQ,MAAM,IAAI,QAA4B,CAAC,YAAY;AACzD,oBAAM,UAAU,MAAM,QAAQ,IAAI;AAClC,wBAAU,KAAK,CAAC,MAAM;AACpB,6BAAa,oBAAoB,SAAS,OAAO;AACjD,wBAAQ,CAAC;AAAA,cACX,CAAC;AACD,2BAAa,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM;AAAA,YAChE,CAAC;AAAA,UACH;AACA,cAAI,UAAU,KAAM,OAAM;AAAA,QAC5B;AAAA,MACF,GAAA;AAAA,IACF;AAAA,IACA,MAAM,KAAK,UAAU,MAAM,aAAa,YAAY;AAClD,UAAI,mBAAmB;AACvB,UAAI;AACJ,UAAI;AACJ,UAAI;AACF,cAAMA,UAAS,WAAW;AAAA,UACxB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAEF,yBAAiB,SAASA,SAAQ;AAChC,cAAI,cAAc,SAAS,OAAO,MAAM,aAAa,UAAU;AAC7D,+BAAmB,MAAM;AAAA,UAC3B;AACA,cAAI,WAAW,SAAS,OAAO,MAAM,UAAU,UAAU;AACvD,4BAAgB,MAAM;AAAA,UACxB;AACA,cAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,aAAa;AAC/D,+BAAmB;AAAA,UACrB;AACA,eAAK,KAAK;AAAA,QACZ;AAKA,YAAI,CAAC,aAAa,WAAW,CAAC,kBAAkB;AAC9C,gBAAM,YAA8B;AAAA,YAClC,MAAM,UAAU;AAAA,YAChB,UAAU;AAAA,cACR,oBAAoB,YAAY;AAAA,cAChC;AAAA,YAAA;AAAA,YAEF,OAAO;AAAA,cACL,iBAAiB,YAAY;AAAA,cAC7B;AAAA,YAAA;AAAA,YAEF,OAAO;AAAA,YACP,WAAW,KAAK,IAAA;AAAA,YAChB,cAAc;AAAA,UAAA;AAEhB,eAAK,SAAS;AAAA,QAChB;AAAA,MACF,SAAS,KAAK;AACZ,YAAI,CAAC,aAAa,WAAW,CAAC,kBAAkB;AAC9C,gBAAM,UACJ,eAAe,QAAQ,IAAI,UAAU;AACvC,gBAAM,YAA2B;AAAA,YAC/B,MAAM,UAAU;AAAA,YAChB,UAAU;AAAA,cACR,oBAAoB,YAAY;AAAA,cAChC;AAAA,YAAA;AAAA,YAEF,OAAO;AAAA,cACL,iBAAiB,YAAY;AAAA,cAC7B;AAAA,YAAA;AAAA,YAEF,WAAW,KAAK,IAAA;AAAA,YAChB;AAAA,UAAA;AAEF,eAAK,SAAS;AAAA,QAChB;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EAAA;AAEJ;AA+CO,SAAS,sBACd,KACA,UAEuE,IAC7C;AAC1B,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,MAAM,aAAa,YAAY;AAEtD,YAAM,cAAc,OAAO,QAAQ,aAAa,QAAQ;AACxD,YAAM,kBACJ,OAAO,YAAY,aAAa,MAAM,YAAY;AAEpD,YAAM,iBAAyC;AAAA,QAC7C,gBAAgB;AAAA,QAChB,GAAG,aAAa,gBAAgB,OAAO;AAAA,MAAA;AAUzC,YAAM,eAAe,iBAAiB,QAA4B;AAClE,YAAM,iBAAiB;AAAA,QACrB,GAAG,gBAAgB;AAAA,QACnB,GAAI,YAAY,kBAAkB,CAAA;AAAA,QAClC,GAAG;AAAA,MAAA;AAEL,YAAM,cAAc;AAAA,QAClB,UAAU,YAAY,YAAY,cAAc,QAAQ;AAAA,QACxD,OAAO,YAAY,SAAS,cAAc,KAAK;AAAA,QAC/C,GAAI,YAAY,gBAAgB,UAAa;AAAA,UAC3C,aAAa,WAAW;AAAA,QAAA;AAAA,QAE1B,OAAO,CAAA;AAAA,QACP,UAAU;AAAA,QACV,OAAO,YAAY,eAAe,CAAA;AAAA,QAClC,SAAS,CAAA;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUA,MAAM,EAAE,GAAG,eAAA;AAAA,MAAe;AAG5B,YAAM,cAAc,gBAAgB,eAAe;AAInD,YAAM,SAAS,eAAe,gBAAgB;AAC9C,YAAM,WAAW,MAAM,YAAY,aAAa;AAAA,QAC9C,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM,KAAK,UAAU,WAAW;AAAA,QAChC,aAAa,gBAAgB,eAAe;AAAA,QAC5C,GAAI,SAAS,EAAE,WAAW,CAAA;AAAA,MAAC,CAC5B;AAED,aAAO,oBAAoB,UAAU,WAAW;AAAA,IAClD;AAAA,EAAA;AAEJ;AAoCO,SAAS,gBACd,KACA,UAEuE,IAC7C;AAC1B,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,MAAM,aAAa,YAAY;AAEtD,YAAM,cAAc,OAAO,QAAQ,aAAa,QAAQ;AACxD,YAAM,kBACJ,OAAO,YAAY,aAAa,MAAM,YAAY;AAEpD,YAAM,iBAAyC;AAAA,QAC7C,gBAAgB;AAAA,QAChB,GAAG,aAAa,gBAAgB,OAAO;AAAA,MAAA;AAUzC,YAAM,eAAe,iBAAiB,QAA4B;AAClE,YAAM,iBAAiB;AAAA,QACrB,GAAG,gBAAgB;AAAA,QACnB,GAAI,YAAY,kBAAkB,CAAA;AAAA,QAClC,GAAG;AAAA,MAAA;AAEL,YAAM,cAAc;AAAA,QAClB,UAAU,YAAY,YAAY,cAAc,QAAQ;AAAA,QACxD,OAAO,YAAY,SAAS,cAAc,KAAK;AAAA,QAC/C,GAAI,YAAY,gBAAgB,UAAa;AAAA,UAC3C,aAAa,WAAW;AAAA,QAAA;AAAA,QAE1B,OAAO,CAAA;AAAA,QACP,UAAU;AAAA,QACV,OAAO,YAAY,eAAe,CAAA;AAAA,QAClC,SAAS,CAAA;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUA,MAAM,EAAE,GAAG,eAAA;AAAA,MAAe;AAG5B,YAAM,cAAc,gBAAgB,eAAe;AAInD,YAAM,SAAS,eAAe,gBAAgB;AAC9C,YAAM,WAAW,MAAM,YAAY,aAAa;AAAA,QAC9C,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAM,KAAK,UAAU,WAAW;AAAA,QAChC,aAAa,gBAAgB,eAAe;AAAA,QAC5C,GAAI,SAAS,EAAE,WAAW,CAAA;AAAA,MAAC,CAC5B;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,IAAI;AAAA,UACR,uBAAuB,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,QAAA;AAAA,MAEjE;AAGA,YAAM,SAAS,SAAS,MAAM,UAAA;AAC9B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,+BAA+B;AAAA,MACjD;AAEA,uBAAiB,QAAQ,gBAAgB,QAAQ,WAAW,GAAG;AAC7D,cAAM,KAAK,MAAM,IAAI;AAAA,MACvB;AAAA,IACF;AAAA,EAAA;AAEJ;AAgBO,SAAS,OACd,eAK0B;AAC1B,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,MAAM,aAAa;AAG1C,aAAO,cAAc,UAAU,MAAM,WAAW;AAAA,IAClD;AAAA,EAAA;AAEJ;AAUO,SAAS,2BACd,SAC0B;AAC1B,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,MAAM,aAAa,YAAY;AACtD,UAAI,CAAC,aAAa;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,QAAA;AAAA,MAEJ;AACA,UAAI,CAAC,YAAY;AACf,cAAM,IAAI;AAAA,UACR;AAAA,QAAA;AAAA,MAEJ;AACA,YAAM,aAAa;AACnB,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,UACE,UAAU;AAAA,UACV;AAAA,UACA,UAAU,WAAW;AAAA,UACrB,OAAO,WAAW;AAAA,QAAA;AAAA,QAEpB,EAAE,QAAQ,YAAA;AAAA,MAAY;AAExB,UAAI,kBAAkB,UAAU;AAC9B,eAAO,oBAAoB,QAAQ,WAAW;AAAA,MAChD,OAAO;AACL,eAAO,kBAAkB,QAAQ,WAAW;AAAA,MAC9C;AAAA,IACF;AAAA,EAAA;AAEJ;AAOA,gBAAgB,kBACd,UACA,QACmB;AACnB,MAAI,OAAO,QAAS;AACpB,QAAM,WAAW,SAAS,OAAO,aAAa,EAAA;AAC9C,QAAM,eAAe,IAAI;AAAA,IACvB,CAAC,YAAY;AACX,aAAO;AAAA,QACL;AAAA,QACA,MAAM,QAAQ,EAAE,MAAM,MAAM,OAAO,QAAW;AAAA,QAC9C,EAAE,MAAM,KAAA;AAAA,MAAK;AAAA,IAEjB;AAAA,EAAA;AAEF,MAAI;AAEF,WAAO,MAAM;AACX,YAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,SAAS,KAAA,GAAQ,YAAY,CAAC;AACjE,UAAI,OAAO,KAAM;AACjB,YAAM,OAAO;AAAA,IACf;AAAA,EACF,UAAA;AACE,UAAM,SAAS,SAAA;AAAA,EACjB;AACF;AAkBO,SAAS,UACd,SAK0B;AAC1B,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,MAAM,aAAa;AAG1C,aAAO,QAAQ,UAAU,MAAM,WAAW;AAAA,IAC5C;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"connection-adapters.js","sources":["../../src/connection-adapters.ts"],"sourcesContent":["import { EventType, uiMessagesToWire } from '@tanstack/ai/client'\nimport {\n createResponseStreamTextDecoder,\n getResponseStreamReader,\n} from './response-stream'\nimport { parseSseDataLine } from './sse-utils'\nimport type {\n ModelMessage,\n RunErrorEvent,\n RunFinishedEvent,\n StreamChunk,\n UIMessage,\n} from '@tanstack/ai/client'\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 = createResponseStreamTextDecoder()\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 = getResponseStreamReader(response)\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 = parseSseDataLine(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 * Options for XHR-based connection adapters.\n */\nexport interface XhrConnectionOptions {\n headers?: Record<string, string> | Headers\n withCredentials?: boolean\n signal?: AbortSignal\n body?: Record<string, any>\n xhrFactory?: () => XMLHttpRequest\n}\n\ntype ResolvedConnectionOptions = Pick<\n FetchConnectionOptions,\n 'body' | 'headers'\n>\n\nfunction buildRunAgentInputBody(\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n options: ResolvedConnectionOptions,\n): Record<string, unknown> {\n // Precedence (later spreads win): static adapter `body` is the base,\n // overridden by `runContext.forwardedProps`, overridden by per-message `data`.\n const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)\n const forwardedProps = {\n ...options.body,\n ...(runContext?.forwardedProps ?? {}),\n ...data,\n }\n\n return {\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 field name.\n data: { ...forwardedProps },\n }\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 requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\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 requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n resolvedOptions,\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 = getResponseStreamReader(response)\n\n for await (const line of readStreamLines(reader, abortSignal)) {\n yield JSON.parse(line) as StreamChunk\n }\n },\n }\n}\n\ntype XhrConnectionOptionsResolver =\n | XhrConnectionOptions\n | (() => XhrConnectionOptions | Promise<XhrConnectionOptions>)\n\nfunction createDefaultXMLHttpRequest(): XMLHttpRequest {\n if (typeof globalThis.XMLHttpRequest !== 'function') {\n throw new Error('XMLHttpRequest is not available in this runtime')\n }\n\n return new globalThis.XMLHttpRequest()\n}\n\nfunction cleanupXhr(\n xhr: XMLHttpRequest,\n abortSignal: AbortSignal | undefined,\n onAbort: (() => void) | undefined,\n): void {\n xhr.onprogress = null\n xhr.onload = null\n xhr.onerror = null\n xhr.onabort = null\n xhr.onloadend = null\n\n if (abortSignal && onAbort) {\n abortSignal.removeEventListener('abort', onAbort)\n }\n}\n\nfunction readXhrLines(\n xhr: XMLHttpRequest,\n abortSignal?: AbortSignal,\n): AsyncGenerator<string> {\n let offset = 0\n let buffer = ''\n const lines: Array<string> = []\n const waiters: Array<() => void> = []\n let done = false\n let aborted = false\n let error: unknown\n let onAbort: (() => void) | undefined\n\n const wake = () => {\n const waiter = waiters.shift()\n waiter?.()\n }\n\n const enqueueDelta = () => {\n if (xhr.status !== 0 && (xhr.status < 200 || xhr.status >= 300)) {\n error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)\n done = true\n return\n }\n\n const responseText = xhr.responseText\n if (responseText.length <= offset) {\n return\n }\n\n buffer += responseText.slice(offset)\n offset = responseText.length\n const splitLines = buffer.split('\\n')\n buffer = splitLines.pop() ?? ''\n\n for (const line of splitLines) {\n const normalized = line.endsWith('\\r') ? line.slice(0, -1) : line\n if (normalized.trim()) {\n lines.push(normalized)\n }\n }\n }\n\n const finish = () => {\n enqueueDelta()\n if (xhr.status < 200 || xhr.status >= 300) {\n error = new Error(`XHR error! status: ${xhr.status} ${xhr.statusText}`)\n } else if (buffer.trim() && !aborted) {\n error = new StreamTruncatedError()\n }\n done = true\n wake()\n }\n\n xhr.onprogress = () => {\n enqueueDelta()\n wake()\n }\n xhr.onload = finish\n xhr.onerror = () => {\n error = new Error('XHR request failed')\n done = true\n wake()\n }\n xhr.onabort = () => {\n aborted = true\n done = true\n wake()\n }\n xhr.onloadend = () => {\n if (!done) {\n finish()\n }\n }\n\n if (abortSignal) {\n onAbort = () => {\n aborted = true\n xhr.abort()\n }\n if (abortSignal.aborted) {\n onAbort()\n } else {\n abortSignal.addEventListener('abort', onAbort, { once: true })\n }\n }\n\n return (async function* () {\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const line = lines.shift()\n if (line !== undefined) {\n yield line\n continue\n }\n\n if (error) {\n throw error\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (done || abortSignal?.aborted) {\n return\n }\n\n await new Promise<void>((resolve) => {\n waiters.push(resolve)\n })\n }\n } finally {\n cleanupXhr(xhr, abortSignal, onAbort)\n }\n })()\n}\n\ninterface ConfiguredXhrRequest {\n xhr: XMLHttpRequest\n body: string\n}\n\nfunction createConfiguredXhrRequest(\n url: string,\n options: XhrConnectionOptions,\n messages: Array<UIMessage> | Array<ModelMessage>,\n data: Record<string, any> | undefined,\n runContext: RunAgentInputContext | undefined,\n): ConfiguredXhrRequest {\n const xhr = options.xhrFactory?.() ?? createDefaultXMLHttpRequest()\n xhr.open('POST', url)\n if (options.withCredentials !== undefined) {\n xhr.withCredentials = options.withCredentials\n }\n\n const requestHeaders: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...mergeHeaders(options.headers),\n }\n\n for (const [name, value] of Object.entries(requestHeaders)) {\n xhr.setRequestHeader(name, value)\n }\n\n const requestBody = buildRunAgentInputBody(\n messages,\n data,\n runContext,\n options,\n )\n\n return { xhr, body: JSON.stringify(requestBody) }\n}\n\nasync function resolveXhrConnectionOptions(\n options: XhrConnectionOptionsResolver,\n): Promise<XhrConnectionOptions> {\n return typeof options === 'function' ? await options() : options\n}\n\n/**\n * Create an XMLHttpRequest-backed Server-Sent Events connection adapter.\n */\nexport function xhrServerSentEvents(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const request = createConfiguredXhrRequest(\n resolvedUrl,\n resolvedOptions,\n messages,\n data,\n runContext,\n )\n const lines = readXhrLines(request.xhr, signal)\n if (signal?.aborted) {\n await lines.next()\n return\n }\n request.xhr.send(request.body)\n let lastThreadId: string | undefined\n let lastRunId: string | undefined\n let lastModel: string | undefined\n\n for await (const line of lines) {\n if (\n line.startsWith(':') ||\n line.startsWith('event:') ||\n line.startsWith('id:') ||\n line.startsWith('retry:')\n ) {\n continue\n }\n\n const chunkData = parseSseDataLine(line)\n if (chunkData === '[DONE]') {\n const synthetic: RunFinishedEvent = {\n type: EventType.RUN_FINISHED,\n threadId: lastThreadId ?? runContext?.threadId ?? '',\n runId: lastRunId ?? runContext?.runId ?? '',\n model: lastModel ?? '',\n timestamp: Date.now(),\n finishReason: 'stop',\n }\n request.xhr.abort()\n yield synthetic\n return\n }\n\n const chunk = JSON.parse(chunkData) 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\n/**\n * Create an XMLHttpRequest-backed newline-delimited JSON stream adapter.\n */\nexport function xhrHttpStream(\n url: string | (() => string),\n options: XhrConnectionOptionsResolver = {},\n): ConnectConnectionAdapter {\n return {\n async *connect(messages, data, abortSignal, runContext) {\n const resolvedUrl = typeof url === 'function' ? url() : url\n const resolvedOptions = await resolveXhrConnectionOptions(options)\n const signal = abortSignal || resolvedOptions.signal\n const request = createConfiguredXhrRequest(\n resolvedUrl,\n resolvedOptions,\n messages,\n data,\n runContext,\n )\n const lines = readXhrLines(request.xhr, signal)\n if (signal?.aborted) {\n await lines.next()\n return\n }\n request.xhr.send(request.body)\n\n for await (const line of lines) {\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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"}
@@ -1,6 +1,6 @@
1
1
  import { ChatClientEventEmitter, ChatClientEventContext, ChatClientRunEventContext } from './events.js';
2
2
  import { AIDevtoolsToolFixture, ChatDevtoolsBridge, ChatDevtoolsBridgeOptions, GenerationDevtoolsBridge, GenerationDevtoolsBridgeOptions, VideoDevtoolsBridge, VideoDevtoolsBridgeOptions } from './devtools.js';
3
- import { StreamChunk } from '@tanstack/ai';
3
+ import { StreamChunk } from '@tanstack/ai/client';
4
4
  export type ChatDevtoolsBridgeFactory = (options: ChatDevtoolsBridgeOptions) => ChatDevtoolsBridge;
5
5
  export type GenerationDevtoolsBridgeFactory = <TOutput>(options: GenerationDevtoolsBridgeOptions<TOutput>) => GenerationDevtoolsBridge<TOutput>;
6
6
  export type VideoDevtoolsBridgeFactory = <TOutput>(options: VideoDevtoolsBridgeOptions<TOutput>) => VideoDevtoolsBridge<TOutput>;
@@ -1 +1 @@
1
- {"version":3,"file":"devtools-noop.js","sources":["../../src/devtools-noop.ts"],"sourcesContent":["// No-op devtools bridge implementations + factories. The chat / generation /\n// video clients import the real bridge classes as types only and accept a\n// factory in options; when no factory is supplied they fall back to the\n// no-op factories here, which never touch `aiEventClient` or any of the\n// heavy preview/fixture machinery in `./devtools`. This keeps `./devtools`\n// outside the main-entry import graph — consumers opt into functional\n// devtools via `@tanstack/ai-client/devtools` (see `package.json#exports`).\nimport { ChatClientEventEmitter } from './events'\nimport type {\n AIDevtoolsToolFixture,\n ChatDevtoolsBridge,\n ChatDevtoolsBridgeOptions,\n GenerationDevtoolsBridge,\n GenerationDevtoolsBridgeOptions,\n VideoDevtoolsBridge,\n VideoDevtoolsBridgeOptions,\n} from './devtools'\nimport type { StreamChunk } from '@tanstack/ai'\nimport type {\n ChatClientEventContext,\n ChatClientRunEventContext,\n} from './events'\n\nexport type ChatDevtoolsBridgeFactory = (\n options: ChatDevtoolsBridgeOptions,\n) => ChatDevtoolsBridge\n\nexport type GenerationDevtoolsBridgeFactory = <TOutput>(\n options: GenerationDevtoolsBridgeOptions<TOutput>,\n) => GenerationDevtoolsBridge<TOutput>\n\nexport type VideoDevtoolsBridgeFactory = <TOutput>(\n options: VideoDevtoolsBridgeOptions<TOutput>,\n) => VideoDevtoolsBridge<TOutput>\n\n// ===========================================================================\n// No-op event emitter — extends the abstract base so it satisfies the type\n// without dragging in any of the event-bus runtime cost.\n// ===========================================================================\n\nclass NoOpChatClientEventEmitter extends ChatClientEventEmitter {\n protected emitEvent(): void {\n // intentionally empty\n }\n}\n\n// ===========================================================================\n// No-op bridges. Methods exist to satisfy the structural shape of the real\n// classes; every emit/record call short-circuits.\n// ===========================================================================\n\nexport class NoOpChatDevtoolsBridge {\n readonly events: ChatClientEventEmitter\n\n constructor(options: ChatDevtoolsBridgeOptions) {\n this.events = new NoOpChatClientEventEmitter(options.clientId)\n }\n\n // base bridge surface\n emitRegistered(): void {}\n emitUpdated(): void {}\n emitSnapshot(): void {}\n emitToolsRegistered(): void {}\n emitRunLifecycle(\n _eventType: unknown,\n _runId: string,\n _status: unknown,\n _options?: { error?: string },\n ): void {}\n deactivate(): void {}\n supersede(): void {}\n dispose(): void {}\n\n // chat-specific surface\n setCurrentStreamId(_streamId: string | null): void {}\n getCurrentStreamId(): string | null {\n return null\n }\n getLastStreamId(): string | null {\n return null\n }\n resolveStreamId(): string {\n return ''\n }\n observeChunk(_chunk: StreamChunk): void {}\n beginRun(_runId: string, _threadId: string): void {}\n getCurrentRunEventContext(): ChatClientRunEventContext | undefined {\n return undefined\n }\n getCurrentOrLastRunEventContext(): ChatClientRunEventContext | undefined {\n return undefined\n }\n findToolCallContext(toolCallId: string): ChatClientEventContext {\n return { toolCallId }\n }\n async applyFixture(_fixture: AIDevtoolsToolFixture): Promise<void> {\n // intentionally empty\n }\n}\n\nexport class NoOpGenerationDevtoolsBridge<TOutput> {\n constructor(_options: GenerationDevtoolsBridgeOptions<TOutput>) {}\n\n // base bridge surface\n emitRegistered(): void {}\n emitUpdated(): void {}\n emitSnapshot(): void {}\n emitToolsRegistered(): void {}\n emitRunLifecycle(): void {}\n deactivate(): void {}\n supersede(): void {}\n dispose(): void {}\n\n // generation-specific surface\n beginRun(_input: unknown): string {\n // Real factories supply a stable id; the no-op still returns a\n // unique value because the generation client passes this run id to\n // the adapter's RunAgentInputContext.\n return `noop-run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n }\n ensureRunStarted(_runId: string): void {}\n finishRun(\n _runId: string,\n _eventType: 'run:completed' | 'run:errored' | 'run:cancelled',\n _status: 'completed' | 'errored' | 'cancelled',\n _error?: string,\n ): void {}\n getActiveRunId(): string | null {\n return null\n }\n resetRuns(): void {}\n recordResultChange(): void {}\n recordLoadingChange(): void {}\n recordErrorChange(_error: Error | undefined): void {}\n recordStatusChange(): void {}\n recordProgressChange(): void {}\n emitState(): void {}\n}\n\nexport class NoOpVideoDevtoolsBridge<\n TOutput,\n> extends NoOpGenerationDevtoolsBridge<TOutput> {\n constructor(options: VideoDevtoolsBridgeOptions<TOutput>) {\n super(options)\n }\n\n recordJobIdChange(): void {}\n recordVideoStatusChange(): void {}\n}\n\n// Compile-time parity checks. If a public method is added to the real\n// bridge class without a matching stub on the no-op, the corresponding\n// `Exclude<...>` will resolve to a non-`never` union and the `as never`\n// assignment below will fail to typecheck — surfacing the drift at build\n// time instead of as a runtime TypeError later.\ntype _ChatBridgeMissing = Exclude<\n keyof ChatDevtoolsBridge,\n keyof NoOpChatDevtoolsBridge\n>\ntype _GenerationBridgeMissing = Exclude<\n keyof GenerationDevtoolsBridge<unknown>,\n keyof NoOpGenerationDevtoolsBridge<unknown>\n>\ntype _VideoBridgeMissing = Exclude<\n keyof VideoDevtoolsBridge<unknown>,\n keyof NoOpVideoDevtoolsBridge<unknown>\n>\nconst _chatBridgeParity: _ChatBridgeMissing = undefined as never\nconst _generationBridgeParity: _GenerationBridgeMissing = undefined as never\nconst _videoBridgeParity: _VideoBridgeMissing = undefined as never\nvoid _chatBridgeParity\nvoid _generationBridgeParity\nvoid _videoBridgeParity\n\n// ===========================================================================\n// Factories — these are what the clients call when no real factory was\n// supplied in options.\n// ===========================================================================\n\n// Casts use `unknown` because the no-op classes don't `extend` the real bridge\n// (that would pull the real implementation into the main-entry import graph).\n// Structural parity is enforced by the `_*BridgeMissing` checks above.\n\nexport const createNoOpChatDevtoolsBridge: ChatDevtoolsBridgeFactory = (\n options,\n) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpChatDevtoolsBridge(options) as unknown as ChatDevtoolsBridge\n\nexport const createNoOpGenerationDevtoolsBridge: GenerationDevtoolsBridgeFactory =\n <TOutput>(options: GenerationDevtoolsBridgeOptions<TOutput>) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpGenerationDevtoolsBridge<TOutput>(\n options,\n ) as unknown as GenerationDevtoolsBridge<TOutput>\n\nexport const createNoOpVideoDevtoolsBridge: VideoDevtoolsBridgeFactory = <\n TOutput,\n>(\n options: VideoDevtoolsBridgeOptions<TOutput>,\n) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpVideoDevtoolsBridge<TOutput>(\n options,\n ) as unknown as VideoDevtoolsBridge<TOutput>\n"],"names":[],"mappings":";AAwCA,MAAM,mCAAmC,uBAAuB;AAAA,EACpD,YAAkB;AAAA,EAE5B;AACF;AAOO,MAAM,uBAAuB;AAAA,EACzB;AAAA,EAET,YAAY,SAAoC;AAC9C,SAAK,SAAS,IAAI,2BAA2B,QAAQ,QAAQ;AAAA,EAC/D;AAAA;AAAA,EAGA,iBAAuB;AAAA,EAAC;AAAA,EACxB,cAAoB;AAAA,EAAC;AAAA,EACrB,eAAqB;AAAA,EAAC;AAAA,EACtB,sBAA4B;AAAA,EAAC;AAAA,EAC7B,iBACE,YACA,QACA,SACA,UACM;AAAA,EAAC;AAAA,EACT,aAAmB;AAAA,EAAC;AAAA,EACpB,YAAkB;AAAA,EAAC;AAAA,EACnB,UAAgB;AAAA,EAAC;AAAA;AAAA,EAGjB,mBAAmB,WAAgC;AAAA,EAAC;AAAA,EACpD,qBAAoC;AAClC,WAAO;AAAA,EACT;AAAA,EACA,kBAAiC;AAC/B,WAAO;AAAA,EACT;AAAA,EACA,kBAA0B;AACxB,WAAO;AAAA,EACT;AAAA,EACA,aAAa,QAA2B;AAAA,EAAC;AAAA,EACzC,SAAS,QAAgB,WAAyB;AAAA,EAAC;AAAA,EACnD,4BAAmE;AACjE,WAAO;AAAA,EACT;AAAA,EACA,kCAAyE;AACvE,WAAO;AAAA,EACT;AAAA,EACA,oBAAoB,YAA4C;AAC9D,WAAO,EAAE,WAAA;AAAA,EACX;AAAA,EACA,MAAM,aAAa,UAAgD;AAAA,EAEnE;AACF;AAEO,MAAM,6BAAsC;AAAA,EACjD,YAAY,UAAoD;AAAA,EAAC;AAAA;AAAA,EAGjE,iBAAuB;AAAA,EAAC;AAAA,EACxB,cAAoB;AAAA,EAAC;AAAA,EACrB,eAAqB;AAAA,EAAC;AAAA,EACtB,sBAA4B;AAAA,EAAC;AAAA,EAC7B,mBAAyB;AAAA,EAAC;AAAA,EAC1B,aAAmB;AAAA,EAAC;AAAA,EACpB,YAAkB;AAAA,EAAC;AAAA,EACnB,UAAgB;AAAA,EAAC;AAAA;AAAA,EAGjB,SAAS,QAAyB;AAIhC,WAAO,YAAY,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,EACzE;AAAA,EACA,iBAAiB,QAAsB;AAAA,EAAC;AAAA,EACxC,UACE,QACA,YACA,SACA,QACM;AAAA,EAAC;AAAA,EACT,iBAAgC;AAC9B,WAAO;AAAA,EACT;AAAA,EACA,YAAkB;AAAA,EAAC;AAAA,EACnB,qBAA2B;AAAA,EAAC;AAAA,EAC5B,sBAA4B;AAAA,EAAC;AAAA,EAC7B,kBAAkB,QAAiC;AAAA,EAAC;AAAA,EACpD,qBAA2B;AAAA,EAAC;AAAA,EAC5B,uBAA6B;AAAA,EAAC;AAAA,EAC9B,YAAkB;AAAA,EAAC;AACrB;AAEO,MAAM,gCAEH,6BAAsC;AAAA,EAC9C,YAAY,SAA8C;AACxD,UAAM,OAAO;AAAA,EACf;AAAA,EAEA,oBAA0B;AAAA,EAAC;AAAA,EAC3B,0BAAgC;AAAA,EAAC;AACnC;AAmCO,MAAM,+BAA0D,CACrE;AAAA;AAAA,EAGA,IAAI,uBAAuB,OAAO;AAAA;AAE7B,MAAM,qCACX,CAAU;AAAA;AAAA,EAER,IAAI;AAAA,IACF;AAAA,EAAA;AAAA;AAGC,MAAM,gCAA4D,CAGvE;AAAA;AAAA,EAGA,IAAI;AAAA,IACF;AAAA,EAAA;AAAA;"}
1
+ {"version":3,"file":"devtools-noop.js","sources":["../../src/devtools-noop.ts"],"sourcesContent":["// No-op devtools bridge implementations + factories. The chat / generation /\n// video clients import the real bridge classes as types only and accept a\n// factory in options; when no factory is supplied they fall back to the\n// no-op factories here, which never touch `aiEventClient` or any of the\n// heavy preview/fixture machinery in `./devtools`. This keeps `./devtools`\n// outside the main-entry import graph — consumers opt into functional\n// devtools via `@tanstack/ai-client/devtools` (see `package.json#exports`).\nimport { ChatClientEventEmitter } from './events'\nimport type {\n AIDevtoolsToolFixture,\n ChatDevtoolsBridge,\n ChatDevtoolsBridgeOptions,\n GenerationDevtoolsBridge,\n GenerationDevtoolsBridgeOptions,\n VideoDevtoolsBridge,\n VideoDevtoolsBridgeOptions,\n} from './devtools'\nimport type { StreamChunk } from '@tanstack/ai/client'\nimport type {\n ChatClientEventContext,\n ChatClientRunEventContext,\n} from './events'\n\nexport type ChatDevtoolsBridgeFactory = (\n options: ChatDevtoolsBridgeOptions,\n) => ChatDevtoolsBridge\n\nexport type GenerationDevtoolsBridgeFactory = <TOutput>(\n options: GenerationDevtoolsBridgeOptions<TOutput>,\n) => GenerationDevtoolsBridge<TOutput>\n\nexport type VideoDevtoolsBridgeFactory = <TOutput>(\n options: VideoDevtoolsBridgeOptions<TOutput>,\n) => VideoDevtoolsBridge<TOutput>\n\n// ===========================================================================\n// No-op event emitter — extends the abstract base so it satisfies the type\n// without dragging in any of the event-bus runtime cost.\n// ===========================================================================\n\nclass NoOpChatClientEventEmitter extends ChatClientEventEmitter {\n protected emitEvent(): void {\n // intentionally empty\n }\n}\n\n// ===========================================================================\n// No-op bridges. Methods exist to satisfy the structural shape of the real\n// classes; every emit/record call short-circuits.\n// ===========================================================================\n\nexport class NoOpChatDevtoolsBridge {\n readonly events: ChatClientEventEmitter\n\n constructor(options: ChatDevtoolsBridgeOptions) {\n this.events = new NoOpChatClientEventEmitter(options.clientId)\n }\n\n // base bridge surface\n emitRegistered(): void {}\n emitUpdated(): void {}\n emitSnapshot(): void {}\n emitToolsRegistered(): void {}\n emitRunLifecycle(\n _eventType: unknown,\n _runId: string,\n _status: unknown,\n _options?: { error?: string },\n ): void {}\n deactivate(): void {}\n supersede(): void {}\n dispose(): void {}\n\n // chat-specific surface\n setCurrentStreamId(_streamId: string | null): void {}\n getCurrentStreamId(): string | null {\n return null\n }\n getLastStreamId(): string | null {\n return null\n }\n resolveStreamId(): string {\n return ''\n }\n observeChunk(_chunk: StreamChunk): void {}\n beginRun(_runId: string, _threadId: string): void {}\n getCurrentRunEventContext(): ChatClientRunEventContext | undefined {\n return undefined\n }\n getCurrentOrLastRunEventContext(): ChatClientRunEventContext | undefined {\n return undefined\n }\n findToolCallContext(toolCallId: string): ChatClientEventContext {\n return { toolCallId }\n }\n async applyFixture(_fixture: AIDevtoolsToolFixture): Promise<void> {\n // intentionally empty\n }\n}\n\nexport class NoOpGenerationDevtoolsBridge<TOutput> {\n constructor(_options: GenerationDevtoolsBridgeOptions<TOutput>) {}\n\n // base bridge surface\n emitRegistered(): void {}\n emitUpdated(): void {}\n emitSnapshot(): void {}\n emitToolsRegistered(): void {}\n emitRunLifecycle(): void {}\n deactivate(): void {}\n supersede(): void {}\n dispose(): void {}\n\n // generation-specific surface\n beginRun(_input: unknown): string {\n // Real factories supply a stable id; the no-op still returns a\n // unique value because the generation client passes this run id to\n // the adapter's RunAgentInputContext.\n return `noop-run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n }\n ensureRunStarted(_runId: string): void {}\n finishRun(\n _runId: string,\n _eventType: 'run:completed' | 'run:errored' | 'run:cancelled',\n _status: 'completed' | 'errored' | 'cancelled',\n _error?: string,\n ): void {}\n getActiveRunId(): string | null {\n return null\n }\n resetRuns(): void {}\n recordResultChange(): void {}\n recordLoadingChange(): void {}\n recordErrorChange(_error: Error | undefined): void {}\n recordStatusChange(): void {}\n recordProgressChange(): void {}\n emitState(): void {}\n}\n\nexport class NoOpVideoDevtoolsBridge<\n TOutput,\n> extends NoOpGenerationDevtoolsBridge<TOutput> {\n constructor(options: VideoDevtoolsBridgeOptions<TOutput>) {\n super(options)\n }\n\n recordJobIdChange(): void {}\n recordVideoStatusChange(): void {}\n}\n\n// Compile-time parity checks. If a public method is added to the real\n// bridge class without a matching stub on the no-op, the corresponding\n// `Exclude<...>` will resolve to a non-`never` union and the `as never`\n// assignment below will fail to typecheck — surfacing the drift at build\n// time instead of as a runtime TypeError later.\ntype _ChatBridgeMissing = Exclude<\n keyof ChatDevtoolsBridge,\n keyof NoOpChatDevtoolsBridge\n>\ntype _GenerationBridgeMissing = Exclude<\n keyof GenerationDevtoolsBridge<unknown>,\n keyof NoOpGenerationDevtoolsBridge<unknown>\n>\ntype _VideoBridgeMissing = Exclude<\n keyof VideoDevtoolsBridge<unknown>,\n keyof NoOpVideoDevtoolsBridge<unknown>\n>\nconst _chatBridgeParity: _ChatBridgeMissing = undefined as never\nconst _generationBridgeParity: _GenerationBridgeMissing = undefined as never\nconst _videoBridgeParity: _VideoBridgeMissing = undefined as never\nvoid _chatBridgeParity\nvoid _generationBridgeParity\nvoid _videoBridgeParity\n\n// ===========================================================================\n// Factories — these are what the clients call when no real factory was\n// supplied in options.\n// ===========================================================================\n\n// Casts use `unknown` because the no-op classes don't `extend` the real bridge\n// (that would pull the real implementation into the main-entry import graph).\n// Structural parity is enforced by the `_*BridgeMissing` checks above.\n\nexport const createNoOpChatDevtoolsBridge: ChatDevtoolsBridgeFactory = (\n options,\n) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpChatDevtoolsBridge(options) as unknown as ChatDevtoolsBridge\n\nexport const createNoOpGenerationDevtoolsBridge: GenerationDevtoolsBridgeFactory =\n <TOutput>(options: GenerationDevtoolsBridgeOptions<TOutput>) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpGenerationDevtoolsBridge<TOutput>(\n options,\n ) as unknown as GenerationDevtoolsBridge<TOutput>\n\nexport const createNoOpVideoDevtoolsBridge: VideoDevtoolsBridgeFactory = <\n TOutput,\n>(\n options: VideoDevtoolsBridgeOptions<TOutput>,\n) =>\n // eslint-disable-next-line no-restricted-syntax -- see comment above\n new NoOpVideoDevtoolsBridge<TOutput>(\n options,\n ) as unknown as VideoDevtoolsBridge<TOutput>\n"],"names":[],"mappings":";AAwCA,MAAM,mCAAmC,uBAAuB;AAAA,EACpD,YAAkB;AAAA,EAE5B;AACF;AAOO,MAAM,uBAAuB;AAAA,EACzB;AAAA,EAET,YAAY,SAAoC;AAC9C,SAAK,SAAS,IAAI,2BAA2B,QAAQ,QAAQ;AAAA,EAC/D;AAAA;AAAA,EAGA,iBAAuB;AAAA,EAAC;AAAA,EACxB,cAAoB;AAAA,EAAC;AAAA,EACrB,eAAqB;AAAA,EAAC;AAAA,EACtB,sBAA4B;AAAA,EAAC;AAAA,EAC7B,iBACE,YACA,QACA,SACA,UACM;AAAA,EAAC;AAAA,EACT,aAAmB;AAAA,EAAC;AAAA,EACpB,YAAkB;AAAA,EAAC;AAAA,EACnB,UAAgB;AAAA,EAAC;AAAA;AAAA,EAGjB,mBAAmB,WAAgC;AAAA,EAAC;AAAA,EACpD,qBAAoC;AAClC,WAAO;AAAA,EACT;AAAA,EACA,kBAAiC;AAC/B,WAAO;AAAA,EACT;AAAA,EACA,kBAA0B;AACxB,WAAO;AAAA,EACT;AAAA,EACA,aAAa,QAA2B;AAAA,EAAC;AAAA,EACzC,SAAS,QAAgB,WAAyB;AAAA,EAAC;AAAA,EACnD,4BAAmE;AACjE,WAAO;AAAA,EACT;AAAA,EACA,kCAAyE;AACvE,WAAO;AAAA,EACT;AAAA,EACA,oBAAoB,YAA4C;AAC9D,WAAO,EAAE,WAAA;AAAA,EACX;AAAA,EACA,MAAM,aAAa,UAAgD;AAAA,EAEnE;AACF;AAEO,MAAM,6BAAsC;AAAA,EACjD,YAAY,UAAoD;AAAA,EAAC;AAAA;AAAA,EAGjE,iBAAuB;AAAA,EAAC;AAAA,EACxB,cAAoB;AAAA,EAAC;AAAA,EACrB,eAAqB;AAAA,EAAC;AAAA,EACtB,sBAA4B;AAAA,EAAC;AAAA,EAC7B,mBAAyB;AAAA,EAAC;AAAA,EAC1B,aAAmB;AAAA,EAAC;AAAA,EACpB,YAAkB;AAAA,EAAC;AAAA,EACnB,UAAgB;AAAA,EAAC;AAAA;AAAA,EAGjB,SAAS,QAAyB;AAIhC,WAAO,YAAY,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAAA,EACzE;AAAA,EACA,iBAAiB,QAAsB;AAAA,EAAC;AAAA,EACxC,UACE,QACA,YACA,SACA,QACM;AAAA,EAAC;AAAA,EACT,iBAAgC;AAC9B,WAAO;AAAA,EACT;AAAA,EACA,YAAkB;AAAA,EAAC;AAAA,EACnB,qBAA2B;AAAA,EAAC;AAAA,EAC5B,sBAA4B;AAAA,EAAC;AAAA,EAC7B,kBAAkB,QAAiC;AAAA,EAAC;AAAA,EACpD,qBAA2B;AAAA,EAAC;AAAA,EAC5B,uBAA6B;AAAA,EAAC;AAAA,EAC9B,YAAkB;AAAA,EAAC;AACrB;AAEO,MAAM,gCAEH,6BAAsC;AAAA,EAC9C,YAAY,SAA8C;AACxD,UAAM,OAAO;AAAA,EACf;AAAA,EAEA,oBAA0B;AAAA,EAAC;AAAA,EAC3B,0BAAgC;AAAA,EAAC;AACnC;AAmCO,MAAM,+BAA0D,CACrE;AAAA;AAAA,EAGA,IAAI,uBAAuB,OAAO;AAAA;AAE7B,MAAM,qCACX,CAAU;AAAA;AAAA,EAER,IAAI;AAAA,IACF;AAAA,EAAA;AAAA;AAGC,MAAM,gCAA4D,CAGvE;AAAA;AAAA,EAGA,IAAI;AAAA,IACF;AAAA,EAAA;AAAA;"}
@@ -1,4 +1,4 @@
1
- import { AnyClientTool, StreamChunk } from '@tanstack/ai';
1
+ import { AnyClientTool, StreamChunk } from '@tanstack/ai/client';
2
2
  import { ChatClientEventContext, ChatClientEventEmitter, ChatClientRunEventContext } from './events.js';
3
3
  import { ChatClientState, ConnectionStatus, UIMessage } from './types.js';
4
4
  export interface AIDevtoolsDisplayOptions {
@@ -1,5 +1,5 @@
1
1
  import { emitAIDevtoolsEvent, aiEventClient, createAIDevtoolsEventEnvelope } from "@tanstack/ai-event-client";
2
- import { convertSchemaToJsonSchema } from "@tanstack/ai";
2
+ import { convertSchemaToJsonSchema } from "@tanstack/ai/client";
3
3
  import { DefaultChatClientEventEmitter } from "./events.js";
4
4
  function createAIDevtoolsGenerationPreview(input) {
5
5
  if (input.outputKind === "image") {