ai-stream-utils 1.6.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,7 +1,8 @@
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 src/consume-ui-message-stream.ts
5
+ //#region src/consume/consume-ui-message-stream.ts
5
6
  /**
6
7
  * Consumes a UIMessageStream by fully reading it and returning the final UI message.
7
8
  *
@@ -30,172 +31,58 @@ async function consumeUIMessageStream(stream) {
30
31
  }
31
32
 
32
33
  //#endregion
33
- //#region node_modules/.pnpm/@ai-sdk+provider@3.0.4/node_modules/@ai-sdk/provider/dist/index.mjs
34
- var marker$1 = "vercel.ai.error";
35
- var symbol$1 = Symbol.for(marker$1);
36
- var _a, _b;
37
- var AISDKError = class _AISDKError extends (_b = Error, _a = symbol$1, _b) {
38
- /**
39
- * Creates an AI SDK Error.
40
- *
41
- * @param {Object} params - The parameters for creating the error.
42
- * @param {string} params.name - The name of the error.
43
- * @param {string} params.message - The error message.
44
- * @param {unknown} [params.cause] - The underlying cause of the error.
45
- */
46
- constructor({ name: name14, message, cause }) {
47
- super(message);
48
- this[_a] = true;
49
- this.name = name14;
50
- this.cause = cause;
51
- }
52
- /**
53
- * Checks if the given error is an AI SDK Error.
54
- * @param {unknown} error - The error to check.
55
- * @returns {boolean} True if the error is an AI SDK Error, false otherwise.
56
- */
57
- static isInstance(error) {
58
- return _AISDKError.hasMarker(error, marker$1);
59
- }
60
- static hasMarker(error, marker15) {
61
- const markerSymbol = Symbol.for(marker15);
62
- return error != null && typeof error === "object" && markerSymbol in error && typeof error[markerSymbol] === "boolean" && error[markerSymbol] === true;
63
- }
64
- };
65
- var name$1 = "AI_APICallError";
66
- var marker2 = `vercel.ai.error.${name$1}`;
67
- var symbol2 = Symbol.for(marker2);
68
- var name2 = "AI_EmptyResponseBodyError";
69
- var marker3 = `vercel.ai.error.${name2}`;
70
- var symbol3 = Symbol.for(marker3);
71
- var name3 = "AI_InvalidArgumentError";
72
- var marker4 = `vercel.ai.error.${name3}`;
73
- var symbol4 = Symbol.for(marker4);
74
- var _a4, _b4;
75
- var InvalidArgumentError = class extends (_b4 = AISDKError, _a4 = symbol4, _b4) {
76
- constructor({ message, cause, argument }) {
77
- super({
78
- name: name3,
79
- message,
80
- cause
81
- });
82
- this[_a4] = true;
83
- this.argument = argument;
84
- }
85
- static isInstance(error) {
86
- return AISDKError.hasMarker(error, marker4);
87
- }
88
- };
89
- var name4 = "AI_InvalidPromptError";
90
- var marker5 = `vercel.ai.error.${name4}`;
91
- var symbol5 = Symbol.for(marker5);
92
- var name5 = "AI_InvalidResponseDataError";
93
- var marker6 = `vercel.ai.error.${name5}`;
94
- var symbol6 = Symbol.for(marker6);
95
- var name6 = "AI_JSONParseError";
96
- var marker7 = `vercel.ai.error.${name6}`;
97
- var symbol7 = Symbol.for(marker7);
98
- var name7 = "AI_LoadAPIKeyError";
99
- var marker8 = `vercel.ai.error.${name7}`;
100
- var symbol8 = Symbol.for(marker8);
101
- var name8 = "AI_LoadSettingError";
102
- var marker9 = `vercel.ai.error.${name8}`;
103
- var symbol9 = Symbol.for(marker9);
104
- var name9 = "AI_NoContentGeneratedError";
105
- var marker10 = `vercel.ai.error.${name9}`;
106
- var symbol10 = Symbol.for(marker10);
107
- var name10 = "AI_NoSuchModelError";
108
- var marker11 = `vercel.ai.error.${name10}`;
109
- var symbol11 = Symbol.for(marker11);
110
- var name11 = "AI_TooManyEmbeddingValuesForCallError";
111
- var marker12 = `vercel.ai.error.${name11}`;
112
- var symbol12 = Symbol.for(marker12);
113
- var name12 = "AI_TypeValidationError";
114
- var marker13 = `vercel.ai.error.${name12}`;
115
- var symbol13 = Symbol.for(marker13);
116
- var name13 = "AI_UnsupportedFunctionalityError";
117
- var marker14 = `vercel.ai.error.${name13}`;
118
- var symbol14 = Symbol.for(marker14);
119
-
120
- //#endregion
121
- //#region node_modules/.pnpm/@ai-sdk+provider-utils@4.0.8_zod@4.3.5/node_modules/@ai-sdk/provider-utils/dist/index.mjs
122
- function convertAsyncIteratorToReadableStream(iterator) {
123
- let cancelled = false;
124
- return new ReadableStream({
125
- async pull(controller) {
126
- if (cancelled) return;
127
- try {
128
- const { value, done } = await iterator.next();
129
- if (done) controller.close();
130
- else controller.enqueue(value);
131
- } catch (error) {
132
- controller.error(error);
133
- }
134
- },
135
- async cancel(reason) {
136
- cancelled = true;
137
- if (iterator.return) try {
138
- await iterator.return(reason);
139
- } catch (e) {}
140
- }
141
- });
142
- }
143
- var { btoa, atob } = globalThis;
144
- var name = "AI_DownloadError";
145
- var marker = `vercel.ai.error.${name}`;
146
- var symbol = Symbol.for(marker);
147
- var createIdGenerator = ({ prefix, size = 16, alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", separator = "-" } = {}) => {
148
- const generator = () => {
149
- const alphabetLength = alphabet.length;
150
- const chars = new Array(size);
151
- for (let i = 0; i < size; i++) chars[i] = alphabet[Math.random() * alphabetLength | 0];
152
- return chars.join("");
153
- };
154
- if (prefix == null) return generator;
155
- if (alphabet.includes(separator)) throw new InvalidArgumentError({
156
- argument: "separator",
157
- message: `The separator "${separator}" must not be part of the alphabet "${alphabet}".`
158
- });
159
- return () => `${prefix}${separator}${generator()}`;
160
- };
161
- var generateId = createIdGenerator();
162
- var schemaSymbol = Symbol.for("vercel.ai.schema");
163
-
164
- //#endregion
165
- //#region src/utils/internal/stream-utils.ts
34
+ //#region src/internal/get-part-type-from-chunk.ts
166
35
  /**
167
- * Maps a chunk to its corresponding UI message part type.
168
- * 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
169
43
  */
170
- function getPartTypeFromChunk(chunk, toolCallStates) {
171
- switch (chunk.type) {
172
- case `tool-input-start`:
173
- case `tool-input-available`:
174
- case `tool-input-error`: {
175
- const toolChunk = chunk;
176
- return toolChunk.dynamic ? `dynamic-tool` : `tool-${toolChunk.toolName}`;
177
- }
178
- case `tool-input-delta`:
179
- case `tool-output-available`:
180
- case `tool-output-error`: {
181
- const toolChunk = chunk;
182
- if (toolChunk.dynamic) return `dynamic-tool`;
183
- const toolState = toolCallStates?.get(toolChunk.toolCallId);
184
- if (toolState) return toolState.dynamic ? `dynamic-tool` : `tool-${toolState.toolName}`;
185
- return `dynamic-tool`;
186
- }
44
+ function getPartTypeFromChunk(chunk, toolCallIdMap) {
45
+ const chunkType = chunk.type;
46
+ switch (chunkType) {
187
47
  case `text-start`:
188
48
  case `text-delta`:
189
49
  case `text-end`: return `text`;
190
50
  case `reasoning-start`:
191
51
  case `reasoning-delta`:
192
52
  case `reasoning-end`: return `reasoning`;
193
- 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
+ }
194
69
  case `source-url`: return `source-url`;
195
70
  case `source-document`: return `source-document`;
196
- 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;
197
80
  }
81
+ throw new Error(`Unable to derive part type from chunk type: ${chunkType}`);
198
82
  }
83
+
84
+ //#endregion
85
+ //#region src/internal/utils.ts
199
86
  /**
200
87
  * Checks if a chunk is a control/meta chunk that should always pass through.
201
88
  */
@@ -230,7 +117,7 @@ function asArray(value) {
230
117
  }
231
118
 
232
119
  //#endregion
233
- //#region src/utils/internal/create-ui-message-stream-reader.ts
120
+ //#region src/internal/create-ui-message-stream-reader.ts
234
121
  /**
235
122
  * Creates an async generator that wraps a UIMessageStream with readUIMessageStream.
236
123
  *
@@ -255,7 +142,7 @@ async function* createUIMessageStreamReader(inputStream) {
255
142
  const iterator = readUIMessageStream({ stream: new ReadableStream({ start(c) {
256
143
  controller = c;
257
144
  } }) })[Symbol.asyncIterator]();
258
- const toolCallStates = /* @__PURE__ */ new Map();
145
+ const toolCallIdMap = /* @__PURE__ */ new Map();
259
146
  try {
260
147
  while (true) {
261
148
  const { done, value: chunk } = await inputReader.read();
@@ -263,13 +150,6 @@ async function* createUIMessageStreamReader(inputStream) {
263
150
  controller.close();
264
151
  break;
265
152
  }
266
- if (chunk.type === `tool-input-start`) {
267
- const toolChunk = chunk;
268
- toolCallStates.set(toolChunk.toolCallId, {
269
- toolName: toolChunk.toolName,
270
- dynamic: toolChunk.dynamic
271
- });
272
- }
273
153
  if (isMetaChunk(chunk) || isStepStartChunk(chunk) || isStepEndChunk(chunk)) {
274
154
  controller.enqueue(chunk);
275
155
  yield {
@@ -291,7 +171,7 @@ async function* createUIMessageStreamReader(inputStream) {
291
171
  const result = await iterator.next();
292
172
  const message = result.done ? void 0 : result.value;
293
173
  const messagePart = message?.parts[message.parts.length - 1];
294
- const expectedPartType = getPartTypeFromChunk(chunk, toolCallStates);
174
+ const expectedPartType = getPartTypeFromChunk(chunk, toolCallIdMap);
295
175
  yield {
296
176
  chunk,
297
177
  message,
@@ -308,7 +188,7 @@ async function* createUIMessageStreamReader(inputStream) {
308
188
  }
309
189
 
310
190
  //#endregion
311
- //#region src/map-ui-message-stream.ts
191
+ //#region src/map/map-ui-message-stream.ts
312
192
  /**
313
193
  * Maps/filters a UIMessageStream at the chunk level using readUIMessageStream.
314
194
  *
@@ -422,39 +302,11 @@ function mapUIMessageStream(stream, mapFn) {
422
302
  if (chunks.length > 0) yield* emitChunks(chunks);
423
303
  }
424
304
  }
425
- return createAsyncIterableStream(convertAsyncIteratorToReadableStream(processChunks()));
305
+ return createAsyncIterableStream(convertAsyncIterableToStream(processChunks()));
426
306
  }
427
307
 
428
308
  //#endregion
429
- //#region src/filter-ui-message-stream.ts
430
- /**
431
- * Creates a filter predicate that includes only the specified part types.
432
- *
433
- * @example
434
- * ```typescript
435
- * filterUIMessageStream(stream, includeParts(['text', 'tool-weather']));
436
- * ```
437
- */
438
- function includeParts(includePartTypes) {
439
- return ({ part }) => {
440
- const partType = part.type;
441
- return includePartTypes.includes(partType);
442
- };
443
- }
444
- /**
445
- * Creates a filter predicate that excludes the specified part types.
446
- *
447
- * @example
448
- * ```typescript
449
- * filterUIMessageStream(stream, excludeParts(['reasoning', 'tool-calculator']));
450
- * ```
451
- */
452
- function excludeParts(excludePartTypes) {
453
- return ({ part }) => {
454
- const partType = part.type;
455
- return !excludePartTypes.includes(partType);
456
- };
457
- }
309
+ //#region src/filter/filter-ui-message-stream.ts
458
310
  /**
459
311
  * Filters a UIMessageStream to include or exclude specific chunks.
460
312
  *
@@ -497,7 +349,11 @@ function filterUIMessageStream(stream, predicate) {
497
349
  }
498
350
 
499
351
  //#endregion
500
- //#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
+ });
501
357
  /**
502
358
  * Type guard to check if a message part is a source-url part.
503
359
  */
@@ -517,27 +373,16 @@ function isStepStartUIPart(part) {
517
373
  return part.type === "step-start";
518
374
  }
519
375
  /**
520
- * Extracts the part ID from a list of chunks belonging to the same part.
521
- */
522
- function getPartId(chunks) {
523
- for (const chunk of chunks) {
524
- if ("id" in chunk && chunk.id) return chunk.id;
525
- if ("toolCallId" in chunk && chunk.toolCallId) return chunk.toolCallId;
526
- }
527
- return "unknown";
528
- }
529
- /**
530
376
  * Serializes a UIMessagePart back to chunks (without step boundaries).
531
377
  *
532
378
  * This function converts a complete part (e.g., TextUIPart, ToolUIPart)
533
379
  * back into the chunk format used by UIMessageStream.
534
380
  *
535
381
  * @param part - The part to serialize
536
- * @param originalChunks - Original chunks for extracting IDs (for text/reasoning parts)
537
382
  * @returns Array of chunks representing the part
538
383
  */
539
- function serializePartToChunks(part, originalChunks) {
540
- if (isStepStartUIPart(part)) return originalChunks;
384
+ function serializePartToChunks(part) {
385
+ if (isStepStartUIPart(part)) return [{ type: "start-step" }];
541
386
  if (isFileUIPart(part)) return [{
542
387
  type: "file",
543
388
  mediaType: part.mediaType,
@@ -563,41 +408,46 @@ function serializePartToChunks(part, originalChunks) {
563
408
  type: part.type,
564
409
  data: part.data
565
410
  }];
566
- const id = getPartId(originalChunks);
567
- if (isTextUIPart(part)) return [
568
- {
569
- type: "text-start",
570
- id,
571
- providerMetadata: part.providerMetadata
572
- },
573
- {
574
- type: "text-delta",
575
- id,
576
- delta: part.text
577
- },
578
- {
579
- type: "text-end",
580
- id,
581
- providerMetadata: part.providerMetadata
582
- }
583
- ];
584
- if (isReasoningUIPart(part)) return [
585
- {
586
- type: "reasoning-start",
587
- id,
588
- providerMetadata: part.providerMetadata
589
- },
590
- {
591
- type: "reasoning-delta",
592
- id,
593
- delta: part.text
594
- },
595
- {
596
- type: "reasoning-end",
597
- id,
598
- providerMetadata: part.providerMetadata
599
- }
600
- ];
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
+ }
601
451
  if (isToolOrDynamicToolUIPart(part)) {
602
452
  const dynamic = part.type === "dynamic-tool";
603
453
  const chunks = [{
@@ -633,11 +483,11 @@ function serializePartToChunks(part, originalChunks) {
633
483
  });
634
484
  return chunks;
635
485
  }
636
- return originalChunks;
486
+ throw new Error(`Cannot serialize unknown part type: ${part.type}`);
637
487
  }
638
488
 
639
489
  //#endregion
640
- //#region src/flat-map-ui-message-stream.ts
490
+ //#region src/flat-map/flat-map-ui-message-stream.ts
641
491
  /**
642
492
  * Creates a predicate that matches parts by their type.
643
493
  */
@@ -692,7 +542,7 @@ function flatMapUIMessageStream(...args) {
692
542
  index: allParts.length - 1,
693
543
  parts: allParts
694
544
  }));
695
- for (const part of parts) yield* emitChunks(serializePartToChunks(part, bufferedChunks));
545
+ for (const part of parts) yield* emitChunks(serializePartToChunks(part));
696
546
  bufferedChunks = [];
697
547
  lastBufferedPart = void 0;
698
548
  }
@@ -710,7 +560,7 @@ function flatMapUIMessageStream(...args) {
710
560
  continue;
711
561
  }
712
562
  if (isStepEndChunk(chunk)) {
713
- if (currentMode === "buffering" && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
563
+ if (currentMode === `buffering` && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
714
564
  if (stepStartEmitted) {
715
565
  yield chunk;
716
566
  stepStartEmitted = false;
@@ -726,35 +576,338 @@ function flatMapUIMessageStream(...args) {
726
576
  index: allParts.length - 1,
727
577
  parts: allParts
728
578
  }));
729
- for (const part$1 of parts) yield* emitChunks([part$1]);
579
+ for (const part of parts) yield* emitChunks([part]);
730
580
  continue;
731
581
  }
732
- 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`);
733
583
  const currentPart = part;
734
584
  if (!currentPart) throw new Error(`Unexpected: received content chunk but part is undefined`);
735
585
  if (message.parts.length > lastPartCount) {
736
- if (currentMode === "buffering" && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
737
- if (currentMode === "streaming" && lastPartCount > 0) {
586
+ if (currentMode === `buffering` && lastBufferedPart) yield* flushBufferedPart(lastBufferedPart);
587
+ if (currentMode === `streaming` && lastPartCount > 0) {
738
588
  const previousPart = message.parts[lastPartCount - 1];
739
589
  if (previousPart) allParts.push(previousPart);
740
590
  }
741
591
  if (!predicate || predicate(currentPart)) {
742
- currentMode = "buffering";
592
+ currentMode = `buffering`;
743
593
  bufferedChunks = [chunk];
744
594
  lastBufferedPart = currentPart;
745
595
  } else {
746
- currentMode = "streaming";
596
+ currentMode = `streaming`;
747
597
  yield* emitChunks([chunk]);
748
598
  }
749
599
  lastPartCount = message.parts.length;
750
- } else if (currentMode === "buffering") {
600
+ } else if (currentMode === `buffering`) {
751
601
  bufferedChunks.push(chunk);
752
602
  lastBufferedPart = currentPart;
753
- } 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;
754
727
  }
728
+ return createAsyncIterableStream(convertAsyncIterableToStream(generator()));
729
+ }
730
+ [Symbol.asyncIterator]() {
731
+ return this.toStream()[Symbol.asyncIterator]();
755
732
  }
756
- 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;
757
910
  }
758
911
 
759
912
  //#endregion
760
- export { consumeUIMessageStream, 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 };