assistant-stream
Version:
Streaming utilities for AI assistants
93 lines (88 loc) • 2.92 kB
text/typescript
import { PipeableTransformStream } from "../utils/stream/PipeableTransformStream";
import { GorpStreamAccumulator } from "./GorpStreamAccumulator";
import { SSEDecoder, SSEEncoder } from "../utils/stream/SSE";
import type { GorpStreamChunk, GorpStreamOperation } from "./types";
export class GorpStreamEncoder extends PipeableTransformStream<
GorpStreamChunk,
Uint8Array
> {
constructor() {
let isFirstChunk = true;
super((readable) =>
readable
.pipeThrough(
new TransformStream<GorpStreamChunk, readonly GorpStreamOperation[]>({
transform(chunk, controller) {
if (isFirstChunk) {
controller.enqueue([
{ type: "set", path: [], value: chunk.snapshot },
]);
} else {
controller.enqueue(chunk.operations);
}
isFirstChunk = false;
},
}),
)
.pipeThrough(new SSEEncoder()),
);
}
}
export class GorpStreamDecoder extends PipeableTransformStream<
Uint8Array<ArrayBuffer>,
GorpStreamChunk
> {
constructor() {
const accumulator = new GorpStreamAccumulator();
super((readable) =>
readable
.pipeThrough(new SSEDecoder<readonly GorpStreamOperation[]>())
.pipeThrough(
new TransformStream<readonly GorpStreamOperation[], GorpStreamChunk>({
transform(operations, controller) {
accumulator.append(operations);
controller.enqueue({
snapshot: accumulator.state,
operations,
});
},
}),
),
);
}
}
export class GorpStreamResponse extends Response {
constructor(body: ReadableStream<GorpStreamChunk>) {
super(body.pipeThrough(new GorpStreamEncoder()), {
headers: new Headers({
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
"Assistant-Stream-Format": "object-stream/v0",
}),
});
}
}
export const fromGorpStreamResponse = (
response: Response,
): ReadableStream<GorpStreamChunk> => {
if (!response.ok) {
void response.body?.cancel().catch(() => undefined);
throw new Error(`Response failed, status ${response.status}`);
}
if (!response.body) throw new Error("Response body is null");
const mediaType = response.headers
.get("Content-Type")
?.split(";", 1)[0]
?.trim()
.toLowerCase();
if (mediaType !== "text/event-stream") {
void response.body?.cancel().catch(() => undefined);
throw new Error("Response is not an event stream");
}
if (response.headers.get("Assistant-Stream-Format") !== "object-stream/v0") {
void response.body?.cancel().catch(() => undefined);
throw new Error("Unsupported Assistant-Stream-Format header");
}
return response.body.pipeThrough(new GorpStreamDecoder());
};