@langchain/langgraph 0.2.19 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import { Runnable, RunnableSequence, getCallbackManagerForConfig, mergeConfigs, patchConfig, _coerceToRunnable, } from "@langchain/core/runnables";
3
3
  import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, uuid5, } from "@langchain/langgraph-checkpoint";
4
- import Deque from "double-ended-queue";
5
4
  import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
6
5
  import { PregelNode } from "./read.js";
7
6
  import { validateGraph, validateKeys } from "./validate.js";
@@ -13,11 +12,12 @@ import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphInterr
13
12
  import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
14
13
  import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
15
14
  import { findSubgraphPregel } from "./utils/subgraph.js";
16
- import { PregelLoop, StreamProtocol } from "./loop.js";
15
+ import { PregelLoop, IterableReadableWritableStream } from "./loop.js";
17
16
  import { executeTasksWithRetry } from "./retry.js";
18
17
  import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, NoopManagedValue, } from "../managed/base.js";
19
18
  import { gatherIterator, patchConfigurable } from "../utils.js";
20
19
  import { ensureLangGraphConfig } from "./utils/config.js";
20
+ import { StreamMessagesHandler } from "./messages.js";
21
21
  function isString(value) {
22
22
  return typeof value === "string";
23
23
  }
@@ -572,6 +572,7 @@ export class Pregel extends Runnable {
572
572
  }
573
573
  _defaults(config) {
574
574
  const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
575
+ let streamModeSingle = true;
575
576
  const defaultDebug = debug !== undefined ? debug : this.debug;
576
577
  let defaultOutputKeys = outputKeys;
577
578
  if (defaultOutputKeys === undefined) {
@@ -592,9 +593,11 @@ export class Pregel extends Runnable {
592
593
  let defaultStreamMode;
593
594
  if (streamMode !== undefined) {
594
595
  defaultStreamMode = Array.isArray(streamMode) ? streamMode : [streamMode];
596
+ streamModeSingle = typeof streamMode === "string";
595
597
  }
596
598
  else {
597
599
  defaultStreamMode = this.streamMode;
600
+ streamModeSingle = true;
598
601
  }
599
602
  // if being called as a node in another graph, always use values mode
600
603
  if (config.configurable?.[CONFIG_KEY_TASK_ID] !== undefined) {
@@ -622,6 +625,7 @@ export class Pregel extends Runnable {
622
625
  defaultInterruptAfter,
623
626
  defaultCheckpointer,
624
627
  defaultStore,
628
+ streamModeSingle,
625
629
  ];
626
630
  }
627
631
  /**
@@ -698,135 +702,180 @@ export class Pregel extends Runnable {
698
702
  inputConfig.configurable === undefined) {
699
703
  throw new Error(`Checkpointer requires one or more of the following "configurable" keys: "thread_id", "checkpoint_ns", "checkpoint_id"`);
700
704
  }
701
- const callbackManager = await getCallbackManagerForConfig(inputConfig);
702
- const runManager = await callbackManager?.handleChainStart(this.toJSON(), _coerceToDict(input, "input"), inputConfig.runId, undefined, undefined, undefined, inputConfig?.runName ?? this.getName());
703
- delete inputConfig.runId;
705
+ const { runId, ...restConfig } = inputConfig;
704
706
  // assign defaults
705
- const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store,] = this._defaults(inputConfig);
707
+ const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle,] = this._defaults(restConfig);
708
+ const stream = new IterableReadableWritableStream({
709
+ modes: new Set(streamMode),
710
+ });
711
+ // set up messages stream mode
712
+ if (streamMode.includes("messages")) {
713
+ const messageStreamer = new StreamMessagesHandler((chunk) => stream.push(chunk));
714
+ const { callbacks } = config;
715
+ if (callbacks === undefined) {
716
+ config.callbacks = [messageStreamer];
717
+ }
718
+ else if (Array.isArray(callbacks)) {
719
+ config.callbacks = callbacks.concat(messageStreamer);
720
+ }
721
+ else {
722
+ const copiedCallbacks = callbacks.copy();
723
+ copiedCallbacks.addHandler(messageStreamer, true);
724
+ config.callbacks = copiedCallbacks;
725
+ }
726
+ }
727
+ // setup custom stream mode
728
+ if (streamMode.includes("custom")) {
729
+ config.writer = (chunk) => stream.push([[], "custom", chunk]);
730
+ }
731
+ const callbackManager = await getCallbackManagerForConfig(config);
732
+ const runManager = await callbackManager?.handleChainStart(this.toJSON(), _coerceToDict(input, "input"), runId, undefined, undefined, undefined, config?.runName ?? this.getName());
706
733
  const { channelSpecs, managed } = await this.prepareSpecs(config);
707
734
  let loop;
708
- const stream = new Deque();
709
- function* emitCurrentLoopOutputs() {
710
- while (loop !== undefined && stream.length > 0) {
711
- const nextItem = stream.shift();
712
- if (nextItem === undefined) {
713
- throw new Error("Data structure error.");
735
+ let loopError;
736
+ const runLoop = async () => {
737
+ try {
738
+ loop = await PregelLoop.initialize({
739
+ input,
740
+ config,
741
+ checkpointer,
742
+ nodes: this.nodes,
743
+ channelSpecs,
744
+ managed,
745
+ outputKeys,
746
+ streamKeys: this.streamChannelsAsIs,
747
+ store,
748
+ stream,
749
+ });
750
+ if (options?.subgraphs) {
751
+ loop.config.configurable = {
752
+ ...loop.config.configurable,
753
+ [CONFIG_KEY_STREAM]: loop.stream,
754
+ };
714
755
  }
715
- const [namespace, mode, payload] = nextItem;
716
- if (streamMode.includes(mode)) {
717
- if (streamSubgraphs && streamMode.length > 1) {
718
- yield [namespace, mode, payload];
719
- }
720
- else if (streamMode.length > 1) {
721
- yield [mode, payload];
722
- }
723
- else if (streamSubgraphs) {
724
- yield [namespace, payload];
756
+ while (await loop.tick({
757
+ inputKeys: this.inputChannels,
758
+ interruptAfter,
759
+ interruptBefore,
760
+ manager: runManager,
761
+ })) {
762
+ if (debug) {
763
+ printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
725
764
  }
726
- else {
727
- yield payload;
765
+ if (debug) {
766
+ printStepTasks(loop.step, Object.values(loop.tasks));
728
767
  }
729
- }
730
- }
731
- }
732
- try {
733
- loop = await PregelLoop.initialize({
734
- input,
735
- config,
736
- checkpointer,
737
- nodes: this.nodes,
738
- channelSpecs,
739
- managed,
740
- outputKeys,
741
- streamKeys: this.streamChannelsAsIs,
742
- store,
743
- stream: new StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
744
- });
745
- if (options?.subgraphs) {
746
- loop.config.configurable = {
747
- ...loop.config.configurable,
748
- [CONFIG_KEY_STREAM]: loop.stream,
749
- };
750
- }
751
- while (await loop.tick({
752
- inputKeys: this.inputChannels,
753
- interruptAfter,
754
- interruptBefore,
755
- manager: runManager,
756
- })) {
757
- if (debug) {
758
- printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
759
- }
760
- yield* emitCurrentLoopOutputs();
761
- if (debug) {
762
- printStepTasks(loop.step, Object.values(loop.tasks));
763
- }
764
- // execute tasks, and wait for one to fail or all to finish.
765
- // each task is independent from all other concurrent tasks
766
- // yield updates/debug output as each task finishes
767
- const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
768
- stepTimeout: this.stepTimeout,
769
- signal: config.signal,
770
- retryPolicy: this.retryPolicy,
771
- });
772
- // Timeouts will be thrown
773
- for await (const { task, error } of taskStream) {
774
- if (error !== undefined) {
775
- if (isGraphInterrupt(error)) {
776
- if (loop.isNested) {
777
- throw error;
768
+ // execute tasks, and wait for one to fail or all to finish.
769
+ // each task is independent from all other concurrent tasks
770
+ // yield updates/debug output as each task finishes
771
+ const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
772
+ stepTimeout: this.stepTimeout,
773
+ signal: config.signal,
774
+ retryPolicy: this.retryPolicy,
775
+ });
776
+ // Timeouts will be thrown
777
+ for await (const { task, error } of taskStream) {
778
+ if (error !== undefined) {
779
+ if (isGraphInterrupt(error)) {
780
+ if (loop.isNested) {
781
+ throw error;
782
+ }
783
+ if (error.interrupts.length) {
784
+ loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
785
+ }
778
786
  }
779
- if (error.interrupts.length) {
780
- loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
787
+ else {
788
+ loop.putWrites(task.id, [
789
+ [ERROR, { message: error.message, name: error.name }],
790
+ ]);
781
791
  }
782
792
  }
783
793
  else {
784
- loop.putWrites(task.id, [
785
- [ERROR, { message: error.message, name: error.name }],
786
- ]);
794
+ loop.putWrites(task.id, task.writes);
795
+ }
796
+ if (error !== undefined && !isGraphInterrupt(error)) {
797
+ throw error;
787
798
  }
788
799
  }
789
- else {
790
- loop.putWrites(task.id, task.writes);
800
+ if (debug) {
801
+ printStepWrites(loop.step, Object.values(loop.tasks)
802
+ .map((task) => task.writes)
803
+ .flat(), this.streamChannelsList);
791
804
  }
792
- yield* emitCurrentLoopOutputs();
793
- if (error !== undefined && !isGraphInterrupt(error)) {
794
- throw error;
805
+ }
806
+ if (loop.status === "out_of_steps") {
807
+ throw new GraphRecursionError([
808
+ `Recursion limit of ${config.recursionLimit} reached`,
809
+ "without hitting a stop condition. You can increase the",
810
+ `limit by setting the "recursionLimit" config key.`,
811
+ ].join(" "), {
812
+ lc_error_code: "GRAPH_RECURSION_LIMIT",
813
+ });
814
+ }
815
+ }
816
+ catch (e) {
817
+ loopError = e;
818
+ }
819
+ finally {
820
+ try {
821
+ // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
822
+ if (loop) {
823
+ await loop.store?.stop();
795
824
  }
825
+ await Promise.all([
826
+ ...(loop?.checkpointerPromises ?? []),
827
+ ...Array.from(managed.values()).map((mv) => mv.promises()),
828
+ ]);
796
829
  }
797
- if (debug) {
798
- printStepWrites(loop.step, Object.values(loop.tasks)
799
- .map((task) => task.writes)
800
- .flat(), this.streamChannelsList);
830
+ catch (e) {
831
+ loopError = loopError ?? e;
832
+ }
833
+ if (loopError) {
834
+ // "Causes any future interactions with the associated stream to error".
835
+ // Wraps ReadableStreamDefaultController#error:
836
+ // https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController/error
837
+ stream.error(loopError);
838
+ }
839
+ else {
840
+ // Will end the iterator outside of this method,
841
+ // keeping previously enqueued chunks.
842
+ // Wraps ReadableStreamDefaultController#close:
843
+ // https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController/close
844
+ stream.close();
801
845
  }
802
846
  }
803
- yield* emitCurrentLoopOutputs();
804
- if (loop.status === "out_of_steps") {
805
- throw new GraphRecursionError([
806
- `Recursion limit of ${config.recursionLimit} reached`,
807
- "without hitting a stop condition. You can increase the",
808
- `limit by setting the "recursionLimit" config key.`,
809
- ].join(" "), {
810
- lc_error_code: "GRAPH_RECURSION_LIMIT",
811
- });
847
+ };
848
+ const runLoopPromise = runLoop();
849
+ try {
850
+ for await (const chunk of stream) {
851
+ if (chunk === undefined) {
852
+ throw new Error("Data structure error.");
853
+ }
854
+ const [namespace, mode, payload] = chunk;
855
+ if (streamMode.includes(mode)) {
856
+ if (streamSubgraphs && !streamModeSingle) {
857
+ yield [namespace, mode, payload];
858
+ }
859
+ else if (!streamModeSingle) {
860
+ yield [mode, payload];
861
+ }
862
+ else if (streamSubgraphs) {
863
+ yield [namespace, payload];
864
+ }
865
+ else {
866
+ yield payload;
867
+ }
868
+ }
812
869
  }
813
- await Promise.all(loop?.checkpointerPromises ?? []);
814
- await runManager?.handleChainEnd(loop.output);
815
870
  }
816
871
  catch (e) {
817
- await runManager?.handleChainError(e);
872
+ await runManager?.handleChainError(loopError);
818
873
  throw e;
819
874
  }
820
875
  finally {
821
- // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
822
- if (loop) {
823
- await loop.store?.stop();
824
- }
825
- await Promise.all([
826
- ...(loop?.checkpointerPromises ?? []),
827
- ...Array.from(managed.values()).map((mv) => mv.promises()),
828
- ]);
876
+ await runLoopPromise;
829
877
  }
878
+ await runManager?.handleChainEnd(loop?.output ?? {});
830
879
  }
831
880
  /**
832
881
  * Run the graph with a single input and config.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PregelLoop = exports.StreamProtocol = void 0;
3
+ exports.PregelLoop = exports.IterableReadableWritableStream = void 0;
4
+ const stream_1 = require("@langchain/core/utils/stream");
4
5
  const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
5
6
  const base_js_1 = require("../channels/base.cjs");
6
7
  const constants_js_1 = require("../constants.cjs");
@@ -14,33 +15,72 @@ const INPUT_DONE = Symbol.for("INPUT_DONE");
14
15
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
15
16
  const DEFAULT_LOOP_LIMIT = 25;
16
17
  const SPECIAL_CHANNELS = [constants_js_1.ERROR, constants_js_1.INTERRUPT];
17
- class StreamProtocol {
18
- constructor(pushFn, modes) {
19
- Object.defineProperty(this, "push", {
18
+ class IterableReadableWritableStream extends stream_1.IterableReadableStream {
19
+ constructor(params) {
20
+ let streamControllerPromiseResolver;
21
+ const streamControllerPromise = new Promise((resolve) => {
22
+ streamControllerPromiseResolver = resolve;
23
+ });
24
+ super({
25
+ start: (controller) => {
26
+ streamControllerPromiseResolver(controller);
27
+ },
28
+ });
29
+ Object.defineProperty(this, "modes", {
20
30
  enumerable: true,
21
31
  configurable: true,
22
32
  writable: true,
23
33
  value: void 0
24
34
  });
25
- Object.defineProperty(this, "modes", {
35
+ Object.defineProperty(this, "controller", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
41
+ Object.defineProperty(this, "passthroughFn", {
26
42
  enumerable: true,
27
43
  configurable: true,
28
44
  writable: true,
29
45
  value: void 0
30
46
  });
31
- this.push = pushFn;
32
- this.modes = modes;
47
+ // .start() will always be called before the stream can be interacted
48
+ // with anyway
49
+ void streamControllerPromise.then((controller) => {
50
+ this.controller = controller;
51
+ });
52
+ this.passthroughFn = params.passthroughFn;
53
+ this.modes = params.modes;
54
+ }
55
+ push(chunk) {
56
+ this.passthroughFn?.(chunk);
57
+ this.controller.enqueue(chunk);
58
+ }
59
+ close() {
60
+ try {
61
+ this.controller.close();
62
+ }
63
+ catch (e) {
64
+ // pass
65
+ }
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ error(e) {
69
+ this.controller.error(e);
33
70
  }
34
71
  }
35
- exports.StreamProtocol = StreamProtocol;
72
+ exports.IterableReadableWritableStream = IterableReadableWritableStream;
36
73
  function createDuplexStream(...streams) {
37
- return new StreamProtocol((value) => {
38
- for (const stream of streams) {
39
- if (stream.modes.has(value[1])) {
40
- stream.push(value);
74
+ return new IterableReadableWritableStream({
75
+ passthroughFn: (value) => {
76
+ for (const stream of streams) {
77
+ if (stream.modes.has(value[1])) {
78
+ stream.push(value);
79
+ }
41
80
  }
42
- }
43
- }, new Set(streams.flatMap((s) => Array.from(s.modes))));
81
+ },
82
+ modes: new Set(streams.flatMap((s) => Array.from(s.modes))),
83
+ });
44
84
  }
45
85
  class PregelLoop {
46
86
  constructor(params) {
@@ -1,5 +1,6 @@
1
1
  import type { RunnableConfig } from "@langchain/core/runnables";
2
2
  import type { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
3
+ import { IterableReadableStream } from "@langchain/core/utils/stream";
3
4
  import { BaseCheckpointSaver, Checkpoint, PendingWrite, CheckpointPendingWrite, CheckpointMetadata, All, BaseStore, AsyncBatchedStore } from "@langchain/langgraph-checkpoint";
4
5
  import { BaseChannel } from "../channels/base.js";
5
6
  import { PregelExecutableTask, StreamMode } from "./types.js";
@@ -16,7 +17,7 @@ export type PregelLoopInitializeParams = {
16
17
  nodes: Record<string, PregelNode>;
17
18
  channelSpecs: Record<string, BaseChannel>;
18
19
  managed: ManagedValueMapping;
19
- stream: StreamProtocol;
20
+ stream: IterableReadableWritableStream;
20
21
  store?: BaseStore;
21
22
  checkSubgraphs?: boolean;
22
23
  };
@@ -39,14 +40,21 @@ type PregelLoopParams = {
39
40
  checkpointNamespace: string[];
40
41
  skipDoneTasks: boolean;
41
42
  isNested: boolean;
42
- stream: StreamProtocol;
43
+ stream: IterableReadableWritableStream;
43
44
  store?: AsyncBatchedStore;
44
45
  prevCheckpointConfig: RunnableConfig | undefined;
45
46
  };
46
- export declare class StreamProtocol {
47
- push: (chunk: StreamChunk) => void;
47
+ export declare class IterableReadableWritableStream extends IterableReadableStream<StreamChunk> {
48
48
  modes: Set<StreamMode>;
49
- constructor(pushFn: (chunk: StreamChunk) => void, modes: Set<StreamMode>);
49
+ private controller;
50
+ private passthroughFn?;
51
+ constructor(params: {
52
+ passthroughFn?: (chunk: StreamChunk) => void;
53
+ modes: Set<StreamMode>;
54
+ });
55
+ push(chunk: StreamChunk): void;
56
+ close(): void;
57
+ error(e: any): void;
50
58
  }
51
59
  export declare class PregelLoop {
52
60
  protected input?: any;
@@ -72,7 +80,7 @@ export declare class PregelLoop {
72
80
  protected prevCheckpointConfig: RunnableConfig | undefined;
73
81
  status: "pending" | "done" | "interrupt_before" | "interrupt_after" | "out_of_steps";
74
82
  tasks: Record<string, PregelExecutableTask<any, any>>;
75
- stream: StreamProtocol;
83
+ stream: IterableReadableWritableStream;
76
84
  checkpointerPromises: Promise<unknown>[];
77
85
  isNested: boolean;
78
86
  protected _checkpointerChainedPromise: Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ import { IterableReadableStream } from "@langchain/core/utils/stream";
1
2
  import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, } from "@langchain/langgraph-checkpoint";
2
3
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
3
4
  import { CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, TAG_HIDDEN, TASKS, } from "../constants.js";
@@ -11,32 +12,71 @@ const INPUT_DONE = Symbol.for("INPUT_DONE");
11
12
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
12
13
  const DEFAULT_LOOP_LIMIT = 25;
13
14
  const SPECIAL_CHANNELS = [ERROR, INTERRUPT];
14
- export class StreamProtocol {
15
- constructor(pushFn, modes) {
16
- Object.defineProperty(this, "push", {
15
+ export class IterableReadableWritableStream extends IterableReadableStream {
16
+ constructor(params) {
17
+ let streamControllerPromiseResolver;
18
+ const streamControllerPromise = new Promise((resolve) => {
19
+ streamControllerPromiseResolver = resolve;
20
+ });
21
+ super({
22
+ start: (controller) => {
23
+ streamControllerPromiseResolver(controller);
24
+ },
25
+ });
26
+ Object.defineProperty(this, "modes", {
17
27
  enumerable: true,
18
28
  configurable: true,
19
29
  writable: true,
20
30
  value: void 0
21
31
  });
22
- Object.defineProperty(this, "modes", {
32
+ Object.defineProperty(this, "controller", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "passthroughFn", {
23
39
  enumerable: true,
24
40
  configurable: true,
25
41
  writable: true,
26
42
  value: void 0
27
43
  });
28
- this.push = pushFn;
29
- this.modes = modes;
44
+ // .start() will always be called before the stream can be interacted
45
+ // with anyway
46
+ void streamControllerPromise.then((controller) => {
47
+ this.controller = controller;
48
+ });
49
+ this.passthroughFn = params.passthroughFn;
50
+ this.modes = params.modes;
51
+ }
52
+ push(chunk) {
53
+ this.passthroughFn?.(chunk);
54
+ this.controller.enqueue(chunk);
55
+ }
56
+ close() {
57
+ try {
58
+ this.controller.close();
59
+ }
60
+ catch (e) {
61
+ // pass
62
+ }
63
+ }
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ error(e) {
66
+ this.controller.error(e);
30
67
  }
31
68
  }
32
69
  function createDuplexStream(...streams) {
33
- return new StreamProtocol((value) => {
34
- for (const stream of streams) {
35
- if (stream.modes.has(value[1])) {
36
- stream.push(value);
70
+ return new IterableReadableWritableStream({
71
+ passthroughFn: (value) => {
72
+ for (const stream of streams) {
73
+ if (stream.modes.has(value[1])) {
74
+ stream.push(value);
75
+ }
37
76
  }
38
- }
39
- }, new Set(streams.flatMap((s) => Array.from(s.modes))));
77
+ },
78
+ modes: new Set(streams.flatMap((s) => Array.from(s.modes))),
79
+ });
40
80
  }
41
81
  export class PregelLoop {
42
82
  constructor(params) {