@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
251 lines (250 loc) • 6.3 kB
JavaScript
import { normalizeStreamEvent } from "./events";
class StreamNormalizer {
state;
accumulated = "";
checkpointInterval;
lastCheckpoint = "";
constructor(options = {}) {
this.checkpointInterval = options.checkpointInterval || 10;
this.state = this.createInitialState();
}
/**
* Create initial stream state
*/
createInitialState() {
return {
started: false,
firstTokenReceived: false,
tokenCount: 0,
complete: false,
aborted: false
};
}
/**
* Process a stream chunk and normalize it
*/
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;
}
}
/**
* Get current state
*/
getState() {
return { ...this.state };
}
/**
* Get accumulated content
*/
getAccumulated() {
return this.accumulated;
}
/**
* Get last checkpoint
*/
getCheckpoint() {
return this.lastCheckpoint;
}
/**
* Reset normalizer state
*/
reset() {
this.state = this.createInitialState();
this.accumulated = "";
this.lastCheckpoint = "";
}
/**
* Create a stream error
*/
createStreamError(type, message) {
const error = new Error(message);
error.type = type;
error.recoverable = type !== "abort";
error.timestamp = Date.now();
return error;
}
}
function createStreamNormalizer(options) {
return new StreamNormalizer(options);
}
async function* normalizeStreamWithTimeout(stream, options = {}) {
const { initialTimeout = 2e3, interTokenTimeout = 5e3, 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);
}
}
}
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;
}
}
async function* mapStream(stream, mapper) {
for await (const event of stream) {
yield mapper(event);
}
}
async function* filterStream(stream, predicate) {
for await (const event of stream) {
if (predicate(event)) {
yield event;
}
}
}
async function* takeStream(stream, count) {
let taken = 0;
for await (const event of stream) {
if (taken >= count) break;
yield event;
taken++;
}
}
async function collectStream(stream) {
const events = [];
for await (const event of stream) {
events.push(event);
}
return events;
}
async function consumeStream(stream) {
let text = "";
for await (const event of stream) {
if (event.type === "token" && event.value) {
text += event.value;
}
}
return text;
}
async function* passthroughStream(stream) {
for await (const event of stream) {
yield event;
}
}
async function* tapStream(stream, callback) {
for await (const event of stream) {
callback(event);
yield event;
}
}
async function* mergeStreams(...streams) {
for (const stream of streams) {
for await (const event of stream) {
yield event;
}
}
}
async function* streamFromArray(events) {
for (const event of events) {
yield event;
}
}
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;
}
}
}
export {
StreamNormalizer,
bufferStream,
collectStream,
consumeStream,
createStreamNormalizer,
debounceStream,
filterStream,
mapStream,
mergeStreams,
normalizeStreamWithTimeout,
passthroughStream,
streamFromArray,
takeStream,
tapStream
};
//# sourceMappingURL=stream.js.map