@langchain/langgraph 0.2.8 → 0.2.9

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.
Files changed (45) hide show
  1. package/dist/constants.cjs +12 -2
  2. package/dist/constants.d.ts +7 -1
  3. package/dist/constants.js +11 -1
  4. package/dist/errors.cjs +5 -4
  5. package/dist/errors.d.ts +1 -1
  6. package/dist/errors.js +5 -4
  7. package/dist/graph/graph.cjs +7 -2
  8. package/dist/graph/graph.js +8 -3
  9. package/dist/graph/message.cjs +2 -0
  10. package/dist/graph/message.js +2 -0
  11. package/dist/graph/state.cjs +8 -3
  12. package/dist/graph/state.d.ts +1 -1
  13. package/dist/graph/state.js +9 -4
  14. package/dist/managed/shared_value.cjs +1 -1
  15. package/dist/managed/shared_value.js +1 -1
  16. package/dist/pregel/algo.cjs +119 -54
  17. package/dist/pregel/algo.d.ts +5 -2
  18. package/dist/pregel/algo.js +117 -53
  19. package/dist/pregel/debug.cjs +15 -18
  20. package/dist/pregel/debug.d.ts +3 -2
  21. package/dist/pregel/debug.js +16 -19
  22. package/dist/pregel/index.cjs +295 -110
  23. package/dist/pregel/index.d.ts +16 -4
  24. package/dist/pregel/index.js +292 -110
  25. package/dist/pregel/io.cjs +15 -8
  26. package/dist/pregel/io.d.ts +2 -2
  27. package/dist/pregel/io.js +15 -8
  28. package/dist/pregel/loop.cjs +256 -111
  29. package/dist/pregel/loop.d.ts +21 -5
  30. package/dist/pregel/loop.js +256 -109
  31. package/dist/pregel/read.cjs +9 -2
  32. package/dist/pregel/read.d.ts +2 -1
  33. package/dist/pregel/read.js +9 -2
  34. package/dist/pregel/retry.d.ts +1 -1
  35. package/dist/pregel/types.d.ts +5 -1
  36. package/dist/pregel/utils/config.cjs +72 -0
  37. package/dist/pregel/utils/config.d.ts +2 -0
  38. package/dist/pregel/utils/config.js +68 -0
  39. package/dist/pregel/{utils.cjs → utils/index.cjs} +33 -10
  40. package/dist/pregel/{utils.d.ts → utils/index.d.ts} +4 -7
  41. package/dist/pregel/{utils.js → utils/index.js} +30 -8
  42. package/dist/utils.cjs +5 -3
  43. package/dist/utils.js +5 -3
  44. package/dist/web.d.ts +2 -1
  45. package/package.json +1 -1
@@ -1,20 +1,22 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, getCallbackManagerForConfig, patchConfig, } from "@langchain/core/runnables";
2
+ import { Runnable, RunnableSequence, _coerceToRunnable, getCallbackManagerForConfig, mergeConfigs, patchConfig, } from "@langchain/core/runnables";
3
3
  import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, uuid5, } from "@langchain/langgraph-checkpoint";
4
+ import Deque from "double-ended-queue";
4
5
  import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
5
6
  import { PregelNode } from "./read.js";
6
7
  import { validateGraph, validateKeys } from "./validate.js";
7
8
  import { readChannels } from "./io.js";
8
9
  import { printStepCheckpoint, printStepTasks, printStepWrites, tasksWithWrites, } from "./debug.js";
9
10
  import { ChannelWrite, PASSTHROUGH } from "./write.js";
10
- import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STORE, ERROR, INTERRUPT, } from "../constants.js";
11
+ import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STORE, ERROR, INTERRUPT, CHECKPOINT_NAMESPACE_SEPARATOR, CHECKPOINT_NAMESPACE_END, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, } from "../constants.js";
11
12
  import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphInterrupt, } from "../errors.js";
12
13
  import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
13
- import { _coerceToDict, getNewChannelVersions } from "./utils.js";
14
- import { PregelLoop } from "./loop.js";
14
+ import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
15
+ import { PregelLoop, StreamProtocol } from "./loop.js";
15
16
  import { executeTasksWithRetry } from "./retry.js";
16
17
  import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, NoopManagedValue, } from "../managed/base.js";
17
- import { patchConfigurable } from "../utils.js";
18
+ import { gatherIterator, patchConfigurable } from "../utils.js";
19
+ import { ensureLangGraphConfig } from "./utils/config.js";
18
20
  function isString(value) {
19
21
  return typeof value === "string";
20
22
  }
@@ -72,6 +74,16 @@ export class Channel {
72
74
  return new ChannelWrite(channelWriteEntries);
73
75
  }
74
76
  }
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ function isPregel(x) {
79
+ return ("inputChannels" in x &&
80
+ x.inputChannels !== undefined &&
81
+ "outputChannels" &&
82
+ x.outputChannels !== undefined);
83
+ }
84
+ function isRunnableSequence(x) {
85
+ return "steps" in x && Array.isArray(x.steps);
86
+ }
75
87
  export class Pregel extends Runnable {
76
88
  static lc_name() {
77
89
  return "LangGraph";
@@ -163,6 +175,12 @@ export class Pregel extends Runnable {
163
175
  writable: true,
164
176
  value: void 0
165
177
  });
178
+ Object.defineProperty(this, "config", {
179
+ enumerable: true,
180
+ configurable: true,
181
+ writable: true,
182
+ value: void 0
183
+ });
166
184
  Object.defineProperty(this, "store", {
167
185
  enumerable: true,
168
186
  configurable: true,
@@ -186,11 +204,18 @@ export class Pregel extends Runnable {
186
204
  this.debug = fields.debug ?? this.debug;
187
205
  this.checkpointer = fields.checkpointer;
188
206
  this.retryPolicy = fields.retryPolicy;
207
+ this.config = fields.config;
189
208
  this.store = fields.store;
190
209
  if (this.autoValidate) {
191
210
  this.validate();
192
211
  }
193
212
  }
213
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
214
+ // @ts-ignore Remove ignore when we remove support for 0.2 versions of core
215
+ withConfig(config) {
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ return new this.constructor({ ...this, config });
218
+ }
194
219
  validate() {
195
220
  validateGraph({
196
221
  nodes: this.nodes,
@@ -222,50 +247,181 @@ export class Pregel extends Runnable {
222
247
  return Object.keys(this.channels);
223
248
  }
224
249
  }
225
- /**
226
- * Get the current state of the graph.
227
- */
228
- async getState(config) {
229
- if (!this.checkpointer) {
230
- throw new GraphValueError("No checkpointer set");
250
+ *getSubgraphs(namespace, recurse
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ ) {
253
+ for (const [name, node] of Object.entries(this.nodes)) {
254
+ // filter by prefix
255
+ if (namespace !== undefined) {
256
+ if (!namespace.startsWith(name)) {
257
+ continue;
258
+ }
259
+ }
260
+ // find the subgraph if any
261
+ let graph;
262
+ const candidates = [node.bound];
263
+ for (const candidate of candidates) {
264
+ if (isPregel(candidate)) {
265
+ graph = candidate;
266
+ break;
267
+ }
268
+ else if (isRunnableSequence(candidate)) {
269
+ candidates.push(...candidate.steps);
270
+ }
271
+ }
272
+ // if found, yield recursively
273
+ if (graph !== undefined) {
274
+ if (name === namespace) {
275
+ yield [name, graph];
276
+ return;
277
+ }
278
+ if (namespace === undefined) {
279
+ yield [name, graph];
280
+ }
281
+ if (recurse) {
282
+ let newNamespace = namespace;
283
+ if (namespace !== undefined) {
284
+ newNamespace = namespace.slice(name.length + 1);
285
+ }
286
+ for (const [subgraphName, subgraph] of graph.getSubgraphs(newNamespace, recurse)) {
287
+ yield [
288
+ `${name}${CHECKPOINT_NAMESPACE_SEPARATOR}${subgraphName}`,
289
+ subgraph,
290
+ ];
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+ async _prepareStateSnapshot({ config, saved, subgraphCheckpointer, }) {
297
+ if (saved === undefined) {
298
+ return {
299
+ values: {},
300
+ next: [],
301
+ config,
302
+ tasks: [],
303
+ };
231
304
  }
232
- const saved = await this.checkpointer.getTuple(config);
233
- const checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
234
- const channels = emptyChannels(this.channels, checkpoint);
235
305
  // Pass `skipManaged: true` as managed values should not be returned in get state calls.
236
306
  const { managed } = await this.prepareSpecs(config, { skipManaged: true });
237
- const nextTasks = _prepareNextTasks(checkpoint, this.nodes, channels, managed, saved !== undefined ? saved.config : config, false, { step: saved ? (saved.metadata?.step ?? -1) + 1 : -1 });
307
+ const channels = emptyChannels(this.channels, saved.checkpoint);
308
+ const nextTasks = Object.values(_prepareNextTasks(saved.checkpoint, this.nodes, channels, managed, saved.config, false, { step: (saved.metadata?.step ?? -1) + 1 }));
309
+ const subgraphs = await gatherIterator(this.getSubgraphs());
310
+ const parentNamespace = saved.config.configurable?.checkpoint_ns ?? "";
311
+ const taskStates = {};
312
+ for (const task of nextTasks) {
313
+ const matchingSubgraph = subgraphs.find(([name]) => name === task.name);
314
+ if (!matchingSubgraph) {
315
+ continue;
316
+ }
317
+ // assemble checkpoint_ns for this task
318
+ let taskNs = `${task.name}${CHECKPOINT_NAMESPACE_END}${task.id}`;
319
+ if (parentNamespace) {
320
+ taskNs = `${parentNamespace}${CHECKPOINT_NAMESPACE_SEPARATOR}${taskNs}`;
321
+ }
322
+ if (subgraphCheckpointer === undefined) {
323
+ // set config as signal that subgraph checkpoints exist
324
+ const config = {
325
+ configurable: {
326
+ thread_id: saved.config.configurable?.thread_id,
327
+ checkpoint_ns: taskNs,
328
+ },
329
+ };
330
+ taskStates[task.id] = config;
331
+ }
332
+ else {
333
+ // get the state of the subgraph
334
+ const config = {
335
+ configurable: {
336
+ [CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,
337
+ thread_id: saved.config.configurable?.thread_id,
338
+ checkpoint_ns: taskNs,
339
+ },
340
+ };
341
+ taskStates[task.id] = await matchingSubgraph[1].getState(config, {
342
+ subgraphs: true,
343
+ });
344
+ }
345
+ }
346
+ // assemble the state snapshot
238
347
  return {
239
348
  values: readChannels(channels, this.streamChannelsAsIs),
240
349
  next: nextTasks.map((task) => task.name),
241
- tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? []),
242
- metadata: saved?.metadata,
243
- config: saved ? saved.config : config,
244
- createdAt: saved?.checkpoint.ts,
245
- parentConfig: saved?.parentConfig,
350
+ tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? [], taskStates),
351
+ metadata: saved.metadata,
352
+ config: patchCheckpointMap(saved.config, saved.metadata),
353
+ createdAt: saved.checkpoint.ts,
354
+ parentConfig: saved.parentConfig,
246
355
  };
247
356
  }
357
+ /**
358
+ * Get the current state of the graph.
359
+ */
360
+ async getState(config, options) {
361
+ const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
362
+ if (!checkpointer) {
363
+ throw new GraphValueError("No checkpointer set");
364
+ }
365
+ const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
366
+ if (checkpointNamespace !== "" &&
367
+ config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
368
+ // remove task_ids from checkpoint_ns
369
+ const recastCheckpointNamespace = checkpointNamespace
370
+ .split(CHECKPOINT_NAMESPACE_SEPARATOR)
371
+ .map((part) => part.split(CHECKPOINT_NAMESPACE_END)[0])
372
+ .join(CHECKPOINT_NAMESPACE_SEPARATOR);
373
+ for (const [name, subgraph] of this.getSubgraphs(recastCheckpointNamespace, true)) {
374
+ if (name === recastCheckpointNamespace) {
375
+ return await subgraph.getState(patchConfigurable(config, {
376
+ [CONFIG_KEY_CHECKPOINTER]: checkpointer,
377
+ }), { subgraphs: options?.subgraphs });
378
+ }
379
+ }
380
+ throw new Error(`Subgraph with namespace "${recastCheckpointNamespace}" not found.`);
381
+ }
382
+ const mergedConfig = mergeConfigs(this.config, config);
383
+ const saved = await checkpointer.getTuple(config);
384
+ const snapshot = await this._prepareStateSnapshot({
385
+ config: mergedConfig,
386
+ saved,
387
+ subgraphCheckpointer: options?.subgraphs ? checkpointer : undefined,
388
+ });
389
+ return snapshot;
390
+ }
248
391
  /**
249
392
  * Get the history of the state of the graph.
250
393
  */
251
394
  async *getStateHistory(config, options) {
252
- if (!this.checkpointer) {
253
- throw new GraphValueError("No checkpointer set");
395
+ const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
396
+ if (!checkpointer) {
397
+ throw new Error("No checkpointer set");
398
+ }
399
+ const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
400
+ if (checkpointNamespace !== "" &&
401
+ config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
402
+ const recastCheckpointNamespace = checkpointNamespace
403
+ .split(CHECKPOINT_NAMESPACE_SEPARATOR)
404
+ .map((part) => part.split(CHECKPOINT_NAMESPACE_END)[0])
405
+ .join(CHECKPOINT_NAMESPACE_SEPARATOR);
406
+ // find the subgraph with the matching name
407
+ for (const [name, pregel] of this.getSubgraphs(recastCheckpointNamespace, true)) {
408
+ if (name === recastCheckpointNamespace) {
409
+ yield* pregel.getStateHistory(patchConfigurable(config, {
410
+ [CONFIG_KEY_CHECKPOINTER]: checkpointer,
411
+ }), options);
412
+ return;
413
+ }
414
+ }
415
+ throw new Error(`Subgraph with namespace "${recastCheckpointNamespace}" not found.`);
254
416
  }
255
- // Pass `skipManaged: true` as managed values should not be returned in get state calls.
256
- const { managed } = await this.prepareSpecs(config, { skipManaged: true });
257
- for await (const saved of this.checkpointer.list(config, options)) {
258
- const channels = emptyChannels(this.channels, saved.checkpoint);
259
- const nextTasks = _prepareNextTasks(saved.checkpoint, this.nodes, channels, managed, saved.config, false, { step: -1 });
260
- yield {
261
- values: readChannels(channels, this.streamChannelsAsIs),
262
- next: nextTasks.map((task) => task.name),
263
- tasks: tasksWithWrites(nextTasks, saved.pendingWrites ?? []),
264
- metadata: saved.metadata,
265
- config: saved.config,
266
- createdAt: saved.checkpoint.ts,
267
- parentConfig: saved.parentConfig,
268
- };
417
+ const mergedConfig = mergeConfigs(this.config, config, {
418
+ configurable: { checkpoint_ns: checkpointNamespace },
419
+ });
420
+ for await (const checkpointTuple of checkpointer.list(mergedConfig, options)) {
421
+ yield this._prepareStateSnapshot({
422
+ config: checkpointTuple.config,
423
+ saved: checkpointTuple,
424
+ });
269
425
  }
270
426
  }
271
427
  /**
@@ -273,34 +429,59 @@ export class Pregel extends Runnable {
273
429
  * node `as_node`. If `as_node` is not provided, it will be set to the last node
274
430
  * that updated the state, if not ambiguous.
275
431
  */
276
- async updateState(config, values, asNode) {
277
- if (!this.checkpointer) {
432
+ async updateState(inputConfig, values, asNode) {
433
+ const checkpointer = inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
434
+ if (!checkpointer) {
278
435
  throw new GraphValueError("No checkpointer set");
279
436
  }
280
- // Get the latest checkpoint
281
- const saved = await this.checkpointer.getTuple(config);
282
- const checkpoint = saved
437
+ // delegate to subgraph
438
+ const checkpointNamespace = inputConfig.configurable?.checkpoint_ns ?? "";
439
+ if (checkpointNamespace !== "" &&
440
+ inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
441
+ // remove task_ids from checkpoint_ns
442
+ const recastCheckpointNamespace = checkpointNamespace
443
+ .split(CHECKPOINT_NAMESPACE_SEPARATOR)
444
+ .map((part) => {
445
+ return part.split(CHECKPOINT_NAMESPACE_END)[0];
446
+ })
447
+ .join(CHECKPOINT_NAMESPACE_SEPARATOR);
448
+ // find the subgraph with the matching name
449
+ // eslint-disable-next-line no-unreachable-loop
450
+ for (const [, pregel] of this.getSubgraphs(recastCheckpointNamespace, true)) {
451
+ return await pregel.updateState(patchConfigurable(inputConfig, {
452
+ [CONFIG_KEY_CHECKPOINTER]: checkpointer,
453
+ }), values, asNode);
454
+ }
455
+ throw new Error(`Subgraph "${recastCheckpointNamespace}" not found`);
456
+ }
457
+ // get last checkpoint
458
+ const config = this.config
459
+ ? mergeConfigs(this.config, inputConfig)
460
+ : inputConfig;
461
+ const saved = await checkpointer.getTuple(config);
462
+ const checkpoint = saved !== undefined
283
463
  ? copyCheckpoint(saved.checkpoint)
284
464
  : emptyCheckpoint();
285
- const checkpointPreviousVersions = saved?.checkpoint.channel_versions ?? {};
465
+ const checkpointPreviousVersions = {
466
+ ...saved?.checkpoint.channel_versions,
467
+ };
286
468
  const step = saved?.metadata?.step ?? -1;
287
469
  // merge configurable fields with previous checkpoint config
288
- const checkpointConfig = {
289
- ...config,
290
- configurable: {
291
- ...config.configurable,
292
- // TODO: add proper support for updating nested subgraph state
293
- checkpoint_ns: "",
294
- ...saved?.config.configurable,
295
- },
296
- };
470
+ let checkpointConfig = patchConfigurable(config, {
471
+ checkpoint_ns: config.configurable?.checkpoint_ns ?? "",
472
+ });
473
+ if (saved) {
474
+ checkpointConfig = patchConfigurable(config, saved.config.configurable);
475
+ }
297
476
  // Find last node that updated the state, if not provided
298
477
  if (values == null && asNode === undefined) {
299
- return await this.checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
478
+ const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
300
479
  source: "update",
301
480
  step,
302
481
  writes: {},
482
+ parents: saved?.metadata?.parents ?? {},
303
483
  }, {});
484
+ return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
304
485
  }
305
486
  const nonNullVersion = Object.values(checkpoint.versions_seen)
306
487
  .map((seenVersions) => {
@@ -315,7 +496,6 @@ export class Pregel extends Runnable {
315
496
  }
316
497
  }
317
498
  else if (asNode === undefined) {
318
- // TODO: Double check
319
499
  const lastSeenByNode = Object.entries(checkpoint.versions_seen)
320
500
  .map(([n, seen]) => {
321
501
  return Object.values(seen).map((v) => {
@@ -372,18 +552,21 @@ export class Pregel extends Runnable {
372
552
  task, select_, fresh_),
373
553
  },
374
554
  }));
555
+ // save task writes
375
556
  if (saved !== undefined) {
376
- await this.checkpointer.putWrites(checkpointConfig, task.writes, task.id);
557
+ await checkpointer.putWrites(checkpointConfig, task.writes, task.id);
377
558
  }
378
559
  // apply to checkpoint
379
560
  // TODO: Why does keyof StrRecord allow number and symbol?
380
- _applyWrites(checkpoint, channels, [task], this.checkpointer.getNextVersion.bind(this.checkpointer));
561
+ _applyWrites(checkpoint, channels, [task], checkpointer.getNextVersion.bind(this.checkpointer));
381
562
  const newVersions = getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
382
- return await this.checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step + 1), {
563
+ const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step + 1), {
383
564
  source: "update",
384
565
  step: step + 1,
385
566
  writes: { [asNode]: values },
567
+ parents: saved?.metadata?.parents ?? {},
386
568
  }, newVersions);
569
+ return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
387
570
  }
388
571
  _defaults(config) {
389
572
  const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
@@ -411,14 +594,13 @@ export class Pregel extends Runnable {
411
594
  else {
412
595
  defaultStreamMode = this.streamMode;
413
596
  }
414
- let defaultCheckpointer;
415
- if (config.configurable !== undefined &&
416
- config.configurable[CONFIG_KEY_READ] !== undefined) {
597
+ // if being called as a node in another graph, always use values mode
598
+ if (config.configurable?.[CONFIG_KEY_TASK_ID] !== undefined) {
417
599
  defaultStreamMode = ["values"];
418
600
  }
601
+ let defaultCheckpointer;
419
602
  if (config !== undefined &&
420
- config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined &&
421
- (defaultInterruptAfter.length > 0 || defaultInterruptBefore.length > 0)) {
603
+ config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined) {
422
604
  defaultCheckpointer = config.configurable[CONFIG_KEY_CHECKPOINTER];
423
605
  }
424
606
  else {
@@ -497,7 +679,8 @@ export class Pregel extends Runnable {
497
679
  };
498
680
  }
499
681
  async *_streamIterator(input, options) {
500
- const inputConfig = ensureConfig(options);
682
+ const streamSubgraphs = options?.subgraphs;
683
+ const inputConfig = ensureLangGraphConfig(this.config, options);
501
684
  if (inputConfig.recursionLimit === undefined ||
502
685
  inputConfig.recursionLimit < 1) {
503
686
  throw new Error(`Passed "recursionLimit" must be at least 1.`);
@@ -513,6 +696,30 @@ export class Pregel extends Runnable {
513
696
  const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
514
697
  const { channelSpecs, managed } = await this.prepareSpecs(config);
515
698
  let loop;
699
+ const stream = new Deque();
700
+ function* emitCurrentLoopOutputs() {
701
+ while (loop !== undefined && stream.length > 0) {
702
+ const nextItem = stream.shift();
703
+ if (nextItem === undefined) {
704
+ throw new Error("Data structure error.");
705
+ }
706
+ const [namespace, mode, payload] = nextItem;
707
+ if (streamMode.includes(mode)) {
708
+ if (streamSubgraphs && streamMode.length > 1) {
709
+ yield [namespace, mode, payload];
710
+ }
711
+ else if (streamMode.length > 1) {
712
+ yield [mode, payload];
713
+ }
714
+ else if (streamSubgraphs) {
715
+ yield [namespace, payload];
716
+ }
717
+ else {
718
+ yield payload;
719
+ }
720
+ }
721
+ }
722
+ }
516
723
  try {
517
724
  loop = await PregelLoop.initialize({
518
725
  input,
@@ -524,7 +731,14 @@ export class Pregel extends Runnable {
524
731
  outputKeys,
525
732
  streamKeys: this.streamChannelsAsIs,
526
733
  store: this.store,
734
+ stream: new StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
527
735
  });
736
+ if (options?.subgraphs) {
737
+ loop.config.configurable = {
738
+ ...loop.config.configurable,
739
+ [CONFIG_KEY_STREAM]: loop.stream,
740
+ };
741
+ }
528
742
  while (await loop.tick({
529
743
  inputKeys: this.inputChannels,
530
744
  interruptAfter,
@@ -534,27 +748,14 @@ export class Pregel extends Runnable {
534
748
  if (debug) {
535
749
  printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
536
750
  }
537
- while (loop.stream.length > 0) {
538
- const nextItem = loop.stream.shift();
539
- if (nextItem === undefined) {
540
- throw new Error("Data structure error.");
541
- }
542
- if (streamMode.includes(nextItem[0])) {
543
- if (streamMode.length === 1) {
544
- yield nextItem[1];
545
- }
546
- else {
547
- yield nextItem;
548
- }
549
- }
550
- }
751
+ yield* emitCurrentLoopOutputs();
551
752
  if (debug) {
552
- printStepTasks(loop.step, loop.tasks);
753
+ printStepTasks(loop.step, Object.values(loop.tasks));
553
754
  }
554
755
  // execute tasks, and wait for one to fail or all to finish.
555
756
  // each task is independent from all other concurrent tasks
556
757
  // yield updates/debug output as each task finishes
557
- const taskStream = executeTasksWithRetry(loop.tasks.filter((task) => task.writes.length === 0), {
758
+ const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
558
759
  stepTimeout: this.stepTimeout,
559
760
  signal: config.signal,
560
761
  retryPolicy: this.retryPolicy,
@@ -563,7 +764,12 @@ export class Pregel extends Runnable {
563
764
  for await (const { task, error } of taskStream) {
564
765
  if (error !== undefined) {
565
766
  if (isGraphInterrupt(error)) {
566
- loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
767
+ if (loop.isNested) {
768
+ throw error;
769
+ }
770
+ if (error.interrupts.length) {
771
+ loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
772
+ }
567
773
  }
568
774
  else {
569
775
  loop.putWrites(task.id, [
@@ -574,42 +780,18 @@ export class Pregel extends Runnable {
574
780
  else {
575
781
  loop.putWrites(task.id, task.writes);
576
782
  }
577
- while (loop.stream.length > 0) {
578
- const nextItem = loop.stream.shift();
579
- if (nextItem === undefined) {
580
- throw new Error("Data structure error.");
581
- }
582
- if (streamMode.includes(nextItem[0])) {
583
- if (streamMode.length === 1) {
584
- yield nextItem[1];
585
- }
586
- else {
587
- yield nextItem;
588
- }
589
- }
590
- }
783
+ yield* emitCurrentLoopOutputs();
591
784
  if (error !== undefined && !isGraphInterrupt(error)) {
592
785
  throw error;
593
786
  }
594
787
  }
595
788
  if (debug) {
596
- printStepWrites(loop.step, loop.tasks.map((task) => task.writes).flat(), this.streamChannelsList);
597
- }
598
- }
599
- while (loop.stream.length > 0) {
600
- const nextItem = loop.stream.shift();
601
- if (nextItem === undefined) {
602
- throw new Error("Data structure error.");
603
- }
604
- if (streamMode.includes(nextItem[0])) {
605
- if (streamMode.length === 1) {
606
- yield nextItem[1];
607
- }
608
- else {
609
- yield nextItem;
610
- }
789
+ printStepWrites(loop.step, Object.values(loop.tasks)
790
+ .map((task) => task.writes)
791
+ .flat(), this.streamChannelsList);
611
792
  }
612
793
  }
794
+ yield* emitCurrentLoopOutputs();
613
795
  if (loop.status === "out_of_steps") {
614
796
  throw new GraphRecursionError([
615
797
  `Recursion limit of ${config.recursionLimit} reached`,
@@ -618,7 +800,7 @@ export class Pregel extends Runnable {
618
800
  ].join(" "));
619
801
  }
620
802
  await Promise.all(loop?.checkpointerPromises ?? []);
621
- await runManager?.handleChainEnd(readChannels(loop.channels, outputKeys));
803
+ await runManager?.handleChainEnd(loop.output);
622
804
  }
623
805
  catch (e) {
624
806
  await runManager?.handleChainError(e);
@@ -653,7 +835,7 @@ export class Pregel extends Runnable {
653
835
  async invoke(input, options) {
654
836
  const streamMode = options?.streamMode ?? "values";
655
837
  const config = {
656
- ...ensureConfig(options),
838
+ ...options,
657
839
  outputKeys: options?.outputKeys ?? this.outputChannels,
658
840
  streamMode,
659
841
  };
@@ -79,12 +79,14 @@ function* mapOutputValues(outputChannels, pendingWrites, channels
79
79
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
80
  ) {
81
81
  if (Array.isArray(outputChannels)) {
82
- if (pendingWrites.find(([chan, _]) => outputChannels.includes(chan))) {
82
+ if (pendingWrites === true ||
83
+ pendingWrites.find(([chan, _]) => outputChannels.includes(chan))) {
83
84
  yield readChannels(channels, outputChannels);
84
85
  }
85
86
  }
86
87
  else {
87
- if (pendingWrites.some(([chan, _]) => chan === outputChannels)) {
88
+ if (pendingWrites === true ||
89
+ pendingWrites.some(([chan, _]) => chan === outputChannels)) {
88
90
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
91
  yield readChannel(channels, outputChannels);
90
92
  }
@@ -94,30 +96,32 @@ exports.mapOutputValues = mapOutputValues;
94
96
  /**
95
97
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
96
98
  */
97
- function* mapOutputUpdates(outputChannels, tasks
99
+ function* mapOutputUpdates(outputChannels, tasks, cached
98
100
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
101
  ) {
100
- const outputTasks = tasks.filter((task) => task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN));
102
+ const outputTasks = tasks.filter(([task]) => {
103
+ return task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN);
104
+ });
101
105
  if (!outputTasks.length) {
102
106
  return;
103
107
  }
104
108
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
109
  let updated;
106
110
  if (!Array.isArray(outputChannels)) {
107
- updated = outputTasks.flatMap((task) => task.writes
111
+ updated = outputTasks.flatMap(([task]) => task.writes
108
112
  .filter(([chan, _]) => chan === outputChannels)
109
113
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
114
  .map(([_, value]) => [task.name, value]));
111
115
  }
112
116
  else {
113
117
  updated = outputTasks
114
- .filter((task) => task.writes.some(([chan]) => outputChannels.includes(chan)))
115
- .map((task) => [
118
+ .filter(([task]) => task.writes.some(([chan]) => outputChannels.includes(chan)))
119
+ .map(([task]) => [
116
120
  task.name,
117
121
  Object.fromEntries(task.writes.filter(([chan]) => outputChannels.includes(chan))),
118
122
  ]);
119
123
  }
120
- const grouped = Object.fromEntries(outputTasks.map((t) => [t.name, []])
124
+ const grouped = Object.fromEntries(outputTasks.map(([t]) => [t.name, []])
121
125
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
126
  );
123
127
  for (const [node, value] of updated) {
@@ -133,6 +137,9 @@ function* mapOutputUpdates(outputChannels, tasks
133
137
  grouped[node] = value[0];
134
138
  }
135
139
  }
140
+ if (cached) {
141
+ grouped["__metadata__"] = { cached };
142
+ }
136
143
  yield grouped;
137
144
  }
138
145
  exports.mapOutputUpdates = mapOutputUpdates;
@@ -10,9 +10,9 @@ export declare function mapInput<C extends PropertyKey>(inputChannels: C | Array
10
10
  /**
11
11
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
12
12
  */
13
- export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C | Array<C>, pendingWrites: readonly PendingWrite<C>[], channels: Record<C, BaseChannel>): Generator<Record<string, any>, any>;
13
+ export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C | Array<C>, pendingWrites: readonly PendingWrite<C>[] | true, channels: Record<C, BaseChannel>): Generator<Record<string, any>, any>;
14
14
  /**
15
15
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
16
16
  */
17
- export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly PregelExecutableTask<N, C>[]): Generator<Record<N, Record<string, any> | Record<string, any>[]>>;
17
+ export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly [PregelExecutableTask<N, C>, PendingWrite<C>[]][], cached?: boolean): Generator<Record<N, Record<string, any> | any>>;
18
18
  export declare function single<T>(iter: IterableIterator<T>): T | null;