@langchain/langgraph
Version:
104 lines (103 loc) • 3.35 kB
JavaScript
import { StreamChannel } from "../stream-channel.js";
import { hasPrefix } from "../mux.js";
import { ChatModelStream } from "@langchain/core/language_models/stream";
//#region src/stream/transformers/messages.ts
function getMessageStreamKey(data) {
const record = data;
if (typeof record.run_id === "string") return `run:${record.run_id}`;
if (data.event === "message-start" && typeof record.id === "string") return `message:${record.id}`;
return "__default__";
}
/**
* Creates a {@link StreamTransformer} that groups `messages` channel events into
* per-message {@link ChatModelStream} instances.
*
* A new `ChatModelStream` is created on `message-start` and closed on
* `message-finish`. Content-block events in between are forwarded to the
* active stream. Only events whose namespace exactly matches {@link path}
* are processed; child namespaces are ignored.
*
* @param path - Namespace prefix to match against incoming events.
* @param nodeFilter - If provided, only events emitted by this graph node
* are processed; all others are skipped.
* @returns A `StreamTransformer` whose projection contains the `messages`
* async iterable.
*/
function createMessagesTransformer(path, nodeFilter) {
const log = StreamChannel.local();
const active = /* @__PURE__ */ new Map();
const ignored = /* @__PURE__ */ new Set();
return {
init: () => ({ messages: log.toAsyncIterable() }),
process(event) {
if (event.method !== "messages") return true;
if (!hasPrefix(event.params.namespace, path)) return true;
if (event.params.namespace.length !== path.length + 1) return true;
if (nodeFilter !== void 0 && event.params.node !== nodeFilter) return true;
const data = event.params.data;
switch (data.event) {
case "message-start": {
const key = getMessageStreamKey(data);
if (data.role === "tool") {
ignored.add(key);
break;
}
const source = StreamChannel.local();
const stream = Object.assign(new ChatModelStream(source.toAsyncIterable()), {
namespace: event.params.namespace,
node: event.params.node
});
active.set(key, {
source,
stream
});
source.push(data);
log.push(stream);
break;
}
case "content-block-start":
case "content-block-delta":
case "content-block-finish":
if (ignored.has(getMessageStreamKey(data))) break;
active.get(getMessageStreamKey(data))?.source.push(data);
break;
case "message-finish": {
const key = getMessageStreamKey(data);
if (ignored.delete(key)) break;
const stream = active.get(key);
if (stream) {
stream.source.push(data);
stream.source.close();
active.delete(key);
}
break;
}
case "error":
if (ignored.has(getMessageStreamKey(data))) break;
active.get(getMessageStreamKey(data))?.source.push(data);
break;
}
return true;
},
finalize() {
for (const [key, stream] of active) {
stream.source.push({ event: "message-finish" });
stream.source.close();
active.delete(key);
}
ignored.clear();
log.close();
},
fail(err) {
for (const [key, stream] of active) {
stream.source.fail(err);
active.delete(key);
}
ignored.clear();
log.fail(err);
}
};
}
//#endregion
export { createMessagesTransformer };
//# sourceMappingURL=messages.js.map