ai-stream-utils 2.1.0 → 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
@@ -170,6 +170,15 @@ const stream = pipe(result.toUIMessageStream())
170
170
 
171
171
  - `chunkType("text-delta")` or `chunkType(["start", "finish"])`: Observe specific chunk types
172
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
173
182
 
174
183
  > [!NOTE]
175
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`.
@@ -195,6 +204,19 @@ const stream = pipe(result.toUIMessageStream())
195
204
  .toStream();
196
205
  ```
197
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
+
198
220
  ### `.toStream()`
199
221
 
200
222
  Convert the pipeline back to a `AsyncIterableStream<InferUIMessageChunk<UI_MESSAGE>>` that can be returned to the client or consumed.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
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
3
  import { AsyncIterableStream as AsyncIterableStream$1, InferUIMessageChunk, UIDataTypes, UIMessage, UITools } from "ai";
4
4
 
@@ -345,6 +345,21 @@ type ToolChunkTypes<UI_MESSAGE extends UIMessage> = Extract<InferUIMessageChunkT
345
345
  * Content chunk types remaining after excluding all tool chunks.
346
346
  */
347
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
+ };
348
363
  //#endregion
349
364
  //#region src/flat-map/flat-map-ui-message-stream.d.ts
350
365
  /**
@@ -576,8 +591,8 @@ declare class ChunkPipeline<UI_MESSAGE extends UIMessage, CHUNK extends InferUIM
576
591
  * Content chunks include a part object with the type, while meta chunks have undefined part.
577
592
  * All chunks pass through regardless of whether the callback is invoked.
578
593
  */
579
- on<NARROWED_CHUNK extends CHUNK, NARROWED_PART extends {
580
- type: PART[`type`];
594
+ on<NARROWED_CHUNK extends InferUIMessageChunk<UI_MESSAGE>, NARROWED_PART extends {
595
+ type: string;
581
596
  } | undefined>(guard: ObserveGuard<UI_MESSAGE, NARROWED_CHUNK, NARROWED_PART>, callback: ChunkObserveFn<NARROWED_CHUNK, NARROWED_PART>): ChunkPipeline<UI_MESSAGE, CHUNK, PART>;
582
597
  /**
583
598
  * Observes chunks matching a predicate without filtering them.
@@ -790,5 +805,58 @@ declare function excludeTools<UI_MESSAGE extends UIMessage>(): FilterGuard<UI_ME
790
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>, {
791
806
  type: Exclude<InferUIMessagePartType<UI_MESSAGE>, `tool-${TOOL_NAME}`>;
792
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
+ }>;
793
861
  //#endregion
794
- 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 };
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
@@ -937,6 +937,35 @@ function excludeTools(toolNames) {
937
937
  };
938
938
  return guard;
939
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
+ }
940
969
 
941
970
  //#endregion
942
- export { chunkType, consumeUIMessageStream, convertArrayToAsyncIterable, convertArrayToStream, convertAsyncIterableToArray, convertAsyncIterableToStream, convertSSEToUIMessageStream, convertStreamToArray, convertUIMessageToSSEStream, createAsyncIterableStream, excludeChunks, excludeParts, excludeTools, filterUIMessageStream, flatMapUIMessageStream, includeChunks, includeParts, includeTools, 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.1.0",
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",