UNPKG

assistant-stream

Version:

Streaming utilities for AI assistants

119 lines (118 loc) • 3.93 kB
import { withPromiseOrValue } from "../core/utils/withPromiseOrValue.js"; import { ResumableStreamError } from "./errors.js"; // src/resumable/ResumableStreamContext.ts const invokeObservabilityHook = (name, hook, ...args) => { if (!hook) return; withPromiseOrValue(() => hook(...args), () => {}, (error) => { console.error(`resumable stream ${name} hook failed:`, error); }); }; function createResumableStreamContext(options) { const { store, waitUntil, onAcquire, onAppend, onFinalize, onError } = options; const acquireOptions = options.ttlMs !== void 0 ? { ttlMs: options.ttlMs } : void 0; return { async run(streamId, makeStream) { const acquisition = store.acquireLease ? await store.acquireLease(streamId, acquireOptions) : { role: await store.acquire(streamId, acquireOptions) }; invokeObservabilityHook("onAcquire", onAcquire, streamId, acquisition.role); if (acquisition.role === "producer") startProducerTask(store, streamId, makeStream, { waitUntil, onAppend, onFinalize, onError }, acquisition.lease); return readFromStore(store, streamId); }, async resume(streamId) { if (await store.status(streamId) === "missing") return null; return readFromStore(store, streamId); }, async requireResume(streamId) { if (await store.status(streamId) === "missing") throw new ResumableStreamError("missing", `resumable stream not found: ${streamId}`); return readFromStore(store, streamId); }, async status(streamId) { return store.status(streamId); }, async delete(streamId) { await store.delete(streamId); } }; } function startProducerTask(store, streamId, makeStream, hooks, lease) { const { waitUntil, onAppend, onFinalize, onError } = hooks; const task = (async () => { let reader; try { reader = makeStream().getReader(); while (true) { const { done, value } = await reader.read(); if (done) break; await store.append(streamId, value, lease); invokeObservabilityHook("onAppend", onAppend, streamId, value.byteLength); } if (await store.finalize(streamId, "done", void 0, lease) === false) { invokeObservabilityHook("onError", onError, streamId, new ResumableStreamError("missing", `Stream no longer owned by this producer: ${streamId}`)); return; } invokeObservabilityHook("onFinalize", onFinalize, streamId, "done"); } catch (err) { invokeObservabilityHook("onError", onError, streamId, err); const message = err instanceof Error ? err.message : String(err); const cancellation = (async () => { try { await reader?.cancel(err); } catch (cancelErr) { console.error("resumable stream reader cancel failed:", cancelErr); } })(); try { if (await store.finalize(streamId, "error", message, lease) === false) return; invokeObservabilityHook("onFinalize", onFinalize, streamId, "error", message); } catch (finalizeErr) { console.error("resumable stream finalize failed:", finalizeErr); } finally { await cancellation; } } finally { reader?.releaseLock(); } })(); if (waitUntil) waitUntil(task); task.catch((err) => { console.error("resumable producer task failed:", err); }); } function readFromStore(store, streamId) { const ac = new AbortController(); let iterator; return new ReadableStream({ start() { iterator = store.read(streamId, "", ac.signal)[Symbol.asyncIterator](); }, async pull(controller) { try { if (!iterator) return; const { done, value } = await iterator.next(); if (done) { controller.close(); return; } controller.enqueue(value.chunk); } catch (err) { ac.abort(); try { await iterator?.return?.(); } catch {} controller.error(err); } }, async cancel() { ac.abort(); try { await iterator?.return?.(); } catch {} } }); } export { createResumableStreamContext }; //# sourceMappingURL=ResumableStreamContext.js.map