ai-stream-utils 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,172 +1,88 @@
1
- import { t as createAsyncIterableStream } from "./create-async-iterable-stream-x_DKVIDi.mjs";
2
- import { getToolOrDynamicToolName, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolOrDynamicToolUIPart, readUIMessageStream } from "ai";
1
+ import { a as convertArrayToStream, c as convertAsyncIterableToStream, i as convertAsyncIterableToArray, n as convertStreamToArray, o as convertArrayToAsyncIterable, r as convertSSEToUIMessageStream, s as createAsyncIterableStream, t as convertUIMessageToSSEStream } from "./convert-ui-message-stream-to-sse-stream-CcQRXKju.mjs";
2
+ import "./utils/index.mjs";
3
+ import { createIdGenerator, getToolOrDynamicToolName, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolOrDynamicToolUIPart, readUIMessageStream } from "ai";
3
4
 
4
- //#region node_modules/.pnpm/@ai-sdk+provider@3.0.4/node_modules/@ai-sdk/provider/dist/index.mjs
5
- var marker$1 = "vercel.ai.error";
6
- var symbol$1 = Symbol.for(marker$1);
7
- var _a, _b;
8
- var AISDKError = class _AISDKError extends (_b = Error, _a = symbol$1, _b) {
9
- /**
10
- * Creates an AI SDK Error.
11
- *
12
- * @param {Object} params - The parameters for creating the error.
13
- * @param {string} params.name - The name of the error.
14
- * @param {string} params.message - The error message.
15
- * @param {unknown} [params.cause] - The underlying cause of the error.
16
- */
17
- constructor({ name: name14, message, cause }) {
18
- super(message);
19
- this[_a] = true;
20
- this.name = name14;
21
- this.cause = cause;
22
- }
23
- /**
24
- * Checks if the given error is an AI SDK Error.
25
- * @param {unknown} error - The error to check.
26
- * @returns {boolean} True if the error is an AI SDK Error, false otherwise.
27
- */
28
- static isInstance(error) {
29
- return _AISDKError.hasMarker(error, marker$1);
30
- }
31
- static hasMarker(error, marker15) {
32
- const markerSymbol = Symbol.for(marker15);
33
- return error != null && typeof error === "object" && markerSymbol in error && typeof error[markerSymbol] === "boolean" && error[markerSymbol] === true;
34
- }
35
- };
36
- var name$1 = "AI_APICallError";
37
- var marker2 = `vercel.ai.error.${name$1}`;
38
- var symbol2 = Symbol.for(marker2);
39
- var name2 = "AI_EmptyResponseBodyError";
40
- var marker3 = `vercel.ai.error.${name2}`;
41
- var symbol3 = Symbol.for(marker3);
42
- var name3 = "AI_InvalidArgumentError";
43
- var marker4 = `vercel.ai.error.${name3}`;
44
- var symbol4 = Symbol.for(marker4);
45
- var _a4, _b4;
46
- var InvalidArgumentError = class extends (_b4 = AISDKError, _a4 = symbol4, _b4) {
47
- constructor({ message, cause, argument }) {
48
- super({
49
- name: name3,
50
- message,
51
- cause
52
- });
53
- this[_a4] = true;
54
- this.argument = argument;
55
- }
56
- static isInstance(error) {
57
- return AISDKError.hasMarker(error, marker4);
58
- }
59
- };
60
- var name4 = "AI_InvalidPromptError";
61
- var marker5 = `vercel.ai.error.${name4}`;
62
- var symbol5 = Symbol.for(marker5);
63
- var name5 = "AI_InvalidResponseDataError";
64
- var marker6 = `vercel.ai.error.${name5}`;
65
- var symbol6 = Symbol.for(marker6);
66
- var name6 = "AI_JSONParseError";
67
- var marker7 = `vercel.ai.error.${name6}`;
68
- var symbol7 = Symbol.for(marker7);
69
- var name7 = "AI_LoadAPIKeyError";
70
- var marker8 = `vercel.ai.error.${name7}`;
71
- var symbol8 = Symbol.for(marker8);
72
- var name8 = "AI_LoadSettingError";
73
- var marker9 = `vercel.ai.error.${name8}`;
74
- var symbol9 = Symbol.for(marker9);
75
- var name9 = "AI_NoContentGeneratedError";
76
- var marker10 = `vercel.ai.error.${name9}`;
77
- var symbol10 = Symbol.for(marker10);
78
- var name10 = "AI_NoSuchModelError";
79
- var marker11 = `vercel.ai.error.${name10}`;
80
- var symbol11 = Symbol.for(marker11);
81
- var name11 = "AI_TooManyEmbeddingValuesForCallError";
82
- var marker12 = `vercel.ai.error.${name11}`;
83
- var symbol12 = Symbol.for(marker12);
84
- var name12 = "AI_TypeValidationError";
85
- var marker13 = `vercel.ai.error.${name12}`;
86
- var symbol13 = Symbol.for(marker13);
87
- var name13 = "AI_UnsupportedFunctionalityError";
88
- var marker14 = `vercel.ai.error.${name13}`;
89
- var symbol14 = Symbol.for(marker14);
90
-
91
- //#endregion
92
- //#region node_modules/.pnpm/@ai-sdk+provider-utils@4.0.8_zod@4.3.5/node_modules/@ai-sdk/provider-utils/dist/index.mjs
93
- function convertAsyncIteratorToReadableStream(iterator) {
94
- let cancelled = false;
95
- return new ReadableStream({
96
- async pull(controller) {
97
- if (cancelled) return;
98
- try {
99
- const { value, done } = await iterator.next();
100
- if (done) controller.close();
101
- else controller.enqueue(value);
102
- } catch (error) {
103
- controller.error(error);
104
- }
105
- },
106
- async cancel(reason) {
107
- cancelled = true;
108
- if (iterator.return) try {
109
- await iterator.return(reason);
110
- } catch (e) {}
111
- }
112
- });
5
+ //#region src/consume/consume-ui-message-stream.ts
6
+ /**
7
+ * Consumes a UIMessageStream by fully reading it and returning the final UI message.
8
+ *
9
+ * This function uses `readUIMessageStream` to process all chunks from the stream
10
+ * and returns the last assembled message, which contains the complete content
11
+ * with all parts fully resolved.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const message = await consumeUIMessageStream(stream);
16
+ * console.log(message.parts); // All parts fully assembled
17
+ * ```
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const stream = result.toUIMessageStream();
22
+ * const filteredStream = filterUIMessageStream(stream, includeParts(['text']));
23
+ * const message = await consumeUIMessageStream(filteredStream);
24
+ * ```
25
+ */
26
+ async function consumeUIMessageStream(stream) {
27
+ let lastMessage;
28
+ for await (const message of readUIMessageStream({ stream })) lastMessage = message;
29
+ if (!lastMessage) throw new Error("Unexpected: stream ended without producing any messages");
30
+ return lastMessage;
113
31
  }
114
- var { btoa, atob } = globalThis;
115
- var name = "AI_DownloadError";
116
- var marker = `vercel.ai.error.${name}`;
117
- var symbol = Symbol.for(marker);
118
- var createIdGenerator = ({ prefix, size = 16, alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", separator = "-" } = {}) => {
119
- const generator = () => {
120
- const alphabetLength = alphabet.length;
121
- const chars = new Array(size);
122
- for (let i = 0; i < size; i++) chars[i] = alphabet[Math.random() * alphabetLength | 0];
123
- return chars.join("");
124
- };
125
- if (prefix == null) return generator;
126
- if (alphabet.includes(separator)) throw new InvalidArgumentError({
127
- argument: "separator",
128
- message: `The separator "${separator}" must not be part of the alphabet "${alphabet}".`
129
- });
130
- return () => `${prefix}${separator}${generator()}`;
131
- };
132
- var generateId = createIdGenerator();
133
- var schemaSymbol = Symbol.for("vercel.ai.schema");
134
32
 
135
33
  //#endregion
136
- //#region src/utils/internal/stream-utils.ts
34
+ //#region src/internal/get-part-type-from-chunk.ts
137
35
  /**
138
- * Maps a chunk to its corresponding UI message part type.
139
- * For tool-related chunks, returns 'tool-{toolName}' or 'dynamic-tool'.
36
+ * Derives the part type directly from a chunk's type.
37
+ * This avoids relying on `message.parts[-1]` which can be incorrect
38
+ * when chunks from different part types are interleaved.
39
+ *
40
+ * @param chunk - The chunk to derive part type from
41
+ * @param toolCallIdMap - Map to track toolCallId → partType for tool chunks
42
+ * @returns The part type string, or undefined for meta chunks
140
43
  */
141
- function getPartTypeFromChunk(chunk, toolCallStates) {
142
- switch (chunk.type) {
143
- case `tool-input-start`:
144
- case `tool-input-available`:
145
- case `tool-input-error`: {
146
- const toolChunk = chunk;
147
- return toolChunk.dynamic ? `dynamic-tool` : `tool-${toolChunk.toolName}`;
148
- }
149
- case `tool-input-delta`:
150
- case `tool-output-available`:
151
- case `tool-output-error`: {
152
- const toolChunk = chunk;
153
- if (toolChunk.dynamic) return `dynamic-tool`;
154
- const toolState = toolCallStates?.get(toolChunk.toolCallId);
155
- if (toolState) return toolState.dynamic ? `dynamic-tool` : `tool-${toolState.toolName}`;
156
- return `dynamic-tool`;
157
- }
44
+ function getPartTypeFromChunk(chunk, toolCallIdMap) {
45
+ const chunkType = chunk.type;
46
+ switch (chunkType) {
158
47
  case `text-start`:
159
48
  case `text-delta`:
160
49
  case `text-end`: return `text`;
161
50
  case `reasoning-start`:
162
51
  case `reasoning-delta`:
163
52
  case `reasoning-end`: return `reasoning`;
164
- case `file`: return `file`;
53
+ case `tool-input-start`: {
54
+ const c = chunk;
55
+ const partType = c.dynamic ? `dynamic-tool` : `tool-${c.toolName}`;
56
+ toolCallIdMap.set(c.toolCallId, partType);
57
+ return partType;
58
+ }
59
+ case `tool-input-delta`:
60
+ case `tool-input-available`:
61
+ case `tool-input-error`:
62
+ case `tool-output-available`:
63
+ case `tool-output-error`:
64
+ case `tool-output-denied`:
65
+ case `tool-approval-request`: {
66
+ const c = chunk;
67
+ return toolCallIdMap.get(c.toolCallId);
68
+ }
165
69
  case `source-url`: return `source-url`;
166
70
  case `source-document`: return `source-document`;
167
- default: return chunk.type;
71
+ case `file`: return `file`;
72
+ case `start`:
73
+ case `finish`:
74
+ case `start-step`:
75
+ case `finish-step`:
76
+ case `error`:
77
+ case `abort`:
78
+ case `message-metadata`: return;
79
+ default: if (chunkType.startsWith(`data-`)) return chunkType;
168
80
  }
81
+ throw new Error(`Unable to derive part type from chunk type: ${chunkType}`);
169
82
  }
83
+
84
+ //#endregion
85
+ //#region src/internal/utils.ts
170
86
  /**
171
87
  * Checks if a chunk is a control/meta chunk that should always pass through.
172
88
  */
@@ -201,7 +117,7 @@ function asArray(value) {
201
117
  }
202
118
 
203
119
  //#endregion
204
- //#region src/utils/internal/create-ui-message-stream-reader.ts
120
+ //#region src/internal/create-ui-message-stream-reader.ts
205
121
  /**
206
122
  * Creates an async generator that wraps a UIMessageStream with readUIMessageStream.
207
123
  *
@@ -226,7 +142,7 @@ async function* createUIMessageStreamReader(inputStream) {
226
142
  const iterator = readUIMessageStream({ stream: new ReadableStream({ start(c) {
227
143
  controller = c;
228
144
  } }) })[Symbol.asyncIterator]();
229
- const toolCallStates = /* @__PURE__ */ new Map();
145
+ const toolCallIdMap = /* @__PURE__ */ new Map();
230
146
  try {
231
147
  while (true) {
232
148
  const { done, value: chunk } = await inputReader.read();
@@ -234,13 +150,6 @@ async function* createUIMessageStreamReader(inputStream) {
234
150
  controller.close();
235
151
  break;
236
152
  }
237
- if (chunk.type === `tool-input-start`) {
238
- const toolChunk = chunk;
239
- toolCallStates.set(toolChunk.toolCallId, {
240
- toolName: toolChunk.toolName,
241
- dynamic: toolChunk.dynamic
242
- });
243
- }
244
153
  if (isMetaChunk(chunk) || isStepStartChunk(chunk) || isStepEndChunk(chunk)) {
245
154
  controller.enqueue(chunk);
246
155
  yield {
@@ -262,7 +171,7 @@ async function* createUIMessageStreamReader(inputStream) {
262
171
  const result = await iterator.next();
263
172
  const message = result.done ? void 0 : result.value;
264
173
  const messagePart = message?.parts[message.parts.length - 1];
265
- const expectedPartType = getPartTypeFromChunk(chunk, toolCallStates);
174
+ const expectedPartType = getPartTypeFromChunk(chunk, toolCallIdMap);
266
175
  yield {
267
176
  chunk,
268
177
  message,
@@ -279,7 +188,7 @@ async function* createUIMessageStreamReader(inputStream) {
279
188
  }
280
189
 
281
190
  //#endregion
282
- //#region src/map-ui-message-stream.ts
191
+ //#region src/map/map-ui-message-stream.ts
283
192
  /**
284
193
  * Maps/filters a UIMessageStream at the chunk level using readUIMessageStream.
285
194
  *
@@ -393,39 +302,11 @@ function mapUIMessageStream(stream, mapFn) {
393
302
  if (chunks.length > 0) yield* emitChunks(chunks);
394
303
  }
395
304
  }
396
- return createAsyncIterableStream(convertAsyncIteratorToReadableStream(processChunks()));
305
+ return createAsyncIterableStream(convertAsyncIterableToStream(processChunks()));
397
306
  }
398
307
 
399
308
  //#endregion
400
- //#region src/filter-ui-message-stream.ts
401
- /**
402
- * Creates a filter predicate that includes only the specified part types.
403
- *
404
- * @example
405
- * ```typescript
406
- * filterUIMessageStream(stream, includeParts(['text', 'tool-weather']));
407
- * ```
408
- */
409
- function includeParts(includePartTypes) {
410
- return ({ part }) => {
411
- const partType = part.type;
412
- return includePartTypes.includes(partType);
413
- };
414
- }
415
- /**
416
- * Creates a filter predicate that excludes the specified part types.
417
- *
418
- * @example
419
- * ```typescript
420
- * filterUIMessageStream(stream, excludeParts(['reasoning', 'tool-calculator']));
421
- * ```
422
- */
423
- function excludeParts(excludePartTypes) {
424
- return ({ part }) => {
425
- const partType = part.type;
426
- return !excludePartTypes.includes(partType);
427
- };
428
- }
309
+ //#region src/filter/filter-ui-message-stream.ts
429
310
  /**
430
311
  * Filters a UIMessageStream to include or exclude specific chunks.
431
312
  *
@@ -468,7 +349,11 @@ function filterUIMessageStream(stream, predicate) {
468
349
  }
469
350
 
470
351
  //#endregion
471
- //#region src/utils/internal/serialize-part-to-chunks.ts
352
+ //#region src/internal/serialize-part-to-chunks.ts
353
+ const generateId = createIdGenerator({
354
+ prefix: `aitxt`,
355
+ size: 24
356
+ });
472
357
  /**
473
358
  * Type guard to check if a message part is a source-url part.
474
359
  */
@@ -488,27 +373,16 @@ function isStepStartUIPart(part) {
488
373
  return part.type === "step-start";
489
374
  }
490
375
  /**
491
- * Extracts the part ID from a list of chunks belonging to the same part.
492
- */
493
- function getPartId(chunks) {
494
- for (const chunk of chunks) {
495
- if ("id" in chunk && chunk.id) return chunk.id;
496
- if ("toolCallId" in chunk && chunk.toolCallId) return chunk.toolCallId;
497
- }
498
- return "unknown";
499
- }
500
- /**
501
376
  * Serializes a UIMessagePart back to chunks (without step boundaries).
502
377
  *
503
378
  * This function converts a complete part (e.g., TextUIPart, ToolUIPart)
504
379
  * back into the chunk format used by UIMessageStream.
505
380
  *
506
381
  * @param part - The part to serialize
507
- * @param originalChunks - Original chunks for extracting IDs (for text/reasoning parts)
508
382
  * @returns Array of chunks representing the part
509
383
  */
510
- function serializePartToChunks(part, originalChunks) {
511
- if (isStepStartUIPart(part)) return originalChunks;
384
+ function serializePartToChunks(part) {
385
+ if (isStepStartUIPart(part)) return [{ type: "start-step" }];
512
386
  if (isFileUIPart(part)) return [{
513
387
  type: "file",
514
388
  mediaType: part.mediaType,
@@ -534,41 +408,46 @@ function serializePartToChunks(part, originalChunks) {
534
408
  type: part.type,
535
409
  data: part.data
536
410
  }];
537
- const id = getPartId(originalChunks);
538
- if (isTextUIPart(part)) return [
539
- {
540
- type: "text-start",
541
- id,
542
- providerMetadata: part.providerMetadata
543
- },
544
- {
545
- type: "text-delta",
546
- id,
547
- delta: part.text
548
- },
549
- {
550
- type: "text-end",
551
- id,
552
- providerMetadata: part.providerMetadata
553
- }
554
- ];
555
- if (isReasoningUIPart(part)) return [
556
- {
557
- type: "reasoning-start",
558
- id,
559
- providerMetadata: part.providerMetadata
560
- },
561
- {
562
- type: "reasoning-delta",
563
- id,
564
- delta: part.text
565
- },
566
- {
567
- type: "reasoning-end",
568
- id,
569
- providerMetadata: part.providerMetadata
570
- }
571
- ];
411
+ if (isTextUIPart(part)) {
412
+ const id = generateId();
413
+ return [
414
+ {
415
+ type: "text-start",
416
+ id,
417
+ providerMetadata: part.providerMetadata
418
+ },
419
+ {
420
+ type: "text-delta",
421
+ id,
422
+ delta: part.text
423
+ },
424
+ {
425
+ type: "text-end",
426
+ id,
427
+ providerMetadata: part.providerMetadata
428
+ }
429
+ ];
430
+ }
431
+ if (isReasoningUIPart(part)) {
432
+ const id = generateId();
433
+ return [
434
+ {
435
+ type: "reasoning-start",
436
+ id,
437
+ providerMetadata: part.providerMetadata
438
+ },
439
+ {
440
+ type: "reasoning-delta",
441
+ id,
442
+ delta: part.text
443
+ },
444
+ {
445
+ type: "reasoning-end",
446
+ id,
447
+ providerMetadata: part.providerMetadata
448
+ }
449
+ ];
450
+ }
572
451
  if (isToolOrDynamicToolUIPart(part)) {
573
452
  const dynamic = part.type === "dynamic-tool";
574
453
  const chunks = [{
@@ -604,11 +483,11 @@ function serializePartToChunks(part, originalChunks) {
604
483
  });
605
484
  return chunks;
606
485
  }
607
- return originalChunks;
486
+ throw new Error(`Cannot serialize unknown part type: ${part.type}`);
608
487
  }
609
488
 
610
489
  //#endregion
611
- //#region src/flat-map-ui-message-stream.ts
490
+ //#region src/flat-map/flat-map-ui-message-stream.ts
612
491
  /**
613
492
  * Creates a predicate that matches parts by their type.
614
493
  */
@@ -663,7 +542,7 @@ function flatMapUIMessageStream(...args) {
663
542
  index: allParts.length - 1,
664
543
  parts: allParts
665
544
  }));
666
- for (const part of parts) yield* emitChunks(serializePartToChunks(part, bufferedChunks));
545
+ for (const part of parts) yield* emitChunks(serializePartToChunks(part));
667
546
  bufferedChunks = [];
668
547
  lastBufferedPart = void 0;
669
548
  }
@@ -681,7 +560,7 @@ function flatMapUIMessageStream(...args) {
681
560
  continue;
682
561
  }
683
562
  if (isStepEndChunk(chunk)) {
684
- if (currentMode === "buffering" && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
563
+ if (currentMode === `buffering` && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
685
564
  if (stepStartEmitted) {
686
565
  yield chunk;
687
566
  stepStartEmitted = false;
@@ -697,35 +576,338 @@ function flatMapUIMessageStream(...args) {
697
576
  index: allParts.length - 1,
698
577
  parts: allParts
699
578
  }));
700
- for (const part$1 of parts) yield* emitChunks([part$1]);
579
+ for (const part of parts) yield* emitChunks([part]);
701
580
  continue;
702
581
  }
703
- if (!message) throw new Error("Unexpected: received content chunk but message is undefined");
582
+ if (!message) throw new Error(`Unexpected: received content chunk but message is undefined`);
704
583
  const currentPart = part;
705
584
  if (!currentPart) throw new Error(`Unexpected: received content chunk but part is undefined`);
706
585
  if (message.parts.length > lastPartCount) {
707
- if (currentMode === "buffering" && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
708
- if (currentMode === "streaming" && lastPartCount > 0) {
586
+ if (currentMode === `buffering` && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
587
+ if (currentMode === `streaming` && lastPartCount > 0) {
709
588
  const previousPart = message.parts[lastPartCount - 1];
710
589
  if (previousPart) allParts.push(previousPart);
711
590
  }
712
591
  if (!predicate || predicate(currentPart)) {
713
- currentMode = "buffering";
592
+ currentMode = `buffering`;
714
593
  bufferedChunks = [chunk];
715
594
  lastBufferedPart = currentPart;
716
595
  } else {
717
- currentMode = "streaming";
596
+ currentMode = `streaming`;
718
597
  yield* emitChunks([chunk]);
719
598
  }
720
599
  lastPartCount = message.parts.length;
721
- } else if (currentMode === "buffering") {
600
+ } else if (currentMode === `buffering`) {
722
601
  bufferedChunks.push(chunk);
723
602
  lastBufferedPart = currentPart;
724
- } else if (currentMode === "streaming") yield* emitChunks([chunk]);
603
+ } else if (currentMode === `streaming`) yield* emitChunks([chunk]);
604
+ }
605
+ }
606
+ return createAsyncIterableStream(convertAsyncIterableToStream(processChunks()));
607
+ }
608
+
609
+ //#endregion
610
+ //#region src/pipe/chunk-pipeline.ts
611
+ /**
612
+ * Pipeline for chunk-based operations.
613
+ * Operations receive individual chunks with their associated part type.
614
+ */
615
+ var ChunkPipeline = class ChunkPipeline {
616
+ consumed = false;
617
+ sourceIterable;
618
+ prevBuilder;
619
+ constructor(sourceIterable, prevBuilder = (s) => s) {
620
+ this.sourceIterable = sourceIterable;
621
+ this.prevBuilder = prevBuilder;
622
+ }
623
+ assertNotConsumed() {
624
+ if (this.consumed) throw new Error(`Pipeline has already been consumed.`);
625
+ }
626
+ filter(predicate) {
627
+ /**
628
+ * The predicate is cast to a simple function type for runtime execution.
629
+ */
630
+ const predicateFn = predicate;
631
+ const nextBuilder = (iterable) => {
632
+ const prevIterable = this.prevBuilder(iterable);
633
+ async function* generator() {
634
+ for await (const item of prevIterable) {
635
+ /**
636
+ * Meta chunks always pass through without filtering.
637
+ */
638
+ if (item.partType === void 0) {
639
+ yield item;
640
+ continue;
641
+ }
642
+ if (predicateFn({
643
+ chunk: item.chunk,
644
+ part: { type: item.partType }
645
+ })) yield item;
646
+ }
647
+ }
648
+ return generator();
649
+ };
650
+ return new ChunkPipeline(this.sourceIterable, nextBuilder);
651
+ }
652
+ /**
653
+ * Transforms chunks by applying a mapping function.
654
+ * The callback only receives content chunks because meta chunks pass through unchanged.
655
+ * Returning null filters out the chunk, while returning an array yields multiple chunks.
656
+ */
657
+ map(fn) {
658
+ const nextBuilder = (iterable) => {
659
+ const prevIterable = this.prevBuilder(iterable);
660
+ async function* generator() {
661
+ for await (const item of prevIterable) {
662
+ /**
663
+ * Meta chunks always pass through unchanged without transformation.
664
+ */
665
+ if (item.partType === void 0) {
666
+ yield item;
667
+ continue;
668
+ }
669
+ /**
670
+ * The asArray utility normalizes the result: null becomes an empty array,
671
+ * a single chunk becomes an array with one element, and arrays pass through as-is.
672
+ * An empty array means the chunk is filtered out and not yielded.
673
+ */
674
+ const chunks = asArray(fn({
675
+ chunk: item.chunk,
676
+ part: { type: item.partType }
677
+ }));
678
+ for (const chunk of chunks) yield {
679
+ chunk,
680
+ partType: item.partType
681
+ };
682
+ }
683
+ }
684
+ return generator();
685
+ };
686
+ return new ChunkPipeline(this.sourceIterable, nextBuilder);
687
+ }
688
+ on(predicate, callback) {
689
+ /**
690
+ * The predicate is cast to a simple function type for runtime execution.
691
+ */
692
+ const predicateFn = predicate;
693
+ const nextBuilder = (iterable) => {
694
+ const prevIterable = this.prevBuilder(iterable);
695
+ async function* generator() {
696
+ for await (const item of prevIterable) {
697
+ /**
698
+ * Build the input object for the predicate and callback.
699
+ * Content chunks get a part object with the type, while meta chunks get undefined.
700
+ */
701
+ const input = {
702
+ chunk: item.chunk,
703
+ part: item.partType !== void 0 ? { type: item.partType } : void 0
704
+ };
705
+ if (predicateFn(input)) await callback(input);
706
+ yield item;
707
+ }
708
+ }
709
+ return generator();
710
+ };
711
+ return new ChunkPipeline(this.sourceIterable, nextBuilder);
712
+ }
713
+ /**
714
+ * Executes the pipeline and returns the resulting stream.
715
+ * All chunks pass through including step boundaries and meta chunks.
716
+ */
717
+ toStream() {
718
+ this.assertNotConsumed();
719
+ this.consumed = true;
720
+ const processedIterable = this.prevBuilder(this.sourceIterable);
721
+ /**
722
+ * Unwrap internal chunks by extracting just the chunk property.
723
+ * This removes the internal partType metadata used for pipeline operations.
724
+ */
725
+ async function* generator() {
726
+ for await (const item of processedIterable) yield item.chunk;
725
727
  }
728
+ return createAsyncIterableStream(convertAsyncIterableToStream(generator()));
729
+ }
730
+ [Symbol.asyncIterator]() {
731
+ return this.toStream()[Symbol.asyncIterator]();
726
732
  }
727
- return createAsyncIterableStream(convertAsyncIteratorToReadableStream(processChunks()));
733
+ };
734
+
735
+ //#endregion
736
+ //#region src/pipe/pipe.ts
737
+ /**
738
+ * Creates an iterable with part type information from a raw chunk stream.
739
+ *
740
+ * Part type is derived directly from the chunk's type rather than from message.parts[-1],
741
+ * which ensures correct association when chunks from different part types are interleaved.
742
+ *
743
+ * Step boundaries (start-step/finish-step) pass through as-is with undefined partType.
744
+ * The AI SDK's readUIMessageStream handles all step boundary scenarios gracefully.
745
+ */
746
+ async function* createIterable(input) {
747
+ /**
748
+ * Tracks toolCallId → partType mapping for tool chunks
749
+ */
750
+ const toolCallIdMap = /* @__PURE__ */ new Map();
751
+ /**
752
+ * Use Symbol.asyncIterator if available (AsyncIterable), otherwise convert ReadableStream
753
+ */
754
+ const iterable = Symbol.asyncIterator in input ? input : createAsyncIterableStream(input);
755
+ for await (const chunk of iterable) yield {
756
+ chunk,
757
+ partType: getPartTypeFromChunk(chunk, toolCallIdMap)
758
+ };
759
+ }
760
+ /**
761
+ * Creates a type-safe pipeline for UIMessageStream operations.
762
+ *
763
+ * @example
764
+ * ```typescript
765
+ * pipe<MyUIMessage>(stream)
766
+ * .filter(isPartType('text'))
767
+ * .map(({ chunk }) => chunk)
768
+ * .toStream();
769
+ * ```
770
+ */
771
+ function pipe(input) {
772
+ return new ChunkPipeline(createIterable(input));
773
+ }
774
+
775
+ //#endregion
776
+ //#region src/pipe/type-guards.ts
777
+ /**
778
+ * Creates a filter guard that includes specific content chunk types.
779
+ * Use with `.filter()` to narrow to specific chunks.
780
+ *
781
+ * @example
782
+ * ```typescript
783
+ * pipe<MyUIMessage>(stream)
784
+ * .filter(includeChunks('text-delta'))
785
+ * .map(({ chunk }) => chunk); // chunk is narrowed to text-delta chunk
786
+ *
787
+ * pipe<MyUIMessage>(stream)
788
+ * .filter(includeChunks(['text-delta', 'text-end']))
789
+ * .map(({ chunk }) => chunk); // chunk is narrowed to text-delta | text-end
790
+ * ```
791
+ */
792
+ function includeChunks(types) {
793
+ const typeArray = Array.isArray(types) ? types : [types];
794
+ const guard = (input) => typeArray.includes(input.chunk.type);
795
+ return guard;
796
+ }
797
+ /**
798
+ * Creates a filter guard that includes specific part types.
799
+ * Use with `.filter()` to narrow to chunks belonging to specific parts.
800
+ *
801
+ * @example
802
+ * ```typescript
803
+ * pipe<MyUIMessage>(stream)
804
+ * .filter(includeParts('text'))
805
+ * .map(({ chunk, part }) => chunk); // chunk is narrowed to text chunks
806
+ *
807
+ * pipe<MyUIMessage>(stream)
808
+ * .filter(includeParts(['text', 'reasoning']))
809
+ * .map(({ chunk }) => chunk); // chunk is text or reasoning chunks
810
+ * ```
811
+ */
812
+ function includeParts(types) {
813
+ const typeArray = Array.isArray(types) ? types : [types];
814
+ const guard = (input) => typeArray.includes(input.part?.type);
815
+ return guard;
816
+ }
817
+ /**
818
+ * Creates a filter guard that excludes specific content chunk types.
819
+ * Use with `.filter()` to keep all chunks except the specified types.
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * pipe<MyUIMessage>(stream)
824
+ * .filter(excludeChunks('text-delta'))
825
+ * .map(({ chunk }) => chunk); // chunk is all content chunks except text-delta
826
+ *
827
+ * pipe<MyUIMessage>(stream)
828
+ * .filter(excludeChunks(['text-start', 'text-end']))
829
+ * .map(({ chunk }) => chunk); // excludes text-start and text-end
830
+ * ```
831
+ */
832
+ function excludeChunks(types) {
833
+ const typeArray = Array.isArray(types) ? types : [types];
834
+ const guard = (input) => !typeArray.includes(input.chunk.type);
835
+ return guard;
836
+ }
837
+ /**
838
+ * Creates a filter guard that excludes specific part types.
839
+ * Use with `.filter()` to keep all chunks except those belonging to specified parts.
840
+ *
841
+ * @example
842
+ * ```typescript
843
+ * pipe<MyUIMessage>(stream)
844
+ * .filter(excludeParts('text'))
845
+ * .map(({ chunk }) => chunk); // excludes all text chunks
846
+ *
847
+ * pipe<MyUIMessage>(stream)
848
+ * .filter(excludeParts(['text', 'reasoning']))
849
+ * .map(({ chunk }) => chunk); // excludes text and reasoning chunks
850
+ * ```
851
+ */
852
+ function excludeParts(types) {
853
+ const typeArray = Array.isArray(types) ? types : [types];
854
+ const guard = (input) => !typeArray.includes(input.part?.type);
855
+ return guard;
856
+ }
857
+ /**
858
+ * Creates an observe guard that matches specific chunk types, including meta chunks.
859
+ * Use with `.on()` to observe specific chunks without filtering.
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * // Observe content chunks
864
+ * pipe<MyUIMessage>(stream)
865
+ * .on(chunkType('text-delta'), ({ chunk, part }) => {
866
+ * // chunk is text-delta, part is { type: 'text' }
867
+ * });
868
+ *
869
+ * // Observe meta chunks
870
+ * pipe<MyUIMessage>(stream)
871
+ * .on(chunkType('start'), ({ chunk, part }) => {
872
+ * // chunk is start chunk, part is undefined
873
+ * });
874
+ *
875
+ * // Observe multiple chunk types
876
+ * pipe<MyUIMessage>(stream)
877
+ * .on(chunkType(['text-delta', 'start']), ({ chunk, part }) => {
878
+ * // chunk is text-delta | start, part is { type: 'text' } | undefined
879
+ * });
880
+ * ```
881
+ */
882
+ function chunkType(types) {
883
+ const typeArray = Array.isArray(types) ? types : [types];
884
+ const guard = (input) => typeArray.includes(input.chunk.type);
885
+ return guard;
886
+ }
887
+ /**
888
+ * Creates an observe guard that matches specific part types.
889
+ * Use with `.on()` to observe chunks belonging to specific parts without filtering.
890
+ *
891
+ * @example
892
+ * ```typescript
893
+ * // Observe text parts
894
+ * pipe<MyUIMessage>(stream)
895
+ * .on(partType('text'), ({ chunk, part }) => {
896
+ * // chunk is text chunk, part is { type: 'text' }
897
+ * });
898
+ *
899
+ * // Observe multiple part types
900
+ * pipe<MyUIMessage>(stream)
901
+ * .on(partType(['text', 'reasoning']), ({ chunk, part }) => {
902
+ * // chunk is text | reasoning chunk, part is { type: 'text' | 'reasoning' }
903
+ * });
904
+ * ```
905
+ */
906
+ function partType(types) {
907
+ const typeArray = Array.isArray(types) ? types : [types];
908
+ const guard = (input) => typeArray.includes(input.part?.type);
909
+ return guard;
728
910
  }
729
911
 
730
912
  //#endregion
731
- export { excludeParts, filterUIMessageStream, flatMapUIMessageStream, includeParts, mapUIMessageStream, partTypeIs };
913
+ export { chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, mapUIMessageStream, partType, partTypeIs, pipe };