@langchain/langgraph 0.2.46 → 0.2.47

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.
@@ -22,9 +22,20 @@ import { IterableReadableWritableStream } from "./stream.js";
22
22
  function isString(value) {
23
23
  return typeof value === "string";
24
24
  }
25
+ /**
26
+ * Utility class for working with channels in the Pregel system.
27
+ * Provides static methods for subscribing to channels and writing to them.
28
+ *
29
+ * Channels are the communication pathways between nodes in a Pregel graph.
30
+ * They enable message passing and state updates between different parts of the graph.
31
+ */
25
32
  export class Channel {
26
33
  static subscribeTo(channels, options) {
27
- const { key, tags } = options ?? {};
34
+ const { key, tags } = {
35
+ key: undefined,
36
+ tags: undefined,
37
+ ...(options ?? {}),
38
+ };
28
39
  if (Array.isArray(channels) && key !== undefined) {
29
40
  throw new Error("Can't specify a key when subscribing to multiple channels");
30
41
  }
@@ -47,7 +58,32 @@ export class Channel {
47
58
  tags,
48
59
  });
49
60
  }
50
- static writeTo(channels, kwargs) {
61
+ /**
62
+ * Creates a ChannelWrite that specifies how to write values to channels.
63
+ * This is used to define how nodes send output to channels.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * // Write to multiple channels
68
+ * const write = Channel.writeTo(["output", "state"]);
69
+ *
70
+ * // Write with specific values
71
+ * const write = Channel.writeTo(["output"], {
72
+ * state: "completed",
73
+ * result: calculateResult()
74
+ * });
75
+ *
76
+ * // Write with a transformation function
77
+ * const write = Channel.writeTo(["output"], {
78
+ * result: (x) => processResult(x)
79
+ * });
80
+ * ```
81
+ *
82
+ * @param channels - Array of channel names to write to
83
+ * @param writes - Optional map of channel names to values or transformations
84
+ * @returns A ChannelWrite object that can be used to write to the specified channels
85
+ */
86
+ static writeTo(channels, writes) {
51
87
  const channelWriteEntries = [];
52
88
  for (const channel of channels) {
53
89
  channelWriteEntries.push({
@@ -56,7 +92,7 @@ export class Channel {
56
92
  skipNone: false,
57
93
  });
58
94
  }
59
- for (const [key, value] of Object.entries(kwargs ?? {})) {
95
+ for (const [key, value] of Object.entries(writes ?? {})) {
60
96
  if (Runnable.isRunnable(value) || typeof value === "function") {
61
97
  channelWriteEntries.push({
62
98
  channel: key,
@@ -76,109 +112,222 @@ export class Channel {
76
112
  return new ChannelWrite(channelWriteEntries);
77
113
  }
78
114
  }
115
+ /**
116
+ * The Pregel class is the core runtime engine of LangGraph, implementing a message-passing graph computation model
117
+ * inspired by [Google's Pregel system](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/).
118
+ * It provides the foundation for building reliable, controllable agent workflows that can evolve state over time.
119
+ *
120
+ * Key features:
121
+ * - Message passing between nodes in discrete "supersteps"
122
+ * - Built-in persistence layer through checkpointers
123
+ * - First-class streaming support for values, updates, and events
124
+ * - Human-in-the-loop capabilities via interrupts
125
+ * - Support for parallel node execution within supersteps
126
+ *
127
+ * The Pregel class is not intended to be instantiated directly by consumers. Instead, use the following higher-level APIs:
128
+ * - {@link StateGraph}: The main graph class for building agent workflows
129
+ * - Compiling a {@link StateGraph} will return a {@link CompiledGraph} instance, which extends `Pregel`
130
+ * - Functional API: A declarative approach using tasks and entrypoints
131
+ * - A `Pregel` instance is returned by the {@link entrypoint} function
132
+ *
133
+ * @example
134
+ * ```typescript
135
+ * // Using StateGraph API
136
+ * const graph = new StateGraph(annotation)
137
+ * .addNode("nodeA", myNodeFunction)
138
+ * .addEdge("nodeA", "nodeB")
139
+ * .compile();
140
+ *
141
+ * // The compiled graph is a Pregel instance
142
+ * const result = await graph.invoke(input);
143
+ * ```
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // Using Functional API
148
+ * import { task, entrypoint } from "@langchain/langgraph";
149
+ * import { MemorySaver } from "@langchain/langgraph-checkpoint";
150
+ *
151
+ * // Define tasks that can be composed
152
+ * const addOne = task("add", async (x: number) => x + 1);
153
+ *
154
+ * // Create a workflow using the entrypoint function
155
+ * const workflow = entrypoint({
156
+ * name: "workflow",
157
+ * checkpointer: new MemorySaver()
158
+ * }, async (numbers: number[]) => {
159
+ * // Tasks can be run in parallel
160
+ * const results = await Promise.all(numbers.map(n => addOne(n)));
161
+ * return results;
162
+ * });
163
+ *
164
+ * // The workflow is a Pregel instance
165
+ * const result = await workflow.invoke([1, 2, 3]); // Returns [2, 3, 4]
166
+ * ```
167
+ *
168
+ * @typeParam Nodes - Mapping of node names to their {@link PregelNode} implementations
169
+ * @typeParam Channels - Mapping of channel names to their {@link BaseChannel} or {@link ManagedValueSpec} implementations
170
+ * @typeParam ConfigurableFieldType - Type of configurable fields that can be passed to the graph
171
+ * @typeParam InputType - Type of input values accepted by the graph
172
+ * @typeParam OutputType - Type of output values produced by the graph
173
+ */
79
174
  export class Pregel extends Runnable {
175
+ /**
176
+ * Name of the class when serialized
177
+ * @internal
178
+ */
80
179
  static lc_name() {
81
180
  return "LangGraph";
82
181
  }
182
+ /**
183
+ * Constructor for Pregel - meant for internal use only.
184
+ *
185
+ * @internal
186
+ */
83
187
  constructor(fields) {
84
188
  super(fields);
85
- // Because Pregel extends `Runnable`.
189
+ /** @internal LangChain namespace for serialization necessary because Pregel extends Runnable */
86
190
  Object.defineProperty(this, "lc_namespace", {
87
191
  enumerable: true,
88
192
  configurable: true,
89
193
  writable: true,
90
194
  value: ["langgraph", "pregel"]
91
195
  });
196
+ /** @internal Flag indicating this is a Pregel instance - necessary for serialization */
92
197
  Object.defineProperty(this, "lg_is_pregel", {
93
198
  enumerable: true,
94
199
  configurable: true,
95
200
  writable: true,
96
201
  value: true
97
202
  });
203
+ /** The nodes in the graph, mapping node names to their PregelNode instances */
98
204
  Object.defineProperty(this, "nodes", {
99
205
  enumerable: true,
100
206
  configurable: true,
101
207
  writable: true,
102
208
  value: void 0
103
209
  });
210
+ /** The channels in the graph, mapping channel names to their BaseChannel or ManagedValueSpec instances */
104
211
  Object.defineProperty(this, "channels", {
105
212
  enumerable: true,
106
213
  configurable: true,
107
214
  writable: true,
108
215
  value: void 0
109
216
  });
217
+ /**
218
+ * The input channels for the graph. These channels receive the initial input when the graph is invoked.
219
+ * Can be a single channel key or an array of channel keys.
220
+ */
110
221
  Object.defineProperty(this, "inputChannels", {
111
222
  enumerable: true,
112
223
  configurable: true,
113
224
  writable: true,
114
225
  value: void 0
115
226
  });
227
+ /**
228
+ * The output channels for the graph. These channels contain the final output when the graph completes.
229
+ * Can be a single channel key or an array of channel keys.
230
+ */
116
231
  Object.defineProperty(this, "outputChannels", {
117
232
  enumerable: true,
118
233
  configurable: true,
119
234
  writable: true,
120
235
  value: void 0
121
236
  });
237
+ /** Whether to automatically validate the graph structure when it is compiled. Defaults to true. */
122
238
  Object.defineProperty(this, "autoValidate", {
123
239
  enumerable: true,
124
240
  configurable: true,
125
241
  writable: true,
126
242
  value: true
127
243
  });
244
+ /**
245
+ * The streaming modes enabled for this graph. Defaults to ["values"].
246
+ * Supported modes:
247
+ * - "values": Streams the full state after each step
248
+ * - "updates": Streams state updates after each step
249
+ * - "messages": Streams messages from within nodes
250
+ * - "custom": Streams custom events from within nodes
251
+ * - "debug": Streams events related to the execution of the graph - useful for tracing & debugging graph execution
252
+ */
128
253
  Object.defineProperty(this, "streamMode", {
129
254
  enumerable: true,
130
255
  configurable: true,
131
256
  writable: true,
132
257
  value: ["values"]
133
258
  });
259
+ /**
260
+ * Optional channels to stream. If not specified, all channels will be streamed.
261
+ * Can be a single channel key or an array of channel keys.
262
+ */
134
263
  Object.defineProperty(this, "streamChannels", {
135
264
  enumerable: true,
136
265
  configurable: true,
137
266
  writable: true,
138
267
  value: void 0
139
268
  });
269
+ /**
270
+ * Optional array of node names or "all" to interrupt after executing these nodes.
271
+ * Used for implementing human-in-the-loop workflows.
272
+ */
140
273
  Object.defineProperty(this, "interruptAfter", {
141
274
  enumerable: true,
142
275
  configurable: true,
143
276
  writable: true,
144
277
  value: void 0
145
278
  });
279
+ /**
280
+ * Optional array of node names or "all" to interrupt before executing these nodes.
281
+ * Used for implementing human-in-the-loop workflows.
282
+ */
146
283
  Object.defineProperty(this, "interruptBefore", {
147
284
  enumerable: true,
148
285
  configurable: true,
149
286
  writable: true,
150
287
  value: void 0
151
288
  });
289
+ /** Optional timeout in milliseconds for the execution of each superstep */
152
290
  Object.defineProperty(this, "stepTimeout", {
153
291
  enumerable: true,
154
292
  configurable: true,
155
293
  writable: true,
156
294
  value: void 0
157
295
  });
296
+ /** Whether to enable debug logging. Defaults to false. */
158
297
  Object.defineProperty(this, "debug", {
159
298
  enumerable: true,
160
299
  configurable: true,
161
300
  writable: true,
162
301
  value: false
163
302
  });
303
+ /**
304
+ * Optional checkpointer for persisting graph state.
305
+ * When provided, saves a checkpoint of the graph state at every superstep.
306
+ * When false or undefined, checkpointing is disabled, and the graph will not be able to save or restore state.
307
+ */
164
308
  Object.defineProperty(this, "checkpointer", {
165
309
  enumerable: true,
166
310
  configurable: true,
167
311
  writable: true,
168
312
  value: void 0
169
313
  });
314
+ /** Optional retry policy for handling failures in node execution */
170
315
  Object.defineProperty(this, "retryPolicy", {
171
316
  enumerable: true,
172
317
  configurable: true,
173
318
  writable: true,
174
319
  value: void 0
175
320
  });
321
+ /** The default configuration for graph execution, can be overridden on a per-invocation basis */
176
322
  Object.defineProperty(this, "config", {
177
323
  enumerable: true,
178
324
  configurable: true,
179
325
  writable: true,
180
326
  value: void 0
181
327
  });
328
+ /**
329
+ * Optional long-term memory store for the graph, allows for persistance & retrieval of data across threads
330
+ */
182
331
  Object.defineProperty(this, "store", {
183
332
  enumerable: true,
184
333
  configurable: true,
@@ -209,6 +358,25 @@ export class Pregel extends Runnable {
209
358
  this.validate();
210
359
  }
211
360
  }
361
+ /**
362
+ * Creates a new instance of the Pregel graph with updated configuration.
363
+ * This method follows the immutable pattern - instead of modifying the current instance,
364
+ * it returns a new instance with the merged configuration.
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * // Create a new instance with debug enabled
369
+ * const debugGraph = graph.withConfig({ debug: true });
370
+ *
371
+ * // Create a new instance with a specific thread ID
372
+ * const threadGraph = graph.withConfig({
373
+ * configurable: { thread_id: "123" }
374
+ * });
375
+ * ```
376
+ *
377
+ * @param config - The configuration to merge with the current configuration
378
+ * @returns A new Pregel instance with the merged configuration
379
+ */
212
380
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
213
381
  // @ts-ignore Remove ignore when we remove support for 0.2 versions of core
214
382
  withConfig(config) {
@@ -216,6 +384,16 @@ export class Pregel extends Runnable {
216
384
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
385
  return new this.constructor({ ...this, config: mergedConfig });
218
386
  }
387
+ /**
388
+ * Validates the graph structure to ensure it is well-formed.
389
+ * Checks for:
390
+ * - No orphaned nodes
391
+ * - Valid input/output channel configurations
392
+ * - Valid interrupt configurations
393
+ *
394
+ * @returns this - The Pregel instance for method chaining
395
+ * @throws {GraphValidationError} If the graph structure is invalid
396
+ */
219
397
  validate() {
220
398
  validateGraph({
221
399
  nodes: this.nodes,
@@ -228,6 +406,13 @@ export class Pregel extends Runnable {
228
406
  });
229
407
  return this;
230
408
  }
409
+ /**
410
+ * Gets a list of all channels that should be streamed.
411
+ * If streamChannels is specified, returns those channels.
412
+ * Otherwise, returns all channels in the graph.
413
+ *
414
+ * @returns Array of channel keys to stream
415
+ */
231
416
  get streamChannelsList() {
232
417
  if (Array.isArray(this.streamChannels)) {
233
418
  return this.streamChannels;
@@ -239,6 +424,13 @@ export class Pregel extends Runnable {
239
424
  return Object.keys(this.channels);
240
425
  }
241
426
  }
427
+ /**
428
+ * Gets the channels to stream in their original format.
429
+ * If streamChannels is specified, returns it as-is (either single key or array).
430
+ * Otherwise, returns all channels in the graph as an array.
431
+ *
432
+ * @returns Channel keys to stream, either as a single key or array
433
+ */
242
434
  get streamChannelsAsIs() {
243
435
  if (this.streamChannels) {
244
436
  return this.streamChannels;
@@ -247,10 +439,25 @@ export class Pregel extends Runnable {
247
439
  return Object.keys(this.channels);
248
440
  }
249
441
  }
442
+ /**
443
+ * Gets a drawable representation of the graph structure.
444
+ * This is an async version of getGraph() and is the preferred method to use.
445
+ *
446
+ * @param config - Configuration for generating the graph visualization
447
+ * @returns A representation of the graph that can be visualized
448
+ */
250
449
  async getGraphAsync(config) {
251
450
  return this.getGraph(config);
252
451
  }
253
- /** @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release. */
452
+ /**
453
+ * Gets all subgraphs within this graph.
454
+ * A subgraph is a Pregel instance that is nested within a node of this graph.
455
+ *
456
+ * @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release.
457
+ * @param namespace - Optional namespace to filter subgraphs
458
+ * @param recurse - Whether to recursively get subgraphs of subgraphs
459
+ * @returns Generator yielding tuples of [name, subgraph]
460
+ */
254
461
  *getSubgraphs(namespace, recurse
255
462
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
463
  ) {
@@ -288,11 +495,29 @@ export class Pregel extends Runnable {
288
495
  }
289
496
  }
290
497
  }
498
+ /**
499
+ * Gets all subgraphs within this graph asynchronously.
500
+ * A subgraph is a Pregel instance that is nested within a node of this graph.
501
+ *
502
+ * @param namespace - Optional namespace to filter subgraphs
503
+ * @param recurse - Whether to recursively get subgraphs of subgraphs
504
+ * @returns AsyncGenerator yielding tuples of [name, subgraph]
505
+ */
291
506
  async *getSubgraphsAsync(namespace, recurse
292
507
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
293
508
  ) {
294
509
  yield* this.getSubgraphs(namespace, recurse);
295
510
  }
511
+ /**
512
+ * Prepares a state snapshot from saved checkpoint data.
513
+ * This is an internal method used by getState and getStateHistory.
514
+ *
515
+ * @param config - Configuration for preparing the snapshot
516
+ * @param saved - Optional saved checkpoint data
517
+ * @param subgraphCheckpointer - Optional checkpointer for subgraphs
518
+ * @returns A snapshot of the graph state
519
+ * @internal
520
+ */
296
521
  async _prepareStateSnapshot({ config, saved, subgraphCheckpointer, }) {
297
522
  if (saved === undefined) {
298
523
  return {
@@ -368,7 +593,13 @@ export class Pregel extends Runnable {
368
593
  };
369
594
  }
370
595
  /**
371
- * Get the current state of the graph.
596
+ * Gets the current state of the graph.
597
+ * Requires a checkpointer to be configured.
598
+ *
599
+ * @param config - Configuration for retrieving the state
600
+ * @param options - Additional options
601
+ * @returns A snapshot of the current graph state
602
+ * @throws {GraphValueError} If no checkpointer is configured
372
603
  */
373
604
  async getState(config, options) {
374
605
  const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
@@ -402,7 +633,17 @@ export class Pregel extends Runnable {
402
633
  return snapshot;
403
634
  }
404
635
  /**
405
- * Get the history of the state of the graph.
636
+ * Gets the history of graph states.
637
+ * Requires a checkpointer to be configured.
638
+ * Useful for:
639
+ * - Debugging execution history
640
+ * - Implementing time travel
641
+ * - Analyzing graph behavior
642
+ *
643
+ * @param config - Configuration for retrieving the history
644
+ * @param options - Options for filtering the history
645
+ * @returns An async iterator of state snapshots
646
+ * @throws {Error} If no checkpointer is configured
406
647
  */
407
648
  async *getStateHistory(config, options) {
408
649
  const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
@@ -438,9 +679,20 @@ export class Pregel extends Runnable {
438
679
  }
439
680
  }
440
681
  /**
441
- * Update the state of the graph with the given values, as if they came from
442
- * node `as_node`. If `as_node` is not provided, it will be set to the last node
443
- * that updated the state, if not ambiguous.
682
+ * Updates the state of the graph with new values.
683
+ * Requires a checkpointer to be configured.
684
+ *
685
+ * This method can be used for:
686
+ * - Implementing human-in-the-loop workflows
687
+ * - Modifying graph state during breakpoints
688
+ * - Integrating external inputs into the graph
689
+ *
690
+ * @param inputConfig - Configuration for the update
691
+ * @param values - The values to update the state with
692
+ * @param asNode - Optional node name to attribute the update to
693
+ * @returns Updated configuration
694
+ * @throws {GraphValueError} If no checkpointer is configured
695
+ * @throws {InvalidUpdateError} If the update cannot be attributed to a node
444
696
  */
445
697
  async updateState(inputConfig, values, asNode) {
446
698
  const checkpointer = inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
@@ -693,6 +945,24 @@ export class Pregel extends Runnable {
693
945
  }
694
946
  return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
695
947
  }
948
+ /**
949
+ * Gets the default values for various graph configuration options.
950
+ * This is an internal method used to process and normalize configuration options.
951
+ *
952
+ * @param config - The input configuration options
953
+ * @returns A tuple containing normalized values for:
954
+ * - debug mode
955
+ * - stream modes
956
+ * - input keys
957
+ * - output keys
958
+ * - remaining config
959
+ * - interrupt before nodes
960
+ * - interrupt after nodes
961
+ * - checkpointer
962
+ * - store
963
+ * - whether stream mode is single
964
+ * @internal
965
+ */
696
966
  _defaults(config) {
697
967
  const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
698
968
  let streamModeSingle = true;
@@ -752,19 +1022,20 @@ export class Pregel extends Runnable {
752
1022
  ];
753
1023
  }
754
1024
  /**
755
- * Stream graph steps for a single input.
756
- * @param input The input to the graph.
757
- * @param options The configuration to use for the run.
758
- * @param options.streamMode The mode to stream output. Defaults to value set on initialization.
759
- * Options are "values", "updates", and "debug". Default is "values".
760
- * values: Emit the current values of the state for each step.
761
- * updates: Emit only the updates to the state for each step.
762
- * Output is a dict with the node name as key and the updated values as value.
763
- * debug: Emit debug events for each step.
764
- * @param options.outputKeys The keys to stream. Defaults to all non-context channels.
765
- * @param options.interruptBefore Nodes to interrupt before.
766
- * @param options.interruptAfter Nodes to interrupt after.
767
- * @param options.debug Whether to print debug information during execution.
1025
+ * Streams the execution of the graph, emitting state updates as they occur.
1026
+ * This is the primary method for observing graph execution in real-time.
1027
+ *
1028
+ * Stream modes:
1029
+ * - "values": Emits complete state after each step
1030
+ * - "updates": Emits only state changes after each step
1031
+ * - "debug": Emits detailed debug information
1032
+ * - "messages": Emits messages from within nodes
1033
+ *
1034
+ * For more details, see the [Streaming how-to guides](../../how-tos/#streaming_1).
1035
+ *
1036
+ * @param input - The input to start graph execution with
1037
+ * @param options - Configuration options for streaming
1038
+ * @returns An async iterable stream of graph state updates
768
1039
  */
769
1040
  async stream(input, options) {
770
1041
  // The ensureConfig method called internally defaults recursionLimit to 25 if not
@@ -778,6 +1049,17 @@ export class Pregel extends Runnable {
778
1049
  };
779
1050
  return super.stream(input, config);
780
1051
  }
1052
+ /**
1053
+ * Prepares channel specifications and managed values for graph execution.
1054
+ * This is an internal method used to set up the graph's communication channels
1055
+ * and managed state before execution.
1056
+ *
1057
+ * @param config - Configuration for preparing specs
1058
+ * @param options - Additional options
1059
+ * @param options.skipManaged - Whether to skip initialization of managed values
1060
+ * @returns Object containing channel specs and managed value mapping
1061
+ * @internal
1062
+ */
781
1063
  async prepareSpecs(config, options) {
782
1064
  const configForManaged = {
783
1065
  ...config,
@@ -822,6 +1104,15 @@ export class Pregel extends Runnable {
822
1104
  managed,
823
1105
  };
824
1106
  }
1107
+ /**
1108
+ * Internal iterator used by stream() to generate state updates.
1109
+ * This method handles the core logic of graph execution and streaming.
1110
+ *
1111
+ * @param input - The input to start graph execution with
1112
+ * @param options - Configuration options for streaming
1113
+ * @returns AsyncGenerator yielding state updates
1114
+ * @internal
1115
+ */
825
1116
  async *_streamIterator(input, options) {
826
1117
  const streamSubgraphs = options?.subgraphs;
827
1118
  const inputConfig = ensureLangGraphConfig(this.config, options);
@@ -971,16 +1262,6 @@ export class Pregel extends Runnable {
971
1262
  * Run the graph with a single input and config.
972
1263
  * @param input The input to the graph.
973
1264
  * @param options The configuration to use for the run.
974
- * @param options.streamMode The mode to stream output. Defaults to value set on initialization.
975
- * Options are "values", "updates", and "debug". Default is "values".
976
- * values: Emit the current values of the state for each step.
977
- * updates: Emit only the updates to the state for each step.
978
- * Output is a dict with the node name as key and the updated values as value.
979
- * debug: Emit debug events for each step.
980
- * @param options.outputKeys The keys to stream. Defaults to all non-context channels.
981
- * @param options.interruptBefore Nodes to interrupt before.
982
- * @param options.interruptAfter Nodes to interrupt after.
983
- * @param options.debug Whether to print debug information during execution.
984
1265
  */
985
1266
  async invoke(input, options) {
986
1267
  const streamMode = options?.streamMode ?? "values";
@@ -157,10 +157,15 @@ function* mapOutputValues(outputChannels, pendingWrites, channels
157
157
  exports.mapOutputValues = mapOutputValues;
158
158
  /**
159
159
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
160
+ * @internal
161
+ *
162
+ * @param outputChannels - The channels to output.
163
+ * @param tasks - The tasks to output.
164
+ * @param cached - Whether the output is cached.
165
+ *
166
+ * @returns A generator that yields the output chunk (if any).
160
167
  */
161
- function* mapOutputUpdates(outputChannels, tasks, cached
162
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
- ) {
168
+ function* mapOutputUpdates(outputChannels, tasks, cached) {
164
169
  const outputTasks = tasks.filter(([task, ww]) => {
165
170
  return ((task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN)) &&
166
171
  ww[0][0] !== constants_js_1.ERROR &&
@@ -169,47 +174,67 @@ function* mapOutputUpdates(outputChannels, tasks, cached
169
174
  if (!outputTasks.length) {
170
175
  return;
171
176
  }
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
177
  let updated;
174
178
  if (outputTasks.some(([task]) => task.writes.some(([chan, _]) => chan === constants_js_1.RETURN))) {
179
+ // TODO: probably should assert that RETURN is the only "non-special" channel (starts with "__")
175
180
  updated = outputTasks.flatMap(([task]) => task.writes
176
181
  .filter(([chan, _]) => chan === constants_js_1.RETURN)
177
182
  .map(([_, value]) => [task.name, value]));
178
183
  }
179
184
  else if (!Array.isArray(outputChannels)) {
185
+ // special case where graph state is a single channel (MessageGraph)
186
+ // probably using this in functional API, too
180
187
  updated = outputTasks.flatMap(([task]) => task.writes
181
188
  .filter(([chan, _]) => chan === outputChannels)
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
189
  .map(([_, value]) => [task.name, value]));
184
190
  }
185
191
  else {
186
- updated = outputTasks
187
- .filter(([task]) => task.writes.some(([chan]) => outputChannels.includes(chan)))
188
- .map(([task]) => [
189
- task.name,
190
- Object.fromEntries(task.writes.filter(([chan]) => outputChannels.includes(chan))),
191
- ]);
192
- }
193
- const grouped = Object.fromEntries(outputTasks.map(([t]) => [t.name, []])
194
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
- );
192
+ updated = outputTasks.flatMap(([task]) => {
193
+ const { writes } = task;
194
+ const counts = {};
195
+ for (const [chan] of writes) {
196
+ if (outputChannels.includes(chan)) {
197
+ counts[chan] = (counts[chan] || 0) + 1;
198
+ }
199
+ }
200
+ if (Object.values(counts).some((count) => count > 1)) {
201
+ // Multiple writes to the same channel: create separate entries
202
+ return writes
203
+ .filter(([chan]) => outputChannels.includes(chan))
204
+ .map(([chan, value]) => [task.name, { [chan]: value }]);
205
+ }
206
+ else {
207
+ // Single write to each channel: create a single combined entry
208
+ return [
209
+ [
210
+ task.name,
211
+ Object.fromEntries(writes.filter(([chan]) => outputChannels.includes(chan))),
212
+ ],
213
+ ];
214
+ }
215
+ });
216
+ }
217
+ const grouped = {};
196
218
  for (const [node, value] of updated) {
219
+ if (!(node in grouped)) {
220
+ grouped[node] = [];
221
+ }
197
222
  grouped[node].push(value);
198
223
  }
199
- for (const [node, value] of Object.entries(grouped)) {
200
- if (value.length === 0) {
201
- delete grouped[node];
224
+ const flattened = {};
225
+ for (const node in grouped) {
226
+ if (grouped[node].length === 1) {
227
+ const [write] = grouped[node];
228
+ flattened[node] = write;
202
229
  }
203
- else if (value.length === 1) {
204
- // TODO: Fix incorrect cast here
205
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
206
- grouped[node] = value[0];
230
+ else {
231
+ flattened[node] = grouped[node];
207
232
  }
208
233
  }
209
234
  if (cached) {
210
- grouped["__metadata__"] = { cached };
235
+ flattened["__metadata__"] = { cached };
211
236
  }
212
- yield grouped;
237
+ yield flattened;
213
238
  }
214
239
  exports.mapOutputUpdates = mapOutputUpdates;
215
240
  function single(iter) {