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 +21 -0
- package/README.md +53 -0
- package/package.json +75 -0
- package/src/ag-ui.d.ts +31 -0
- package/src/ag-ui.js +14 -0
- package/src/anthropic.d.ts +43 -0
- package/src/anthropic.js +34 -0
- package/src/assistant-ui.d.ts +41 -0
- package/src/assistant-ui.js +31 -0
- package/src/gemini.d.ts +42 -0
- package/src/gemini.js +50 -0
- package/src/index.d.ts +118 -0
- package/src/index.js +345 -0
- package/src/internal/integration.js +43 -0
- package/src/internal/wasm-binary.js +1 -0
- package/src/internal/wasm-runtime.js +227 -0
- package/src/langchain.d.ts +26 -0
- package/src/langchain.js +31 -0
- package/src/openai.d.ts +37 -0
- package/src/openai.js +20 -0
- package/src/vercel-ai.d.ts +52 -0
- package/src/vercel-ai.js +22 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { wasmBinaryBase64 } from "./wasm-binary.js";
|
|
2
|
+
|
|
3
|
+
const DEPTH_MASK = 0x00ff_ffff;
|
|
4
|
+
const COMPLETE_FLAG = 1 << 24;
|
|
5
|
+
const IN_STRING_FLAG = 1 << 25;
|
|
6
|
+
const ERROR_SHIFT = 28;
|
|
7
|
+
const PATCH_SET_OBJECT = 0;
|
|
8
|
+
const PATCH_SET_ARRAY = 1;
|
|
9
|
+
const PATCH_SET_STRING = 2;
|
|
10
|
+
const PATCH_APPEND_STRING = 3;
|
|
11
|
+
const PATCH_SET_NUMBER = 4;
|
|
12
|
+
const PATCH_SET_TRUE = 5;
|
|
13
|
+
const PATCH_SET_FALSE = 6;
|
|
14
|
+
const PATCH_SET_NULL = 7;
|
|
15
|
+
const PATCH_COMPLETE = 8;
|
|
16
|
+
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
let exports;
|
|
19
|
+
|
|
20
|
+
const decodeBase64 = (base64) => {
|
|
21
|
+
const binary = globalThis.atob(base64);
|
|
22
|
+
const bytes = new Uint8Array(binary.length);
|
|
23
|
+
for (let index = 0; index < binary.length; index++) {
|
|
24
|
+
bytes[index] = binary.charCodeAt(index);
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getExports = () => {
|
|
30
|
+
if (exports !== undefined) return exports;
|
|
31
|
+
if (typeof WebAssembly !== "object") {
|
|
32
|
+
throw new Error("Streamfold requires WebAssembly support");
|
|
33
|
+
}
|
|
34
|
+
const module = new WebAssembly.Module(decodeBase64(wasmBinaryBase64));
|
|
35
|
+
exports = new WebAssembly.Instance(module, {}).exports;
|
|
36
|
+
return exports;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const parserError = ({ wasm, handle, maxBytes, maxDepth }, code) => {
|
|
40
|
+
const offset = wasm.streamfold_parser_error_offset(handle) >>> 0;
|
|
41
|
+
const byte = wasm.streamfold_parser_error_byte(handle);
|
|
42
|
+
if (code === 1) {
|
|
43
|
+
const kind = byte === 44 || byte === 58 ? "separator" : "closing";
|
|
44
|
+
return new SyntaxError(`Unexpected ${kind} at ${offset}`);
|
|
45
|
+
}
|
|
46
|
+
if (code === 2) return new SyntaxError(`Mismatched closing at ${offset}`);
|
|
47
|
+
if (code === 3) return new SyntaxError(`Trailing data at ${offset}`);
|
|
48
|
+
if (code === 4) return new SyntaxError("Empty JSON input");
|
|
49
|
+
if (code === 5) return new SyntaxError(`Incomplete JSON at ${offset}`);
|
|
50
|
+
if (code === 6) return new SyntaxError(`Invalid JSON at ${offset}`);
|
|
51
|
+
if (code === 7) {
|
|
52
|
+
return new RangeError(
|
|
53
|
+
`Structured stream exceeds maxBytes (${maxBytes}) at ${offset}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (code === 8) {
|
|
57
|
+
return new RangeError(
|
|
58
|
+
`Structured stream exceeds maxDepth (${maxDepth}) at ${offset}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return new SyntaxError(`Rust parser failed with error code ${code}`);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const readState = (parser, encoded) => {
|
|
65
|
+
const { wasm, handle } = parser;
|
|
66
|
+
const error = encoded >>> ERROR_SHIFT;
|
|
67
|
+
if (error !== 0) throw parserError(parser, error);
|
|
68
|
+
return {
|
|
69
|
+
bytesSeen: wasm.streamfold_parser_bytes_seen(handle) >>> 0,
|
|
70
|
+
depth: encoded & DEPTH_MASK,
|
|
71
|
+
complete: (encoded & COMPLETE_FLAG) !== 0,
|
|
72
|
+
inString: (encoded & IN_STRING_FLAG) !== 0,
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const readString = (view, cursor, length) => {
|
|
77
|
+
let value = "";
|
|
78
|
+
const batch = [];
|
|
79
|
+
for (let index = 0; index < length; index++) {
|
|
80
|
+
batch.push(view.getUint16(cursor.offset, true));
|
|
81
|
+
cursor.offset += 2;
|
|
82
|
+
if (batch.length === 4096) {
|
|
83
|
+
value += String.fromCharCode(...batch);
|
|
84
|
+
batch.length = 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (batch.length > 0) value += String.fromCharCode(...batch);
|
|
88
|
+
return value;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const readPath = (view, cursor) => {
|
|
92
|
+
const length = view.getUint16(cursor.offset, true);
|
|
93
|
+
cursor.offset += 2;
|
|
94
|
+
const path = [];
|
|
95
|
+
for (let index = 0; index < length; index++) {
|
|
96
|
+
const kind = view.getUint8(cursor.offset++);
|
|
97
|
+
if (kind === 0) {
|
|
98
|
+
const units = view.getUint32(cursor.offset, true);
|
|
99
|
+
cursor.offset += 4;
|
|
100
|
+
path.push(readString(view, cursor, units));
|
|
101
|
+
} else {
|
|
102
|
+
path.push(view.getUint32(cursor.offset, true));
|
|
103
|
+
cursor.offset += 4;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return path;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const readNumber = (view, cursor) => {
|
|
110
|
+
const length = view.getUint32(cursor.offset, true);
|
|
111
|
+
cursor.offset += 4;
|
|
112
|
+
let text = "";
|
|
113
|
+
for (let index = 0; index < length; index++) {
|
|
114
|
+
text += String.fromCharCode(view.getUint8(cursor.offset++));
|
|
115
|
+
}
|
|
116
|
+
return Number(text);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const readPatches = (wasm, handle) => {
|
|
120
|
+
const length = wasm.streamfold_parser_output_len(handle) >>> 0;
|
|
121
|
+
if (length === 0) return [];
|
|
122
|
+
const pointer = wasm.streamfold_parser_output(handle) >>> 0;
|
|
123
|
+
const view = new DataView(wasm.memory.buffer, pointer, length);
|
|
124
|
+
const cursor = { offset: 0 };
|
|
125
|
+
const patches = [];
|
|
126
|
+
|
|
127
|
+
while (cursor.offset < length) {
|
|
128
|
+
const operation = view.getUint8(cursor.offset++);
|
|
129
|
+
const path = readPath(view, cursor);
|
|
130
|
+
if (operation === PATCH_SET_OBJECT) {
|
|
131
|
+
patches.push({ op: "set", path, value: {} });
|
|
132
|
+
} else if (operation === PATCH_SET_ARRAY) {
|
|
133
|
+
patches.push({ op: "set", path, value: [] });
|
|
134
|
+
} else if (operation === PATCH_SET_STRING) {
|
|
135
|
+
patches.push({ op: "set", path, value: "" });
|
|
136
|
+
} else if (operation === PATCH_APPEND_STRING) {
|
|
137
|
+
const units = view.getUint32(cursor.offset, true);
|
|
138
|
+
cursor.offset += 4;
|
|
139
|
+
patches.push({
|
|
140
|
+
op: "append",
|
|
141
|
+
path,
|
|
142
|
+
value: readString(view, cursor, units),
|
|
143
|
+
});
|
|
144
|
+
} else if (operation === PATCH_SET_NUMBER) {
|
|
145
|
+
patches.push({ op: "set", path, value: readNumber(view, cursor) });
|
|
146
|
+
} else if (operation === PATCH_SET_TRUE) {
|
|
147
|
+
patches.push({ op: "set", path, value: true });
|
|
148
|
+
} else if (operation === PATCH_SET_FALSE) {
|
|
149
|
+
patches.push({ op: "set", path, value: false });
|
|
150
|
+
} else if (operation === PATCH_SET_NULL) {
|
|
151
|
+
patches.push({ op: "set", path, value: null });
|
|
152
|
+
} else if (operation === PATCH_COMPLETE) {
|
|
153
|
+
patches.push({ op: "complete", path });
|
|
154
|
+
} else {
|
|
155
|
+
throw new Error(`Unknown Rust patch operation: ${operation}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return patches;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const createWasmParser = ({ maxBytes, maxDepth }) => {
|
|
163
|
+
const wasm = getExports();
|
|
164
|
+
const handle = wasm.streamfold_parser_new(maxDepth, maxBytes);
|
|
165
|
+
if (handle === 0) throw new Error("Unable to allocate the Rust parser");
|
|
166
|
+
return {
|
|
167
|
+
wasm,
|
|
168
|
+
handle,
|
|
169
|
+
maxBytes,
|
|
170
|
+
maxDepth,
|
|
171
|
+
pendingHighSurrogate: "",
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const pushChunk = (parser, chunk) => {
|
|
176
|
+
const { wasm, handle } = parser;
|
|
177
|
+
const capacity = chunk.length * 3;
|
|
178
|
+
const pointer = wasm.streamfold_parser_input(handle, capacity);
|
|
179
|
+
let length = 0;
|
|
180
|
+
if (capacity > 0) {
|
|
181
|
+
const input = new Uint8Array(wasm.memory.buffer, pointer, capacity);
|
|
182
|
+
const result = encoder.encodeInto(chunk, input);
|
|
183
|
+
if (result.read !== chunk.length) {
|
|
184
|
+
throw new Error("Unable to encode the complete stream fragment");
|
|
185
|
+
}
|
|
186
|
+
length = result.written;
|
|
187
|
+
}
|
|
188
|
+
const state = readState(parser, wasm.streamfold_parser_push(handle, length));
|
|
189
|
+
return { state, changes: readPatches(wasm, handle) };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const pushWasmParser = (parser, chunk) => {
|
|
193
|
+
let input = parser.pendingHighSurrogate + chunk;
|
|
194
|
+
parser.pendingHighSurrogate = "";
|
|
195
|
+
|
|
196
|
+
if (input.length > 0) {
|
|
197
|
+
const lastUnit = input.charCodeAt(input.length - 1);
|
|
198
|
+
if (lastUnit >= 0xd800 && lastUnit <= 0xdbff) {
|
|
199
|
+
parser.pendingHighSurrogate = input.at(-1);
|
|
200
|
+
input = input.slice(0, -1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return pushChunk(parser, input);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const finishWasmParser = (parser) => {
|
|
208
|
+
const { wasm, handle } = parser;
|
|
209
|
+
const changes = [];
|
|
210
|
+
if (parser.pendingHighSurrogate.length > 0) {
|
|
211
|
+
changes.push(...pushChunk(parser, parser.pendingHighSurrogate).changes);
|
|
212
|
+
parser.pendingHighSurrogate = "";
|
|
213
|
+
}
|
|
214
|
+
const state = readState(parser, wasm.streamfold_parser_finish(handle));
|
|
215
|
+
changes.push(...readPatches(wasm, handle));
|
|
216
|
+
return { state, changes };
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const readWasmParser = (parser) =>
|
|
220
|
+
readState(
|
|
221
|
+
parser,
|
|
222
|
+
parser.wasm.streamfold_parser_state(parser.handle),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
export const freeWasmParser = ({ wasm, handle }) => {
|
|
226
|
+
wasm.streamfold_parser_free(handle);
|
|
227
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventStructuredStream,
|
|
3
|
+
StructuredStreamIntegration,
|
|
4
|
+
StructuredStreamPool,
|
|
5
|
+
} from "./index.js";
|
|
6
|
+
|
|
7
|
+
export interface LangChainToolCallChunk {
|
|
8
|
+
readonly type?: string;
|
|
9
|
+
readonly index: number | string;
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
readonly name?: string;
|
|
12
|
+
readonly args?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LangChainToolCallMessage {
|
|
16
|
+
readonly tool_call_chunks?: readonly LangChainToolCallChunk[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const langchain: StructuredStreamIntegration<
|
|
20
|
+
LangChainToolCallMessage,
|
|
21
|
+
string
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export function createStructuredStream(
|
|
25
|
+
pool?: StructuredStreamPool<string>,
|
|
26
|
+
): EventStructuredStream<LangChainToolCallMessage, string>;
|
package/src/langchain.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createStructuredStreamPool } from "./index.js";
|
|
2
|
+
import { createIntegration } from "./internal/integration.js";
|
|
3
|
+
|
|
4
|
+
export const langchain = (pool = createStructuredStreamPool()) => {
|
|
5
|
+
const idsByIndex = new Map();
|
|
6
|
+
|
|
7
|
+
return createIntegration(
|
|
8
|
+
pool,
|
|
9
|
+
(message) => {
|
|
10
|
+
let update;
|
|
11
|
+
for (const chunk of message.tool_call_chunks ?? []) {
|
|
12
|
+
let id = idsByIndex.get(chunk.index);
|
|
13
|
+
if (id === undefined && chunk.id) {
|
|
14
|
+
id = chunk.id;
|
|
15
|
+
idsByIndex.set(chunk.index, id);
|
|
16
|
+
update = pool.start(id);
|
|
17
|
+
}
|
|
18
|
+
if (id !== undefined && chunk.args) {
|
|
19
|
+
update = pool.push(id, chunk.args);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return update;
|
|
23
|
+
},
|
|
24
|
+
(complete) => {
|
|
25
|
+
for (const id of idsByIndex.values()) complete(id);
|
|
26
|
+
idsByIndex.clear();
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { langchain as createStructuredStream };
|
package/src/openai.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventStructuredStream,
|
|
3
|
+
StructuredStreamIntegration,
|
|
4
|
+
StructuredStreamPool,
|
|
5
|
+
} from "./index.js";
|
|
6
|
+
|
|
7
|
+
export type OpenAiResponsesToolEvent =
|
|
8
|
+
| {
|
|
9
|
+
readonly type: "response.output_item.added";
|
|
10
|
+
readonly item: {
|
|
11
|
+
readonly type: string;
|
|
12
|
+
readonly id: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
readonly type: "response.function_call_arguments.delta";
|
|
17
|
+
readonly item_id: string;
|
|
18
|
+
readonly delta: string;
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
readonly type: "response.function_call_arguments.done";
|
|
22
|
+
readonly item_id: string;
|
|
23
|
+
readonly arguments: string;
|
|
24
|
+
}
|
|
25
|
+
| {
|
|
26
|
+
readonly type: string;
|
|
27
|
+
readonly [key: string]: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const openAI: StructuredStreamIntegration<
|
|
31
|
+
OpenAiResponsesToolEvent,
|
|
32
|
+
string
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
export function createStructuredStream(
|
|
36
|
+
pool?: StructuredStreamPool<string>,
|
|
37
|
+
): EventStructuredStream<OpenAiResponsesToolEvent, string>;
|
package/src/openai.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createStructuredStreamPool } from "./index.js";
|
|
2
|
+
import { append, createIntegration } from "./internal/integration.js";
|
|
3
|
+
|
|
4
|
+
export const openAI = (pool = createStructuredStreamPool()) =>
|
|
5
|
+
createIntegration(pool, (event, complete) => {
|
|
6
|
+
if (event.type === "response.output_item.added") {
|
|
7
|
+
if (event.item.type !== "function_call") return undefined;
|
|
8
|
+
return pool.start(event.item.id);
|
|
9
|
+
}
|
|
10
|
+
if (event.type === "response.function_call_arguments.delta") {
|
|
11
|
+
return append(pool, event.item_id, event.delta);
|
|
12
|
+
}
|
|
13
|
+
if (event.type === "response.function_call_arguments.done") {
|
|
14
|
+
if (!pool.has(event.item_id)) pool.start(event.item_id, event.arguments);
|
|
15
|
+
return complete(event.item_id);
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export { openAI as createStructuredStream };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EventStructuredStream,
|
|
3
|
+
StructuredStreamIntegration,
|
|
4
|
+
StructuredStreamPool,
|
|
5
|
+
} from "./index.js";
|
|
6
|
+
|
|
7
|
+
export type VercelAiToolInputEvent =
|
|
8
|
+
| {
|
|
9
|
+
readonly type: "tool-input-start";
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly toolName: string;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
readonly type: "tool-input-start";
|
|
15
|
+
readonly toolCallId: string;
|
|
16
|
+
readonly toolName: string;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
readonly type: "tool-input-delta";
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly delta: string;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
readonly type: "tool-input-delta";
|
|
25
|
+
readonly toolCallId: string;
|
|
26
|
+
readonly inputTextDelta: string;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
readonly type: "tool-input-end";
|
|
30
|
+
readonly id: string;
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
readonly type: "tool-input-available";
|
|
34
|
+
readonly toolCallId: string;
|
|
35
|
+
readonly toolName: string;
|
|
36
|
+
readonly input: unknown;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
readonly type: string;
|
|
40
|
+
readonly id?: string;
|
|
41
|
+
readonly toolCallId?: string;
|
|
42
|
+
readonly [key: string]: unknown;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const vercelAI: StructuredStreamIntegration<
|
|
46
|
+
VercelAiToolInputEvent,
|
|
47
|
+
string
|
|
48
|
+
>;
|
|
49
|
+
|
|
50
|
+
export function createStructuredStream(
|
|
51
|
+
pool?: StructuredStreamPool<string>,
|
|
52
|
+
): EventStructuredStream<VercelAiToolInputEvent, string>;
|
package/src/vercel-ai.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createStructuredStreamPool } from "./index.js";
|
|
2
|
+
import { createIntegration } from "./internal/integration.js";
|
|
3
|
+
|
|
4
|
+
export const vercelAI = (pool = createStructuredStreamPool()) =>
|
|
5
|
+
createIntegration(pool, (event, complete) => {
|
|
6
|
+
const id = event.id ?? event.toolCallId;
|
|
7
|
+
if (id === undefined) return undefined;
|
|
8
|
+
|
|
9
|
+
if (event.type === "tool-input-start") return pool.start(id);
|
|
10
|
+
if (event.type === "tool-input-delta") {
|
|
11
|
+
return pool.push(id, event.delta ?? event.inputTextDelta);
|
|
12
|
+
}
|
|
13
|
+
if (
|
|
14
|
+
event.type === "tool-input-end" ||
|
|
15
|
+
event.type === "tool-input-available"
|
|
16
|
+
) {
|
|
17
|
+
return complete(id);
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export { vercelAI as createStructuredStream };
|