@earendil-works/pi-ai
Version:
Unified LLM API with automatic model discovery and provider configuration
124 lines • 4.03 kB
JavaScript
class FifoQueue {
incoming = [];
outgoing = [];
get length() {
return this.incoming.length + this.outgoing.length;
}
enqueue(value) {
this.incoming.push(value);
}
dequeue() {
if (this.outgoing.length === 0) {
while (this.incoming.length > 0) {
this.outgoing.push(this.incoming.pop());
}
}
return this.outgoing.pop();
}
}
// Generic event stream class for async iteration
export class EventStream {
queue = new FifoQueue();
waiting = new FifoQueue();
done = false;
finalResultPromise;
resolveFinalResult;
isComplete;
extractResult;
constructor(isComplete, extractResult) {
this.isComplete = isComplete;
this.extractResult = extractResult;
this.finalResultPromise = new Promise((resolve) => {
this.resolveFinalResult = resolve;
});
}
push(event) {
if (this.done)
return;
if (this.isComplete(event)) {
this.done = true;
this.resolveFinalResult(this.extractResult(event));
}
// Deliver to waiting consumer or queue it
const waiter = this.waiting.dequeue();
if (waiter) {
waiter({ value: event, done: false });
}
else {
this.queue.enqueue(event);
}
}
end(result) {
this.done = true;
if (result !== undefined) {
this.resolveFinalResult(result);
}
// Notify all waiting consumers that we're done
while (this.waiting.length > 0) {
const waiter = this.waiting.dequeue();
waiter({ value: undefined, done: true });
}
}
async *[Symbol.asyncIterator]() {
while (true) {
if (this.queue.length > 0) {
yield this.queue.dequeue();
}
else if (this.done) {
return;
}
else {
const result = await new Promise((resolve) => this.waiting.enqueue(resolve));
if (result.done)
return;
yield result.value;
}
}
}
result() {
return this.finalResultPromise;
}
}
/**
* Event stream of one assistant response. It also times the response: the final message (`done` or `error` event, or
* the result passed to `end()`) gets `durationMs`, measured with a monotonic clock from the stream's creation, unless
* the message already has one or its `timestamp` predates the stream. A stream that forwards a response which started
* elsewhere, such as a deferred result fetched later, therefore leaves it untimed.
*/
export class AssistantMessageEventStream extends EventStream {
#startedAt = Date.now();
#startedAtMonotonic = performance.now();
constructor() {
super((event) => event.type === "done" || event.type === "error", (event) => {
if (event.type === "done") {
return event.message;
}
else if (event.type === "error") {
return event.error;
}
throw new Error("Unexpected event type for final result");
});
}
push(event) {
if (event.type === "done")
this.#time(event.message);
else if (event.type === "error")
this.#time(event.error);
super.push(event);
}
end(result) {
if (result !== undefined)
this.#time(result);
super.end(result);
}
#time(message) {
if (this.done || message.durationMs !== undefined || message.timestamp < this.#startedAt)
return;
message.durationMs = Math.max(0, Math.round(performance.now() - this.#startedAtMonotonic));
}
}
/** Factory function for AssistantMessageEventStream (for use in extensions) */
export function createAssistantMessageEventStream() {
return new AssistantMessageEventStream();
}
//# sourceMappingURL=event-stream.js.map