streamfold 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Streamfold contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # streamfold
2
+
3
+ Incremental structured state for streamed AI tool calls.
4
+
5
+ Streamfold retains JSON parser and value state across deltas. Its Rust/WebAssembly
6
+ engine emits compact patches that update a live partial JavaScript value without
7
+ reparsing the complete accumulated input.
8
+
9
+ ```bash
10
+ pnpm add streamfold
11
+ ```
12
+
13
+ ```ts
14
+ import { createStructuredStream } from "streamfold";
15
+
16
+ const stream = createStructuredStream();
17
+ const update = stream.push('{"city":"San');
18
+
19
+ console.log(update.partialValue); // { city: "San" }
20
+ console.log(update.changes); // compact set/append/complete patches
21
+
22
+ stream.push(' Francisco"}');
23
+ console.log(stream.getFieldState(["city"])); // "complete"
24
+ ```
25
+
26
+ Resource limits are configurable and enabled by default:
27
+
28
+ ```ts
29
+ const stream = createStructuredStream({
30
+ maxBytes: 2 * 1024 * 1024,
31
+ maxDepth: 64,
32
+ });
33
+ ```
34
+
35
+ Use an isolated event integration when consuming an SDK stream:
36
+
37
+ ```ts
38
+ import { createStructuredStream } from "streamfold/assistant-ui";
39
+
40
+ const toolCalls = createStructuredStream();
41
+
42
+ for await (const event of assistantStream) {
43
+ const update = toolCalls.push(event);
44
+ if (update) renderToolInput(update.id, update.partialValue);
45
+ }
46
+ ```
47
+
48
+ Available subpaths are `assistant-ui`, `vercel-ai`, `openai`, `anthropic`,
49
+ `gemini`, `langchain`, and `ag-ui`. They consume structural event shapes and do
50
+ not install or load provider SDKs.
51
+
52
+ See the [repository README](https://github.com/assistant-ui/streamfold#readme)
53
+ for benchmarks, methodology, and development instructions.
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "streamfold",
3
+ "version": "0.1.1",
4
+ "description": "Protocol-neutral incremental state for structured AI streams",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "types": "./src/index.d.ts",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "src/**/*.js",
11
+ "src/**/*.d.ts",
12
+ "!src/**/*.test.js"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./src/index.d.ts",
17
+ "default": "./src/index.js"
18
+ },
19
+ "./ag-ui": {
20
+ "types": "./src/ag-ui.d.ts",
21
+ "default": "./src/ag-ui.js"
22
+ },
23
+ "./anthropic": {
24
+ "types": "./src/anthropic.d.ts",
25
+ "default": "./src/anthropic.js"
26
+ },
27
+ "./assistant-ui": {
28
+ "types": "./src/assistant-ui.d.ts",
29
+ "default": "./src/assistant-ui.js"
30
+ },
31
+ "./gemini": {
32
+ "types": "./src/gemini.d.ts",
33
+ "default": "./src/gemini.js"
34
+ },
35
+ "./langchain": {
36
+ "types": "./src/langchain.d.ts",
37
+ "default": "./src/langchain.js"
38
+ },
39
+ "./openai": {
40
+ "types": "./src/openai.d.ts",
41
+ "default": "./src/openai.js"
42
+ },
43
+ "./vercel-ai": {
44
+ "types": "./src/vercel-ai.d.ts",
45
+ "default": "./src/vercel-ai.js"
46
+ }
47
+ },
48
+ "engines": {
49
+ "node": ">=20"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/assistant-ui/streamfold.git",
54
+ "directory": "packages/core"
55
+ },
56
+ "homepage": "https://github.com/assistant-ui/streamfold#readme",
57
+ "bugs": {
58
+ "url": "https://github.com/assistant-ui/streamfold/issues"
59
+ },
60
+ "keywords": [
61
+ "ai",
62
+ "agents",
63
+ "incremental",
64
+ "json",
65
+ "rust",
66
+ "streaming",
67
+ "tool-calling",
68
+ "webassembly"
69
+ ],
70
+ "publishConfig": {
71
+ "access": "public",
72
+ "provenance": true
73
+ },
74
+ "scripts": {}
75
+ }
package/src/ag-ui.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type {
2
+ EventStructuredStream,
3
+ StructuredStreamIntegration,
4
+ StructuredStreamPool,
5
+ } from "./index.js";
6
+
7
+ export type AgUiToolCallEvent =
8
+ | {
9
+ readonly type: "TOOL_CALL_START";
10
+ readonly toolCallId: string;
11
+ }
12
+ | {
13
+ readonly type: "TOOL_CALL_ARGS";
14
+ readonly toolCallId: string;
15
+ readonly delta: string;
16
+ }
17
+ | {
18
+ readonly type: "TOOL_CALL_END";
19
+ readonly toolCallId: string;
20
+ }
21
+ | {
22
+ readonly type: string;
23
+ readonly toolCallId?: string;
24
+ readonly [key: string]: unknown;
25
+ };
26
+
27
+ export const agUI: StructuredStreamIntegration<AgUiToolCallEvent, string>;
28
+
29
+ export function createStructuredStream(
30
+ pool?: StructuredStreamPool<string>,
31
+ ): EventStructuredStream<AgUiToolCallEvent, string>;
package/src/ag-ui.js ADDED
@@ -0,0 +1,14 @@
1
+ import { createStructuredStreamPool } from "./index.js";
2
+ import { createIntegration } from "./internal/integration.js";
3
+
4
+ export const agUI = (pool = createStructuredStreamPool()) =>
5
+ createIntegration(pool, (event, complete) => {
6
+ if (event.type === "TOOL_CALL_START") return pool.start(event.toolCallId);
7
+ if (event.type === "TOOL_CALL_ARGS") {
8
+ return pool.push(event.toolCallId, event.delta);
9
+ }
10
+ if (event.type === "TOOL_CALL_END") return complete(event.toolCallId);
11
+ return undefined;
12
+ });
13
+
14
+ export { agUI as createStructuredStream };
@@ -0,0 +1,43 @@
1
+ import type {
2
+ EventStructuredStream,
3
+ StructuredStreamIntegration,
4
+ StructuredStreamPool,
5
+ } from "./index.js";
6
+
7
+ export type AnthropicToolInputEvent =
8
+ | {
9
+ readonly type: "content_block_start";
10
+ readonly index: number;
11
+ readonly content_block: {
12
+ readonly type: string;
13
+ readonly id: string;
14
+ };
15
+ }
16
+ | {
17
+ readonly type: "content_block_delta";
18
+ readonly index: number;
19
+ readonly delta:
20
+ | {
21
+ readonly type: "input_json_delta";
22
+ readonly partial_json: string;
23
+ }
24
+ | { readonly type: string };
25
+ }
26
+ | {
27
+ readonly type: "content_block_stop";
28
+ readonly index: number;
29
+ }
30
+ | {
31
+ readonly type: string;
32
+ readonly index?: number;
33
+ readonly [key: string]: unknown;
34
+ };
35
+
36
+ export const anthropic: StructuredStreamIntegration<
37
+ AnthropicToolInputEvent,
38
+ string
39
+ >;
40
+
41
+ export function createStructuredStream(
42
+ pool?: StructuredStreamPool<string>,
43
+ ): EventStructuredStream<AnthropicToolInputEvent, string>;
@@ -0,0 +1,34 @@
1
+ import { createStructuredStreamPool } from "./index.js";
2
+ import { createIntegration } from "./internal/integration.js";
3
+
4
+ export const anthropic = (pool = createStructuredStreamPool()) => {
5
+ const idsByIndex = new Map();
6
+
7
+ return createIntegration(pool, (event, complete) => {
8
+ if (
9
+ event.type === "content_block_start" &&
10
+ (event.content_block.type === "tool_use" ||
11
+ event.content_block.type === "server_tool_use")
12
+ ) {
13
+ idsByIndex.set(event.index, event.content_block.id);
14
+ return pool.start(event.content_block.id);
15
+ }
16
+
17
+ const id = idsByIndex.get(event.index);
18
+ if (id === undefined) return undefined;
19
+
20
+ if (
21
+ event.type === "content_block_delta" &&
22
+ event.delta.type === "input_json_delta"
23
+ ) {
24
+ return pool.push(id, event.delta.partial_json);
25
+ }
26
+ if (event.type === "content_block_stop") {
27
+ idsByIndex.delete(event.index);
28
+ return complete(id);
29
+ }
30
+ return undefined;
31
+ });
32
+ };
33
+
34
+ export { anthropic as createStructuredStream };
@@ -0,0 +1,41 @@
1
+ import type {
2
+ EventStructuredStream,
3
+ StructuredStreamIntegration,
4
+ StructuredStreamPool,
5
+ } from "./index.js";
6
+
7
+ export type AssistantUiStreamEvent =
8
+ | {
9
+ readonly type: "part-start";
10
+ readonly path: readonly number[];
11
+ readonly part:
12
+ | {
13
+ readonly type: "tool-call";
14
+ readonly toolCallId: string;
15
+ readonly toolName: string;
16
+ }
17
+ | { readonly type: string };
18
+ }
19
+ | {
20
+ readonly type: "text-delta";
21
+ readonly path: readonly number[];
22
+ readonly textDelta: string;
23
+ }
24
+ | {
25
+ readonly type: "tool-call-args-text-finish";
26
+ readonly path: readonly number[];
27
+ }
28
+ | {
29
+ readonly type: string;
30
+ readonly path: readonly number[];
31
+ readonly [key: string]: unknown;
32
+ };
33
+
34
+ export const assistantUI: StructuredStreamIntegration<
35
+ AssistantUiStreamEvent,
36
+ string
37
+ >;
38
+
39
+ export function createStructuredStream(
40
+ pool?: StructuredStreamPool<string>,
41
+ ): EventStructuredStream<AssistantUiStreamEvent, string>;
@@ -0,0 +1,31 @@
1
+ import { createStructuredStreamPool } from "./index.js";
2
+ import { createIntegration } from "./internal/integration.js";
3
+
4
+ const pathKey = (path) => path.join("/");
5
+
6
+ export const assistantUI = (pool = createStructuredStreamPool()) => {
7
+ const idsByPath = new Map();
8
+
9
+ return createIntegration(pool, (event, complete) => {
10
+ const key = pathKey(event.path);
11
+
12
+ if (event.type === "part-start" && event.part.type === "tool-call") {
13
+ idsByPath.set(key, event.part.toolCallId);
14
+ return pool.start(event.part.toolCallId);
15
+ }
16
+
17
+ const id = idsByPath.get(key);
18
+ if (id === undefined) return undefined;
19
+
20
+ if (event.type === "text-delta") {
21
+ return pool.push(id, event.textDelta);
22
+ }
23
+ if (event.type === "tool-call-args-text-finish") {
24
+ idsByPath.delete(key);
25
+ return complete(id);
26
+ }
27
+ return undefined;
28
+ });
29
+ };
30
+
31
+ export { assistantUI as createStructuredStream };
@@ -0,0 +1,42 @@
1
+ import type {
2
+ EventStructuredStream,
3
+ StructuredStreamIntegration,
4
+ StructuredStreamPool,
5
+ } from "./index.js";
6
+
7
+ export type GeminiToolCallEvent =
8
+ | {
9
+ readonly event_type: "step.start";
10
+ readonly index: number;
11
+ readonly step: {
12
+ readonly type: string;
13
+ readonly id: string;
14
+ readonly arguments?: string | object;
15
+ };
16
+ }
17
+ | {
18
+ readonly event_type: "step.delta";
19
+ readonly index: number;
20
+ readonly delta:
21
+ | {
22
+ readonly type: "arguments";
23
+ readonly partial_arguments: string;
24
+ }
25
+ | { readonly type: string };
26
+ }
27
+ | {
28
+ readonly event_type: "interaction.completed" | "interaction.complete";
29
+ }
30
+ | {
31
+ readonly event_type: string;
32
+ readonly [key: string]: unknown;
33
+ };
34
+
35
+ export const gemini: StructuredStreamIntegration<
36
+ GeminiToolCallEvent,
37
+ string
38
+ >;
39
+
40
+ export function createStructuredStream(
41
+ pool?: StructuredStreamPool<string>,
42
+ ): EventStructuredStream<GeminiToolCallEvent, string>;
package/src/gemini.js ADDED
@@ -0,0 +1,50 @@
1
+ import { createStructuredStreamPool } from "./index.js";
2
+ import { createIntegration } from "./internal/integration.js";
3
+
4
+ export const gemini = (pool = createStructuredStreamPool()) => {
5
+ const idsByIndex = new Map();
6
+
7
+ return createIntegration(
8
+ pool,
9
+ (event, complete) => {
10
+ if (
11
+ event.event_type === "step.start" &&
12
+ event.step.type === "function_call"
13
+ ) {
14
+ idsByIndex.set(event.index, event.step.id);
15
+ const initial =
16
+ typeof event.step.arguments === "string"
17
+ ? event.step.arguments
18
+ : event.step.arguments
19
+ ? JSON.stringify(event.step.arguments)
20
+ : "";
21
+ return pool.start(event.step.id, initial);
22
+ }
23
+
24
+ if (
25
+ event.event_type === "step.delta" &&
26
+ event.delta.type === "arguments"
27
+ ) {
28
+ const id = idsByIndex.get(event.index);
29
+ if (id !== undefined) {
30
+ return pool.push(id, event.delta.partial_arguments);
31
+ }
32
+ }
33
+
34
+ if (
35
+ event.event_type === "interaction.completed" ||
36
+ event.event_type === "interaction.complete"
37
+ ) {
38
+ for (const id of idsByIndex.values()) complete(id);
39
+ idsByIndex.clear();
40
+ }
41
+ return undefined;
42
+ },
43
+ (complete) => {
44
+ for (const id of idsByIndex.values()) complete(id);
45
+ idsByIndex.clear();
46
+ },
47
+ );
48
+ };
49
+
50
+ export { gemini as createStructuredStream };
package/src/index.d.ts ADDED
@@ -0,0 +1,118 @@
1
+ export type JsonValue =
2
+ | null
3
+ | boolean
4
+ | number
5
+ | string
6
+ | readonly JsonValue[]
7
+ | { readonly [key: string]: JsonValue };
8
+
9
+ export type StructuredStreamPath = readonly (string | number)[];
10
+ export type StructuredStreamFieldState = "partial" | "complete";
11
+
12
+ export interface StructuredStreamOptions {
13
+ /** Maximum UTF-8 bytes accepted by one stream. Defaults to 16 MiB. */
14
+ readonly maxBytes?: number;
15
+ /** Maximum nested object/array depth. Defaults to 128. */
16
+ readonly maxDepth?: number;
17
+ }
18
+
19
+ export interface StructuredStreamPoolOptions extends StructuredStreamOptions {
20
+ /** Maximum number of concurrently active streams. Defaults to 256. */
21
+ readonly maxActiveStreams?: number;
22
+ }
23
+
24
+ export type StructuredStreamPatch =
25
+ | {
26
+ readonly op: "set";
27
+ readonly path: StructuredStreamPath;
28
+ readonly value: JsonValue;
29
+ }
30
+ | {
31
+ readonly op: "append";
32
+ readonly path: StructuredStreamPath;
33
+ readonly value: string;
34
+ }
35
+ | {
36
+ readonly op: "complete";
37
+ readonly path: StructuredStreamPath;
38
+ };
39
+
40
+ export interface StreamState {
41
+ /** Number of UTF-8 bytes processed by the Rust parser. */
42
+ readonly bytesSeen: number;
43
+ readonly depth: number;
44
+ readonly complete: boolean;
45
+ readonly inString: boolean;
46
+ readonly changes: readonly StructuredStreamPatch[];
47
+ /** A live view updated in place; use `changes` for reactive state updates. */
48
+ readonly partialValue: JsonValue | undefined;
49
+ }
50
+
51
+ export interface CompletedStructuredStream<Id = string> extends StreamState {
52
+ readonly id: Id;
53
+ readonly text: string;
54
+ readonly value: JsonValue;
55
+ }
56
+
57
+ export interface ActiveStructuredStream<Id = string> extends StreamState {
58
+ readonly id: Id;
59
+ }
60
+
61
+ export type StructuredStreamUpdate<Id = string> =
62
+ | ActiveStructuredStream<Id>
63
+ | CompletedStructuredStream<Id>
64
+ | undefined;
65
+
66
+ export interface EventStructuredStream<Event, Id = string> {
67
+ push(event: Event): StructuredStreamUpdate<Id>;
68
+ finish(): readonly CompletedStructuredStream<Id>[];
69
+ }
70
+
71
+ export interface StructuredStreamIntegration<Event, Id = string> {
72
+ (
73
+ pool?: StructuredStreamPool<Id>,
74
+ ): EventStructuredStream<Event, Id>;
75
+ }
76
+
77
+ export class IncrementalJsonScanner {
78
+ constructor(options?: StructuredStreamOptions);
79
+ push(chunk: string): StreamState;
80
+ finish(): StreamState;
81
+ getFieldState(path: StructuredStreamPath): StructuredStreamFieldState;
82
+ dispose(): void;
83
+ readonly backend: "rust-wasm";
84
+ readonly state: StreamState;
85
+ /** A live view updated in place; use `StreamState.changes` for reactive updates. */
86
+ readonly value: JsonValue | undefined;
87
+ }
88
+
89
+ export class StructuredStreamPool<Id = string> {
90
+ constructor(options?: StructuredStreamPoolOptions);
91
+ start(id: Id, initialChunk?: string): ActiveStructuredStream<Id>;
92
+ push(id: Id, delta: string): ActiveStructuredStream<Id>;
93
+ finish(id: Id): CompletedStructuredStream<Id>;
94
+ getFieldState(
95
+ id: Id,
96
+ path: StructuredStreamPath,
97
+ ): StructuredStreamFieldState;
98
+ abort(id: Id): boolean;
99
+ has(id: Id): boolean;
100
+ readonly activeIds: readonly Id[];
101
+ readonly size: number;
102
+ }
103
+
104
+ export function createStructuredStream(): IncrementalJsonScanner;
105
+ export function createStructuredStream(
106
+ options: StructuredStreamOptions,
107
+ ): IncrementalJsonScanner;
108
+ export function createStructuredStream<Event, Id = string>(
109
+ integration: StructuredStreamIntegration<Event, Id>,
110
+ ): EventStructuredStream<Event, Id>;
111
+ export function createStructuredStreamPool<Id = string>(
112
+ options?: StructuredStreamPoolOptions,
113
+ ): StructuredStreamPool<Id>;
114
+
115
+ export const STREAMFOLD_ENGINE: "rust-wasm";
116
+ export const DEFAULT_STREAM_LIMITS: Readonly<
117
+ Required<StructuredStreamPoolOptions>
118
+ >;