@tanstack/ai-client 0.15.2 → 0.16.2

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.
@@ -0,0 +1,86 @@
1
+ import { StreamChunk } from '@tanstack/ai/client';
2
+ import { ChatClientPersistence, UIMessage } from './types.js';
3
+ /**
4
+ * Encapsulates everything persistence-related for `ChatClient` so the client
5
+ * itself stays focused on streaming and message state.
6
+ *
7
+ * Two responsibilities live here:
8
+ *
9
+ * 1. **Storage orchestration** — hydrate from `getItem(id)` on creation, save to
10
+ * `setItem(id, messages)` on every change through an ordered write queue, and
11
+ * `removeItem(id)` on clear. A generation counter discards stale writes when a
12
+ * removal or a newer conversation supersedes an in-flight async operation.
13
+ * 2. **Clear-during-stream suppression** — when a conversation is cleared while a
14
+ * stream is still producing, late chunks for the cleared run(s) must not
15
+ * repopulate the now-empty state. The persistor tracks the cleared ids and
16
+ * decides, per chunk, whether the client should ignore it.
17
+ *
18
+ * All adapter calls are best-effort: a throwing or rejecting adapter is swallowed
19
+ * so storage problems never break the chat.
20
+ */
21
+ export declare class ChatPersistor {
22
+ private readonly adapter;
23
+ private readonly id;
24
+ private readonly applyMessages;
25
+ private skipNextPersist;
26
+ private generation;
27
+ private queue;
28
+ private queuePending;
29
+ private messagesGeneration;
30
+ private readonly clearedMessageIds;
31
+ private readonly clearedRunIds;
32
+ private readonly ignoredActiveRunIds;
33
+ private readonly clearedToolCallIds;
34
+ private currentRunlessRunId;
35
+ constructor(adapter: ChatClientPersistence, id: string, applyMessages: (messages: Array<UIMessage>) => void);
36
+ /**
37
+ * Synchronously read the persisted messages for constructor-time hydration.
38
+ * Returns the raw `getItem` result (which may be a promise for async stores).
39
+ */
40
+ readInitial(): Array<UIMessage> | null | undefined | Promise<Array<UIMessage> | null | undefined>;
41
+ /**
42
+ * Apply messages from an async `getItem` once it resolves, unless the message
43
+ * list has already changed since hydration began.
44
+ */
45
+ hydrateAsync(persistedMessages: Array<UIMessage> | null | undefined | Promise<Array<UIMessage> | null | undefined>): void;
46
+ /**
47
+ * Record a message-list change and queue a `setItem` write for it. Skips a
48
+ * single write after {@link beginClear} so the clear's empty snapshot isn't
49
+ * persisted between `clearMessages()` and {@link remove}.
50
+ */
51
+ notifyMessagesChanged(messages: Array<UIMessage>): void;
52
+ /** Remove the persisted conversation. Invalidates any queued writes. */
53
+ remove(): void;
54
+ private runOperation;
55
+ /**
56
+ * Capture the message/run ids that exist at the moment of a clear so chunks
57
+ * still arriving for them can be ignored.
58
+ */
59
+ snapshotClear(context: {
60
+ messages: Array<UIMessage>;
61
+ activeRunIds: Set<string>;
62
+ currentRunId: string | null;
63
+ }): void;
64
+ /** Mark that the next persisted message change (the clear itself) is skipped. */
65
+ beginClear(): void;
66
+ /** Whether a chunk belongs to cleared state and should not be processed. */
67
+ shouldIgnoreChunk(chunk: StreamChunk): boolean;
68
+ /**
69
+ * The owning client calls this when a run starts so runless content chunks
70
+ * (adapters that omit `runId` on content events) can be attributed to it.
71
+ */
72
+ onRunStarted(runId: string): void;
73
+ /** Forget a settled run, advancing the runless pointer to another ignored run. */
74
+ onRunSettled(runId: string): void;
75
+ /** A session-level (runId-less) RUN_ERROR clears all ignored-run tracking. */
76
+ onSessionRunError(): void;
77
+ /** Clear the ignored-active-run markers (mirrors a session-generating reset). */
78
+ resetIgnored(): void;
79
+ /**
80
+ * Consume the current runless run id (if any), forgetting it. Used when an
81
+ * ignored, runId-less RUN_ERROR drains the run the client is still tracking.
82
+ */
83
+ takeRunlessRunId(): string | null;
84
+ private markIgnoredChunkIds;
85
+ private isRunlessChunkFromIgnoredRun;
86
+ }
@@ -0,0 +1,243 @@
1
+ import { getChunkRunId } from "./connection-adapters.js";
2
+ function getChunkToolCallId(chunk) {
3
+ return "toolCallId" in chunk && typeof chunk.toolCallId === "string" ? chunk.toolCallId : void 0;
4
+ }
5
+ function getChunkMessageId(chunk) {
6
+ return "messageId" in chunk && typeof chunk.messageId === "string" ? chunk.messageId : void 0;
7
+ }
8
+ function getChunkParentMessageId(chunk) {
9
+ return "parentMessageId" in chunk && typeof chunk.parentMessageId === "string" ? chunk.parentMessageId : void 0;
10
+ }
11
+ class ChatPersistor {
12
+ constructor(adapter, id, applyMessages) {
13
+ this.adapter = adapter;
14
+ this.id = id;
15
+ this.applyMessages = applyMessages;
16
+ }
17
+ adapter;
18
+ id;
19
+ applyMessages;
20
+ // --- storage queue state ---
21
+ skipNextPersist = false;
22
+ generation = 0;
23
+ queue = Promise.resolve();
24
+ queuePending = false;
25
+ // Bumped on every message change; lets an in-flight async hydration detect
26
+ // that the message list moved on and avoid clobbering it.
27
+ messagesGeneration = 0;
28
+ // --- clear-during-stream suppression state ---
29
+ clearedMessageIds = /* @__PURE__ */ new Set();
30
+ clearedRunIds = /* @__PURE__ */ new Set();
31
+ ignoredActiveRunIds = /* @__PURE__ */ new Set();
32
+ clearedToolCallIds = /* @__PURE__ */ new Set();
33
+ currentRunlessRunId = null;
34
+ // ---------------------------------------------------------------------------
35
+ // Storage orchestration
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Synchronously read the persisted messages for constructor-time hydration.
39
+ * Returns the raw `getItem` result (which may be a promise for async stores).
40
+ */
41
+ readInitial() {
42
+ try {
43
+ return this.adapter.getItem(this.id);
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ /**
49
+ * Apply messages from an async `getItem` once it resolves, unless the message
50
+ * list has already changed since hydration began.
51
+ */
52
+ hydrateAsync(persistedMessages) {
53
+ if (!(persistedMessages instanceof Promise)) {
54
+ return;
55
+ }
56
+ const hydrationGeneration = this.messagesGeneration;
57
+ persistedMessages.then((messages) => {
58
+ if (Array.isArray(messages) && this.messagesGeneration === hydrationGeneration) {
59
+ this.applyMessages(messages);
60
+ }
61
+ }).catch(() => {
62
+ });
63
+ }
64
+ /**
65
+ * Record a message-list change and queue a `setItem` write for it. Skips a
66
+ * single write after {@link beginClear} so the clear's empty snapshot isn't
67
+ * persisted between `clearMessages()` and {@link remove}.
68
+ */
69
+ notifyMessagesChanged(messages) {
70
+ this.messagesGeneration++;
71
+ if (this.skipNextPersist) {
72
+ this.skipNextPersist = false;
73
+ return;
74
+ }
75
+ const generation = this.generation;
76
+ const messagesSnapshot = [...messages];
77
+ this.runOperation(() => {
78
+ if (generation !== this.generation) {
79
+ return;
80
+ }
81
+ return this.adapter.setItem(this.id, messagesSnapshot);
82
+ });
83
+ }
84
+ /** Remove the persisted conversation. Invalidates any queued writes. */
85
+ remove() {
86
+ const generation = ++this.generation;
87
+ this.runOperation(() => {
88
+ if (generation !== this.generation) {
89
+ return;
90
+ }
91
+ return this.adapter.removeItem(this.id);
92
+ });
93
+ }
94
+ runOperation(operation) {
95
+ if (this.queuePending) {
96
+ const queued = this.queue.then(operation).catch(() => {
97
+ });
98
+ this.queue = queued;
99
+ void queued.finally(() => {
100
+ if (this.queue === queued) {
101
+ this.queuePending = false;
102
+ }
103
+ });
104
+ return;
105
+ }
106
+ try {
107
+ const result = operation();
108
+ if (result instanceof Promise) {
109
+ this.queuePending = true;
110
+ const queued = result.catch(() => {
111
+ });
112
+ this.queue = queued;
113
+ void queued.finally(() => {
114
+ if (this.queue === queued) {
115
+ this.queuePending = false;
116
+ }
117
+ });
118
+ }
119
+ } catch {
120
+ }
121
+ }
122
+ // ---------------------------------------------------------------------------
123
+ // Clear-during-stream suppression
124
+ // ---------------------------------------------------------------------------
125
+ /**
126
+ * Capture the message/run ids that exist at the moment of a clear so chunks
127
+ * still arriving for them can be ignored.
128
+ */
129
+ snapshotClear(context) {
130
+ for (const message of context.messages) {
131
+ this.clearedMessageIds.add(message.id);
132
+ }
133
+ for (const runId of context.activeRunIds) {
134
+ this.clearedRunIds.add(runId);
135
+ this.ignoredActiveRunIds.add(runId);
136
+ }
137
+ if (context.currentRunId) {
138
+ this.clearedRunIds.add(context.currentRunId);
139
+ this.ignoredActiveRunIds.add(context.currentRunId);
140
+ }
141
+ }
142
+ /** Mark that the next persisted message change (the clear itself) is skipped. */
143
+ beginClear() {
144
+ this.skipNextPersist = true;
145
+ }
146
+ /** Whether a chunk belongs to cleared state and should not be processed. */
147
+ shouldIgnoreChunk(chunk) {
148
+ const runId = getChunkRunId(chunk);
149
+ if (runId && this.clearedRunIds.has(runId)) {
150
+ if (chunk.type === "RUN_STARTED") {
151
+ this.ignoredActiveRunIds.add(runId);
152
+ this.currentRunlessRunId = runId;
153
+ }
154
+ this.markIgnoredChunkIds(chunk);
155
+ return true;
156
+ }
157
+ if (runId && this.ignoredActiveRunIds.has(runId)) {
158
+ this.markIgnoredChunkIds(chunk);
159
+ return true;
160
+ }
161
+ if (this.isRunlessChunkFromIgnoredRun(chunk)) {
162
+ this.markIgnoredChunkIds(chunk);
163
+ return true;
164
+ }
165
+ const toolCallId = getChunkToolCallId(chunk);
166
+ if (toolCallId && this.clearedToolCallIds.has(toolCallId)) {
167
+ return true;
168
+ }
169
+ const parentMessageId = getChunkParentMessageId(chunk);
170
+ if (parentMessageId && this.clearedMessageIds.has(parentMessageId)) {
171
+ if (toolCallId) {
172
+ this.clearedToolCallIds.add(toolCallId);
173
+ }
174
+ return true;
175
+ }
176
+ const messageId = getChunkMessageId(chunk);
177
+ if (!messageId) {
178
+ return false;
179
+ }
180
+ if (this.clearedMessageIds.has(messageId)) {
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+ /**
186
+ * The owning client calls this when a run starts so runless content chunks
187
+ * (adapters that omit `runId` on content events) can be attributed to it.
188
+ */
189
+ onRunStarted(runId) {
190
+ this.currentRunlessRunId = runId;
191
+ }
192
+ /** Forget a settled run, advancing the runless pointer to another ignored run. */
193
+ onRunSettled(runId) {
194
+ this.ignoredActiveRunIds.delete(runId);
195
+ this.clearedRunIds.delete(runId);
196
+ if (this.currentRunlessRunId === runId) {
197
+ this.currentRunlessRunId = this.ignoredActiveRunIds.values().next().value ?? null;
198
+ }
199
+ }
200
+ /** A session-level (runId-less) RUN_ERROR clears all ignored-run tracking. */
201
+ onSessionRunError() {
202
+ this.ignoredActiveRunIds.clear();
203
+ this.currentRunlessRunId = null;
204
+ }
205
+ /** Clear the ignored-active-run markers (mirrors a session-generating reset). */
206
+ resetIgnored() {
207
+ this.ignoredActiveRunIds.clear();
208
+ }
209
+ /**
210
+ * Consume the current runless run id (if any), forgetting it. Used when an
211
+ * ignored, runId-less RUN_ERROR drains the run the client is still tracking.
212
+ */
213
+ takeRunlessRunId() {
214
+ const runId = this.currentRunlessRunId;
215
+ if (!runId) return null;
216
+ this.ignoredActiveRunIds.delete(runId);
217
+ this.clearedRunIds.delete(runId);
218
+ this.currentRunlessRunId = this.ignoredActiveRunIds.values().next().value ?? null;
219
+ return runId;
220
+ }
221
+ markIgnoredChunkIds(chunk) {
222
+ const messageId = getChunkMessageId(chunk);
223
+ if (messageId) {
224
+ this.clearedMessageIds.add(messageId);
225
+ }
226
+ const toolCallId = getChunkToolCallId(chunk);
227
+ if (toolCallId) {
228
+ this.clearedToolCallIds.add(toolCallId);
229
+ }
230
+ }
231
+ isRunlessChunkFromIgnoredRun(chunk) {
232
+ const runId = getChunkRunId(chunk);
233
+ if (runId || !this.currentRunlessRunId) return false;
234
+ if (!this.ignoredActiveRunIds.has(this.currentRunlessRunId) && !this.clearedRunIds.has(this.currentRunlessRunId)) {
235
+ return false;
236
+ }
237
+ return chunk.type === "TEXT_MESSAGE_START" || chunk.type === "TEXT_MESSAGE_CONTENT" || chunk.type === "TOOL_CALL_START" || chunk.type === "TOOL_CALL_ARGS" || chunk.type === "TOOL_CALL_END" || chunk.type === "TOOL_CALL_RESULT" || chunk.type === "MESSAGES_SNAPSHOT" || chunk.type === "RUN_ERROR";
238
+ }
239
+ }
240
+ export {
241
+ ChatPersistor
242
+ };
243
+ //# sourceMappingURL=client-persistor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-persistor.js","sources":["../../src/client-persistor.ts"],"sourcesContent":["import { getChunkRunId } from './connection-adapters'\nimport type { StreamChunk } from '@tanstack/ai/client'\nimport type { ChatClientPersistence, UIMessage } from './types'\n\n// `StreamChunk` is a discriminated union; `toolCallId` / `messageId` /\n// `parentMessageId` exist on only some members. Narrow with `in` (matching\n// `getChunkRunId`) instead of asserting a shape, so the field's real type is\n// preserved and a protocol rename can't be read past silently.\nfunction getChunkToolCallId(chunk: StreamChunk): string | undefined {\n return 'toolCallId' in chunk && typeof chunk.toolCallId === 'string'\n ? chunk.toolCallId\n : undefined\n}\n\nfunction getChunkMessageId(chunk: StreamChunk): string | undefined {\n return 'messageId' in chunk && typeof chunk.messageId === 'string'\n ? chunk.messageId\n : undefined\n}\n\nfunction getChunkParentMessageId(chunk: StreamChunk): string | undefined {\n return 'parentMessageId' in chunk && typeof chunk.parentMessageId === 'string'\n ? chunk.parentMessageId\n : undefined\n}\n\n/**\n * Encapsulates everything persistence-related for `ChatClient` so the client\n * itself stays focused on streaming and message state.\n *\n * Two responsibilities live here:\n *\n * 1. **Storage orchestration** — hydrate from `getItem(id)` on creation, save to\n * `setItem(id, messages)` on every change through an ordered write queue, and\n * `removeItem(id)` on clear. A generation counter discards stale writes when a\n * removal or a newer conversation supersedes an in-flight async operation.\n * 2. **Clear-during-stream suppression** — when a conversation is cleared while a\n * stream is still producing, late chunks for the cleared run(s) must not\n * repopulate the now-empty state. The persistor tracks the cleared ids and\n * decides, per chunk, whether the client should ignore it.\n *\n * All adapter calls are best-effort: a throwing or rejecting adapter is swallowed\n * so storage problems never break the chat.\n */\nexport class ChatPersistor {\n // --- storage queue state ---\n private skipNextPersist = false\n private generation = 0\n private queue: Promise<void> = Promise.resolve()\n private queuePending = false\n // Bumped on every message change; lets an in-flight async hydration detect\n // that the message list moved on and avoid clobbering it.\n private messagesGeneration = 0\n\n // --- clear-during-stream suppression state ---\n private readonly clearedMessageIds = new Set<string>()\n private readonly clearedRunIds = new Set<string>()\n private readonly ignoredActiveRunIds = new Set<string>()\n private readonly clearedToolCallIds = new Set<string>()\n private currentRunlessRunId: string | null = null\n\n constructor(\n private readonly adapter: ChatClientPersistence,\n private readonly id: string,\n private readonly applyMessages: (messages: Array<UIMessage>) => void,\n ) {}\n\n // ---------------------------------------------------------------------------\n // Storage orchestration\n // ---------------------------------------------------------------------------\n\n /**\n * Synchronously read the persisted messages for constructor-time hydration.\n * Returns the raw `getItem` result (which may be a promise for async stores).\n */\n readInitial():\n | Array<UIMessage>\n | null\n | undefined\n | Promise<Array<UIMessage> | null | undefined> {\n try {\n return this.adapter.getItem(this.id)\n } catch {\n return undefined\n }\n }\n\n /**\n * Apply messages from an async `getItem` once it resolves, unless the message\n * list has already changed since hydration began.\n */\n hydrateAsync(\n persistedMessages:\n | Array<UIMessage>\n | null\n | undefined\n | Promise<Array<UIMessage> | null | undefined>,\n ): void {\n if (!(persistedMessages instanceof Promise)) {\n return\n }\n\n const hydrationGeneration = this.messagesGeneration\n persistedMessages\n .then((messages) => {\n if (\n Array.isArray(messages) &&\n this.messagesGeneration === hydrationGeneration\n ) {\n this.applyMessages(messages)\n }\n })\n .catch(() => {\n // Persistence adapters are best-effort and must not break chat setup.\n })\n }\n\n /**\n * Record a message-list change and queue a `setItem` write for it. Skips a\n * single write after {@link beginClear} so the clear's empty snapshot isn't\n * persisted between `clearMessages()` and {@link remove}.\n */\n notifyMessagesChanged(messages: Array<UIMessage>): void {\n this.messagesGeneration++\n if (this.skipNextPersist) {\n this.skipNextPersist = false\n return\n }\n const generation = this.generation\n const messagesSnapshot = [...messages]\n this.runOperation(() => {\n if (generation !== this.generation) {\n return\n }\n return this.adapter.setItem(this.id, messagesSnapshot)\n })\n }\n\n /** Remove the persisted conversation. Invalidates any queued writes. */\n remove(): void {\n const generation = ++this.generation\n this.runOperation(() => {\n if (generation !== this.generation) {\n return\n }\n return this.adapter.removeItem(this.id)\n })\n }\n\n private runOperation(operation: () => void | Promise<void>): void {\n if (this.queuePending) {\n const queued = this.queue.then(operation).catch(() => {\n // Persistence adapters are best-effort and must not break chat updates.\n })\n this.queue = queued\n void queued.finally(() => {\n if (this.queue === queued) {\n this.queuePending = false\n }\n })\n return\n }\n\n try {\n const result = operation()\n if (result instanceof Promise) {\n this.queuePending = true\n const queued = result.catch(() => {\n // Persistence adapters are best-effort and must not break chat updates.\n })\n this.queue = queued\n void queued.finally(() => {\n if (this.queue === queued) {\n this.queuePending = false\n }\n })\n }\n } catch {\n // Persistence adapters are best-effort and must not break chat updates.\n }\n }\n\n // ---------------------------------------------------------------------------\n // Clear-during-stream suppression\n // ---------------------------------------------------------------------------\n\n /**\n * Capture the message/run ids that exist at the moment of a clear so chunks\n * still arriving for them can be ignored.\n */\n snapshotClear(context: {\n messages: Array<UIMessage>\n activeRunIds: Set<string>\n currentRunId: string | null\n }): void {\n for (const message of context.messages) {\n this.clearedMessageIds.add(message.id)\n }\n for (const runId of context.activeRunIds) {\n this.clearedRunIds.add(runId)\n this.ignoredActiveRunIds.add(runId)\n }\n if (context.currentRunId) {\n this.clearedRunIds.add(context.currentRunId)\n this.ignoredActiveRunIds.add(context.currentRunId)\n }\n }\n\n /** Mark that the next persisted message change (the clear itself) is skipped. */\n beginClear(): void {\n this.skipNextPersist = true\n }\n\n /** Whether a chunk belongs to cleared state and should not be processed. */\n shouldIgnoreChunk(chunk: StreamChunk): boolean {\n const runId = getChunkRunId(chunk)\n if (runId && this.clearedRunIds.has(runId)) {\n if (chunk.type === 'RUN_STARTED') {\n this.ignoredActiveRunIds.add(runId)\n this.currentRunlessRunId = runId\n }\n this.markIgnoredChunkIds(chunk)\n return true\n }\n\n if (runId && this.ignoredActiveRunIds.has(runId)) {\n this.markIgnoredChunkIds(chunk)\n return true\n }\n\n if (this.isRunlessChunkFromIgnoredRun(chunk)) {\n this.markIgnoredChunkIds(chunk)\n return true\n }\n\n const toolCallId = getChunkToolCallId(chunk)\n if (toolCallId && this.clearedToolCallIds.has(toolCallId)) {\n return true\n }\n\n const parentMessageId = getChunkParentMessageId(chunk)\n if (parentMessageId && this.clearedMessageIds.has(parentMessageId)) {\n if (toolCallId) {\n this.clearedToolCallIds.add(toolCallId)\n }\n return true\n }\n\n const messageId = getChunkMessageId(chunk)\n if (!messageId) {\n return false\n }\n if (this.clearedMessageIds.has(messageId)) {\n return true\n }\n\n return false\n }\n\n /**\n * The owning client calls this when a run starts so runless content chunks\n * (adapters that omit `runId` on content events) can be attributed to it.\n */\n onRunStarted(runId: string): void {\n this.currentRunlessRunId = runId\n }\n\n /** Forget a settled run, advancing the runless pointer to another ignored run. */\n onRunSettled(runId: string): void {\n this.ignoredActiveRunIds.delete(runId)\n this.clearedRunIds.delete(runId)\n if (this.currentRunlessRunId === runId) {\n this.currentRunlessRunId =\n this.ignoredActiveRunIds.values().next().value ?? null\n }\n }\n\n /** A session-level (runId-less) RUN_ERROR clears all ignored-run tracking. */\n onSessionRunError(): void {\n this.ignoredActiveRunIds.clear()\n this.currentRunlessRunId = null\n }\n\n /** Clear the ignored-active-run markers (mirrors a session-generating reset). */\n resetIgnored(): void {\n this.ignoredActiveRunIds.clear()\n }\n\n /**\n * Consume the current runless run id (if any), forgetting it. Used when an\n * ignored, runId-less RUN_ERROR drains the run the client is still tracking.\n */\n takeRunlessRunId(): string | null {\n const runId = this.currentRunlessRunId\n if (!runId) return null\n this.ignoredActiveRunIds.delete(runId)\n this.clearedRunIds.delete(runId)\n // Advance to another still-ignored run (mirroring `onRunSettled`) so that\n // when two cleared runs drain concurrently, draining one via a runId-less\n // RUN_ERROR doesn't stop suppressing the other's runless content.\n this.currentRunlessRunId =\n this.ignoredActiveRunIds.values().next().value ?? null\n return runId\n }\n\n private markIgnoredChunkIds(chunk: StreamChunk): void {\n const messageId = getChunkMessageId(chunk)\n if (messageId) {\n this.clearedMessageIds.add(messageId)\n }\n const toolCallId = getChunkToolCallId(chunk)\n if (toolCallId) {\n this.clearedToolCallIds.add(toolCallId)\n }\n }\n\n private isRunlessChunkFromIgnoredRun(chunk: StreamChunk): boolean {\n const runId = getChunkRunId(chunk)\n if (runId || !this.currentRunlessRunId) return false\n if (\n !this.ignoredActiveRunIds.has(this.currentRunlessRunId) &&\n !this.clearedRunIds.has(this.currentRunlessRunId)\n ) {\n return false\n }\n return (\n chunk.type === 'TEXT_MESSAGE_START' ||\n chunk.type === 'TEXT_MESSAGE_CONTENT' ||\n chunk.type === 'TOOL_CALL_START' ||\n chunk.type === 'TOOL_CALL_ARGS' ||\n chunk.type === 'TOOL_CALL_END' ||\n chunk.type === 'TOOL_CALL_RESULT' ||\n chunk.type === 'MESSAGES_SNAPSHOT' ||\n chunk.type === 'RUN_ERROR'\n )\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,11 @@
1
1
  import { ModelMessage, StreamChunk, UIMessage } from '@tanstack/ai/client';
2
2
  import { ChatFetcher } from './types.js';
3
+ /**
4
+ * Resolve a chunk's run id, preferring the value on the chunk itself
5
+ * (RUN_STARTED / RUN_FINISHED / RUN_ERROR carry one) and falling back to the
6
+ * run the connect wrapper stamped it with.
7
+ */
8
+ export declare function getChunkRunId(chunk: StreamChunk): string | undefined;
3
9
  /**
4
10
  * Thrown when an SSE/HTTP stream ends with a non-empty unterminated buffer.
5
11
  * Indicates the connection was cut mid-line (server crash, dropped TCP, proxy
@@ -1,6 +1,10 @@
1
1
  import { EventType, uiMessagesToWire } from "@tanstack/ai/client";
2
2
  import { getResponseStreamReader, createResponseStreamTextDecoder } from "./response-stream.js";
3
3
  import { parseSseDataLine } from "./sse-utils.js";
4
+ const chunkRunIds = /* @__PURE__ */ new WeakMap();
5
+ function getChunkRunId(chunk) {
6
+ return "runId" in chunk && typeof chunk.runId === "string" ? chunk.runId : chunkRunIds.get(chunk);
7
+ }
4
8
  class StreamTruncatedError extends Error {
5
9
  constructor() {
6
10
  super(
@@ -121,7 +125,10 @@ function normalizeConnectionAdapter(connection) {
121
125
  }
122
126
  let activeBuffer = [];
123
127
  let activeWaiters = [];
124
- function push(chunk) {
128
+ function push(chunk, runId) {
129
+ if (runId) {
130
+ chunkRunIds.set(chunk, runId);
131
+ }
125
132
  const waiter = activeWaiters.shift();
126
133
  if (waiter) {
127
134
  waiter(chunk);
@@ -176,7 +183,7 @@ function normalizeConnectionAdapter(connection) {
176
183
  if (chunk.type === "RUN_FINISHED" || chunk.type === "RUN_ERROR") {
177
184
  hasTerminalEvent = true;
178
185
  }
179
- push(chunk);
186
+ push(chunk, runContext?.runId);
180
187
  }
181
188
  if (!abortSignal?.aborted && !hasTerminalEvent) {
182
189
  const synthetic = {
@@ -594,6 +601,7 @@ export {
594
601
  fetchHttpStream,
595
602
  fetchServerSentEvents,
596
603
  fetcherToConnectionAdapter,
604
+ getChunkRunId,
597
605
  normalizeConnectionAdapter,
598
606
  rpcStream,
599
607
  stream,
@@ -1 +1 @@
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
+ {"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 * Associates connect-wrapped chunks with the run they were produced under.\n * Content events (TEXT_MESSAGE_CONTENT, TOOL_CALL_*, …) carry no `runId` of\n * their own, so the connect wrapper stamps the caller's run id here. Lets\n * run-scoped consumers (e.g. clear-during-stream suppression) attribute those\n * otherwise-runless chunks to their originating request.\n */\nconst chunkRunIds = new WeakMap<StreamChunk, string>()\n\n/**\n * Resolve a chunk's run id, preferring the value on the chunk itself\n * (RUN_STARTED / RUN_FINISHED / RUN_ERROR carry one) and falling back to the\n * run the connect wrapper stamped it with.\n */\nexport function getChunkRunId(chunk: StreamChunk): string | undefined {\n return 'runId' in chunk && typeof chunk.runId === 'string'\n ? chunk.runId\n : chunkRunIds.get(chunk)\n}\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, runId?: string): void {\n if (runId) {\n chunkRunIds.set(chunk, runId)\n }\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, runContext?.runId)\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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"}
@@ -2,7 +2,7 @@ export { ChatClient } from './chat-client.js';
2
2
  export { RealtimeClient } from './realtime-client.js';
3
3
  export { GenerationClient } from './generation-client.js';
4
4
  export { VideoGenerationClient } from './video-generation-client.js';
5
- export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientOptions, ClientContextOptionFromTools, ChatRequestBody, InferChatMessages, InferredClientContext, ChatClientState, ConnectionStatus, ChatFetcher, ChatFetcherInput, ChatFetcherOptions, ChatTransport, DistributedOmit, MultimodalContent, } from './types.js';
5
+ export type { UIMessage, MessagePart, TextPart, ToolCallPart, ToolResultPart, ThinkingPart, StructuredOutputPart, ChatClientPersistence, ChatClientOptions, ClientContextOptionFromTools, ChatRequestBody, InferChatMessages, InferredClientContext, ChatClientState, ConnectionStatus, ChatFetcher, ChatFetcherInput, ChatFetcherOptions, ChatTransport, DistributedOmit, MultimodalContent, } from './types.js';
6
6
  export type { InferGenerationOutput, GenerationClientState, GenerationClientOptions, GenerationFetcher, GenerationFetcherOptions, GenerationTransport, VideoGenerationClientOptions, VideoStatusInfo, VideoGenerateResult, ImageGenerateInput, AudioGenerateInput, SpeechGenerateInput, TranscriptionGenerateInput, SummarizeGenerateInput, VideoGenerateInput, } from './generation-types.js';
7
7
  export { GENERATION_EVENTS } from './generation-types.js';
8
8
  export { UnsupportedResponseStreamError } from './response-stream.js';