@ai2070/l0
Version:
L0: The Missing Reliability Substrate for AI
223 lines • 6.8 kB
JavaScript
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