mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 23:52:23 +02:00
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
// in this case don't quite match.
|
|
type IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;
|
|
|
|
/*
|
|
* Support async iterator syntax for ReadableStreams in all environments.
|
|
* Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
|
|
*/
|
|
export class IterableReadableStream<T>
|
|
extends ReadableStream<T>
|
|
implements IterableReadableStreamInterface<T>
|
|
{
|
|
public reader: ReadableStreamDefaultReader<T>;
|
|
|
|
ensureReader() {
|
|
if (!this.reader) {
|
|
this.reader = this.getReader();
|
|
}
|
|
}
|
|
|
|
async next(): Promise<IteratorResult<T>> {
|
|
this.ensureReader();
|
|
try {
|
|
const result = await this.reader.read();
|
|
if (result.done) {
|
|
this.reader.releaseLock(); // release lock when stream becomes closed
|
|
return {
|
|
done: true,
|
|
value: undefined,
|
|
};
|
|
} else {
|
|
return {
|
|
done: false,
|
|
value: result.value,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
this.reader.releaseLock(); // release lock when stream becomes errored
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async return(): Promise<IteratorResult<T>> {
|
|
this.ensureReader();
|
|
// If wrapped in a Node stream, cancel is already called.
|
|
if (this.locked) {
|
|
const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
|
|
this.reader.releaseLock(); // release lock first
|
|
await cancelPromise; // now await it
|
|
}
|
|
return { done: true, value: undefined };
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async throw(e: any): Promise<IteratorResult<T>> {
|
|
this.ensureReader();
|
|
if (this.locked) {
|
|
const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
|
|
this.reader.releaseLock(); // release lock first
|
|
await cancelPromise; // now await it
|
|
}
|
|
throw e;
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore Not present in Node 18 types, required in latest Node 22
|
|
async [Symbol.asyncDispose]() {
|
|
await this.return();
|
|
}
|
|
|
|
[Symbol.asyncIterator]() {
|
|
return this;
|
|
}
|
|
|
|
static fromReadableStream<T>(stream: ReadableStream<T>) {
|
|
// From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream
|
|
const reader = stream.getReader();
|
|
return new IterableReadableStream<T>({
|
|
start(controller) {
|
|
return pump();
|
|
function pump(): Promise<T | undefined> {
|
|
return reader.read().then(({ done, value }) => {
|
|
// When no more data needs to be consumed, close the stream
|
|
if (done) {
|
|
controller.close();
|
|
return;
|
|
}
|
|
// Enqueue the next data chunk into our target stream
|
|
controller.enqueue(value);
|
|
return pump();
|
|
});
|
|
}
|
|
},
|
|
cancel() {
|
|
reader.releaseLock();
|
|
},
|
|
});
|
|
}
|
|
|
|
static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {
|
|
return new IterableReadableStream<T>({
|
|
async pull(controller) {
|
|
const { value, done } = await generator.next();
|
|
// When no more data needs to be consumed, close the stream
|
|
if (done) {
|
|
controller.close();
|
|
}
|
|
// Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled
|
|
controller.enqueue(value);
|
|
},
|
|
async cancel(reason) {
|
|
await generator.return(reason);
|
|
},
|
|
});
|
|
}
|
|
}
|