UNPKG

@ai2070/l0

Version:

L0: The Missing Reliability Substrate for AI

223 lines 6.8 kB
import { normalizeStreamEvent } from "./events"; export class StreamNormalizer { state; accumulated = ""; checkpointInterval; lastCheckpoint = ""; constructor(options = {}) { this.checkpointInterval = options.checkpointInterval || 10; this.state = this.createInitialState(); } createInitialState() { return { started: false, firstTokenReceived: false, tokenCount: 0, complete: false, aborted: false, }; } async *normalize(stream, signal) { this.state.started = true; this.state.startTime = Date.now(); try { for await (const chunk of stream) { if (signal?.aborted) { this.state.aborted = true; throw this.createStreamError("abort", "Stream aborted by signal"); } const event = normalizeStreamEvent(chunk); if (event.type === "token" && event.value) { if (!this.state.firstTokenReceived) { this.state.firstTokenReceived = true; this.state.firstTokenTime = Date.now(); } this.accumulated += event.value; this.state.tokenCount++; this.state.lastTokenTime = Date.now(); if (this.state.tokenCount % this.checkpointInterval === 0) { this.lastCheckpoint = this.accumulated; } } else if (event.type === "error") { this.state.error = event.error; throw event.error; } else if (event.type === "complete") { this.state.complete = true; } yield event; } if (!this.state.complete) { this.state.complete = true; } } catch (error) { this.state.error = error instanceof Error ? error : new Error(String(error)); throw this.state.error; } } getState() { return { ...this.state }; } getAccumulated() { return this.accumulated; } getCheckpoint() { return this.lastCheckpoint; } reset() { this.state = this.createInitialState(); this.accumulated = ""; this.lastCheckpoint = ""; } createStreamError(type, message) { const error = new Error(message); error.type = type; error.recoverable = type !== "abort"; error.timestamp = Date.now(); return error; } } export function createStreamNormalizer(options) { return new StreamNormalizer(options); } export async function* normalizeStreamWithTimeout(stream, options = {}) { const { initialTimeout = 2000, interTokenTimeout = 5000, signal } = options; const normalizer = new StreamNormalizer(); let firstTokenReceived = false; let lastTokenTime = Date.now(); let initialTimeoutId = null; let initialTimeoutReached = false; if (initialTimeout > 0) { initialTimeoutId = setTimeout(() => { initialTimeoutReached = true; }, initialTimeout); } try { for await (const event of normalizer.normalize(stream, signal)) { if (initialTimeoutId && !firstTokenReceived && event.type === "token") { clearTimeout(initialTimeoutId); initialTimeoutId = null; firstTokenReceived = true; } if (initialTimeoutReached && !firstTokenReceived) { throw new Error(`Initial token timeout after ${initialTimeout}ms`); } if (firstTokenReceived && interTokenTimeout > 0 && event.type === "token") { const timeSinceLastToken = Date.now() - lastTokenTime; if (timeSinceLastToken > interTokenTimeout) { throw new Error(`Inter-token timeout after ${timeSinceLastToken}ms`); } lastTokenTime = Date.now(); } yield event; } } finally { if (initialTimeoutId) { clearTimeout(initialTimeoutId); } } } export async function* bufferStream(stream, bufferSize = 10) { let buffer = []; for await (const event of stream) { buffer.push(event); if (buffer.length >= bufferSize || event.type === "complete" || event.type === "error") { yield buffer; buffer = []; } } if (buffer.length > 0) { yield buffer; } } export async function* mapStream(stream, mapper) { for await (const event of stream) { yield mapper(event); } } export async function* filterStream(stream, predicate) { for await (const event of stream) { if (predicate(event)) { yield event; } } } export async function* takeStream(stream, count) { let taken = 0; for await (const event of stream) { if (taken >= count) break; yield event; taken++; } } export async function collectStream(stream) { const events = []; for await (const event of stream) { events.push(event); } return events; } export async function consumeStream(stream) { let text = ""; for await (const event of stream) { if (event.type === "token" && event.value) { text += event.value; } } return text; } export async function* passthroughStream(stream) { for await (const event of stream) { yield event; } } export async function* tapStream(stream, callback) { for await (const event of stream) { callback(event); yield event; } } export async function* mergeStreams(...streams) { for (const stream of streams) { for await (const event of stream) { yield event; } } } export async function* streamFromArray(events) { for (const event of events) { yield event; } } export async function* debounceStream(stream, delayMs) { let lastEvent = null; let timeoutId = null; const events = []; for await (const event of stream) { events.push(event); } for (const event of events) { lastEvent = event; if (timeoutId) { clearTimeout(timeoutId); } await new Promise((resolve) => { timeoutId = setTimeout(() => { resolve(); }, delayMs); }); if (lastEvent === event) { yield event; } } } //# sourceMappingURL=stream.js.map