assistant-stream 0.2.0 → 0.2.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.
Files changed (199) hide show
  1. package/dist/ai-sdk/index.d.ts +5 -0
  2. package/dist/ai-sdk/index.d.ts.map +1 -0
  3. package/dist/ai-sdk/index.js +4 -3
  4. package/dist/ai-sdk/index.js.map +1 -1
  5. package/dist/ai-sdk/language-model.d.ts +6 -0
  6. package/dist/ai-sdk/language-model.d.ts.map +1 -0
  7. package/dist/ai-sdk/language-model.js +4 -3
  8. package/dist/ai-sdk/language-model.js.map +1 -1
  9. package/dist/ai-sdk.d.ts +3 -0
  10. package/dist/ai-sdk.d.ts.map +1 -0
  11. package/dist/ai-sdk.js +3 -2
  12. package/dist/ai-sdk.js.map +1 -1
  13. package/dist/core/AssistantStream.d.ts +12 -0
  14. package/dist/core/AssistantStream.d.ts.map +1 -0
  15. package/dist/core/AssistantStream.js +2 -1
  16. package/dist/core/AssistantStream.js.map +1 -1
  17. package/dist/core/AssistantStreamChunk.d.ts +64 -0
  18. package/dist/core/AssistantStreamChunk.d.ts.map +1 -0
  19. package/dist/core/accumulators/AssistantMessageStream.d.ts +13 -0
  20. package/dist/core/accumulators/AssistantMessageStream.d.ts.map +1 -0
  21. package/dist/core/accumulators/AssistantMessageStream.js +7 -6
  22. package/dist/core/accumulators/AssistantMessageStream.js.map +1 -1
  23. package/dist/core/accumulators/assistant-message-accumulator.d.ts +8 -0
  24. package/dist/core/accumulators/assistant-message-accumulator.d.ts.map +1 -0
  25. package/dist/core/accumulators/assistant-message-accumulator.js +19 -18
  26. package/dist/core/accumulators/assistant-message-accumulator.js.map +1 -1
  27. package/dist/core/index.d.ts +11 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +8 -7
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/modules/assistant-stream.d.ts +31 -0
  32. package/dist/core/modules/assistant-stream.d.ts.map +1 -0
  33. package/dist/core/modules/assistant-stream.js +12 -11
  34. package/dist/core/modules/assistant-stream.js.map +1 -1
  35. package/dist/core/modules/text.d.ts +9 -0
  36. package/dist/core/modules/text.d.ts.map +1 -0
  37. package/dist/core/modules/text.js +5 -4
  38. package/dist/core/modules/text.js.map +1 -1
  39. package/dist/core/modules/tool-call.d.ts +13 -0
  40. package/dist/core/modules/tool-call.d.ts.map +1 -0
  41. package/dist/core/modules/tool-call.js +6 -5
  42. package/dist/core/modules/tool-call.js.map +1 -1
  43. package/dist/core/serialization/PlainText.d.ts +11 -0
  44. package/dist/core/serialization/PlainText.d.ts.map +1 -0
  45. package/dist/core/serialization/PlainText.js +7 -6
  46. package/dist/core/serialization/PlainText.js.map +1 -1
  47. package/dist/core/serialization/data-stream/DataStream.d.ts +11 -0
  48. package/dist/core/serialization/data-stream/DataStream.d.ts.map +1 -0
  49. package/dist/core/serialization/data-stream/DataStream.js +12 -11
  50. package/dist/core/serialization/data-stream/DataStream.js.map +1 -1
  51. package/dist/core/serialization/data-stream/chunk-types.d.ts +86 -0
  52. package/dist/core/serialization/data-stream/chunk-types.d.ts.map +1 -0
  53. package/dist/core/serialization/data-stream/chunk-types.js +1 -0
  54. package/dist/core/serialization/data-stream/chunk-types.js.map +1 -1
  55. package/dist/core/serialization/data-stream/serialization.d.ts +8 -0
  56. package/dist/core/serialization/data-stream/serialization.d.ts.map +1 -0
  57. package/dist/core/serialization/data-stream/serialization.js +5 -4
  58. package/dist/core/serialization/data-stream/serialization.js.map +1 -1
  59. package/dist/core/tool/ToolCallReader.d.ts +33 -0
  60. package/dist/core/tool/ToolCallReader.d.ts.map +1 -0
  61. package/dist/core/tool/ToolCallReader.js +17 -16
  62. package/dist/core/tool/ToolCallReader.js.map +1 -1
  63. package/dist/core/tool/ToolExecutionStream.d.ts +24 -0
  64. package/dist/core/tool/ToolExecutionStream.d.ts.map +1 -0
  65. package/dist/core/tool/ToolExecutionStream.js +8 -7
  66. package/dist/core/tool/ToolExecutionStream.js.map +1 -1
  67. package/dist/core/tool/ToolResponse.d.ts +17 -0
  68. package/dist/core/tool/ToolResponse.d.ts.map +1 -0
  69. package/dist/core/tool/ToolResponse.js +4 -3
  70. package/dist/core/tool/ToolResponse.js.map +1 -1
  71. package/dist/core/tool/index.d.ts +6 -0
  72. package/dist/core/tool/index.d.ts.map +1 -0
  73. package/dist/core/tool/index.js +4 -3
  74. package/dist/core/tool/index.js.map +1 -1
  75. package/dist/core/tool/tool-types.d.ts +76 -0
  76. package/dist/core/tool/tool-types.d.ts.map +1 -0
  77. package/dist/core/tool/toolResultStream.d.ts +6 -0
  78. package/dist/core/tool/toolResultStream.d.ts.map +1 -0
  79. package/dist/core/tool/toolResultStream.js +4 -3
  80. package/dist/core/tool/toolResultStream.js.map +1 -1
  81. package/dist/core/tool/type-path-utils.d.ts +23 -0
  82. package/dist/core/tool/type-path-utils.d.ts.map +1 -0
  83. package/dist/core/utils/Counter.d.ts +5 -0
  84. package/dist/core/utils/Counter.d.ts.map +1 -0
  85. package/dist/core/utils/Counter.js +3 -2
  86. package/dist/core/utils/Counter.js.map +1 -1
  87. package/dist/core/utils/generateId.d.ts +2 -0
  88. package/dist/core/utils/generateId.d.ts.map +1 -0
  89. package/dist/core/utils/generateId.js +2 -1
  90. package/dist/core/utils/generateId.js.map +1 -1
  91. package/dist/core/utils/stream/AssistantMetaTransformStream.d.ts +20 -0
  92. package/dist/core/utils/stream/AssistantMetaTransformStream.d.ts.map +1 -0
  93. package/dist/core/utils/stream/AssistantMetaTransformStream.js +3 -2
  94. package/dist/core/utils/stream/AssistantMetaTransformStream.js.map +1 -1
  95. package/dist/core/utils/stream/AssistantTransformStream.d.ts +15 -0
  96. package/dist/core/utils/stream/AssistantTransformStream.d.ts.map +1 -0
  97. package/dist/core/utils/stream/AssistantTransformStream.js +4 -3
  98. package/dist/core/utils/stream/AssistantTransformStream.js.map +1 -1
  99. package/dist/core/utils/stream/LineDecoderStream.d.ts +5 -0
  100. package/dist/core/utils/stream/LineDecoderStream.d.ts.map +1 -0
  101. package/dist/core/utils/stream/LineDecoderStream.js +3 -2
  102. package/dist/core/utils/stream/LineDecoderStream.js.map +1 -1
  103. package/dist/core/utils/stream/PipeableTransformStream.d.ts +4 -0
  104. package/dist/core/utils/stream/PipeableTransformStream.d.ts.map +1 -0
  105. package/dist/core/utils/stream/PipeableTransformStream.js +3 -2
  106. package/dist/core/utils/stream/PipeableTransformStream.js.map +1 -1
  107. package/dist/core/utils/stream/UnderlyingReadable.d.ts +6 -0
  108. package/dist/core/utils/stream/UnderlyingReadable.d.ts.map +1 -0
  109. package/dist/core/utils/stream/merge.d.ts +9 -0
  110. package/dist/core/utils/stream/merge.d.ts.map +1 -0
  111. package/dist/core/utils/stream/merge.js +3 -2
  112. package/dist/core/utils/stream/merge.js.map +1 -1
  113. package/dist/core/utils/stream/path-utils.d.ts +12 -0
  114. package/dist/core/utils/stream/path-utils.d.ts.map +1 -0
  115. package/dist/core/utils/stream/path-utils.js +8 -7
  116. package/dist/core/utils/stream/path-utils.js.map +1 -1
  117. package/dist/core/utils/types.d.ts +102 -0
  118. package/dist/core/utils/types.d.ts.map +1 -0
  119. package/dist/core/utils/withPromiseOrValue.d.ts +2 -0
  120. package/dist/core/utils/withPromiseOrValue.d.ts.map +1 -0
  121. package/dist/core/utils/withPromiseOrValue.js +1 -0
  122. package/dist/core/utils/withPromiseOrValue.js.map +1 -1
  123. package/dist/index.d.ts +2 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +2 -1
  126. package/dist/index.js.map +1 -1
  127. package/dist/utils/AsyncIterableStream.d.ts +3 -0
  128. package/dist/utils/AsyncIterableStream.d.ts.map +1 -0
  129. package/dist/utils/AsyncIterableStream.js +1 -0
  130. package/dist/utils/AsyncIterableStream.js.map +1 -1
  131. package/dist/utils/json/fix-json.d.ts +2 -0
  132. package/dist/utils/json/fix-json.d.ts.map +1 -0
  133. package/dist/utils/json/fix-json.js +1 -0
  134. package/dist/utils/json/fix-json.js.map +1 -1
  135. package/dist/utils/json/is-json.d.ts +5 -0
  136. package/dist/utils/json/is-json.d.ts.map +1 -0
  137. package/dist/utils/json/is-json.js +1 -0
  138. package/dist/utils/json/is-json.js.map +1 -1
  139. package/dist/utils/json/json-value.d.ts +6 -0
  140. package/dist/utils/json/json-value.d.ts.map +1 -0
  141. package/dist/utils/json/parse-partial-json-object.d.ts +14 -0
  142. package/dist/utils/json/parse-partial-json-object.d.ts.map +1 -0
  143. package/dist/utils/json/parse-partial-json-object.js +7 -6
  144. package/dist/utils/json/parse-partial-json-object.js.map +1 -1
  145. package/dist/utils/json/parse-partial-json-object.test.d.ts +2 -0
  146. package/dist/utils/json/parse-partial-json-object.test.d.ts.map +1 -0
  147. package/dist/utils/promiseWithResolvers.d.ts +6 -0
  148. package/dist/utils/promiseWithResolvers.d.ts.map +1 -0
  149. package/dist/utils/promiseWithResolvers.js +2 -1
  150. package/dist/utils/promiseWithResolvers.js.map +1 -1
  151. package/dist/utils.d.ts +4 -0
  152. package/dist/utils.d.ts.map +1 -0
  153. package/dist/utils.js +3 -2
  154. package/dist/utils.js.map +1 -1
  155. package/package.json +3 -2
  156. package/src/ai-sdk/index.ts +204 -0
  157. package/src/ai-sdk/language-model.ts +122 -0
  158. package/src/ai-sdk.ts +2 -0
  159. package/src/core/AssistantStream.ts +39 -0
  160. package/src/core/AssistantStreamChunk.ts +93 -0
  161. package/src/core/accumulators/AssistantMessageStream.ts +56 -0
  162. package/src/core/accumulators/assistant-message-accumulator.ts +394 -0
  163. package/src/core/index.ts +17 -0
  164. package/src/core/modules/assistant-stream.ts +264 -0
  165. package/src/core/modules/text.ts +64 -0
  166. package/src/core/modules/tool-call.ts +105 -0
  167. package/src/core/serialization/PlainText.ts +68 -0
  168. package/src/core/serialization/data-stream/DataStream.ts +355 -0
  169. package/src/core/serialization/data-stream/chunk-types.ts +93 -0
  170. package/src/core/serialization/data-stream/serialization.ts +32 -0
  171. package/src/core/tool/ToolCallReader.ts +432 -0
  172. package/src/core/tool/ToolExecutionStream.ts +180 -0
  173. package/src/core/tool/ToolResponse.ts +31 -0
  174. package/src/core/tool/index.ts +8 -0
  175. package/src/core/tool/tool-types.ts +107 -0
  176. package/src/core/tool/toolResultStream.ts +119 -0
  177. package/src/core/tool/type-path-utils.ts +36 -0
  178. package/src/core/utils/Counter.ts +7 -0
  179. package/src/core/utils/generateId.tsx +6 -0
  180. package/src/core/utils/stream/AssistantMetaTransformStream.ts +74 -0
  181. package/src/core/utils/stream/AssistantTransformStream.ts +74 -0
  182. package/src/core/utils/stream/LineDecoderStream.ts +29 -0
  183. package/src/core/utils/stream/PipeableTransformStream.ts +10 -0
  184. package/src/core/utils/stream/UnderlyingReadable.ts +5 -0
  185. package/src/core/utils/stream/merge.ts +212 -0
  186. package/src/core/utils/stream/path-utils.ts +71 -0
  187. package/src/core/utils/types.ts +150 -0
  188. package/src/core/utils/withPromiseOrValue.ts +20 -0
  189. package/src/index.ts +1 -0
  190. package/src/utils/AsyncIterableStream.ts +24 -0
  191. package/src/utils/json/fix-json.ts +488 -0
  192. package/src/utils/json/is-json.ts +43 -0
  193. package/src/utils/json/json-value.ts +13 -0
  194. package/src/utils/json/parse-partial-json-object.test.ts +225 -0
  195. package/src/utils/json/parse-partial-json-object.ts +103 -0
  196. package/src/utils/promiseWithResolvers.ts +10 -0
  197. package/src/utils.ts +13 -0
  198. package/utils/README.md +0 -1
  199. package/utils/package.json +0 -5
@@ -0,0 +1,432 @@
1
+ import { promiseWithResolvers } from "../../utils/promiseWithResolvers";
2
+ import {
3
+ parsePartialJsonObject,
4
+ getPartialJsonObjectFieldState,
5
+ } from "../../utils/json/parse-partial-json-object";
6
+ import {
7
+ ToolCallArgsReader,
8
+ ToolCallReader,
9
+ ToolCallResponseReader,
10
+ } from "./tool-types";
11
+ import { TypeAtPath, TypePath } from "./type-path-utils";
12
+ import { ToolResponse } from "./ToolResponse";
13
+
14
+ // TODO: remove dispose
15
+
16
+ function getField<T>(obj: T, fieldPath: (string | number)[]): any {
17
+ let current: any = obj;
18
+ for (const key of fieldPath) {
19
+ if (current === undefined || current === null) {
20
+ return undefined;
21
+ }
22
+ current = current[key as string | number];
23
+ }
24
+ return current;
25
+ }
26
+
27
+ interface Handle {
28
+ update(args: unknown): void;
29
+ dispose(): void;
30
+ }
31
+
32
+ class GetHandle<T> implements Handle {
33
+ private resolve: (value: any) => void;
34
+ private reject: (reason: unknown) => void;
35
+ private disposed = false;
36
+ private fieldPath: (string | number)[];
37
+
38
+ constructor(
39
+ resolve: (value: any) => void,
40
+ reject: (reason: unknown) => void,
41
+ fieldPath: (string | number)[],
42
+ ) {
43
+ this.resolve = resolve;
44
+ this.reject = reject;
45
+ this.fieldPath = fieldPath;
46
+ }
47
+
48
+ update(args: unknown): void {
49
+ if (this.disposed) return;
50
+
51
+ try {
52
+ // Check if the field is complete
53
+ if (
54
+ getPartialJsonObjectFieldState(
55
+ args as Record<string, unknown>,
56
+ this.fieldPath,
57
+ ) === "complete"
58
+ ) {
59
+ const value = getField(args as T, this.fieldPath);
60
+ if (value !== undefined) {
61
+ this.resolve(value);
62
+ this.dispose();
63
+ }
64
+ }
65
+ } catch (e) {
66
+ this.reject(e);
67
+ this.dispose();
68
+ }
69
+ }
70
+
71
+ dispose(): void {
72
+ this.disposed = true;
73
+ }
74
+ }
75
+
76
+ class StreamValuesHandle<T> implements Handle {
77
+ private controller: ReadableStreamDefaultController<any>;
78
+ private disposed = false;
79
+ private fieldPath: (string | number)[];
80
+
81
+ constructor(
82
+ controller: ReadableStreamDefaultController<any>,
83
+ fieldPath: (string | number)[],
84
+ ) {
85
+ this.controller = controller;
86
+ this.fieldPath = fieldPath;
87
+ }
88
+
89
+ update(args: unknown): void {
90
+ if (this.disposed) return;
91
+
92
+ try {
93
+ const value = getField(args as T, this.fieldPath);
94
+
95
+ if (value !== undefined) {
96
+ this.controller.enqueue(value);
97
+ }
98
+
99
+ // Check if the field is complete, if so close the stream
100
+ if (
101
+ getPartialJsonObjectFieldState(
102
+ args as Record<string, unknown>,
103
+ this.fieldPath,
104
+ ) === "complete"
105
+ ) {
106
+ this.controller.close();
107
+ this.dispose();
108
+ }
109
+ } catch (e) {
110
+ this.controller.error(e);
111
+ this.dispose();
112
+ }
113
+ }
114
+
115
+ dispose(): void {
116
+ this.disposed = true;
117
+ }
118
+ }
119
+
120
+ class StreamTextHandle<T> implements Handle {
121
+ private controller: ReadableStreamDefaultController<any>;
122
+ private disposed = false;
123
+ private fieldPath: (string | number)[];
124
+ private lastValue: any = undefined;
125
+
126
+ constructor(
127
+ controller: ReadableStreamDefaultController<any>,
128
+ fieldPath: (string | number)[],
129
+ ) {
130
+ this.controller = controller;
131
+ this.fieldPath = fieldPath;
132
+ }
133
+
134
+ update(args: unknown): void {
135
+ if (this.disposed) return;
136
+
137
+ try {
138
+ const value = getField(args as T, this.fieldPath);
139
+
140
+ if (value !== undefined && typeof value === "string") {
141
+ const delta = value.substring(this.lastValue?.length || 0);
142
+ this.lastValue = value;
143
+ this.controller.enqueue(delta);
144
+ }
145
+
146
+ // Check if the field is complete, if so close the stream
147
+ if (
148
+ getPartialJsonObjectFieldState(
149
+ args as Record<string, unknown>,
150
+ this.fieldPath,
151
+ ) === "complete"
152
+ ) {
153
+ this.controller.close();
154
+ this.dispose();
155
+ }
156
+ } catch (e) {
157
+ this.controller.error(e);
158
+ this.dispose();
159
+ }
160
+ }
161
+
162
+ dispose(): void {
163
+ this.disposed = true;
164
+ }
165
+ }
166
+
167
+ class ForEachHandle<T> implements Handle {
168
+ private controller: ReadableStreamDefaultController<any>;
169
+ private disposed = false;
170
+ private fieldPath: (string | number)[];
171
+ private processedIndexes = new Set<number>();
172
+
173
+ constructor(
174
+ controller: ReadableStreamDefaultController<any>,
175
+ fieldPath: (string | number)[],
176
+ ) {
177
+ this.controller = controller;
178
+ this.fieldPath = fieldPath;
179
+ }
180
+
181
+ update(args: unknown): void {
182
+ if (this.disposed) return;
183
+
184
+ try {
185
+ const array = getField(args as T, this.fieldPath) as unknown as any[];
186
+
187
+ if (!Array.isArray(array)) {
188
+ return;
189
+ }
190
+
191
+ // Check each array element and emit completed ones that haven't been processed
192
+ for (let i = 0; i < array.length; i++) {
193
+ if (!this.processedIndexes.has(i)) {
194
+ const elementPath = [...this.fieldPath, i];
195
+ if (
196
+ getPartialJsonObjectFieldState(
197
+ args as Record<string, unknown>,
198
+ elementPath,
199
+ ) === "complete"
200
+ ) {
201
+ this.controller.enqueue(array[i]);
202
+ this.processedIndexes.add(i);
203
+ }
204
+ }
205
+ }
206
+
207
+ // Check if the entire array is complete
208
+ if (
209
+ getPartialJsonObjectFieldState(
210
+ args as Record<string, unknown>,
211
+ this.fieldPath,
212
+ ) === "complete"
213
+ ) {
214
+ this.controller.close();
215
+ this.dispose();
216
+ }
217
+ } catch (e) {
218
+ this.controller.error(e);
219
+ this.dispose();
220
+ }
221
+ }
222
+
223
+ dispose(): void {
224
+ this.disposed = true;
225
+ }
226
+ }
227
+
228
+ // Implementation of ToolCallReader that uses stream of partial JSON
229
+ export class ToolCallArgsReaderImpl<T> implements ToolCallArgsReader<T> {
230
+ private argTextDeltas: ReadableStream<string>;
231
+ private handles: Set<Handle> = new Set();
232
+ private args: any = parsePartialJsonObject("");
233
+
234
+ constructor(argTextDeltas: ReadableStream<string>) {
235
+ this.argTextDeltas = argTextDeltas;
236
+ this.processStream();
237
+ }
238
+
239
+ private async processStream(): Promise<void> {
240
+ try {
241
+ let accumulatedText = "";
242
+ const reader = this.argTextDeltas.getReader();
243
+
244
+ while (true) {
245
+ const { value, done } = await reader.read();
246
+ if (done) break;
247
+
248
+ accumulatedText += value;
249
+ const parsedArgs = parsePartialJsonObject(accumulatedText);
250
+
251
+ if (parsedArgs !== undefined) {
252
+ this.args = parsedArgs;
253
+ // Notify all handles of the updated args
254
+ for (const handle of this.handles) {
255
+ handle.update(parsedArgs);
256
+ }
257
+ }
258
+ }
259
+ } catch (error) {
260
+ console.error("Error processing argument stream:", error);
261
+ // Notify handles of the error
262
+ for (const handle of this.handles) {
263
+ handle.dispose();
264
+ }
265
+ }
266
+ }
267
+
268
+ get<PathT extends TypePath<T>>(
269
+ ...fieldPath: PathT
270
+ ): Promise<TypeAtPath<T, PathT>> {
271
+ return new Promise<any>((resolve, reject) => {
272
+ const handle = new GetHandle<T>(resolve, reject, fieldPath);
273
+
274
+ // Check if the field is already complete in current args
275
+ if (
276
+ this.args &&
277
+ getPartialJsonObjectFieldState(
278
+ this.args as Record<string, unknown>,
279
+ fieldPath,
280
+ ) === "complete"
281
+ ) {
282
+ const value = getField(this.args as T, fieldPath);
283
+ if (value !== undefined) {
284
+ resolve(value);
285
+ return;
286
+ }
287
+ }
288
+
289
+ this.handles.add(handle);
290
+ handle.update(this.args);
291
+ });
292
+ }
293
+
294
+ streamValues<PathT extends TypePath<T>>(...fieldPath: PathT): any {
295
+ // Use a type assertion to convert the complex TypePath to a simple array
296
+ const simplePath = fieldPath as unknown as (string | number)[];
297
+
298
+ const stream = new ReadableStream<any>({
299
+ start: (controller) => {
300
+ const handle = new StreamValuesHandle<T>(controller, simplePath);
301
+ this.handles.add(handle);
302
+
303
+ // Check current args immediately
304
+ handle.update(this.args);
305
+ },
306
+ cancel: () => {
307
+ // Find and dispose the corresponding handle
308
+ for (const handle of this.handles) {
309
+ if (handle instanceof StreamValuesHandle) {
310
+ handle.dispose();
311
+ this.handles.delete(handle);
312
+ break;
313
+ }
314
+ }
315
+ },
316
+ });
317
+
318
+ // For type compatibility, cast the stream to the required type
319
+ return stream as any;
320
+ }
321
+
322
+ streamText<PathT extends TypePath<T>>(...fieldPath: PathT): any {
323
+ // Use a type assertion to convert the complex TypePath to a simple array
324
+ const simplePath = fieldPath as unknown as (string | number)[];
325
+
326
+ const stream = new ReadableStream<any>({
327
+ start: (controller) => {
328
+ const handle = new StreamTextHandle<T>(controller, simplePath);
329
+ this.handles.add(handle);
330
+
331
+ // Check current args immediately
332
+ handle.update(this.args);
333
+ },
334
+ cancel: () => {
335
+ // Find and dispose the corresponding handle
336
+ for (const handle of this.handles) {
337
+ if (handle instanceof StreamTextHandle) {
338
+ handle.dispose();
339
+ this.handles.delete(handle);
340
+ break;
341
+ }
342
+ }
343
+ },
344
+ });
345
+
346
+ // For type compatibility, cast the stream to the required type
347
+ return stream as any;
348
+ }
349
+
350
+ forEach<PathT extends TypePath<T>>(...fieldPath: PathT): any {
351
+ // Use a type assertion to convert the complex TypePath to a simple array
352
+ const simplePath = fieldPath as unknown as (string | number)[];
353
+
354
+ const stream = new ReadableStream<any>({
355
+ start: (controller) => {
356
+ const handle = new ForEachHandle<T>(controller, simplePath);
357
+ this.handles.add(handle);
358
+
359
+ // Check current args immediately
360
+ handle.update(this.args);
361
+ },
362
+ cancel: () => {
363
+ // Find and dispose the corresponding handle
364
+ for (const handle of this.handles) {
365
+ if (handle instanceof ForEachHandle) {
366
+ handle.dispose();
367
+ this.handles.delete(handle);
368
+ break;
369
+ }
370
+ }
371
+ },
372
+ });
373
+
374
+ // For type compatibility, cast the stream to the required type
375
+ return stream as any;
376
+ }
377
+ }
378
+
379
+ export class ToolCallResponseReaderImpl<TResult>
380
+ implements ToolCallResponseReader<TResult>
381
+ {
382
+ constructor(private readonly promise: Promise<ToolResponse<TResult>>) {}
383
+
384
+ public get() {
385
+ return this.promise;
386
+ }
387
+ }
388
+
389
+ export class ToolCallReaderImpl<TArgs, TResult>
390
+ implements ToolCallReader<TArgs, TResult>
391
+ {
392
+ public readonly args: ToolCallArgsReaderImpl<TArgs>;
393
+ public readonly response: ToolCallResponseReaderImpl<TResult>;
394
+ private readonly writable: WritableStream<string>;
395
+ private readonly resolve: (value: ToolResponse<TResult>) => void;
396
+
397
+ public argsText: string = "";
398
+
399
+ constructor() {
400
+ const stream = new TransformStream<string, string>();
401
+ this.writable = stream.writable;
402
+ this.args = new ToolCallArgsReaderImpl<TArgs>(stream.readable);
403
+
404
+ const { promise, resolve } = promiseWithResolvers<ToolResponse<TResult>>();
405
+ this.resolve = resolve;
406
+ this.response = new ToolCallResponseReaderImpl<TResult>(promise);
407
+ }
408
+
409
+ async appendArgsTextDelta(text: string): Promise<void> {
410
+ const writer = this.writable.getWriter();
411
+ try {
412
+ await writer.write(text);
413
+ } catch (err) {
414
+ console.warn(err);
415
+ } finally {
416
+ writer.releaseLock();
417
+ }
418
+
419
+ this.argsText += text;
420
+ }
421
+
422
+ setResponse(value: ToolResponse<TResult>): void {
423
+ this.resolve(value);
424
+ }
425
+
426
+ result = {
427
+ get: async () => {
428
+ const response = await this.response.get();
429
+ return response.result;
430
+ },
431
+ };
432
+ }
@@ -0,0 +1,180 @@
1
+ import sjson from "secure-json-parse";
2
+ import { AssistantStreamChunk } from "../AssistantStreamChunk";
3
+ import {
4
+ AssistantMetaStreamChunk,
5
+ AssistantMetaTransformStream,
6
+ } from "../utils/stream/AssistantMetaTransformStream";
7
+ import { PipeableTransformStream } from "../utils/stream/PipeableTransformStream";
8
+ import { ReadonlyJSONValue } from "../../utils/json/json-value";
9
+ import { ToolResponse } from "./ToolResponse";
10
+ import { withPromiseOrValue } from "../utils/withPromiseOrValue";
11
+ import { ToolCallReaderImpl } from "./ToolCallReader";
12
+ import { ToolCallReader } from "./tool-types";
13
+
14
+ type ToolCallback = (toolCall: {
15
+ toolCallId: string;
16
+ toolName: string;
17
+ args: unknown;
18
+ }) =>
19
+ | Promise<ToolResponse<ReadonlyJSONValue>>
20
+ | ToolResponse<ReadonlyJSONValue>
21
+ | undefined;
22
+
23
+ type ToolStreamCallback = <TArgs, TResult>(toolCall: {
24
+ reader: ToolCallReader<TArgs, TResult>;
25
+ toolCallId: string;
26
+ toolName: string;
27
+ }) => void;
28
+
29
+ type ToolExecutionOptions = {
30
+ execute: ToolCallback;
31
+ streamCall: ToolStreamCallback;
32
+ };
33
+
34
+ export class ToolExecutionStream extends PipeableTransformStream<
35
+ AssistantStreamChunk,
36
+ AssistantStreamChunk
37
+ > {
38
+ constructor(options: ToolExecutionOptions) {
39
+ const toolCallPromises = new Map<string, PromiseLike<void>>();
40
+ const toolCallControllers = new Map<
41
+ string,
42
+ ToolCallReaderImpl<unknown, unknown>
43
+ >();
44
+
45
+ super((readable) => {
46
+ const transform = new TransformStream<
47
+ AssistantMetaStreamChunk,
48
+ AssistantStreamChunk
49
+ >({
50
+ transform(chunk, controller) {
51
+ // forward everything
52
+ if (chunk.type !== "part-finish" || chunk.meta.type !== "tool-call") {
53
+ controller.enqueue(chunk);
54
+ }
55
+
56
+ const type = chunk.type;
57
+
58
+ switch (type) {
59
+ case "part-start":
60
+ if (chunk.part.type === "tool-call") {
61
+ const reader = new ToolCallReaderImpl<unknown, unknown>();
62
+ toolCallControllers.set(chunk.part.toolCallId, reader);
63
+
64
+ options.streamCall({
65
+ reader,
66
+ toolCallId: chunk.part.toolCallId,
67
+ toolName: chunk.part.toolName,
68
+ });
69
+ }
70
+ break;
71
+ case "text-delta": {
72
+ if (chunk.meta.type === "tool-call") {
73
+ const toolCallId = chunk.meta.toolCallId;
74
+
75
+ const controller = toolCallControllers.get(toolCallId);
76
+ if (!controller)
77
+ throw new Error("No controller found for tool call");
78
+ controller.appendArgsTextDelta(chunk.textDelta);
79
+ }
80
+ break;
81
+ }
82
+ case "tool-call-args-text-finish": {
83
+ if (chunk.meta.type !== "tool-call") break;
84
+
85
+ const { toolCallId, toolName } = chunk.meta;
86
+ const streamController = toolCallControllers.get(toolCallId)!;
87
+ if (!streamController)
88
+ throw new Error("No controller found for tool call");
89
+
90
+ const promise = withPromiseOrValue(
91
+ () => {
92
+ if (!streamController.argsText) {
93
+ console.log(
94
+ "Encountered tool call without args, this should never happen",
95
+ );
96
+ throw new Error(
97
+ "Encountered tool call without args, this is unexpected.",
98
+ );
99
+ }
100
+
101
+ let args;
102
+ try {
103
+ args = sjson.parse(streamController.argsText);
104
+ } catch (e) {
105
+ throw new Error(
106
+ `Function parameter parsing failed. ${JSON.stringify((e as Error).message)}`,
107
+ );
108
+ }
109
+
110
+ return options.execute({
111
+ toolCallId,
112
+ toolName,
113
+ args,
114
+ });
115
+ },
116
+ (c) => {
117
+ if (c === undefined) return;
118
+
119
+ // TODO how to handle new ToolResult({ result: undefined })?
120
+ const result = new ToolResponse({
121
+ artifact: c.artifact,
122
+ result: c.result,
123
+ isError: c.isError,
124
+ });
125
+ streamController.setResponse(result);
126
+ controller.enqueue({
127
+ type: "result",
128
+ path: chunk.path,
129
+ ...result,
130
+ });
131
+ },
132
+ (e) => {
133
+ const result = new ToolResponse({
134
+ result: String(e),
135
+ isError: true,
136
+ });
137
+
138
+ streamController.setResponse(result);
139
+ controller.enqueue({
140
+ type: "result",
141
+ path: chunk.path,
142
+ ...result,
143
+ });
144
+ },
145
+ );
146
+ if (promise) {
147
+ toolCallPromises.set(toolCallId, promise);
148
+ }
149
+ break;
150
+ }
151
+
152
+ case "part-finish": {
153
+ if (chunk.meta.type !== "tool-call") break;
154
+
155
+ const { toolCallId } = chunk.meta;
156
+ const toolCallPromise = toolCallPromises.get(toolCallId);
157
+ if (toolCallPromise) {
158
+ toolCallPromise.then(() => {
159
+ toolCallPromises.delete(toolCallId);
160
+ toolCallControllers.delete(toolCallId);
161
+
162
+ controller.enqueue(chunk);
163
+ });
164
+ } else {
165
+ controller.enqueue(chunk);
166
+ }
167
+ }
168
+ }
169
+ },
170
+ async flush() {
171
+ await Promise.all(toolCallPromises.values());
172
+ },
173
+ });
174
+
175
+ return readable
176
+ .pipeThrough(new AssistantMetaTransformStream())
177
+ .pipeThrough(transform);
178
+ });
179
+ }
180
+ }
@@ -0,0 +1,31 @@
1
+ import { ReadonlyJSONValue } from "../../utils/json/json-value";
2
+
3
+ const TOOL_RESPONSE_SYMBOL = Symbol.for("aui.tool-response");
4
+
5
+ export type ToolResponseInit<TResult> = {
6
+ result: TResult;
7
+ artifact?: ReadonlyJSONValue | undefined;
8
+ isError?: boolean | undefined;
9
+ };
10
+
11
+ export class ToolResponse<TResult> {
12
+ get [TOOL_RESPONSE_SYMBOL]() {
13
+ return true;
14
+ }
15
+
16
+ readonly artifact?: ReadonlyJSONValue | undefined;
17
+ readonly result: TResult;
18
+ readonly isError: boolean;
19
+
20
+ constructor(options: ToolResponseInit<TResult>) {
21
+ this.artifact = options.artifact;
22
+ this.result = options.result;
23
+ this.isError = options.isError ?? false;
24
+ }
25
+
26
+ static [Symbol.hasInstance](obj: unknown): obj is ToolResponse<unknown> {
27
+ return (
28
+ typeof obj === "object" && obj !== null && TOOL_RESPONSE_SYMBOL in obj
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ export type { Tool } from "./tool-types";
2
+ export { ToolResponse } from "./ToolResponse";
3
+ export { ToolExecutionStream } from "./ToolExecutionStream";
4
+ export type { ToolCallReader } from "./tool-types";
5
+ export {
6
+ toolResultStream as unstable_toolResultStream,
7
+ unstable_runPendingTools,
8
+ } from "./toolResultStream";