ai-stream-utils 2.0.1 → 2.2.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/README.md CHANGED
@@ -15,9 +15,9 @@ This library provides composable filter and transformation utilities for UI mess
15
15
 
16
16
  The AI SDK UI message stream created by [`toUIMessageStream()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text#to-ui-message-stream) streams all parts (text, tools, reasoning, etc.) to the client by default. However, you may want to:
17
17
 
18
- - **Filter**: Tool calls like database queries often contain large amounts of data or sensitive information that should not be streamed to the client
18
+ - **Filter**: Tool calls like database searches often contain large amounts of data or sensitive information that should not be streamed to the client
19
19
  - **Transform**: Modify text or tool outputs while they are streamed to the client
20
- - **Observe**: Log stream lifecycle events, tool calls, or other chunks without consuming or modifying the stream
20
+ - **Observe**: Log stream lifecycle events, update states, or run side-effects without modifying the stream
21
21
 
22
22
  This library provides type-safe, composable utilities for all these use cases.
23
23
 
@@ -52,21 +52,56 @@ const stream = pipe(result.toUIMessageStream())
52
52
  .toStream();
53
53
  ```
54
54
 
55
- **Type guards** provide a simpler API for common filtering patterns:
55
+ #### Type Guards
56
56
 
57
- - `includeChunks("text-delta")` or `includeChunks(["text-delta", "text-end"])`: Include specific chunk types
58
- - `excludeChunks("text-delta")` or `excludeChunks(["text-delta", "text-end"])`: Exclude specific chunk types
59
- - `includeParts("text")` or `includeParts(["text", "reasoning"])`: Include specific part types
60
- - `excludeParts("reasoning")` or `excludeParts(["reasoning", "tool-database"])`: Exclude specific part types
57
+ Generic type guards provide a simpler API for common filtering patterns:
61
58
 
62
- **Example:** Exclude tool calls from the client.
59
+ - `includeChunks("text-delta")` or `includeChunks(["text-delta", "text-end"])`: Include **only** specific chunk types
60
+ - `excludeChunks("text-delta")` or `excludeChunks(["text-delta", "text-end"])`: Exclude **only** specific chunk types
61
+ - `includeParts("text")` or `includeParts(["text", "reasoning"])`: Include **only** specific part types
62
+ - `excludeParts("reasoning")` or `excludeParts(["reasoning", "tool-database"])`: Exclude **only** specific part types
63
+
64
+ Filtering tools is the most common use case and the tool-filter type guards provide a convenient API for filtering tool chunks by tool name:
65
+
66
+ - `excludeTools()` or `excludeTools("weather")` or `excludeTools(["weather", "database"])`: Exclude all tools or specific tools by name
67
+ - `includeTools()` or `includeTools("weather")` or `includeTools(["weather", "database"])`: Include all tools or specific tools by name
68
+
69
+ > [!NOTE]
70
+ > The tool-filter type guards only affect tool chunks. Non-tool chunks will pass through.
71
+
72
+ #### Examples
73
+
74
+ Exclude tool calls from the client.
63
75
 
64
76
  ```typescript
77
+ // Exclude by part type (requires "tool-" prefix)
65
78
  const stream = pipe(result.toUIMessageStream())
66
79
  .filter(excludeParts(["tool-weather", "tool-database"]))
67
80
  .toStream();
81
+
82
+ // Exclude by tool name (without "tool-" prefix)
83
+ const stream = pipe(result.toUIMessageStream())
84
+ .filter(excludeTools(["weather", "database"]))
85
+ .toStream();
86
+
87
+ // Exclude all tools
88
+ const stream = pipe(result.toUIMessageStream()).filter(excludeTools()).toStream();
89
+
90
+ // Include only specific tools (without "tool-" prefix)
91
+ const stream = pipe(result.toUIMessageStream())
92
+ .filter(includeTools(["weather"]))
93
+ .toStream();
68
94
  ```
69
95
 
96
+ > [!NOTE]
97
+ > `excludeTools()` and `includeTools()` filters tool chunks on the server before streaming to the client. This affects all tool types including:
98
+ >
99
+ > - Server-side tools with `execute` functions
100
+ > - Client-side tools without `execute` functions
101
+ > - Tools that require human approval via `needsApproval`
102
+ >
103
+ > Excluded tools will not appear in the client's message parts, so users won't see tool call UI or be able to approve/reject filtered tools.
104
+
70
105
  ### `.map()`
71
106
 
72
107
  Transform chunks by returning a chunk, an array of chunks, or `null` to exclude.
@@ -94,7 +129,9 @@ const stream = pipe(result.toUIMessageStream())
94
129
  .toStream();
95
130
  ```
96
131
 
97
- **Example:** Convert text to uppercase.
132
+ #### Examples
133
+
134
+ Convert text to uppercase.
98
135
 
99
136
  ```typescript
100
137
  const stream = pipe(result.toUIMessageStream())
@@ -127,30 +164,59 @@ const stream = pipe(result.toUIMessageStream())
127
164
  .toStream();
128
165
  ```
129
166
 
167
+ #### Type Guards
168
+
130
169
  **Type guard** provides a type-safe way to observe specific chunk types:
131
170
 
132
171
  - `chunkType("text-delta")` or `chunkType(["start", "finish"])`: Observe specific chunk types
133
172
  - `partType("text")` or `partType(["text", "reasoning"])`: Observe chunks belonging to specific part types
173
+ - `toolCall()` or `toolCall({ tool: "weather" })` or `toolCall({ state: "output-available" })`: Observe tool state transitions
174
+
175
+ The `toolCall()` guard matches tool chunks representing state transitions (not streaming events):
176
+
177
+ - `input-available`: Tool input fully parsed
178
+ - `approval-requested`: Tool awaiting user approval
179
+ - `output-available`: Tool execution completed
180
+ - `output-error`: Tool execution failed
181
+ - `output-denied`: User denied approval
134
182
 
135
183
  > [!NOTE]
136
184
  > The `partType` type guard still operates on chunks. That means `partType("text")` will match any text chunks such as `text-start`, `text-delta`, and `text-end`.
137
185
 
138
- **Example:** Log stream lifecycle events.
186
+ #### Examples
187
+
188
+ Log stream lifecycle events.
139
189
 
140
190
  ```typescript
141
191
  const stream = pipe(result.toUIMessageStream())
142
- .on(chunkType("start"), () => {
143
- console.log("Stream started");
192
+ .on(chunkType("start"), ({ chunk }) => {
193
+ console.log("Stream started:", chunk.messageId);
144
194
  })
145
195
  .on(chunkType("finish"), ({ chunk }) => {
146
196
  console.log("Stream finished:", chunk.finishReason);
147
197
  })
148
198
  .on(chunkType("tool-input-available"), ({ chunk }) => {
149
- console.log("Tool called:", chunk.toolName, chunk.input);
199
+ console.log("Tool input:", chunk.toolName, chunk.input);
200
+ })
201
+ .on(chunkType("tool-output-available"), ({ chunk }) => {
202
+ console.log("Tool output:", chunk.toolName, chunk.output);
150
203
  })
151
204
  .toStream();
152
205
  ```
153
206
 
207
+ Observe tool state transitions.
208
+
209
+ ```typescript
210
+ const stream = pipe(result.toUIMessageStream())
211
+ .on(toolCall({ tool: "weather", state: "approval-requested" }), ({ chunk }) => {
212
+ console.log("Weather tool needs approval");
213
+ })
214
+ .toStream();
215
+ ```
216
+
217
+ > [!NOTE]
218
+ > The `tool` option filters by part type (`tool-{name}`). Dynamic tools have part type `dynamic-tool`, so `toolCall({ tool: "myTool" })` will not match dynamic tools. Use `toolCall()` without the `tool` option to observe all tools including dynamic ones.
219
+
154
220
  ### `.toStream()`
155
221
 
156
222
  Convert the pipeline back to a `AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>` that can be returned to the client or consumed.
@@ -183,8 +249,8 @@ Multiple operators can be chained together. After filtering with type guards, ch
183
249
  const stream = pipe<MyUIMessage>(result.toUIMessageStream())
184
250
  .filter(includeParts("text"))
185
251
  .map(({ chunk, part }) => {
186
- // chunk is narrowed to text chunks only
187
- // part.type is narrowed to "text"
252
+ // chunk is narrowed to text chunks: "text-start" | "text-delta" | "text-end"
253
+ // part is narrowed to "text"
188
254
  return chunk;
189
255
  })
190
256
  .toStream();
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as convertSSEToUIMessageStream, c as convertArrayToStream, i as convertStreamToArray, l as convertArrayToAsyncIterable, n as createAsyncIterableStream, o as convertAsyncIterableToStream, r as convertUIMessageToSSEStream, s as convertAsyncIterableToArray, t as AsyncIterableStream } from "./types-B4nePmEd.mjs";
1
+ import { a as convertSSEToUIMessageStream, c as convertArrayToStream, i as convertStreamToArray, l as convertArrayToAsyncIterable, n as createAsyncIterableStream, o as convertAsyncIterableToStream, r as convertUIMessageToSSEStream, s as convertAsyncIterableToArray, t as AsyncIterableStream } from "./types-CNApJ39t.mjs";
2
2
  import "./utils/index.mjs";
3
- import { AsyncIterableStream as AsyncIterableStream$1, InferUIMessageChunk, UIMessage } from "ai";
3
+ import { AsyncIterableStream as AsyncIterableStream$1, InferUIMessageChunk, UIDataTypes, UIMessage, UITools } from "ai";
4
4
 
5
5
  //#region src/consume/consume-ui-message-stream.d.ts
6
6
  /**
@@ -181,6 +181,7 @@ declare function filterUIMessageStream<UI_MESSAGE extends UIMessage>(stream: Rea
181
181
  type InferUIMessagePart<UI_MESSAGE extends UIMessage> = UI_MESSAGE["parts"][number];
182
182
  type InferUIMessagePartType<UI_MESSAGE extends UIMessage> = InferUIMessagePart<UI_MESSAGE>["type"];
183
183
  type InferUIMessageChunkType<UI_MESSAGE extends UIMessage> = InferUIMessageChunk<UI_MESSAGE>["type"];
184
+ type InferUIMessageTools<UI_MESSAGE extends UIMessage> = UI_MESSAGE extends UIMessage<unknown, UIDataTypes, infer TOOLS> ? TOOLS : UITools;
184
185
  /**
185
186
  * Extracts chunk type strings that match the prefix exactly or as `${PREFIX}-*`.
186
187
  * Dynamically derives chunk types from the actual UIMessageChunk union.
@@ -318,6 +319,47 @@ type InferPartForChunk<UI_MESSAGE extends UIMessage, CHUNK_TYPE extends string>
318
319
  * ```
319
320
  */
320
321
  type ExcludePartForChunks<UI_MESSAGE extends UIMessage, EXCLUDED_CHUNK_TYPE extends string> = { [PT in InferUIMessagePartType<UI_MESSAGE>]: [Exclude<PartTypeToChunkTypes<UI_MESSAGE, PT>, EXCLUDED_CHUNK_TYPE>] extends [never] ? never : PT }[InferUIMessagePartType<UI_MESSAGE>];
322
+ /**
323
+ * Extract tool names from UIMessage tools.
324
+ *
325
+ * @example
326
+ * ```typescript
327
+ * type Tools = InferToolName<MyUIMessage>;
328
+ * // => 'weather' | 'calculator'
329
+ * ```
330
+ */
331
+ type InferToolName<UI_MESSAGE extends UIMessage> = keyof InferUIMessageTools<UI_MESSAGE> & string;
332
+ /**
333
+ * All tool-related part types (tool-* and dynamic-tool).
334
+ */
335
+ type ToolPartTypes<UI_MESSAGE extends UIMessage> = Extract<InferUIMessagePartType<UI_MESSAGE>, `tool-${string}` | `dynamic-tool`>;
336
+ /**
337
+ * Part types remaining after excluding all tools.
338
+ */
339
+ type ExcludeToolPartTypes<UI_MESSAGE extends UIMessage> = Exclude<InferUIMessagePartType<UI_MESSAGE>, ToolPartTypes<UI_MESSAGE>>;
340
+ /**
341
+ * Chunk types for tools (tool-input-*, tool-output-*, etc.).
342
+ */
343
+ type ToolChunkTypes<UI_MESSAGE extends UIMessage> = Extract<InferUIMessageChunkType<UI_MESSAGE>, `tool-${string}`>;
344
+ /**
345
+ * Content chunk types remaining after excluding all tool chunks.
346
+ */
347
+ type ExcludeToolChunkTypes<UI_MESSAGE extends UIMessage> = Exclude<ContentChunkType<UI_MESSAGE>, ToolChunkTypes<UI_MESSAGE>>;
348
+ /**
349
+ * Tool states that have corresponding stream chunks.
350
+ * These are the "final" states from UIToolInvocation, not streaming events.
351
+ */
352
+ type ToolCallState = "input-available" | "approval-requested" | "output-available" | "output-error" | "output-denied";
353
+ /**
354
+ * Map tool states to their corresponding chunk types.
355
+ */
356
+ type ToolStateToChunkType = {
357
+ "input-available": "tool-input-available";
358
+ "approval-requested": "tool-approval-request";
359
+ "output-available": "tool-output-available";
360
+ "output-error": "tool-output-error";
361
+ "output-denied": "tool-output-denied";
362
+ };
321
363
  //#endregion
322
364
  //#region src/flat-map/flat-map-ui-message-stream.d.ts
323
365
  /**
@@ -549,8 +591,8 @@ declare class ChunkPipeline<UI_MESSAGE extends UIMessage, CHUNK extends InferUIM
549
591
  * Content chunks include a part object with the type, while meta chunks have undefined part.
550
592
  * All chunks pass through regardless of whether the callback is invoked.
551
593
  */
552
- on<NARROWED_CHUNK extends CHUNK, NARROWED_PART extends {
553
- type: PART[`type`];
594
+ on<NARROWED_CHUNK extends InferUIMessageChunk<UI_MESSAGE>, NARROWED_PART extends {
595
+ type: string;
554
596
  } | undefined>(guard: ObserveGuard<UI_MESSAGE, NARROWED_CHUNK, NARROWED_PART>, callback: ChunkObserveFn<NARROWED_CHUNK, NARROWED_PART>): ChunkPipeline<UI_MESSAGE, CHUNK, PART>;
555
597
  /**
556
598
  * Observes chunks matching a predicate without filtering them.
@@ -707,5 +749,114 @@ declare function chunkType<UI_MESSAGE extends UIMessage, CHUNK_TYPE extends Infe
707
749
  declare function partType<UI_MESSAGE extends UIMessage, PART_TYPE extends InferUIMessagePartType<UI_MESSAGE>>(types: PART_TYPE | Array<PART_TYPE>): ObserveGuard<UI_MESSAGE, ExtractChunkForPart<UI_MESSAGE, ExtractPart<UI_MESSAGE, PART_TYPE>>, {
708
750
  type: PART_TYPE;
709
751
  }>;
752
+ /**
753
+ * Creates a filter guard that includes only specific tools while keeping non-tool chunks.
754
+ * Use with `.filter()` to include only specific tools.
755
+ *
756
+ * @example
757
+ * ```typescript
758
+ * // No-op: all chunks pass through
759
+ * pipe<MyUIMessage>(stream)
760
+ * .filter(includeTools())
761
+ * .map(({ chunk }) => chunk); // all chunks pass
762
+ *
763
+ * // Include specific tool (non-tool chunks still pass)
764
+ * pipe<MyUIMessage>(stream)
765
+ * .filter(includeTools('weather'))
766
+ * .map(({ chunk }) => chunk); // text + tool-weather chunks
767
+ *
768
+ * // Include multiple tools
769
+ * pipe<MyUIMessage>(stream)
770
+ * .filter(includeTools(['weather', 'calculator']))
771
+ * .map(({ chunk }) => chunk); // text + specified tool chunks
772
+ * ```
773
+ */
774
+ declare function includeTools<UI_MESSAGE extends UIMessage>(): FilterGuard<UI_MESSAGE, InferUIMessageChunk<UI_MESSAGE>, {
775
+ type: InferUIMessagePartType<UI_MESSAGE>;
776
+ }>;
777
+ declare function includeTools<UI_MESSAGE extends UIMessage, TOOL_NAME extends InferToolName<UI_MESSAGE>>(toolNames: TOOL_NAME | Array<TOOL_NAME>): FilterGuard<UI_MESSAGE, InferUIMessageChunk<UI_MESSAGE>, {
778
+ type: ExcludeToolPartTypes<UI_MESSAGE> | `tool-${TOOL_NAME}`;
779
+ }>;
780
+ /**
781
+ * Creates a filter guard that excludes all or only specific tools while keeping non-tool chunks.
782
+ * Use with `.filter()` to exclude specific tools or all tools.
783
+ *
784
+ * @example
785
+ * ```typescript
786
+ * // Exclude all tools
787
+ * pipe<MyUIMessage>(stream)
788
+ * .filter(excludeTools())
789
+ * .map(({ chunk }) => chunk); // no tool chunks
790
+ *
791
+ * // Exclude specific tool
792
+ * pipe<MyUIMessage>(stream)
793
+ * .filter(excludeTools('weather'))
794
+ * .map(({ chunk }) => chunk); // excludes tool-weather chunks
795
+ *
796
+ * // Exclude multiple tools
797
+ * pipe<MyUIMessage>(stream)
798
+ * .filter(excludeTools(['weather', 'calculator']))
799
+ * .map(({ chunk }) => chunk); // excludes weather and calculator
800
+ * ```
801
+ */
802
+ declare function excludeTools<UI_MESSAGE extends UIMessage>(): FilterGuard<UI_MESSAGE, ExtractChunk<UI_MESSAGE, ExcludeToolChunkTypes<UI_MESSAGE>>, {
803
+ type: ExcludeToolPartTypes<UI_MESSAGE>;
804
+ }>;
805
+ declare function excludeTools<UI_MESSAGE extends UIMessage, TOOL_NAME extends InferToolName<UI_MESSAGE>>(toolNames: TOOL_NAME | Array<TOOL_NAME>): FilterGuard<UI_MESSAGE, InferUIMessageChunk<UI_MESSAGE>, {
806
+ type: Exclude<InferUIMessagePartType<UI_MESSAGE>, `tool-${TOOL_NAME}`>;
807
+ }>;
808
+ /**
809
+ * Creates an observe guard that matches tool call chunks by tool name and/or state.
810
+ * Use with `.on()` to observe tool call state transitions without filtering.
811
+ *
812
+ * @example
813
+ * ```typescript
814
+ * // Match all tool state transitions (any tool, any state)
815
+ * pipe<MyUIMessage>(stream)
816
+ * .on(toolCall(), ({ chunk, part }) => {
817
+ * // Observes: tool-input-available, tool-approval-request,
818
+ * // tool-output-available, tool-output-error, tool-output-denied
819
+ * });
820
+ *
821
+ * // Match specific tool (any state)
822
+ * pipe<MyUIMessage>(stream)
823
+ * .on(toolCall({ tool: "weather" }), ({ chunk, part }) => {
824
+ * // part.type is 'tool-weather'
825
+ * });
826
+ *
827
+ * // Match specific state (all tools)
828
+ * pipe<MyUIMessage>(stream)
829
+ * .on(toolCall({ state: "output-available" }), ({ chunk, part }) => {
830
+ * // chunk.type is 'tool-output-available'
831
+ * });
832
+ *
833
+ * // Match specific tool AND state
834
+ * pipe<MyUIMessage>(stream)
835
+ * .on(toolCall({ tool: "weather", state: "output-available" }), ({ chunk, part }) => {
836
+ * // chunk.type is 'tool-output-available', part.type is 'tool-weather'
837
+ * });
838
+ * ```
839
+ */
840
+ declare function toolCall<UI_MESSAGE extends UIMessage>(): ObserveGuard<UI_MESSAGE, ExtractChunk<UI_MESSAGE, ToolStateToChunkType[ToolCallState]>, {
841
+ type: `tool-${InferToolName<UI_MESSAGE>}` | "dynamic-tool";
842
+ }>;
843
+ declare function toolCall<UI_MESSAGE extends UIMessage, TOOL_NAME extends InferToolName<UI_MESSAGE>, STATE extends ToolCallState>(options: {
844
+ tool: TOOL_NAME;
845
+ state: STATE;
846
+ }): ObserveGuard<UI_MESSAGE, ExtractChunk<UI_MESSAGE, ToolStateToChunkType[STATE]>, {
847
+ type: `tool-${TOOL_NAME}`;
848
+ }>;
849
+ declare function toolCall<UI_MESSAGE extends UIMessage, TOOL_NAME extends InferToolName<UI_MESSAGE>>(options: {
850
+ tool: TOOL_NAME;
851
+ state?: undefined;
852
+ }): ObserveGuard<UI_MESSAGE, ExtractChunk<UI_MESSAGE, ToolStateToChunkType[ToolCallState]>, {
853
+ type: `tool-${TOOL_NAME}`;
854
+ }>;
855
+ declare function toolCall<UI_MESSAGE extends UIMessage, STATE extends ToolCallState>(options: {
856
+ tool?: undefined;
857
+ state: STATE;
858
+ }): ObserveGuard<UI_MESSAGE, ExtractChunk<UI_MESSAGE, ToolStateToChunkType[STATE]>, {
859
+ type: `tool-${InferToolName<UI_MESSAGE>}` | "dynamic-tool";
860
+ }>;
710
861
  //#endregion
711
- export { AsyncIterableStream, type FlatMapContext, type FlatMapInput, type FlatMapUIMessageStreamFn, type FlatMapUIMessageStreamPredicate, type MapInput, type MapUIMessageStreamFn, chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, mapUIMessageStream, partType, partTypeIs, pipe };
862
+ export { AsyncIterableStream, type FlatMapContext, type FlatMapInput, type FlatMapUIMessageStreamFn, type FlatMapUIMessageStreamPredicate, type MapInput, type MapUIMessageStreamFn, chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, excludeTools, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, includeTools, mapUIMessageStream, partType, partTypeIs, pipe, toolCall };
package/dist/index.mjs CHANGED
@@ -908,6 +908,64 @@ function partType(types) {
908
908
  const guard = (input) => typeArray.includes(input.part?.type);
909
909
  return guard;
910
910
  }
911
+ function includeTools(toolNames) {
912
+ const toolNameArray = toolNames === void 0 ? void 0 : Array.isArray(toolNames) ? toolNames : [toolNames];
913
+ const guard = (input) => {
914
+ /** No args = no-op, all chunks pass */
915
+ if (toolNameArray === void 0) return true;
916
+ const partType = input.part?.type;
917
+ if (!partType) return true;
918
+ /** Non-tool chunks pass */
919
+ if (!partType.startsWith(`tool-`) && partType !== `dynamic-tool`) return true;
920
+ /** Only matching tool chunks pass */
921
+ for (const name of toolNameArray) if (partType === `tool-${name}`) return true;
922
+ return false;
923
+ };
924
+ return guard;
925
+ }
926
+ function excludeTools(toolNames) {
927
+ const toolNameArray = toolNames === void 0 ? void 0 : Array.isArray(toolNames) ? toolNames : [toolNames];
928
+ const guard = (input) => {
929
+ const partType = input.part?.type;
930
+ /** Meta chunks pass (no part type) */
931
+ if (!partType) return true;
932
+ /** No args = exclude all tool chunks */
933
+ if (toolNameArray === void 0) return !partType.startsWith(`tool-`) && partType !== `dynamic-tool`;
934
+ /** Exclude only matching tool chunks */
935
+ for (const name of toolNameArray) if (partType === `tool-${name}`) return false;
936
+ return true;
937
+ };
938
+ return guard;
939
+ }
940
+ /**
941
+ * Reverse mapping from chunk type to state.
942
+ */
943
+ const chunkTypeToState = Object.fromEntries(Object.entries({
944
+ "input-available": "tool-input-available",
945
+ "approval-requested": "tool-approval-request",
946
+ "output-available": "tool-output-available",
947
+ "output-error": "tool-output-error",
948
+ "output-denied": "tool-output-denied"
949
+ }).map(([state, chunkType]) => [chunkType, state]));
950
+ function toolCall(options) {
951
+ const toolName = options?.tool;
952
+ const state = options?.state;
953
+ const guard = (input) => {
954
+ const chunkType = input.chunk.type;
955
+ const partType = input.part?.type;
956
+ /** Must be a tool chunk type that maps to a state */
957
+ const matchingState = chunkTypeToState[chunkType];
958
+ if (!matchingState) return false;
959
+ /** Check state match */
960
+ if (state && matchingState !== state) return false;
961
+ /** Check tool name match */
962
+ if (toolName && partType) {
963
+ if (partType !== `tool-${toolName}`) return false;
964
+ }
965
+ return true;
966
+ };
967
+ return guard;
968
+ }
911
969
 
912
970
  //#endregion
913
- export { chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, mapUIMessageStream, partType, partTypeIs, pipe };
971
+ export { chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, excludeTools, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, includeTools, mapUIMessageStream, partType, partTypeIs, pipe, toolCall };
@@ -1,4 +1,4 @@
1
- import { AsyncIterableStream, AsyncIterableStream as AsyncIterableStream$1, UIMessageChunk } from "ai";
1
+ import { AsyncIterableStream, AsyncIterableStream as AsyncIterableStream$1, InferUIMessageChunk, UIMessage, UIMessageChunk } from "ai";
2
2
 
3
3
  //#region src/utils/convert-array-to-async-iterable.d.ts
4
4
  /**
@@ -29,7 +29,7 @@ declare function convertAsyncIterableToStream<T>(iterable: AsyncIterable<T>): Re
29
29
  /**
30
30
  * Converts an SSE stream to a UI message stream.
31
31
  */
32
- declare function convertSSEToUIMessageStream(stream: ReadableStream<string>): ReadableStream<UIMessageChunk>;
32
+ declare function convertSSEToUIMessageStream<UI_MESSAGE extends UIMessage = UIMessage>(stream: ReadableStream<string>): ReadableStream<InferUIMessageChunk<UI_MESSAGE>>;
33
33
  //#endregion
34
34
  //#region src/utils/convert-stream-to-array.d.ts
35
35
  /**
@@ -1,2 +1,2 @@
1
- import { a as convertSSEToUIMessageStream, c as convertArrayToStream, i as convertStreamToArray, l as convertArrayToAsyncIterable, n as createAsyncIterableStream, o as convertAsyncIterableToStream, r as convertUIMessageToSSEStream, s as convertAsyncIterableToArray, t as AsyncIterableStream } from "../types-B4nePmEd.mjs";
1
+ import { a as convertSSEToUIMessageStream, c as convertArrayToStream, i as convertStreamToArray, l as convertArrayToAsyncIterable, n as createAsyncIterableStream, o as convertAsyncIterableToStream, r as convertUIMessageToSSEStream, s as convertAsyncIterableToArray, t as AsyncIterableStream } from "../types-CNApJ39t.mjs";
2
2
  export { type AsyncIterableStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-stream-utils",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "AI SDK: Filter and transform UI messages while streaming to the client",
5
5
  "keywords": [
6
6
  "ai",
@@ -41,6 +41,7 @@
41
41
  "@ai-sdk/openai": "^3.0.29",
42
42
  "@ai-sdk/provider": "^3.0.8",
43
43
  "@ai-sdk/provider-utils": "^4.0.15",
44
+ "@ai-sdk/react": "^3.0.110",
44
45
  "@arethetypeswrong/cli": "^0.18.2",
45
46
  "@total-typescript/tsconfig": "^1.0.4",
46
47
  "@types/node": "^25.2.3",