streamfold 0.1.4 → 0.1.5

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.
@@ -1,4 +1,5 @@
1
1
  import { wasmBinaryBase64 } from "./wasm-binary.js";
2
+ import { streamError } from "./errors.js";
2
3
 
3
4
  const DEPTH_MASK = 0x00ff_ffff;
4
5
  const COMPLETE_FLAG = 1 << 24;
@@ -36,7 +37,7 @@ const getExports = () => {
36
37
  return exports;
37
38
  };
38
39
 
39
- const parserError = ({ wasm, handle, maxBytes, maxDepth }, code) => {
40
+ const describeParserError = ({ wasm, handle, maxBytes, maxDepth }, code) => {
40
41
  const offset = wasm.streamfold_parser_error_offset(handle) >>> 0;
41
42
  const byte = wasm.streamfold_parser_error_byte(handle);
42
43
  if (code === 1) {
@@ -61,6 +62,28 @@ const parserError = ({ wasm, handle, maxBytes, maxDepth }, code) => {
61
62
  return new SyntaxError(`Rust parser failed with error code ${code}`);
62
63
  };
63
64
 
65
+ const parserErrorCodes = [
66
+ "PARSER_ERROR",
67
+ "UNEXPECTED_TOKEN",
68
+ "MISMATCHED_CLOSING",
69
+ "TRAILING_DATA",
70
+ "EMPTY_INPUT",
71
+ "INCOMPLETE_JSON",
72
+ "INVALID_JSON",
73
+ "MAX_BYTES_EXCEEDED",
74
+ "MAX_DEPTH_EXCEEDED",
75
+ ];
76
+
77
+ const parserError = (parser, code) =>
78
+ streamError(
79
+ describeParserError(parser, code),
80
+ parserErrorCodes[code] ?? "PARSER_ERROR",
81
+ {
82
+ byteOffset:
83
+ parser.wasm.streamfold_parser_error_offset(parser.handle) >>> 0,
84
+ },
85
+ );
86
+
64
87
  const readState = (parser, encoded) => {
65
88
  const { wasm, handle } = parser;
66
89
  const error = encoded >>> ERROR_SHIFT;
@@ -190,6 +213,12 @@ const pushChunk = (parser, chunk) => {
190
213
  };
191
214
 
192
215
  export const pushWasmParser = (parser, chunk) => {
216
+ if (typeof chunk !== "string") {
217
+ throw streamError(
218
+ new TypeError("A structured stream chunk must be a string"),
219
+ "INVALID_CHUNK",
220
+ );
221
+ }
193
222
  let input = parser.pendingHighSurrogate + chunk;
194
223
  parser.pendingHighSurrogate = "";
195
224
 
@@ -217,10 +246,7 @@ export const finishWasmParser = (parser) => {
217
246
  };
218
247
 
219
248
  export const readWasmParser = (parser) =>
220
- readState(
221
- parser,
222
- parser.wasm.streamfold_parser_state(parser.handle),
223
- );
249
+ readState(parser, parser.wasm.streamfold_parser_state(parser.handle));
224
250
 
225
251
  export const freeWasmParser = ({ wasm, handle }) => {
226
252
  wasm.streamfold_parser_free(handle);
@@ -1,6 +1,7 @@
1
1
  import type {
2
- EventStructuredStream,
3
- StructuredStreamIntegration,
2
+ BatchEventStructuredStream,
3
+ BatchStructuredStreamIntegration,
4
+ StructuredStreamIntegrationOptions,
4
5
  StructuredStreamPool,
5
6
  } from "./index.js";
6
7
 
@@ -16,11 +17,12 @@ export interface LangChainToolCallMessage {
16
17
  readonly tool_call_chunks?: readonly LangChainToolCallChunk[];
17
18
  }
18
19
 
19
- export const langchain: StructuredStreamIntegration<
20
+ export const langchain: BatchStructuredStreamIntegration<
20
21
  LangChainToolCallMessage,
21
22
  string
22
23
  >;
23
24
 
24
25
  export function createStructuredStream(
25
26
  pool?: StructuredStreamPool<string>,
26
- ): EventStructuredStream<LangChainToolCallMessage, string>;
27
+ options?: StructuredStreamIntegrationOptions,
28
+ ): BatchEventStructuredStream<LangChainToolCallMessage, string>;
package/src/langchain.js CHANGED
@@ -1,22 +1,24 @@
1
1
  import { createStructuredStreamPool } from "./index.js";
2
2
  import { createIntegration } from "./internal/integration.js";
3
3
 
4
- export const langchain = (pool = createStructuredStreamPool()) => {
4
+ export const langchain = (pool = createStructuredStreamPool(), options) => {
5
5
  const idsByIndex = new Map();
6
6
 
7
7
  return createIntegration(
8
8
  pool,
9
- (message) => {
9
+ (message, calls) => {
10
10
  let update;
11
11
  for (const chunk of message.tool_call_chunks ?? []) {
12
12
  let id = idsByIndex.get(chunk.index);
13
13
  if (id === undefined && chunk.id) {
14
14
  id = chunk.id;
15
15
  idsByIndex.set(chunk.index, id);
16
- update = pool.start(id);
16
+ update = calls.start(id);
17
17
  }
18
18
  if (id !== undefined && chunk.args) {
19
- update = pool.push(id, chunk.args);
19
+ update = calls.push(id, chunk.args);
20
+ } else if (id === undefined && chunk.args) {
21
+ calls.unmatched("Arguments delta has no matching tool call index");
20
22
  }
21
23
  }
22
24
  return update;
@@ -25,6 +27,8 @@ export const langchain = (pool = createStructuredStreamPool()) => {
25
27
  for (const id of idsByIndex.values()) complete(id);
26
28
  idsByIndex.clear();
27
29
  },
30
+ "langchain",
31
+ options,
28
32
  );
29
33
  };
30
34
 
package/src/openai.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type {
2
- EventStructuredStream,
3
- StructuredStreamIntegration,
2
+ BatchEventStructuredStream,
3
+ BatchStructuredStreamIntegration,
4
+ StructuredStreamIntegrationOptions,
4
5
  StructuredStreamPool,
5
6
  } from "./index.js";
6
7
 
@@ -27,11 +28,12 @@ export type OpenAiResponsesToolEvent =
27
28
  readonly [key: string]: unknown;
28
29
  };
29
30
 
30
- export const openAI: StructuredStreamIntegration<
31
+ export const openAI: BatchStructuredStreamIntegration<
31
32
  OpenAiResponsesToolEvent,
32
33
  string
33
34
  >;
34
35
 
35
36
  export function createStructuredStream(
36
37
  pool?: StructuredStreamPool<string>,
37
- ): EventStructuredStream<OpenAiResponsesToolEvent, string>;
38
+ options?: StructuredStreamIntegrationOptions,
39
+ ): BatchEventStructuredStream<OpenAiResponsesToolEvent, string>;
package/src/openai.js CHANGED
@@ -1,20 +1,27 @@
1
1
  import { createStructuredStreamPool } from "./index.js";
2
2
  import { append, createIntegration } from "./internal/integration.js";
3
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
- });
4
+ export const openAI = (pool = createStructuredStreamPool(), options) =>
5
+ createIntegration(
6
+ pool,
7
+ (event, calls) => {
8
+ if (event.type === "response.output_item.added") {
9
+ if (event.item.type !== "function_call") return undefined;
10
+ return calls.start(event.item.id);
11
+ }
12
+ if (event.type === "response.function_call_arguments.delta") {
13
+ return append(calls, event.item_id, event.delta);
14
+ }
15
+ if (event.type === "response.function_call_arguments.done") {
16
+ if (!calls.has(event.item_id))
17
+ calls.start(event.item_id, event.arguments);
18
+ return calls.complete(event.item_id);
19
+ }
20
+ return undefined;
21
+ },
22
+ undefined,
23
+ "openai",
24
+ options,
25
+ );
19
26
 
20
27
  export { openAI as createStructuredStream };
@@ -0,0 +1,48 @@
1
+ import { createStructuredStreamPool } from "./index.js";
2
+ import { streamError } from "./internal/errors.js";
3
+
4
+ export async function* readStructured(
5
+ events,
6
+ { adapter, integration, limits, onDiagnostic },
7
+ ) {
8
+ if (
9
+ (adapter === undefined) === (integration === undefined) ||
10
+ (adapter !== undefined && typeof adapter !== "function") ||
11
+ (integration !== undefined && typeof integration !== "function")
12
+ ) {
13
+ throw streamError(
14
+ new TypeError("Choose exactly one adapter or integration factory"),
15
+ "INVALID_OPTIONS",
16
+ );
17
+ }
18
+ let pool;
19
+ let stream;
20
+ try {
21
+ if (integration !== undefined) {
22
+ pool = createStructuredStreamPool(limits);
23
+ stream = integration(pool, { onDiagnostic });
24
+ } else {
25
+ stream = adapter(
26
+ onDiagnostic === undefined ? limits : { ...limits, onDiagnostic },
27
+ );
28
+ }
29
+ for await (const event of events) {
30
+ for (const update of stream.pushAll(event)) yield update;
31
+ }
32
+ if (pool === undefined) {
33
+ for (const completed of stream.finish()) yield completed;
34
+ } else {
35
+ // SDK finish() appends pending calls to its legacy completion history.
36
+ const pending = pool.size;
37
+ const completed = stream.finish();
38
+ if (pending > 0) {
39
+ for (const result of completed.slice(-pending)) {
40
+ yield { type: "complete", ...result };
41
+ }
42
+ }
43
+ }
44
+ } finally {
45
+ if (pool === undefined) stream?.dispose();
46
+ else for (const id of pool.activeIds) pool.abort(id);
47
+ }
48
+ }
@@ -1,6 +1,7 @@
1
1
  import type {
2
- EventStructuredStream,
3
- StructuredStreamIntegration,
2
+ BatchEventStructuredStream,
3
+ BatchStructuredStreamIntegration,
4
+ StructuredStreamIntegrationOptions,
4
5
  StructuredStreamPool,
5
6
  } from "./index.js";
6
7
 
@@ -42,11 +43,12 @@ export type VercelAiToolInputEvent =
42
43
  readonly [key: string]: unknown;
43
44
  };
44
45
 
45
- export const vercelAI: StructuredStreamIntegration<
46
+ export const vercelAI: BatchStructuredStreamIntegration<
46
47
  VercelAiToolInputEvent,
47
48
  string
48
49
  >;
49
50
 
50
51
  export function createStructuredStream(
51
52
  pool?: StructuredStreamPool<string>,
52
- ): EventStructuredStream<VercelAiToolInputEvent, string>;
53
+ options?: StructuredStreamIntegrationOptions,
54
+ ): BatchEventStructuredStream<VercelAiToolInputEvent, string>;
package/src/vercel-ai.js CHANGED
@@ -1,22 +1,40 @@
1
1
  import { createStructuredStreamPool } from "./index.js";
2
2
  import { createIntegration } from "./internal/integration.js";
3
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;
4
+ export const vercelAI = (pool = createStructuredStreamPool(), options) =>
5
+ createIntegration(
6
+ pool,
7
+ (event, calls) => {
8
+ const id = event.id ?? event.toolCallId;
9
+ if (id === undefined) {
10
+ if (
11
+ [
12
+ "tool-input-start",
13
+ "tool-input-delta",
14
+ "tool-input-end",
15
+ "tool-input-available",
16
+ ].includes(event.type)
17
+ ) {
18
+ calls.unmatched("Tool input event is missing its call id");
19
+ }
20
+ return undefined;
21
+ }
8
22
 
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
- });
23
+ if (event.type === "tool-input-start") return calls.start(id);
24
+ if (event.type === "tool-input-delta") {
25
+ return calls.push(id, event.delta ?? event.inputTextDelta);
26
+ }
27
+ if (
28
+ event.type === "tool-input-end" ||
29
+ event.type === "tool-input-available"
30
+ ) {
31
+ return calls.complete(id);
32
+ }
33
+ return undefined;
34
+ },
35
+ undefined,
36
+ "vercel-ai",
37
+ options,
38
+ );
21
39
 
22
40
  export { vercelAI as createStructuredStream };