@workflow/world-vercel 4.1.1 → 4.1.2
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/dist/streamer.d.ts +0 -22
- package/dist/streamer.d.ts.map +1 -1
- package/dist/streamer.js +13 -111
- package/dist/streamer.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/streamer.d.ts
CHANGED
|
@@ -5,28 +5,6 @@ import { type APIConfig } from './utils.js';
|
|
|
5
5
|
* MAX_CHUNKS_PER_BATCH. Larger batches are split into multiple requests.
|
|
6
6
|
*/
|
|
7
7
|
export declare const MAX_CHUNKS_PER_REQUEST = 1000;
|
|
8
|
-
/**
|
|
9
|
-
* Stream control frame constants, mirroring workflow-server's format.
|
|
10
|
-
*
|
|
11
|
-
* Control frame (13 bytes):
|
|
12
|
-
* [0-3] Zero-frame marker (0x00 0x00 0x00 0x00)
|
|
13
|
-
* [4] Flags — bit 0: done (1 = complete, 0 = timeout/reconnect)
|
|
14
|
-
* [5-8] nextIndex — big-endian uint32, chunk index to resume from
|
|
15
|
-
* [9-12] Magic footer — "WFCT" (0x57 0x46 0x43 0x54)
|
|
16
|
-
*/
|
|
17
|
-
export declare const STREAM_CONTROL_FRAME_SIZE = 13;
|
|
18
|
-
export interface StreamControlFrame {
|
|
19
|
-
done: boolean;
|
|
20
|
-
nextIndex: number;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Try to parse a stream control frame from the tail of a buffer.
|
|
24
|
-
* Returns the parsed frame and the byte length of the control data,
|
|
25
|
-
* or null if no valid control frame is present.
|
|
26
|
-
*/
|
|
27
|
-
export declare function parseStreamControlFrame(buffer: Uint8Array): (StreamControlFrame & {
|
|
28
|
-
totalLength: number;
|
|
29
|
-
}) | null;
|
|
30
8
|
/**
|
|
31
9
|
* Encode multiple chunks into a length-prefixed binary format.
|
|
32
10
|
* Format: [4 bytes big-endian length][chunk bytes][4 bytes length][chunk bytes]...
|
package/dist/streamer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAoB3C;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,GAAG,UAAU,CA0B7E;AAwBD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,CA4J3D"}
|
package/dist/streamer.js
CHANGED
|
@@ -9,49 +9,6 @@ export const MAX_CHUNKS_PER_REQUEST = 1000;
|
|
|
9
9
|
// The dispatcher's retry logic doesn't apply well to streaming operations
|
|
10
10
|
// (partial writes, long-lived reads), and duplex streams are incompatible
|
|
11
11
|
// with undici's experimental H2 support.
|
|
12
|
-
/**
|
|
13
|
-
* Stream control frame constants, mirroring workflow-server's format.
|
|
14
|
-
*
|
|
15
|
-
* Control frame (13 bytes):
|
|
16
|
-
* [0-3] Zero-frame marker (0x00 0x00 0x00 0x00)
|
|
17
|
-
* [4] Flags — bit 0: done (1 = complete, 0 = timeout/reconnect)
|
|
18
|
-
* [5-8] nextIndex — big-endian uint32, chunk index to resume from
|
|
19
|
-
* [9-12] Magic footer — "WFCT" (0x57 0x46 0x43 0x54)
|
|
20
|
-
*/
|
|
21
|
-
export const STREAM_CONTROL_FRAME_SIZE = 13;
|
|
22
|
-
const STREAM_CONTROL_MAGIC = new Uint8Array([0x57, 0x46, 0x43, 0x54]);
|
|
23
|
-
/**
|
|
24
|
-
* Try to parse a stream control frame from the tail of a buffer.
|
|
25
|
-
* Returns the parsed frame and the byte length of the control data,
|
|
26
|
-
* or null if no valid control frame is present.
|
|
27
|
-
*/
|
|
28
|
-
export function parseStreamControlFrame(buffer) {
|
|
29
|
-
if (buffer.length < STREAM_CONTROL_FRAME_SIZE)
|
|
30
|
-
return null;
|
|
31
|
-
const offset = buffer.length - STREAM_CONTROL_FRAME_SIZE;
|
|
32
|
-
// Check zero-frame marker (bytes 0-3 must be 0x00)
|
|
33
|
-
if (buffer[offset] !== 0 ||
|
|
34
|
-
buffer[offset + 1] !== 0 ||
|
|
35
|
-
buffer[offset + 2] !== 0 ||
|
|
36
|
-
buffer[offset + 3] !== 0) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
// Check magic footer at bytes 9-12
|
|
40
|
-
if (buffer[offset + 9] !== STREAM_CONTROL_MAGIC[0] ||
|
|
41
|
-
buffer[offset + 10] !== STREAM_CONTROL_MAGIC[1] ||
|
|
42
|
-
buffer[offset + 11] !== STREAM_CONTROL_MAGIC[2] ||
|
|
43
|
-
buffer[offset + 12] !== STREAM_CONTROL_MAGIC[3]) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
const flags = buffer[offset + 4];
|
|
47
|
-
const view = new DataView(buffer.buffer, buffer.byteOffset + offset + 5, 4);
|
|
48
|
-
const nextIndex = view.getUint32(0, false);
|
|
49
|
-
return {
|
|
50
|
-
done: (flags & 1) === 1,
|
|
51
|
-
nextIndex,
|
|
52
|
-
totalLength: STREAM_CONTROL_FRAME_SIZE,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
12
|
function getStreamUrl(name, runId, httpConfig) {
|
|
56
13
|
if (runId) {
|
|
57
14
|
return new URL(`${httpConfig.baseUrl}/v2/runs/${encodeURIComponent(runId)}/stream/${encodeURIComponent(name)}`);
|
|
@@ -169,76 +126,21 @@ export function createStreamer(config) {
|
|
|
169
126
|
}
|
|
170
127
|
},
|
|
171
128
|
async readFromStream(name, startIndex) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const MAX_RECONNECTS = 50;
|
|
177
|
-
let reconnectCount = 0;
|
|
178
|
-
const connect = async () => {
|
|
179
|
-
const httpConfig = await getHttpConfig(config);
|
|
180
|
-
// Use v3 to receive the stream control frame for reconnection.
|
|
181
|
-
const url = new URL(`${httpConfig.baseUrl}/v3/stream/${encodeURIComponent(name)}`);
|
|
182
|
-
url.searchParams.set('startIndex', String(currentStartIndex));
|
|
183
|
-
const response = await fetch(url, {
|
|
184
|
-
headers: httpConfig.headers,
|
|
185
|
-
});
|
|
186
|
-
if (!response.ok) {
|
|
187
|
-
throw new Error(`Failed to fetch stream: ${response.status}`);
|
|
188
|
-
}
|
|
189
|
-
if (!response.body) {
|
|
190
|
-
throw new Error('No response body for stream');
|
|
191
|
-
}
|
|
192
|
-
return response;
|
|
193
|
-
};
|
|
194
|
-
// Read the entire response as bytes, then strip any trailing
|
|
195
|
-
// control frame. This avoids the complexity of hold-back buffering
|
|
196
|
-
// inside a ReadableStream pull loop, which interacts poorly with
|
|
197
|
-
// the proxy and byte-stream wrappers in production.
|
|
198
|
-
const readFull = async () => {
|
|
199
|
-
const response = await connect();
|
|
200
|
-
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
201
|
-
const control = parseStreamControlFrame(buffer);
|
|
202
|
-
if (control) {
|
|
203
|
-
const dataLen = buffer.length - control.totalLength;
|
|
204
|
-
return {
|
|
205
|
-
data: dataLen > 0 ? buffer.subarray(0, dataLen) : new Uint8Array(0),
|
|
206
|
-
control,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
return { data: buffer, control: null };
|
|
210
|
-
};
|
|
211
|
-
// Collect all data, transparently reconnecting on server timeout.
|
|
212
|
-
const parts = [];
|
|
213
|
-
for (;;) {
|
|
214
|
-
const { data, control } = await readFull();
|
|
215
|
-
if (data.length > 0) {
|
|
216
|
-
parts.push(data);
|
|
217
|
-
}
|
|
218
|
-
if (!control || control.done) {
|
|
219
|
-
// Stream complete or no control frame (older server).
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
// Timeout — reconnect from the next chunk index.
|
|
223
|
-
reconnectCount++;
|
|
224
|
-
if (reconnectCount > MAX_RECONNECTS) {
|
|
225
|
-
throw new Error(`Stream exceeded maximum reconnection attempts (${MAX_RECONNECTS})`);
|
|
226
|
-
}
|
|
227
|
-
currentStartIndex = control.nextIndex;
|
|
129
|
+
const httpConfig = await getHttpConfig(config);
|
|
130
|
+
const url = getStreamUrl(name, undefined, httpConfig);
|
|
131
|
+
if (typeof startIndex === 'number') {
|
|
132
|
+
url.searchParams.set('startIndex', String(startIndex));
|
|
228
133
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return new ReadableStream({
|
|
232
|
-
pull(controller) {
|
|
233
|
-
if (!emitted) {
|
|
234
|
-
emitted = true;
|
|
235
|
-
for (const part of parts) {
|
|
236
|
-
controller.enqueue(part);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
controller.close();
|
|
240
|
-
},
|
|
134
|
+
const response = await fetch(url, {
|
|
135
|
+
headers: httpConfig.headers,
|
|
241
136
|
});
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`Failed to fetch stream: ${response.status}`);
|
|
139
|
+
}
|
|
140
|
+
if (!response.body) {
|
|
141
|
+
throw new Error('No response body for stream');
|
|
142
|
+
}
|
|
143
|
+
return response.body;
|
|
242
144
|
},
|
|
243
145
|
async getStreamChunks(name, runId, options) {
|
|
244
146
|
const params = new URLSearchParams();
|
package/dist/streamer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { z } from 'zod';\nimport {\n type APIConfig,\n getHttpConfig,\n type HttpConfig,\n makeRequest,\n} from './utils.js';\n\n/**\n * Maximum number of chunks per request, matching the server-side\n * MAX_CHUNKS_PER_BATCH. Larger batches are split into multiple requests.\n */\nexport const MAX_CHUNKS_PER_REQUEST = 1000;\n\n// Streaming calls use plain fetch() without the undici dispatcher.\n// The dispatcher's retry logic doesn't apply well to streaming operations\n// (partial writes, long-lived reads), and duplex streams are incompatible\n// with undici's experimental H2 support.\n\n/**\n * Stream control frame constants, mirroring workflow-server's format.\n *\n * Control frame (13 bytes):\n * [0-3] Zero-frame marker (0x00 0x00 0x00 0x00)\n * [4] Flags — bit 0: done (1 = complete, 0 = timeout/reconnect)\n * [5-8] nextIndex — big-endian uint32, chunk index to resume from\n * [9-12] Magic footer — \"WFCT\" (0x57 0x46 0x43 0x54)\n */\nexport const STREAM_CONTROL_FRAME_SIZE = 13;\nconst STREAM_CONTROL_MAGIC = new Uint8Array([0x57, 0x46, 0x43, 0x54]);\n\nexport interface StreamControlFrame {\n done: boolean;\n nextIndex: number;\n}\n\n/**\n * Try to parse a stream control frame from the tail of a buffer.\n * Returns the parsed frame and the byte length of the control data,\n * or null if no valid control frame is present.\n */\nexport function parseStreamControlFrame(\n buffer: Uint8Array\n): (StreamControlFrame & { totalLength: number }) | null {\n if (buffer.length < STREAM_CONTROL_FRAME_SIZE) return null;\n\n const offset = buffer.length - STREAM_CONTROL_FRAME_SIZE;\n\n // Check zero-frame marker (bytes 0-3 must be 0x00)\n if (\n buffer[offset] !== 0 ||\n buffer[offset + 1] !== 0 ||\n buffer[offset + 2] !== 0 ||\n buffer[offset + 3] !== 0\n ) {\n return null;\n }\n\n // Check magic footer at bytes 9-12\n if (\n buffer[offset + 9] !== STREAM_CONTROL_MAGIC[0] ||\n buffer[offset + 10] !== STREAM_CONTROL_MAGIC[1] ||\n buffer[offset + 11] !== STREAM_CONTROL_MAGIC[2] ||\n buffer[offset + 12] !== STREAM_CONTROL_MAGIC[3]\n ) {\n return null;\n }\n\n const flags = buffer[offset + 4];\n const view = new DataView(buffer.buffer, buffer.byteOffset + offset + 5, 4);\n const nextIndex = view.getUint32(0, false);\n\n return {\n done: (flags & 1) === 1,\n nextIndex,\n totalLength: STREAM_CONTROL_FRAME_SIZE,\n };\n}\n\nfunction getStreamUrl(\n name: string,\n runId: string | undefined,\n httpConfig: HttpConfig\n) {\n if (runId) {\n return new URL(\n `${httpConfig.baseUrl}/v2/runs/${encodeURIComponent(runId)}/stream/${encodeURIComponent(name)}`\n );\n }\n return new URL(`${httpConfig.baseUrl}/v2/stream/${encodeURIComponent(name)}`);\n}\n\n/**\n * Encode multiple chunks into a length-prefixed binary format.\n * Format: [4 bytes big-endian length][chunk bytes][4 bytes length][chunk bytes]...\n *\n * This preserves chunk boundaries so the server can store them as separate\n * chunks, maintaining correct startIndex semantics for readers.\n *\n * @internal Exported for testing purposes\n */\nexport function encodeMultiChunks(chunks: (string | Uint8Array)[]): Uint8Array {\n const encoder = new TextEncoder();\n\n // Convert all chunks to Uint8Array and calculate total size\n const binaryChunks: Uint8Array[] = [];\n let totalSize = 0;\n\n for (const chunk of chunks) {\n const binary = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n binaryChunks.push(binary);\n totalSize += 4 + binary.length; // 4 bytes for length prefix\n }\n\n // Allocate buffer and write length-prefixed chunks\n const result = new Uint8Array(totalSize);\n const view = new DataView(result.buffer);\n let offset = 0;\n\n for (const binary of binaryChunks) {\n view.setUint32(offset, binary.length, false); // big-endian\n offset += 4;\n result.set(binary, offset);\n offset += binary.length;\n }\n\n return result;\n}\n\nconst StreamInfoResponseSchema = z.object({\n tailIndex: z.number(),\n done: z.boolean(),\n});\n\n/**\n * Zod schema for the paginated stream chunks response from the server.\n * When using CBOR (the default for makeRequest), chunk data arrives as\n * native Uint8Array byte strings — no base64 decoding required.\n */\nconst StreamChunksResponseSchema = z.object({\n data: z.array(\n z.object({\n index: z.number(),\n data: z.instanceof(Uint8Array),\n })\n ),\n cursor: z.string().nullable(),\n hasMore: z.boolean(),\n done: z.boolean(),\n});\n\n/** Creates the HTTP-backed streamer that talks to workflow-server. */\nexport function createStreamer(config?: APIConfig): Streamer {\n return {\n async writeToStream(\n name: string,\n runId: string | Promise<string>,\n chunk: string | Uint8Array\n ) {\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n body: chunk,\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream write failed: HTTP ${response.status}: ${text}`\n );\n }\n },\n\n async writeToStreamMulti(\n name: string,\n runId: string | Promise<string>,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n\n // Signal to server that this is a multi-chunk batch\n httpConfig.headers.set('X-Stream-Multi', 'true');\n\n // Send in pages of MAX_CHUNKS_PER_REQUEST to stay within the\n // server's per-batch limit (MAX_CHUNKS_PER_BATCH).\n // Note: for batches spanning multiple pages, atomicity is relaxed —\n // earlier pages may persist while a later page fails. The caller\n // retains the full buffer on error, so chunks from successful pages\n // will be re-sent on retry, producing duplicates. This is acceptable\n // because the alternative (400 on all >1000 chunk flushes) is worse,\n // and the scenario requires a network failure mid-batch.\n for (let i = 0; i < chunks.length; i += MAX_CHUNKS_PER_REQUEST) {\n const batch = chunks.slice(i, i + MAX_CHUNKS_PER_REQUEST);\n const body = encodeMultiChunks(batch);\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n body,\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream write failed: HTTP ${response.status}: ${text}`\n );\n }\n }\n },\n\n async closeStream(name: string, runId: string | Promise<string>) {\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n httpConfig.headers.set('X-Stream-Done', 'true');\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream close failed: HTTP ${response.status}: ${text}`\n );\n }\n },\n\n async readFromStream(name: string, startIndex?: number) {\n let currentStartIndex = startIndex ?? 0;\n\n // Cap reconnections to prevent infinite loops if the server\n // never completes the stream. 50 reconnects at 2-min server\n // timeout ≈ 100 minutes of streaming.\n const MAX_RECONNECTS = 50;\n let reconnectCount = 0;\n\n const connect = async (): Promise<Response> => {\n const httpConfig = await getHttpConfig(config);\n // Use v3 to receive the stream control frame for reconnection.\n const url = new URL(\n `${httpConfig.baseUrl}/v3/stream/${encodeURIComponent(name)}`\n );\n url.searchParams.set('startIndex', String(currentStartIndex));\n const response = await fetch(url, {\n headers: httpConfig.headers,\n });\n if (!response.ok) {\n throw new Error(`Failed to fetch stream: ${response.status}`);\n }\n if (!response.body) {\n throw new Error('No response body for stream');\n }\n return response;\n };\n\n // Read the entire response as bytes, then strip any trailing\n // control frame. This avoids the complexity of hold-back buffering\n // inside a ReadableStream pull loop, which interacts poorly with\n // the proxy and byte-stream wrappers in production.\n const readFull = async (): Promise<{\n data: Uint8Array;\n control: (StreamControlFrame & { totalLength: number }) | null;\n }> => {\n const response = await connect();\n const buffer = new Uint8Array(await response.arrayBuffer());\n const control = parseStreamControlFrame(buffer);\n if (control) {\n const dataLen = buffer.length - control.totalLength;\n return {\n data: dataLen > 0 ? buffer.subarray(0, dataLen) : new Uint8Array(0),\n control,\n };\n }\n return { data: buffer, control: null };\n };\n\n // Collect all data, transparently reconnecting on server timeout.\n const parts: Uint8Array[] = [];\n for (;;) {\n const { data, control } = await readFull();\n if (data.length > 0) {\n parts.push(data);\n }\n\n if (!control || control.done) {\n // Stream complete or no control frame (older server).\n break;\n }\n\n // Timeout — reconnect from the next chunk index.\n reconnectCount++;\n if (reconnectCount > MAX_RECONNECTS) {\n throw new Error(\n `Stream exceeded maximum reconnection attempts (${MAX_RECONNECTS})`\n );\n }\n currentStartIndex = control.nextIndex;\n }\n\n // Return a ReadableStream that emits the collected data.\n let emitted = false;\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n if (!emitted) {\n emitted = true;\n for (const part of parts) {\n controller.enqueue(part);\n }\n }\n controller.close();\n },\n });\n },\n\n async getStreamChunks(\n name: string,\n runId: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const params = new URLSearchParams();\n if (options?.limit != null) {\n params.set('limit', String(options.limit));\n }\n if (options?.cursor) {\n params.set('cursor', options.cursor);\n }\n const qs = params.toString();\n const endpoint = `/v2/runs/${encodeURIComponent(runId)}/streams/${encodeURIComponent(name)}/chunks${qs ? `?${qs}` : ''}`;\n return makeRequest({\n endpoint,\n config,\n schema: StreamChunksResponseSchema,\n });\n },\n\n async getStreamInfo(\n name: string,\n runId: string\n ): Promise<StreamInfoResponse> {\n const endpoint = `/v2/runs/${encodeURIComponent(runId)}/streams/${encodeURIComponent(name)}/info`;\n return makeRequest({\n endpoint,\n config,\n schema: StreamInfoResponseSchema,\n });\n },\n\n async listStreamsByRunId(runId: string) {\n const httpConfig = await getHttpConfig(config);\n const url = new URL(\n `${httpConfig.baseUrl}/v2/runs/${encodeURIComponent(runId)}/streams`\n );\n const response = await fetch(url, {\n headers: httpConfig.headers,\n });\n if (!response.ok) {\n throw new Error(`Failed to list streams: ${response.status}`);\n }\n return (await response.json()) as string[];\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { z } from 'zod';\nimport {\n type APIConfig,\n getHttpConfig,\n type HttpConfig,\n makeRequest,\n} from './utils.js';\n\n/**\n * Maximum number of chunks per request, matching the server-side\n * MAX_CHUNKS_PER_BATCH. Larger batches are split into multiple requests.\n */\nexport const MAX_CHUNKS_PER_REQUEST = 1000;\n\n// Streaming calls use plain fetch() without the undici dispatcher.\n// The dispatcher's retry logic doesn't apply well to streaming operations\n// (partial writes, long-lived reads), and duplex streams are incompatible\n// with undici's experimental H2 support.\n\nfunction getStreamUrl(\n name: string,\n runId: string | undefined,\n httpConfig: HttpConfig\n) {\n if (runId) {\n return new URL(\n `${httpConfig.baseUrl}/v2/runs/${encodeURIComponent(runId)}/stream/${encodeURIComponent(name)}`\n );\n }\n return new URL(`${httpConfig.baseUrl}/v2/stream/${encodeURIComponent(name)}`);\n}\n\n/**\n * Encode multiple chunks into a length-prefixed binary format.\n * Format: [4 bytes big-endian length][chunk bytes][4 bytes length][chunk bytes]...\n *\n * This preserves chunk boundaries so the server can store them as separate\n * chunks, maintaining correct startIndex semantics for readers.\n *\n * @internal Exported for testing purposes\n */\nexport function encodeMultiChunks(chunks: (string | Uint8Array)[]): Uint8Array {\n const encoder = new TextEncoder();\n\n // Convert all chunks to Uint8Array and calculate total size\n const binaryChunks: Uint8Array[] = [];\n let totalSize = 0;\n\n for (const chunk of chunks) {\n const binary = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n binaryChunks.push(binary);\n totalSize += 4 + binary.length; // 4 bytes for length prefix\n }\n\n // Allocate buffer and write length-prefixed chunks\n const result = new Uint8Array(totalSize);\n const view = new DataView(result.buffer);\n let offset = 0;\n\n for (const binary of binaryChunks) {\n view.setUint32(offset, binary.length, false); // big-endian\n offset += 4;\n result.set(binary, offset);\n offset += binary.length;\n }\n\n return result;\n}\n\nconst StreamInfoResponseSchema = z.object({\n tailIndex: z.number(),\n done: z.boolean(),\n});\n\n/**\n * Zod schema for the paginated stream chunks response from the server.\n * When using CBOR (the default for makeRequest), chunk data arrives as\n * native Uint8Array byte strings — no base64 decoding required.\n */\nconst StreamChunksResponseSchema = z.object({\n data: z.array(\n z.object({\n index: z.number(),\n data: z.instanceof(Uint8Array),\n })\n ),\n cursor: z.string().nullable(),\n hasMore: z.boolean(),\n done: z.boolean(),\n});\n\n/** Creates the HTTP-backed streamer that talks to workflow-server. */\nexport function createStreamer(config?: APIConfig): Streamer {\n return {\n async writeToStream(\n name: string,\n runId: string | Promise<string>,\n chunk: string | Uint8Array\n ) {\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n body: chunk,\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream write failed: HTTP ${response.status}: ${text}`\n );\n }\n },\n\n async writeToStreamMulti(\n name: string,\n runId: string | Promise<string>,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n\n // Signal to server that this is a multi-chunk batch\n httpConfig.headers.set('X-Stream-Multi', 'true');\n\n // Send in pages of MAX_CHUNKS_PER_REQUEST to stay within the\n // server's per-batch limit (MAX_CHUNKS_PER_BATCH).\n // Note: for batches spanning multiple pages, atomicity is relaxed —\n // earlier pages may persist while a later page fails. The caller\n // retains the full buffer on error, so chunks from successful pages\n // will be re-sent on retry, producing duplicates. This is acceptable\n // because the alternative (400 on all >1000 chunk flushes) is worse,\n // and the scenario requires a network failure mid-batch.\n for (let i = 0; i < chunks.length; i += MAX_CHUNKS_PER_REQUEST) {\n const batch = chunks.slice(i, i + MAX_CHUNKS_PER_REQUEST);\n const body = encodeMultiChunks(batch);\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n body,\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream write failed: HTTP ${response.status}: ${text}`\n );\n }\n }\n },\n\n async closeStream(name: string, runId: string | Promise<string>) {\n // Await runId if it's a promise to ensure proper flushing\n const resolvedRunId = await runId;\n\n const httpConfig = await getHttpConfig(config);\n httpConfig.headers.set('X-Stream-Done', 'true');\n const response = await fetch(\n getStreamUrl(name, resolvedRunId, httpConfig),\n {\n method: 'PUT',\n headers: httpConfig.headers,\n }\n );\n const text = await response.text();\n if (!response.ok) {\n throw new Error(\n `Stream close failed: HTTP ${response.status}: ${text}`\n );\n }\n },\n\n async readFromStream(name: string, startIndex?: number) {\n const httpConfig = await getHttpConfig(config);\n const url = getStreamUrl(name, undefined, httpConfig);\n if (typeof startIndex === 'number') {\n url.searchParams.set('startIndex', String(startIndex));\n }\n const response = await fetch(url, {\n headers: httpConfig.headers,\n });\n if (!response.ok) {\n throw new Error(`Failed to fetch stream: ${response.status}`);\n }\n if (!response.body) {\n throw new Error('No response body for stream');\n }\n return response.body as ReadableStream<Uint8Array>;\n },\n\n async getStreamChunks(\n name: string,\n runId: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const params = new URLSearchParams();\n if (options?.limit != null) {\n params.set('limit', String(options.limit));\n }\n if (options?.cursor) {\n params.set('cursor', options.cursor);\n }\n const qs = params.toString();\n const endpoint = `/v2/runs/${encodeURIComponent(runId)}/streams/${encodeURIComponent(name)}/chunks${qs ? `?${qs}` : ''}`;\n return makeRequest({\n endpoint,\n config,\n schema: StreamChunksResponseSchema,\n });\n },\n\n async getStreamInfo(\n name: string,\n runId: string\n ): Promise<StreamInfoResponse> {\n const endpoint = `/v2/runs/${encodeURIComponent(runId)}/streams/${encodeURIComponent(name)}/info`;\n return makeRequest({\n endpoint,\n config,\n schema: StreamInfoResponseSchema,\n });\n },\n\n async listStreamsByRunId(runId: string) {\n const httpConfig = await getHttpConfig(config);\n const url = new URL(\n `${httpConfig.baseUrl}/v2/runs/${encodeURIComponent(runId)}/streams`\n );\n const response = await fetch(url, {\n headers: httpConfig.headers,\n });\n if (!response.ok) {\n throw new Error(`Failed to list streams: ${response.status}`);\n }\n return (await response.json()) as string[];\n },\n };\n}\n"]}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "4.1.
|
|
1
|
+
export declare const version = "4.1.2";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA","sourcesContent":["// Generated by genversion.\nexport const version = '4.1.
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA","sourcesContent":["// Generated by genversion.\nexport const version = '4.1.2'\n"]}
|