@langchain/langgraph
Version:
124 lines (123 loc) • 4.67 kB
JavaScript
import { hasPrefix, nsKey } from "../mux.js";
//#region src/stream/transformers/subgraphs.ts
/**
* SubgraphDiscoveryTransformer - materializes a {@link StreamHandle} for
* each newly observed top-level subgraph namespace and announces it on
* the mux's shared {@link StreamMux._discoveries} channel.
*
* Previously this work was inlined in {@link StreamMux.push}. Extracting
* it into a transformer aligns discovery with the rest of the stream
* architecture (lifecycle, values, messages are all transformers),
* isolates the factory wiring, and makes discovery behavior
* independently testable.
*
* The transformer also owns the read-side of discovery: it exposes an
* `AsyncIterable<TStream>` projection (`subgraphs`) scoped to the root
* namespace, and a {@link filterSubgraphHandles} helper that callers
* can use to scope the same channel to any descendant namespace. This
* lets `GraphRunStream` drop its bespoke `subscribeSubgraphs`
* delegation and surface child streams via the standard native
* projection pattern.
*
* Only first-level namespace segments are announced. Deeper segments
* (e.g. `["researcher:uuid", "tools:uuid"]`) are internal Pregel
* checkpoint namespaces for nodes inside a subgraph and should not
* appear as user-facing `SubgraphRunStream` instances; the mux still
* resolves their values via its own `#streamMap` when registered
* elsewhere.
*/
/**
* Filter a {@link SubgraphDiscovery} channel to only the direct children
* of a given namespace.
*
* Returns an `AsyncIterable` whose iterator yields stream handles for
* discoveries whose namespace is exactly one segment deeper than
* {@link path} and shares it as a prefix. Iteration begins at
* {@link startAt} (so each caller picks up only discoveries added
* after its construction) and terminates when the underlying log
* closes or fails.
*
* @typeParam TStream - Concrete stream type recorded in the log.
* Callers may cast if the log was populated by a specific factory.
* @param log - The shared discovery channel (`mux._discoveries`).
* @param path - Parent namespace whose direct children should be
* yielded.
* @param startAt - Zero-based index into the discovery log to begin
* from.
* @returns An async iterable of stream handles.
*/
function filterSubgraphHandles(log, path, startAt = 0) {
const targetDepth = path.length + 1;
return { [Symbol.asyncIterator]() {
const base = log.iterate(startAt);
return { async next() {
while (true) {
const result = await base.next();
if (result.done) return {
value: void 0,
done: true
};
const { ns, stream } = result.value;
if (ns.length === targetDepth && hasPrefix(ns, path)) return {
value: stream,
done: false
};
}
} };
} };
}
/**
* Create the subgraph discovery transformer.
*
* Registering this transformer against a mux replaces the legacy
* inline behavior that previously lived in {@link StreamMux.push}.
* The mux no longer knows about the subgraph factory: instead, this
* transformer is the single component that materializes stream
* handles and announces them on `_discoveries`.
*
* Marked as a {@link NativeStreamTransformer} so the projection is
* treated as internal wiring (not merged into `run.extensions` and
* not auto-forwarded via {@link StreamMux.wireChannels}).
*
* @typeParam TStream - Concrete stream handle type produced by
* {@link SubgraphDiscoveryTransformerOptions.createStream}.
* Defaults to the base {@link StreamHandle} interface.
* @param mux - The mux whose `_discoveries` log should receive
* discovery entries and whose `register` will be called for each
* new stream handle.
* @param options - Factory and related wiring.
* @returns A native transformer that populates
* {@link StreamMux._discoveries} and exposes a root-scoped
* `subgraphs` iterable via its projection.
*/
function createSubgraphDiscoveryTransformer(mux, options) {
const { createStream } = options;
const seen = /* @__PURE__ */ new Set();
return {
__native: true,
init() {
return {
_discoveries: mux._discoveries,
subgraphs: filterSubgraphHandles(mux._discoveries, [], 0)
};
},
process(event) {
const ns = event.params.namespace;
if (ns.length === 0) return true;
const topNs = ns.slice(0, 1);
const topKey = nsKey(topNs);
if (seen.has(topKey)) return true;
seen.add(topKey);
const stream = createStream(topNs, mux._discoveries.size, mux._events.size);
mux.register(topNs, stream);
mux._discoveries.push({
ns: topNs,
stream
});
return true;
}
};
}
//#endregion
export { createSubgraphDiscoveryTransformer, filterSubgraphHandles };
//# sourceMappingURL=subgraphs.js.map