@langchain/langgraph
Version:
434 lines (433 loc) • 16.2 kB
JavaScript
import { REJECT_VALUES, RESOLVE_VALUES, StreamMux, pump } from "./mux.js";
import { createLifecycleTransformer, filterLifecycleEntries } from "./transformers/lifecycle.js";
import { createMessagesTransformer } from "./transformers/messages.js";
import { createSubgraphDiscoveryTransformer, filterSubgraphHandles } from "./transformers/subgraphs.js";
import { createValuesTransformer } from "./transformers/values.js";
import "./transformers/index.js";
import { isNativeTransformer } from "./types.js";
//#region src/stream/run-stream.ts
/**
* Symbol key for attaching the values log to a stream handle.
* Using a symbol keeps this off the public autocomplete surface.
*/
const SET_VALUES_LOG = Symbol("setValuesLog");
/**
* Symbol key for attaching the messages iterable to a stream handle.
* Using a symbol keeps this off the public autocomplete surface.
*/
const SET_MESSAGES_ITERABLE = Symbol("setMessagesIterable");
/**
* Symbol key for attaching the lifecycle iterable to a stream handle.
* Using a symbol keeps this off the public autocomplete surface.
*/
const SET_LIFECYCLE_ITERABLE = Symbol("setLifecycleIterable");
/**
* Symbol key for attaching the subgraphs iterable to a stream handle.
* Using a symbol keeps this off the public autocomplete surface.
*/
const SET_SUBGRAPHS_ITERABLE = Symbol("setSubgraphsIterable");
/**
* Shared empty async iterable, returned from getters that haven't
* been wired by {@link createGraphRunStream}. Avoids allocating a
* fresh empty iterable on every access.
*/
const EMPTY_ASYNC_ITERABLE = { [Symbol.asyncIterator]() {
return { next: () => Promise.resolve({
value: void 0,
done: true
}) };
} };
/**
* Primary run stream for a LangGraph execution.
*
* Implements {@link AsyncIterable} over {@link ProtocolEvent} and exposes
* ergonomic projections for values, messages, subgraphs, output, and
* interrupts. Created by {@link createGraphRunStream}.
*
* @typeParam TValues - Shape of the graph's state values.
* @typeParam TExtensions - Shape of additional transformer projections merged
* into {@link GraphRunStream.extensions}.
*/
var GraphRunStream = class {
/**
* Namespace path identifying this stream's position in the agent tree.
* An empty array for the root stream.
*/
path;
/**
* Merged projections from user-supplied {@link StreamTransformer} factories.
* Each transformer's `init()` return value is spread into this object.
*/
extensions;
/**
* The central stream multiplexer that drives event dispatch and transformer
* pipelines. Accessible to subclasses for direct event subscription.
*
* @internal
*/
_mux;
#eventStart;
#discoveryStart;
#abortController;
#resolveValuesFn;
#rejectValuesFn;
#valuesDone;
#valuesLog;
#messagesIterable;
#lifecycleIterable;
#subgraphsIterable;
/**
* @param path - Namespace path for this stream (empty array for root).
* @param mux - The {@link StreamMux} driving this run.
* @param discoveryStart - Cursor offset into the mux discovery log.
* @param eventStart - Cursor offset into the mux event log.
* @param extensions - Pre-initialized transformer projections.
* @param abortController - Controller for programmatic cancellation.
*/
constructor(path, mux, discoveryStart = 0, eventStart = 0, extensions, abortController) {
this.path = path;
this._mux = mux;
this.#discoveryStart = discoveryStart;
this.#eventStart = eventStart;
this.extensions = extensions ?? {};
this.#abortController = abortController ?? new AbortController();
this.#valuesDone = new Promise((resolve, reject) => {
this.#resolveValuesFn = resolve;
this.#rejectValuesFn = reject;
});
this.#valuesDone.catch(() => {});
}
/**
* Async iterator over all {@link ProtocolEvent}s at or below this
* stream's namespace, starting from the configured event offset.
*
* @returns An async iterator yielding protocol events in arrival order.
*/
[Symbol.asyncIterator]() {
return this._mux.subscribeEvents(this.path, this.#eventStart);
}
/**
* Async iterable of child {@link SubgraphRunStream} instances discovered
* during the run. Each yielded stream represents a direct child namespace.
*
* Backed by the shared `_discoveries` log on the mux, populated by
* {@link createSubgraphDiscoveryTransformer}. For streams created
* through {@link createGraphRunStream} the iterable is pre-wired
* (via {@link SET_SUBGRAPHS_ITERABLE}) so iteration is cheap.
* Streams constructed directly (e.g. in unit tests) fall back to
* filtering `_mux._discoveries` on demand, preserving the original
* behavior without requiring explicit wiring.
*
* @returns An async iterable of subgraph run streams.
*/
get subgraphs() {
if (this.#subgraphsIterable) return this.#subgraphsIterable;
return filterSubgraphHandles(this._mux._discoveries, this.path, this.#discoveryStart);
}
/**
* Dual-interface accessor for graph state snapshots.
*
* As an {@link AsyncIterable}, yields each intermediate state snapshot
* as it arrives. As a {@link PromiseLike}, resolves with the final
* state value when the run completes.
*
* @returns A combined async iterable and promise-like for state values.
*/
get values() {
const log = this.#valuesLog;
const done = this.#valuesDone;
const mux = this._mux;
const eventStart = this.#eventStart;
const path = this.path;
const iterable = log ? log.toAsyncIterable() : { [Symbol.asyncIterator]: () => {
const base = mux.subscribeEvents(path, eventStart);
return { async next() {
while (true) {
const result = await base.next();
if (result.done) return {
value: void 0,
done: true
};
if (result.value.method === "values" && result.value.params.namespace.length === path.length) return {
value: result.value.params.data,
done: false
};
}
} };
} };
return {
[Symbol.asyncIterator]: () => iterable[Symbol.asyncIterator](),
then: done.then.bind(done)
};
}
/**
* All AI message lifecycles observed at this namespace level, in order.
* Each yielded {@link ChatModelStream} represents one message-start →
* message-finish lifecycle with streaming `.text`, `.reasoning`, and
* `.usage` projections.
*
* @returns An async iterable of chat model streams.
*/
get messages() {
if (this.#messagesIterable) return this.#messagesIterable;
const transformer = createMessagesTransformer(this.path);
const projection = transformer.init();
this._mux.addTransformer(transformer);
this.#messagesIterable = projection.messages;
return this.#messagesIterable;
}
/**
* Sequence of {@link LifecycleEntry} records tracking the
* `lifecycle` channel: when the run starts, when each subgraph
* enters/exits, and the terminal status of the run as a whole.
*
* Backed by the built-in {@link createLifecycleTransformer}; the
* root stream's iterable is wired during
* {@link createGraphRunStream} setup, and each
* {@link SubgraphRunStream} is wired in the subgraph discovery
* factory with a subtree-scoped view (via
* {@link filterLifecycleEntries}). Streams constructed outside
* `createGraphRunStream` and not wired will yield nothing.
*
* @returns An async iterable of lifecycle entries in emission order.
*/
get lifecycle() {
return this.#lifecycleIterable ?? EMPTY_ASYNC_ITERABLE;
}
/**
* Messages produced by a specific graph node. Use when the run has
* multiple model-calling nodes and you only want messages from one.
*
* @param node - The graph node name to filter messages by.
* @returns An async iterable of chat model streams from the given node.
*/
messagesFrom(node) {
const transformer = createMessagesTransformer(this.path, node);
const projection = transformer.init();
this._mux.addTransformer(transformer);
return projection.messages;
}
/**
* Promise that resolves with the final graph state when the run completes,
* or rejects if the run fails.
*
* @returns A promise resolving to the final state values.
*/
get output() {
return this.#valuesDone;
}
/**
* Whether the run ended due to a human-in-the-loop interrupt.
*
* @returns `true` if the run was interrupted.
*/
get interrupted() {
return this._mux.interrupted;
}
/**
* Interrupt payloads collected during the run, if any.
*
* @returns A readonly array of interrupt payloads.
*/
get interrupts() {
return this._mux.interrupts;
}
/**
* Programmatically abort this run. Equivalent to calling
* `signal.abort(reason)`.
*
* @param reason - Optional abort reason passed to the signal.
*/
abort(reason) {
this.#abortController.abort(reason);
}
/**
* The {@link AbortSignal} wired into this run for cancellation support.
*
* @returns The abort signal for this stream.
*/
get signal() {
return this.#abortController.signal;
}
/**
* Resolve the output/values promise with the final state snapshot.
* Called by {@link StreamMux.close}.
*
* @param values - The final state values, or `undefined` if none.
* @internal
*/
[RESOLVE_VALUES](values) {
this.#resolveValuesFn?.(values);
this.#resolveValuesFn = void 0;
}
/**
* Reject the output/values promise with a run error.
* Called by {@link StreamMux.fail}.
*
* @param err - The error that caused the run to fail.
* @internal
*/
[REJECT_VALUES](err) {
this.#rejectValuesFn?.(err);
this.#rejectValuesFn = void 0;
}
/**
* Attach the transformer-populated event log backing the `.values` iterable.
* Called during stream setup in {@link createGraphRunStream}.
*
* @param log - The event log from the values transformer projection.
* @internal
*/
[SET_VALUES_LOG](log) {
this.#valuesLog = log;
}
/**
* Attach the transformer-populated async iterable backing the `.messages`
* accessor. Called during stream setup in {@link createGraphRunStream}.
*
* @param iterable - The async iterable from the messages transformer projection.
* @internal
*/
[SET_MESSAGES_ITERABLE](iterable) {
this.#messagesIterable = iterable;
}
/**
* Attach the transformer-populated async iterable backing the
* `.lifecycle` accessor. Called during stream setup in
* {@link createGraphRunStream}.
*
* @param iterable - The async iterable from the lifecycle transformer projection.
* @internal
*/
[SET_LIFECYCLE_ITERABLE](iterable) {
this.#lifecycleIterable = iterable;
}
/**
* Attach the transformer-populated async iterable backing the
* `.subgraphs` accessor. Called during root stream setup in
* {@link createGraphRunStream} and during child stream
* construction in the discovery transformer factory.
*
* @param iterable - The async iterable of direct-child stream handles.
* @internal
*/
[SET_SUBGRAPHS_ITERABLE](iterable) {
this.#subgraphsIterable = iterable;
}
};
/**
* A run stream for a child subgraph within a parent graph execution.
*
* Extends {@link GraphRunStream} with a parsed {@link name} and
* {@link index} extracted from the last segment of the namespace path.
* The segment is expected to follow the `"name:index"` convention;
* when no numeric suffix is present, {@link index} defaults to `0`.
*
* @typeParam TValues - Shape of the subgraph's state values.
* @typeParam TExtensions - Shape of additional transformer projections.
*/
var SubgraphRunStream = class extends GraphRunStream {
/**
* The node name extracted from the last segment of the namespace path
* (everything before the final colon, or the full segment if no colon).
*/
name;
/**
* The invocation index parsed from the `"name:N"` suffix of the last
* namespace segment. Defaults to `0` when no numeric suffix is present.
*/
index;
/**
* @param path - Namespace path for this subgraph stream.
* @param mux - The {@link StreamMux} driving this run.
* @param discoveryStart - Cursor offset into the mux discovery log.
* @param eventStart - Cursor offset into the mux event log.
* @param extensions - Pre-initialized transformer projections.
* @param abortController - Controller for programmatic cancellation.
*/
constructor(path, mux, discoveryStart = 0, eventStart = 0, extensions, abortController) {
super(path, mux, discoveryStart, eventStart, extensions, abortController);
const lastSegment = path[path.length - 1] ?? "";
const colonIdx = lastSegment.lastIndexOf(":");
if (colonIdx >= 0) {
this.name = lastSegment.slice(0, colonIdx);
const suffix = lastSegment.slice(colonIdx + 1);
this.index = /^\d+$/.test(suffix) ? Number(suffix) : 0;
} else {
this.name = lastSegment;
this.index = 0;
}
}
};
/**
* Creates a {@link GraphRunStream} with built-in transformers and kicks off the
* background pump that feeds raw stream chunks through the transformer pipeline.
*
* Built-in transformers are registered in this order:
* 1. subgraph discovery — materializes SubgraphRunStream handles
* for each newly observed top-level namespace and announces them
* on the mux `_discoveries` log.
* 2. lifecycle — synthesizes `lifecycle` channel events.
* 3. values — powers `run.values` / `run.output`.
* 4. messages — powers `run.messages` / `.messagesFrom`.
*
* Subgraph discovery is registered first so that downstream
* transformers (notably lifecycle) observe child namespaces with
* their stream handles already in place. User-supplied transformer
* factories are registered afterwards.
*
* @typeParam TValues - Shape of the graph's state values.
* @param source - Raw async iterable from `graph.stream(…, { subgraphs: true })`.
* @param transformers - User-supplied transformer factories.
* @param optionsOrAbortController - Either a full
* {@link CreateGraphRunStreamOptions} object or (for backward
* compatibility) a bare `AbortController`.
* @returns A {@link GraphRunStream} for the root namespace.
*/
function createGraphRunStream(source, transformers = [], optionsOrAbortController) {
const { abortController } = optionsOrAbortController instanceof AbortController ? { abortController: optionsOrAbortController } : optionsOrAbortController ?? {};
const mux = new StreamMux();
const lifecycleTransformer = createLifecycleTransformer();
const lifecycleProjection = lifecycleTransformer.init();
const lifecycleLog = lifecycleProjection._lifecycleLog;
const subgraphDiscoveryTransformer = createSubgraphDiscoveryTransformer(mux, { createStream: (path, discoveryStart, eventStart) => {
const sub = new SubgraphRunStream(path, mux, discoveryStart, eventStart);
sub[SET_SUBGRAPHS_ITERABLE](filterSubgraphHandles(mux._discoveries, path, discoveryStart));
sub[SET_LIFECYCLE_ITERABLE](filterLifecycleEntries(lifecycleLog, path, lifecycleLog.size));
return sub;
} });
const subgraphsProjection = subgraphDiscoveryTransformer.init();
mux.addTransformer(subgraphDiscoveryTransformer);
mux.addTransformer(lifecycleTransformer);
const valuesTransformer = createValuesTransformer([]);
const messagesTransformer = createMessagesTransformer([]);
mux.addTransformer(valuesTransformer);
mux.addTransformer(messagesTransformer);
const extensions = {};
const nativeProjections = [];
for (const factory of transformers) {
const transformer = factory();
mux.addTransformer(transformer);
const projection = transformer.init();
if (isNativeTransformer(transformer)) nativeProjections.push(projection);
else Object.assign(extensions, projection);
if (typeof projection === "object" && projection !== null && !isNativeTransformer(transformer)) mux.wireChannels(projection);
}
const root = new GraphRunStream([], mux, 0, 0, extensions, abortController);
/**
* Assign native transformer projections to the root stream.
*/
for (const proj of nativeProjections) Object.assign(root, proj);
const valuesProjection = valuesTransformer.init();
root[SET_VALUES_LOG](valuesProjection._valuesLog);
const messagesProjection = messagesTransformer.init();
root[SET_MESSAGES_ITERABLE](messagesProjection.messages);
root[SET_LIFECYCLE_ITERABLE](lifecycleProjection.lifecycle);
root[SET_SUBGRAPHS_ITERABLE](subgraphsProjection.subgraphs);
mux.register([], root);
pump(source, mux).catch((err) => {});
return root;
}
//#endregion
export { GraphRunStream, SET_LIFECYCLE_ITERABLE, SET_MESSAGES_ITERABLE, SET_VALUES_LOG, SubgraphRunStream, createGraphRunStream };
//# sourceMappingURL=run-stream.js.map