assistant-stream 0.3.26 → 0.3.28
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/core/AssistantStream.d.ts.map +1 -1
- package/dist/core/AssistantStreamChunk.d.ts +3 -3
- package/dist/core/accumulators/AssistantMessageStream.d.ts.map +1 -1
- package/dist/core/accumulators/AssistantMessageStream.js +0 -1
- package/dist/core/accumulators/AssistantMessageStream.js.map +1 -1
- package/dist/core/accumulators/assistant-message-accumulator.d.ts.map +1 -1
- package/dist/core/accumulators/assistant-message-accumulator.js +65 -28
- package/dist/core/accumulators/assistant-message-accumulator.js.map +1 -1
- package/dist/core/converters/toGenericMessages.js.map +1 -1
- package/dist/core/gorp/GorpStreamAccumulator.d.ts +15 -0
- package/dist/core/gorp/GorpStreamAccumulator.d.ts.map +1 -0
- package/dist/core/{object/ObjectStreamAccumulator.js → gorp/GorpStreamAccumulator.js} +11 -9
- package/dist/core/gorp/GorpStreamAccumulator.js.map +1 -0
- package/dist/core/gorp/GorpStreamDeltaTracker.d.ts +17 -0
- package/dist/core/gorp/GorpStreamDeltaTracker.d.ts.map +1 -0
- package/dist/core/gorp/GorpStreamDeltaTracker.js +38 -0
- package/dist/core/gorp/GorpStreamDeltaTracker.js.map +1 -0
- package/dist/core/gorp/GorpStreamResponse.d.ts +16 -0
- package/dist/core/gorp/GorpStreamResponse.d.ts.map +1 -0
- package/dist/core/gorp/GorpStreamResponse.js +51 -0
- package/dist/core/gorp/GorpStreamResponse.js.map +1 -0
- package/dist/core/gorp/changeTree.d.ts +16 -0
- package/dist/core/gorp/changeTree.d.ts.map +1 -0
- package/dist/core/gorp/changeTree.js +77 -0
- package/dist/core/gorp/changeTree.js.map +1 -0
- package/dist/core/gorp/createGorpStream.d.ts +16 -0
- package/dist/core/gorp/createGorpStream.d.ts.map +1 -0
- package/dist/core/{object/createObjectStream.js → gorp/createGorpStream.js} +13 -8
- package/dist/core/gorp/createGorpStream.js.map +1 -0
- package/dist/core/{object → gorp}/types.d.ts +6 -5
- package/dist/core/gorp/types.d.ts.map +1 -0
- package/dist/core/modules/assistant-stream.d.ts +5 -4
- package/dist/core/modules/assistant-stream.d.ts.map +1 -1
- package/dist/core/modules/assistant-stream.js +10 -6
- package/dist/core/modules/assistant-stream.js.map +1 -1
- package/dist/core/modules/tool-call.d.ts.map +1 -1
- package/dist/core/modules/tool-call.js +3 -3
- package/dist/core/modules/tool-call.js.map +1 -1
- package/dist/core/serialization/assistant-transport/AssistantTransport.d.ts.map +1 -1
- package/dist/core/serialization/assistant-transport/AssistantTransport.js +91 -1
- package/dist/core/serialization/assistant-transport/AssistantTransport.js.map +1 -1
- package/dist/core/serialization/data-stream/DataStream.js +48 -48
- package/dist/core/serialization/data-stream/DataStream.js.map +1 -1
- package/dist/core/serialization/data-stream/chunk-types.d.ts +25 -24
- package/dist/core/serialization/data-stream/chunk-types.d.ts.map +1 -1
- package/dist/core/serialization/data-stream/chunk-types.js +22 -23
- package/dist/core/serialization/data-stream/chunk-types.js.map +1 -1
- package/dist/core/serialization/data-stream/serialization.d.ts.map +1 -1
- package/dist/core/serialization/data-stream/serialization.js +14 -2
- package/dist/core/serialization/data-stream/serialization.js.map +1 -1
- package/dist/core/serialization/ui-message-stream/UIMessageStream.d.ts.map +1 -1
- package/dist/core/serialization/ui-message-stream/UIMessageStream.js +12 -1
- package/dist/core/serialization/ui-message-stream/UIMessageStream.js.map +1 -1
- package/dist/core/tool/ToolCallReader.d.ts.map +1 -1
- package/dist/core/tool/ToolCallReader.js.map +1 -1
- package/dist/core/tool/ToolExecutionStream.d.ts.map +1 -1
- package/dist/core/tool/ToolExecutionStream.js +9 -1
- package/dist/core/tool/ToolExecutionStream.js.map +1 -1
- package/dist/core/tool/ToolResponse.d.ts.map +1 -1
- package/dist/core/tool/tool-types.d.ts +6 -0
- package/dist/core/tool/tool-types.d.ts.map +1 -1
- package/dist/core/tool/toolResultStream.d.ts.map +1 -1
- package/dist/core/tool/toolResultStream.js +7 -2
- package/dist/core/tool/toolResultStream.js.map +1 -1
- package/dist/core/utils/stream/AssistantTransformStream.d.ts.map +1 -1
- package/dist/core/utils/stream/PipeableTransformStream.d.ts.map +1 -1
- package/dist/core/utils/stream/SSE.d.ts.map +1 -1
- package/dist/core/utils/stream/SSE.js +11 -2
- package/dist/core/utils/stream/SSE.js.map +1 -1
- package/dist/core/utils/stream/SSEEventDecoder.d.ts.map +1 -1
- package/dist/core/utils/stream/merge.d.ts +2 -0
- package/dist/core/utils/stream/merge.d.ts.map +1 -1
- package/dist/core/utils/stream/merge.js +27 -8
- package/dist/core/utils/stream/merge.js.map +1 -1
- package/dist/core/utils/stream/path-utils.d.ts.map +1 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -0
- package/dist/resumable/ResumableStreamContext.d.ts.map +1 -1
- package/dist/resumable/ResumableStreamContext.js +12 -5
- package/dist/resumable/ResumableStreamContext.js.map +1 -1
- package/dist/resumable/errors.d.ts.map +1 -1
- package/dist/resumable/stores/redis-impl.d.ts.map +1 -1
- package/dist/utils/AsyncIterableStream.d.ts.map +1 -1
- package/dist/utils/AsyncIterableStream.js +18 -3
- package/dist/utils/AsyncIterableStream.js.map +1 -1
- package/dist/utils/promiseWithResolvers.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/core/AssistantStreamChunk.ts +3 -3
- package/src/core/accumulators/AssistantMessageStream.ts +3 -1
- package/src/core/accumulators/assistant-message-accumulator.test.ts +374 -1
- package/src/core/accumulators/assistant-message-accumulator.ts +75 -26
- package/src/core/converters/toGenericMessages.test.ts +3 -39
- package/src/core/{object/ObjectStream.test.ts → gorp/GorpStream.test.ts} +86 -25
- package/src/core/gorp/GorpStreamAccumulator.test.ts +28 -0
- package/src/core/{object/ObjectStreamAccumulator.ts → gorp/GorpStreamAccumulator.ts} +13 -15
- package/src/core/gorp/GorpStreamDeltaTracker.test.ts +212 -0
- package/src/core/gorp/GorpStreamDeltaTracker.ts +55 -0
- package/src/core/{object/ObjectStreamResponse.test.ts → gorp/GorpStreamResponse.test.ts} +6 -6
- package/src/core/gorp/GorpStreamResponse.ts +88 -0
- package/src/core/gorp/changeTree.ts +96 -0
- package/src/core/{object/createObjectStream.ts → gorp/createGorpStream.ts} +23 -17
- package/src/core/{object → gorp}/types.ts +5 -3
- package/src/core/modules/assistant-stream.test.ts +154 -2
- package/src/core/modules/assistant-stream.ts +16 -6
- package/src/core/modules/tool-call.test.ts +67 -0
- package/src/core/modules/tool-call.ts +7 -4
- package/src/core/serialization/assistant-transport/AssistantTransport.test.ts +174 -1
- package/src/core/serialization/assistant-transport/AssistantTransport.ts +77 -1
- package/src/core/serialization/data-stream/DataStream.ts +1 -1
- package/src/core/serialization/data-stream/chunk-types.ts +26 -24
- package/src/core/serialization/data-stream/serialization.test.ts +133 -0
- package/src/core/serialization/data-stream/serialization.ts +20 -2
- package/src/core/serialization/ui-message-stream/UIMessageStream.test.ts +135 -0
- package/src/core/serialization/ui-message-stream/UIMessageStream.ts +21 -1
- package/src/core/tool/ToolCallReader.ts +5 -1
- package/src/core/tool/ToolExecutionStream.ts +10 -0
- package/src/core/tool/tool-types.ts +7 -0
- package/src/core/tool/toolResultStream.test.ts +50 -0
- package/src/core/tool/toolResultStream.ts +7 -2
- package/src/core/utils/stream/SSE.test.ts +97 -0
- package/src/core/utils/stream/SSE.ts +13 -2
- package/src/core/utils/stream/merge.ts +31 -9
- package/src/gorp.test.ts +32 -0
- package/src/index.ts +21 -6
- package/src/resumable/ResumableStreamContext.test.ts +116 -1
- package/src/resumable/ResumableStreamContext.ts +32 -5
- package/src/resumable/createResumableAssistantStreamResponse.test.ts +1 -25
- package/src/utils/AsyncIterableStream.test.ts +61 -0
- package/src/utils/AsyncIterableStream.ts +18 -3
- package/dist/core/object/ObjectStreamAccumulator.d.ts +0 -15
- package/dist/core/object/ObjectStreamAccumulator.d.ts.map +0 -1
- package/dist/core/object/ObjectStreamAccumulator.js.map +0 -1
- package/dist/core/object/ObjectStreamResponse.d.ts +0 -16
- package/dist/core/object/ObjectStreamResponse.d.ts.map +0 -1
- package/dist/core/object/ObjectStreamResponse.js +0 -57
- package/dist/core/object/ObjectStreamResponse.js.map +0 -1
- package/dist/core/object/createObjectStream.d.ts +0 -16
- package/dist/core/object/createObjectStream.d.ts.map +0 -1
- package/dist/core/object/createObjectStream.js.map +0 -1
- package/dist/core/object/types.d.ts.map +0 -1
- package/src/core/accumulators/TimingTracker.test.ts +0 -100
- package/src/core/object/ObjectStreamResponse.ts +0 -114
- /package/dist/core/{object → gorp}/types.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
RESUMABLE_STREAM_ID_HEADER,
|
|
4
4
|
createResumableAssistantStreamResponse,
|
|
@@ -140,25 +140,6 @@ describe("createResumableAssistantStreamResponse", () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
describe("producer crash", () => {
|
|
143
|
-
let suppressed: unknown[];
|
|
144
|
-
let original: NodeJS.UnhandledRejectionListener[];
|
|
145
|
-
const swallow: NodeJS.UnhandledRejectionListener = (reason) => {
|
|
146
|
-
suppressed.push(reason);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
beforeEach(() => {
|
|
150
|
-
suppressed = [];
|
|
151
|
-
original = process.listeners("unhandledRejection");
|
|
152
|
-
for (const l of original) process.off("unhandledRejection", l);
|
|
153
|
-
process.on("unhandledRejection", swallow);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
afterEach(async () => {
|
|
157
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
158
|
-
process.off("unhandledRejection", swallow);
|
|
159
|
-
for (const l of original) process.on("unhandledRejection", l);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
143
|
it("synchronous callback throw is encoded into the body and finalizes the stream", async () => {
|
|
163
144
|
const ctx = createResumableStreamContext({
|
|
164
145
|
store: createInMemoryResumableStreamStore(),
|
|
@@ -184,11 +165,6 @@ describe("createResumableAssistantStreamResponse", () => {
|
|
|
184
165
|
expect(replay).not.toBeNull();
|
|
185
166
|
const replayBytes = await new Response(replay).arrayBuffer();
|
|
186
167
|
expect(new Uint8Array(replayBytes)).toEqual(new Uint8Array(firstBytes));
|
|
187
|
-
|
|
188
|
-
while (suppressed.length === 0) {
|
|
189
|
-
await new Promise((r) => setTimeout(r, 5));
|
|
190
|
-
}
|
|
191
|
-
expect(suppressed.map((e) => (e as Error).message)).toContain("boom");
|
|
192
168
|
});
|
|
193
169
|
});
|
|
194
170
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { asAsyncIterableStream } from "./AsyncIterableStream";
|
|
3
|
+
|
|
4
|
+
const forcePolyfilledIterator = <T>(
|
|
5
|
+
stream: ReadableStream<T>,
|
|
6
|
+
): AsyncIterable<T> & ReadableStream<T> => {
|
|
7
|
+
Object.defineProperty(stream, Symbol.asyncIterator, {
|
|
8
|
+
configurable: true,
|
|
9
|
+
value: undefined,
|
|
10
|
+
writable: true,
|
|
11
|
+
});
|
|
12
|
+
return asAsyncIterableStream(stream);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("asAsyncIterableStream", () => {
|
|
16
|
+
it("cancels the source when iteration stops early", async () => {
|
|
17
|
+
const cancel = vi.fn();
|
|
18
|
+
const stream = forcePolyfilledIterator(
|
|
19
|
+
new ReadableStream<string>({
|
|
20
|
+
start(controller) {
|
|
21
|
+
controller.enqueue("first");
|
|
22
|
+
controller.enqueue("second");
|
|
23
|
+
},
|
|
24
|
+
cancel,
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const values: string[] = [];
|
|
29
|
+
for await (const value of stream) {
|
|
30
|
+
values.push(value);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
expect(values).toEqual(["first"]);
|
|
35
|
+
expect(cancel).toHaveBeenCalledOnce();
|
|
36
|
+
expect(stream.locked).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not cancel a fully consumed source", async () => {
|
|
40
|
+
const cancel = vi.fn();
|
|
41
|
+
const stream = forcePolyfilledIterator(
|
|
42
|
+
new ReadableStream<string>({
|
|
43
|
+
start(controller) {
|
|
44
|
+
controller.enqueue("first");
|
|
45
|
+
controller.enqueue("second");
|
|
46
|
+
controller.close();
|
|
47
|
+
},
|
|
48
|
+
cancel,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const values: string[] = [];
|
|
53
|
+
for await (const value of stream) {
|
|
54
|
+
values.push(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
expect(values).toEqual(["first", "second"]);
|
|
58
|
+
expect(cancel).not.toHaveBeenCalled();
|
|
59
|
+
expect(stream.locked).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -4,14 +4,29 @@ async function* streamGeneratorPolyfill<T>(
|
|
|
4
4
|
this: ReadableStream<T>,
|
|
5
5
|
): AsyncGenerator<T, undefined, unknown> {
|
|
6
6
|
const reader = this.getReader();
|
|
7
|
+
let shouldCancel = true;
|
|
7
8
|
try {
|
|
8
9
|
while (true) {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
let result: ReadableStreamReadResult<T>;
|
|
11
|
+
try {
|
|
12
|
+
result = await reader.read();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
shouldCancel = false;
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
if (result.done) {
|
|
18
|
+
shouldCancel = false;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const { value } = result;
|
|
11
22
|
yield value;
|
|
12
23
|
}
|
|
13
24
|
} finally {
|
|
14
|
-
|
|
25
|
+
try {
|
|
26
|
+
if (shouldCancel) await reader.cancel();
|
|
27
|
+
} finally {
|
|
28
|
+
reader.releaseLock();
|
|
29
|
+
}
|
|
15
30
|
}
|
|
16
31
|
}
|
|
17
32
|
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ReadonlyJSONValue } from "../../utils/json/json-value.js";
|
|
2
|
-
import "../../utils.js";
|
|
3
|
-
import { ObjectStreamOperation } from "./types.js";
|
|
4
|
-
//#region src/core/object/ObjectStreamAccumulator.d.ts
|
|
5
|
-
declare class ObjectStreamAccumulator {
|
|
6
|
-
private _state;
|
|
7
|
-
constructor(initialValue?: ReadonlyJSONValue);
|
|
8
|
-
get state(): ReadonlyJSONValue;
|
|
9
|
-
append(ops: readonly ObjectStreamOperation[]): void;
|
|
10
|
-
private static apply;
|
|
11
|
-
private static updatePath;
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
export { ObjectStreamAccumulator };
|
|
15
|
-
//# sourceMappingURL=ObjectStreamAccumulator.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectStreamAccumulator.d.ts","names":[],"sources":["../../../src/core/object/ObjectStreamAccumulator.ts"],"mappings":";;;;cAGa;UACH;cAEI,eAAc;MAItB,SAAK;EAIT,OAAO,cAAc;iBAON;iBAuBA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectStreamAccumulator.js","names":["_exhaustiveCheck"],"sources":["../../../src/core/object/ObjectStreamAccumulator.ts"],"sourcesContent":["import type { ReadonlyJSONValue, ReadonlyJSONObject } from \"../../utils\";\nimport type { ObjectStreamOperation } from \"./types\";\n\nexport class ObjectStreamAccumulator {\n private _state: ReadonlyJSONValue;\n\n constructor(initialValue: ReadonlyJSONValue = null) {\n this._state = initialValue;\n }\n\n get state() {\n return this._state;\n }\n\n append(ops: readonly ObjectStreamOperation[]) {\n this._state = ops.reduce(\n (state, op) => ObjectStreamAccumulator.apply(state, op),\n this._state,\n );\n }\n\n private static apply(state: ReadonlyJSONValue, op: ObjectStreamOperation) {\n const type = op.type;\n switch (type) {\n case \"set\":\n return ObjectStreamAccumulator.updatePath(\n state,\n op.path,\n () => op.value,\n );\n case \"append-text\":\n return ObjectStreamAccumulator.updatePath(state, op.path, (current) => {\n if (typeof current !== \"string\")\n throw new Error(`Expected string at path [${op.path.join(\", \")}]`);\n return current + op.value;\n });\n\n default: {\n const _exhaustiveCheck: never = type;\n throw new Error(`Invalid operation type: ${_exhaustiveCheck}`);\n }\n }\n }\n\n private static updatePath(\n state: ReadonlyJSONValue | undefined,\n path: readonly string[],\n updater: (current: ReadonlyJSONValue | undefined) => ReadonlyJSONValue,\n ): ReadonlyJSONValue {\n if (path.length === 0) return updater(state);\n\n // Initialize state as empty object if it's null and we're trying to set a property\n state ??= {};\n\n if (typeof state !== \"object\") {\n throw new Error(`Invalid path: [${path.join(\", \")}]`);\n }\n\n const [key, ...rest] = path as [string, ...(readonly string[])];\n if (Array.isArray(state)) {\n const idx = Number(key);\n if (Number.isNaN(idx))\n throw new Error(`Expected array index at [${path.join(\", \")}]`);\n if (idx > state.length || idx < 0)\n throw new Error(`Insert array index out of bounds`);\n\n const nextState = [...state];\n nextState[idx] = ObjectStreamAccumulator.updatePath(\n nextState[idx],\n rest,\n updater,\n );\n\n return nextState;\n }\n\n const nextState = { ...(state as ReadonlyJSONObject) };\n nextState[key] = ObjectStreamAccumulator.updatePath(\n nextState[key],\n rest,\n updater,\n );\n\n return nextState;\n }\n}\n"],"mappings":";AAGA,IAAa,0BAAb,MAAa,wBAAwB;CACnC;CAEA,YAAY,eAAkC,MAAM;EAClD,KAAK,SAAS;CAChB;CAEA,IAAI,QAAQ;EACV,OAAO,KAAK;CACd;CAEA,OAAO,KAAuC;EAC5C,KAAK,SAAS,IAAI,QACf,OAAO,OAAO,wBAAwB,MAAM,OAAO,EAAE,GACtD,KAAK,MACP;CACF;CAEA,OAAe,MAAM,OAA0B,IAA2B;EACxE,MAAM,OAAO,GAAG;EAChB,QAAQ,MAAR;GACE,KAAK,OACH,OAAO,wBAAwB,WAC7B,OACA,GAAG,YACG,GAAG,KACX;GACF,KAAK,eACH,OAAO,wBAAwB,WAAW,OAAO,GAAG,OAAO,YAAY;IACrE,IAAI,OAAO,YAAY,UACrB,MAAM,IAAI,MAAM,4BAA4B,GAAG,KAAK,KAAK,IAAI,EAAE,EAAE;IACnE,OAAO,UAAU,GAAG;GACtB,CAAC;GAEH,SAEE,MAAM,IAAI,MAAM,2BAA2BA,MAAkB;EAEjE;CACF;CAEA,OAAe,WACb,OACA,MACA,SACmB;EACnB,IAAI,KAAK,WAAW,GAAG,OAAO,QAAQ,KAAK;EAG3C,UAAU,CAAC;EAEX,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,MAAM,kBAAkB,KAAK,KAAK,IAAI,EAAE,EAAE;EAGtD,MAAM,CAAC,KAAK,GAAG,QAAQ;EACvB,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,MAAM,MAAM,OAAO,GAAG;GACtB,IAAI,OAAO,MAAM,GAAG,GAClB,MAAM,IAAI,MAAM,4BAA4B,KAAK,KAAK,IAAI,EAAE,EAAE;GAChE,IAAI,MAAM,MAAM,UAAU,MAAM,GAC9B,MAAM,IAAI,MAAM,kCAAkC;GAEpD,MAAM,YAAY,CAAC,GAAG,KAAK;GAC3B,UAAU,OAAO,wBAAwB,WACvC,UAAU,MACV,MACA,OACF;GAEA,OAAO;EACT;EAEA,MAAM,YAAY,EAAE,GAAI,MAA6B;EACrD,UAAU,OAAO,wBAAwB,WACvC,UAAU,MACV,MACA,OACF;EAEA,OAAO;CACT;AACF"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ObjectStreamChunk } from "./types.js";
|
|
2
|
-
import { PipeableTransformStream } from "../utils/stream/PipeableTransformStream.js";
|
|
3
|
-
//#region src/core/object/ObjectStreamResponse.d.ts
|
|
4
|
-
declare class ObjectStreamEncoder extends PipeableTransformStream<ObjectStreamChunk, Uint8Array> {
|
|
5
|
-
constructor();
|
|
6
|
-
}
|
|
7
|
-
declare class ObjectStreamDecoder extends PipeableTransformStream<Uint8Array<ArrayBuffer>, ObjectStreamChunk> {
|
|
8
|
-
constructor();
|
|
9
|
-
}
|
|
10
|
-
declare class ObjectStreamResponse extends Response {
|
|
11
|
-
constructor(body: ReadableStream<ObjectStreamChunk>);
|
|
12
|
-
}
|
|
13
|
-
declare const fromObjectStreamResponse: (response: Response) => ReadableStream<ObjectStreamChunk>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { ObjectStreamDecoder, ObjectStreamEncoder, ObjectStreamResponse, fromObjectStreamResponse };
|
|
16
|
-
//# sourceMappingURL=ObjectStreamResponse.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectStreamResponse.d.ts","names":[],"sources":["../../../src/core/object/ObjectStreamResponse.ts"],"mappings":";;;cAKa,4BAA4B,wBACvC,mBACA;;;cAgDW,4BAA4B,wBACvC,WAAW,cACX;;;cAyBW,6BAA6B;cAC5B,MAAM,eAAe;;cAYtB,2BACX,UAAU,aACT,eAAe"}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { PipeableTransformStream } from "../utils/stream/PipeableTransformStream.js";
|
|
2
|
-
import { ObjectStreamAccumulator } from "./ObjectStreamAccumulator.js";
|
|
3
|
-
import { SSEDecoder, SSEEncoder } from "../utils/stream/SSE.js";
|
|
4
|
-
//#region src/core/object/ObjectStreamResponse.ts
|
|
5
|
-
var ObjectStreamEncoder = class extends PipeableTransformStream {
|
|
6
|
-
constructor() {
|
|
7
|
-
super((readable) => readable.pipeThrough((() => {
|
|
8
|
-
class ObjectStreamTransformer {
|
|
9
|
-
#isFirstChunk = true;
|
|
10
|
-
start() {}
|
|
11
|
-
transform(chunk, controller) {
|
|
12
|
-
if (this.#isFirstChunk && chunk.snapshot && Object.keys(chunk.snapshot).length > 0) controller.enqueue([{
|
|
13
|
-
type: "set",
|
|
14
|
-
path: [],
|
|
15
|
-
value: chunk.snapshot
|
|
16
|
-
}, ...chunk.operations]);
|
|
17
|
-
else controller.enqueue(chunk.operations);
|
|
18
|
-
this.#isFirstChunk = false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return new TransformStream(new ObjectStreamTransformer());
|
|
22
|
-
})()).pipeThrough(new SSEEncoder()));
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
var ObjectStreamDecoder = class extends PipeableTransformStream {
|
|
26
|
-
constructor() {
|
|
27
|
-
const accumulator = new ObjectStreamAccumulator();
|
|
28
|
-
super((readable) => readable.pipeThrough(new SSEDecoder()).pipeThrough(new TransformStream({ transform(operations, controller) {
|
|
29
|
-
accumulator.append(operations);
|
|
30
|
-
controller.enqueue({
|
|
31
|
-
snapshot: accumulator.state,
|
|
32
|
-
operations
|
|
33
|
-
});
|
|
34
|
-
} })));
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
var ObjectStreamResponse = class extends Response {
|
|
38
|
-
constructor(body) {
|
|
39
|
-
super(body.pipeThrough(new ObjectStreamEncoder()), { headers: new Headers({
|
|
40
|
-
"Content-Type": "text/event-stream",
|
|
41
|
-
"Cache-Control": "no-cache",
|
|
42
|
-
Connection: "keep-alive",
|
|
43
|
-
"Assistant-Stream-Format": "object-stream/v0"
|
|
44
|
-
}) });
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
const fromObjectStreamResponse = (response) => {
|
|
48
|
-
if (!response.ok) throw new Error(`Response failed, status ${response.status}`);
|
|
49
|
-
if (!response.body) throw new Error("Response body is null");
|
|
50
|
-
if (response.headers.get("Content-Type")?.split(";", 1)[0]?.trim().toLowerCase() !== "text/event-stream") throw new Error("Response is not an event stream");
|
|
51
|
-
if (response.headers.get("Assistant-Stream-Format") !== "object-stream/v0") throw new Error("Unsupported Assistant-Stream-Format header");
|
|
52
|
-
return response.body.pipeThrough(new ObjectStreamDecoder());
|
|
53
|
-
};
|
|
54
|
-
//#endregion
|
|
55
|
-
export { ObjectStreamDecoder, ObjectStreamEncoder, ObjectStreamResponse, fromObjectStreamResponse };
|
|
56
|
-
|
|
57
|
-
//# sourceMappingURL=ObjectStreamResponse.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ObjectStreamResponse.js","names":["#isFirstChunk"],"sources":["../../../src/core/object/ObjectStreamResponse.ts"],"sourcesContent":["import { PipeableTransformStream } from \"../utils/stream/PipeableTransformStream\";\nimport { ObjectStreamAccumulator } from \"./ObjectStreamAccumulator\";\nimport { SSEDecoder, SSEEncoder } from \"../utils/stream/SSE\";\nimport type { ObjectStreamChunk, ObjectStreamOperation } from \"./types\";\n\nexport class ObjectStreamEncoder extends PipeableTransformStream<\n ObjectStreamChunk,\n Uint8Array\n> {\n constructor() {\n super((readable) =>\n readable\n .pipeThrough(\n (() => {\n class ObjectStreamTransformer implements Transformer<\n ObjectStreamChunk,\n readonly ObjectStreamOperation[]\n > {\n #isFirstChunk = true;\n\n start() {\n // Nothing needed here since we initialize in the field declaration\n }\n\n transform(\n chunk: ObjectStreamChunk,\n controller: TransformStreamDefaultController<\n readonly ObjectStreamOperation[]\n >,\n ) {\n if (\n this.#isFirstChunk &&\n chunk.snapshot &&\n Object.keys(chunk.snapshot).length > 0\n ) {\n // For the first chunk, if there's an initial state that's not empty,\n // prepend a set operation for the initial state\n controller.enqueue([\n { type: \"set\", path: [], value: chunk.snapshot },\n ...chunk.operations,\n ]);\n } else {\n controller.enqueue(chunk.operations);\n }\n this.#isFirstChunk = false;\n }\n }\n return new TransformStream(new ObjectStreamTransformer());\n })(),\n )\n .pipeThrough(new SSEEncoder()),\n );\n }\n}\n\nexport class ObjectStreamDecoder extends PipeableTransformStream<\n Uint8Array<ArrayBuffer>,\n ObjectStreamChunk\n> {\n constructor() {\n const accumulator = new ObjectStreamAccumulator();\n super((readable) =>\n readable\n .pipeThrough(new SSEDecoder<readonly ObjectStreamOperation[]>())\n .pipeThrough(\n new TransformStream<\n readonly ObjectStreamOperation[],\n ObjectStreamChunk\n >({\n transform(operations, controller) {\n accumulator.append(operations);\n controller.enqueue({\n snapshot: accumulator.state,\n operations,\n });\n },\n }),\n ),\n );\n }\n}\n\nexport class ObjectStreamResponse extends Response {\n constructor(body: ReadableStream<ObjectStreamChunk>) {\n super(body.pipeThrough(new ObjectStreamEncoder()), {\n headers: new Headers({\n \"Content-Type\": \"text/event-stream\",\n \"Cache-Control\": \"no-cache\",\n Connection: \"keep-alive\",\n \"Assistant-Stream-Format\": \"object-stream/v0\",\n }),\n });\n }\n}\n\nexport const fromObjectStreamResponse = (\n response: Response,\n): ReadableStream<ObjectStreamChunk> => {\n if (!response.ok)\n throw new Error(`Response failed, status ${response.status}`);\n if (!response.body) throw new Error(\"Response body is null\");\n const mediaType = response.headers\n .get(\"Content-Type\")\n ?.split(\";\", 1)[0]\n ?.trim()\n .toLowerCase();\n if (mediaType !== \"text/event-stream\") {\n throw new Error(\"Response is not an event stream\");\n }\n if (response.headers.get(\"Assistant-Stream-Format\") !== \"object-stream/v0\") {\n throw new Error(\"Unsupported Assistant-Stream-Format header\");\n }\n return response.body.pipeThrough(new ObjectStreamDecoder());\n};\n"],"mappings":";;;;AAKA,IAAa,sBAAb,cAAyC,wBAGvC;CACA,cAAc;EACZ,OAAO,aACL,SACG,mBACQ;GACL,MAAM,wBAGJ;IACA,gBAAgB;IAEhB,QAAQ,CAER;IAEA,UACE,OACA,YAGA;KACA,IACE,KAAKA,iBACL,MAAM,YACN,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,SAAS,GAIrC,WAAW,QAAQ,CACjB;MAAE,MAAM;MAAO,MAAM,CAAC;MAAG,OAAO,MAAM;KAAS,GAC/C,GAAG,MAAM,UACX,CAAC;UAED,WAAW,QAAQ,MAAM,UAAU;KAErC,KAAKA,gBAAgB;IACvB;GACF;GACA,OAAO,IAAI,gBAAgB,IAAI,wBAAwB,CAAC;EAC1D,EAAA,CAAG,CACL,CAAC,CACA,YAAY,IAAI,WAAW,CAAC,CACjC;CACF;AACF;AAEA,IAAa,sBAAb,cAAyC,wBAGvC;CACA,cAAc;EACZ,MAAM,cAAc,IAAI,wBAAwB;EAChD,OAAO,aACL,SACG,YAAY,IAAI,WAA6C,CAAC,CAAC,CAC/D,YACC,IAAI,gBAGF,EACA,UAAU,YAAY,YAAY;GAChC,YAAY,OAAO,UAAU;GAC7B,WAAW,QAAQ;IACjB,UAAU,YAAY;IACtB;GACF,CAAC;EACH,EACF,CAAC,CACH,CACJ;CACF;AACF;AAEA,IAAa,uBAAb,cAA0C,SAAS;CACjD,YAAY,MAAyC;EACnD,MAAM,KAAK,YAAY,IAAI,oBAAoB,CAAC,GAAG,EACjD,SAAS,IAAI,QAAQ;GACnB,gBAAgB;GAChB,iBAAiB;GACjB,YAAY;GACZ,2BAA2B;EAC7B,CAAC,EACH,CAAC;CACH;AACF;AAEA,MAAa,4BACX,aACsC;CACtC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,2BAA2B,SAAS,QAAQ;CAC9D,IAAI,CAAC,SAAS,MAAM,MAAM,IAAI,MAAM,uBAAuB;CAM3D,IALkB,SAAS,QACxB,IAAI,cAAc,CAAC,EAClB,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,EAChB,KAAK,CAAC,CACP,YAAY,MACG,qBAChB,MAAM,IAAI,MAAM,iCAAiC;CAEnD,IAAI,SAAS,QAAQ,IAAI,yBAAyB,MAAM,oBACtD,MAAM,IAAI,MAAM,4CAA4C;CAE9D,OAAO,SAAS,KAAK,YAAY,IAAI,oBAAoB,CAAC;AAC5D"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ReadonlyJSONValue } from "../../utils/json/json-value.js";
|
|
2
|
-
import "../../utils.js";
|
|
3
|
-
import { ObjectStreamChunk, ObjectStreamOperation } from "./types.js";
|
|
4
|
-
//#region src/core/object/createObjectStream.d.ts
|
|
5
|
-
type ObjectStreamController = {
|
|
6
|
-
readonly abortSignal: AbortSignal;
|
|
7
|
-
enqueue(operations: readonly ObjectStreamOperation[]): void;
|
|
8
|
-
};
|
|
9
|
-
type CreateObjectStreamOptions = {
|
|
10
|
-
execute: (controller: ObjectStreamController) => void | PromiseLike<void>;
|
|
11
|
-
defaultValue?: ReadonlyJSONValue;
|
|
12
|
-
};
|
|
13
|
-
declare const createObjectStream: ({ execute, defaultValue }: CreateObjectStreamOptions) => ReadableStream<ObjectStreamChunk>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { createObjectStream };
|
|
16
|
-
//# sourceMappingURL=createObjectStream.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObjectStream.d.ts","names":[],"sources":["../../../src/core/object/createObjectStream.ts"],"mappings":";;;;KAKK;WACM,aAAa;EAEtB,QAAQ,qBAAqB;;KAwD1B;EACH,UAAU,YAAY,kCAAkC;EACxD,eAAe;;cAGJ,uBAAsB,SAAA,gBAGhC,8BAAyB,eAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createObjectStream.js","names":[],"sources":["../../../src/core/object/createObjectStream.ts"],"sourcesContent":["import type { ReadonlyJSONValue } from \"../../utils\";\nimport { withPromiseOrValue } from \"../utils/withPromiseOrValue\";\nimport { ObjectStreamAccumulator } from \"./ObjectStreamAccumulator\";\nimport type { ObjectStreamOperation, ObjectStreamChunk } from \"./types\";\n\ntype ObjectStreamController = {\n readonly abortSignal: AbortSignal;\n\n enqueue(operations: readonly ObjectStreamOperation[]): void;\n};\n\nclass ObjectStreamControllerImpl implements ObjectStreamController {\n private _controller: ReadableStreamDefaultController<ObjectStreamChunk>;\n private _abortController = new AbortController();\n private _accumulator: ObjectStreamAccumulator;\n\n get abortSignal() {\n return this._abortController.signal;\n }\n\n constructor(\n controller: ReadableStreamDefaultController<ObjectStreamChunk>,\n defaultValue: ReadonlyJSONValue,\n ) {\n this._controller = controller;\n this._accumulator = new ObjectStreamAccumulator(defaultValue);\n }\n\n enqueue(operations: readonly ObjectStreamOperation[]) {\n this._accumulator.append(operations);\n\n this._controller.enqueue({\n snapshot: this._accumulator.state,\n operations,\n });\n }\n\n __internalError(error: unknown) {\n this._controller.error(error);\n }\n\n __internalClose() {\n this._controller.close();\n }\n\n __internalCancel(reason?: unknown) {\n this._abortController.abort(reason);\n }\n}\n\nconst getStreamControllerPair = (defaultValue: ReadonlyJSONValue) => {\n let controller!: ObjectStreamControllerImpl;\n const stream = new ReadableStream<ObjectStreamChunk>({\n start(c) {\n controller = new ObjectStreamControllerImpl(c, defaultValue);\n },\n cancel(reason: unknown) {\n controller.__internalCancel(reason);\n },\n });\n\n return [stream, controller] as const;\n};\n\ntype CreateObjectStreamOptions = {\n execute: (controller: ObjectStreamController) => void | PromiseLike<void>;\n defaultValue?: ReadonlyJSONValue;\n};\n\nexport const createObjectStream = ({\n execute,\n defaultValue = {},\n}: CreateObjectStreamOptions) => {\n const [stream, controller] = getStreamControllerPair(defaultValue);\n\n withPromiseOrValue(\n () => execute(controller),\n () => {\n controller.__internalClose();\n },\n (e: unknown) => {\n controller.__internalError(e);\n },\n );\n\n return stream;\n};\n"],"mappings":";;;AAWA,IAAM,6BAAN,MAAmE;CACjE;CACA,mBAA2B,IAAI,gBAAgB;CAC/C;CAEA,IAAI,cAAc;EAChB,OAAO,KAAK,iBAAiB;CAC/B;CAEA,YACE,YACA,cACA;EACA,KAAK,cAAc;EACnB,KAAK,eAAe,IAAI,wBAAwB,YAAY;CAC9D;CAEA,QAAQ,YAA8C;EACpD,KAAK,aAAa,OAAO,UAAU;EAEnC,KAAK,YAAY,QAAQ;GACvB,UAAU,KAAK,aAAa;GAC5B;EACF,CAAC;CACH;CAEA,gBAAgB,OAAgB;EAC9B,KAAK,YAAY,MAAM,KAAK;CAC9B;CAEA,kBAAkB;EAChB,KAAK,YAAY,MAAM;CACzB;CAEA,iBAAiB,QAAkB;EACjC,KAAK,iBAAiB,MAAM,MAAM;CACpC;AACF;AAEA,MAAM,2BAA2B,iBAAoC;CACnE,IAAI;CAUJ,OAAO,CAAC,IATW,eAAkC;EACnD,MAAM,GAAG;GACP,aAAa,IAAI,2BAA2B,GAAG,YAAY;EAC7D;EACA,OAAO,QAAiB;GACtB,WAAW,iBAAiB,MAAM;EACpC;CACF,CAEa,GAAG,UAAU;AAC5B;AAOA,MAAa,sBAAsB,EACjC,SACA,eAAe,CAAC,QACe;CAC/B,MAAM,CAAC,QAAQ,cAAc,wBAAwB,YAAY;CAEjE,yBACQ,QAAQ,UAAU,SAClB;EACJ,WAAW,gBAAgB;CAC7B,IACC,MAAe;EACd,WAAW,gBAAgB,CAAC;CAC9B,CACF;CAEA,OAAO;AACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/core/object/types.ts"],"mappings":";;;KAEY;WAEG;WACA;WACA,OAAO;;WAGP;WACA;WACA;;KAGH;WACD,UAAU;WACV,qBAAqB"}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { TimingTracker } from "./TimingTracker";
|
|
3
|
-
|
|
4
|
-
describe("TimingTracker", () => {
|
|
5
|
-
beforeEach(() => {
|
|
6
|
-
vi.useFakeTimers();
|
|
7
|
-
vi.setSystemTime(1000);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
vi.useRealTimers();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should record stream start time", () => {
|
|
15
|
-
const tracker = new TimingTracker();
|
|
16
|
-
const timing = tracker.getTiming();
|
|
17
|
-
expect(timing.streamStartTime).toBe(1000);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should count chunks", () => {
|
|
21
|
-
const tracker = new TimingTracker();
|
|
22
|
-
tracker.recordChunk();
|
|
23
|
-
tracker.recordChunk();
|
|
24
|
-
tracker.recordChunk();
|
|
25
|
-
const timing = tracker.getTiming();
|
|
26
|
-
expect(timing.totalChunks).toBe(3);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("should record first token time", () => {
|
|
30
|
-
const tracker = new TimingTracker();
|
|
31
|
-
vi.advanceTimersByTime(50);
|
|
32
|
-
tracker.recordFirstToken();
|
|
33
|
-
vi.advanceTimersByTime(100);
|
|
34
|
-
tracker.recordFirstToken(); // should not overwrite
|
|
35
|
-
const timing = tracker.getTiming();
|
|
36
|
-
expect(timing.firstTokenTime).toBe(50);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should not set firstTokenTime if no text tokens", () => {
|
|
40
|
-
const tracker = new TimingTracker();
|
|
41
|
-
const timing = tracker.getTiming();
|
|
42
|
-
expect(timing.firstTokenTime).toBeUndefined();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should track unique tool calls", () => {
|
|
46
|
-
const tracker = new TimingTracker();
|
|
47
|
-
tracker.recordToolCallStart("tc-1");
|
|
48
|
-
tracker.recordToolCallStart("tc-2");
|
|
49
|
-
tracker.recordToolCallStart("tc-1"); // duplicate
|
|
50
|
-
const timing = tracker.getTiming();
|
|
51
|
-
expect(timing.toolCallCount).toBe(2);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("should compute totalStreamTime", () => {
|
|
55
|
-
const tracker = new TimingTracker();
|
|
56
|
-
vi.advanceTimersByTime(200);
|
|
57
|
-
const timing = tracker.getTiming();
|
|
58
|
-
expect(timing.totalStreamTime).toBe(200);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should use outputTokens when available", () => {
|
|
62
|
-
const tracker = new TimingTracker();
|
|
63
|
-
vi.advanceTimersByTime(1000);
|
|
64
|
-
const timing = tracker.getTiming(42, "some text");
|
|
65
|
-
expect(timing.tokenCount).toBe(42);
|
|
66
|
-
expect(timing.tokensPerSecond).toBe(42);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should estimate tokens from text when no outputTokens", () => {
|
|
70
|
-
const tracker = new TimingTracker();
|
|
71
|
-
vi.advanceTimersByTime(1000);
|
|
72
|
-
// 20 chars / 4 = 5 tokens
|
|
73
|
-
const timing = tracker.getTiming(undefined, "12345678901234567890");
|
|
74
|
-
expect(timing.tokenCount).toBe(5);
|
|
75
|
-
expect(timing.tokensPerSecond).toBe(5);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("should handle zero outputTokens by falling back to text", () => {
|
|
79
|
-
const tracker = new TimingTracker();
|
|
80
|
-
vi.advanceTimersByTime(1000);
|
|
81
|
-
const timing = tracker.getTiming(0, "1234567890"); // 10 chars -> 3 tokens
|
|
82
|
-
expect(timing.tokenCount).toBe(3);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("should handle no tokens and no text", () => {
|
|
86
|
-
const tracker = new TimingTracker();
|
|
87
|
-
vi.advanceTimersByTime(100);
|
|
88
|
-
const timing = tracker.getTiming();
|
|
89
|
-
expect(timing.tokenCount).toBeUndefined();
|
|
90
|
-
expect(timing.tokensPerSecond).toBeUndefined();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("should handle zero stream time (no division by zero)", () => {
|
|
94
|
-
const tracker = new TimingTracker();
|
|
95
|
-
// Don't advance time — totalStreamTime = 0
|
|
96
|
-
const timing = tracker.getTiming(100, "text");
|
|
97
|
-
expect(timing.totalStreamTime).toBe(0);
|
|
98
|
-
expect(timing.tokensPerSecond).toBeUndefined();
|
|
99
|
-
});
|
|
100
|
-
});
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { PipeableTransformStream } from "../utils/stream/PipeableTransformStream";
|
|
2
|
-
import { ObjectStreamAccumulator } from "./ObjectStreamAccumulator";
|
|
3
|
-
import { SSEDecoder, SSEEncoder } from "../utils/stream/SSE";
|
|
4
|
-
import type { ObjectStreamChunk, ObjectStreamOperation } from "./types";
|
|
5
|
-
|
|
6
|
-
export class ObjectStreamEncoder extends PipeableTransformStream<
|
|
7
|
-
ObjectStreamChunk,
|
|
8
|
-
Uint8Array
|
|
9
|
-
> {
|
|
10
|
-
constructor() {
|
|
11
|
-
super((readable) =>
|
|
12
|
-
readable
|
|
13
|
-
.pipeThrough(
|
|
14
|
-
(() => {
|
|
15
|
-
class ObjectStreamTransformer implements Transformer<
|
|
16
|
-
ObjectStreamChunk,
|
|
17
|
-
readonly ObjectStreamOperation[]
|
|
18
|
-
> {
|
|
19
|
-
#isFirstChunk = true;
|
|
20
|
-
|
|
21
|
-
start() {
|
|
22
|
-
// Nothing needed here since we initialize in the field declaration
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
transform(
|
|
26
|
-
chunk: ObjectStreamChunk,
|
|
27
|
-
controller: TransformStreamDefaultController<
|
|
28
|
-
readonly ObjectStreamOperation[]
|
|
29
|
-
>,
|
|
30
|
-
) {
|
|
31
|
-
if (
|
|
32
|
-
this.#isFirstChunk &&
|
|
33
|
-
chunk.snapshot &&
|
|
34
|
-
Object.keys(chunk.snapshot).length > 0
|
|
35
|
-
) {
|
|
36
|
-
// For the first chunk, if there's an initial state that's not empty,
|
|
37
|
-
// prepend a set operation for the initial state
|
|
38
|
-
controller.enqueue([
|
|
39
|
-
{ type: "set", path: [], value: chunk.snapshot },
|
|
40
|
-
...chunk.operations,
|
|
41
|
-
]);
|
|
42
|
-
} else {
|
|
43
|
-
controller.enqueue(chunk.operations);
|
|
44
|
-
}
|
|
45
|
-
this.#isFirstChunk = false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return new TransformStream(new ObjectStreamTransformer());
|
|
49
|
-
})(),
|
|
50
|
-
)
|
|
51
|
-
.pipeThrough(new SSEEncoder()),
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class ObjectStreamDecoder extends PipeableTransformStream<
|
|
57
|
-
Uint8Array<ArrayBuffer>,
|
|
58
|
-
ObjectStreamChunk
|
|
59
|
-
> {
|
|
60
|
-
constructor() {
|
|
61
|
-
const accumulator = new ObjectStreamAccumulator();
|
|
62
|
-
super((readable) =>
|
|
63
|
-
readable
|
|
64
|
-
.pipeThrough(new SSEDecoder<readonly ObjectStreamOperation[]>())
|
|
65
|
-
.pipeThrough(
|
|
66
|
-
new TransformStream<
|
|
67
|
-
readonly ObjectStreamOperation[],
|
|
68
|
-
ObjectStreamChunk
|
|
69
|
-
>({
|
|
70
|
-
transform(operations, controller) {
|
|
71
|
-
accumulator.append(operations);
|
|
72
|
-
controller.enqueue({
|
|
73
|
-
snapshot: accumulator.state,
|
|
74
|
-
operations,
|
|
75
|
-
});
|
|
76
|
-
},
|
|
77
|
-
}),
|
|
78
|
-
),
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export class ObjectStreamResponse extends Response {
|
|
84
|
-
constructor(body: ReadableStream<ObjectStreamChunk>) {
|
|
85
|
-
super(body.pipeThrough(new ObjectStreamEncoder()), {
|
|
86
|
-
headers: new Headers({
|
|
87
|
-
"Content-Type": "text/event-stream",
|
|
88
|
-
"Cache-Control": "no-cache",
|
|
89
|
-
Connection: "keep-alive",
|
|
90
|
-
"Assistant-Stream-Format": "object-stream/v0",
|
|
91
|
-
}),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const fromObjectStreamResponse = (
|
|
97
|
-
response: Response,
|
|
98
|
-
): ReadableStream<ObjectStreamChunk> => {
|
|
99
|
-
if (!response.ok)
|
|
100
|
-
throw new Error(`Response failed, status ${response.status}`);
|
|
101
|
-
if (!response.body) throw new Error("Response body is null");
|
|
102
|
-
const mediaType = response.headers
|
|
103
|
-
.get("Content-Type")
|
|
104
|
-
?.split(";", 1)[0]
|
|
105
|
-
?.trim()
|
|
106
|
-
.toLowerCase();
|
|
107
|
-
if (mediaType !== "text/event-stream") {
|
|
108
|
-
throw new Error("Response is not an event stream");
|
|
109
|
-
}
|
|
110
|
-
if (response.headers.get("Assistant-Stream-Format") !== "object-stream/v0") {
|
|
111
|
-
throw new Error("Unsupported Assistant-Stream-Format header");
|
|
112
|
-
}
|
|
113
|
-
return response.body.pipeThrough(new ObjectStreamDecoder());
|
|
114
|
-
};
|
|
File without changes
|