@langchain/langgraph 0.2.41 → 0.2.43-rc.0

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 (86) hide show
  1. package/README.md +237 -154
  2. package/dist/channels/any_value.cjs +10 -10
  3. package/dist/channels/any_value.d.ts +1 -1
  4. package/dist/channels/any_value.js +10 -10
  5. package/dist/channels/ephemeral_value.cjs +10 -9
  6. package/dist/channels/ephemeral_value.d.ts +1 -1
  7. package/dist/channels/ephemeral_value.js +10 -9
  8. package/dist/channels/last_value.cjs +8 -7
  9. package/dist/channels/last_value.d.ts +1 -1
  10. package/dist/channels/last_value.js +8 -7
  11. package/dist/constants.cjs +33 -6
  12. package/dist/constants.d.ts +17 -2
  13. package/dist/constants.js +32 -5
  14. package/dist/errors.d.ts +3 -3
  15. package/dist/func/index.cjs +272 -0
  16. package/dist/func/index.d.ts +310 -0
  17. package/dist/func/index.js +267 -0
  18. package/dist/func/types.cjs +15 -0
  19. package/dist/func/types.d.ts +59 -0
  20. package/dist/func/types.js +11 -0
  21. package/dist/graph/graph.cjs +31 -35
  22. package/dist/graph/graph.d.ts +1 -5
  23. package/dist/graph/graph.js +1 -5
  24. package/dist/graph/index.cjs +1 -3
  25. package/dist/graph/index.d.ts +1 -1
  26. package/dist/graph/index.js +1 -1
  27. package/dist/graph/message.d.ts +1 -1
  28. package/dist/graph/state.cjs +17 -17
  29. package/dist/graph/state.d.ts +2 -1
  30. package/dist/graph/state.js +2 -2
  31. package/dist/index.cjs +8 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.js +3 -0
  34. package/dist/interrupt.cjs +21 -34
  35. package/dist/interrupt.d.ts +1 -1
  36. package/dist/interrupt.js +22 -35
  37. package/dist/prebuilt/agent_executor.cjs +3 -3
  38. package/dist/prebuilt/agent_executor.d.ts +1 -1
  39. package/dist/prebuilt/agent_executor.js +1 -1
  40. package/dist/prebuilt/chat_agent_executor.cjs +3 -3
  41. package/dist/prebuilt/chat_agent_executor.d.ts +1 -1
  42. package/dist/prebuilt/chat_agent_executor.js +1 -1
  43. package/dist/prebuilt/react_agent_executor.cjs +33 -8
  44. package/dist/prebuilt/react_agent_executor.d.ts +4 -1
  45. package/dist/prebuilt/react_agent_executor.js +31 -6
  46. package/dist/prebuilt/tool_node.cjs +1 -2
  47. package/dist/prebuilt/tool_node.d.ts +1 -1
  48. package/dist/prebuilt/tool_node.js +1 -2
  49. package/dist/pregel/algo.cjs +121 -12
  50. package/dist/pregel/algo.d.ts +8 -6
  51. package/dist/pregel/algo.js +122 -13
  52. package/dist/pregel/call.cjs +77 -0
  53. package/dist/pregel/call.d.ts +15 -0
  54. package/dist/pregel/call.js +71 -0
  55. package/dist/pregel/index.cjs +59 -96
  56. package/dist/pregel/index.d.ts +1 -10
  57. package/dist/pregel/index.js +61 -98
  58. package/dist/pregel/io.cjs +6 -1
  59. package/dist/pregel/io.js +7 -2
  60. package/dist/pregel/loop.cjs +109 -75
  61. package/dist/pregel/loop.d.ts +17 -23
  62. package/dist/pregel/loop.js +110 -75
  63. package/dist/pregel/messages.d.ts +1 -1
  64. package/dist/pregel/retry.cjs +22 -50
  65. package/dist/pregel/retry.d.ts +6 -6
  66. package/dist/pregel/retry.js +22 -50
  67. package/dist/pregel/runner.cjs +275 -0
  68. package/dist/pregel/runner.d.ts +64 -0
  69. package/dist/pregel/runner.js +271 -0
  70. package/dist/pregel/stream.cjs +71 -0
  71. package/dist/pregel/stream.d.ts +17 -0
  72. package/dist/pregel/stream.js +67 -0
  73. package/dist/pregel/types.cjs +54 -0
  74. package/dist/pregel/types.d.ts +78 -6
  75. package/dist/pregel/types.js +51 -1
  76. package/dist/pregel/utils/config.cjs +26 -1
  77. package/dist/pregel/utils/config.d.ts +14 -0
  78. package/dist/pregel/utils/config.js +22 -0
  79. package/dist/pregel/write.d.ts +1 -1
  80. package/dist/utils.cjs +15 -1
  81. package/dist/utils.d.ts +3 -1
  82. package/dist/utils.js +12 -0
  83. package/dist/web.cjs +7 -5
  84. package/dist/web.d.ts +4 -4
  85. package/dist/web.js +3 -3
  86. package/package.json +8 -8
@@ -1,70 +1,16 @@
1
- import { IterableReadableStream } from "@langchain/core/utils/stream";
2
1
  import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
3
2
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
4
- import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, } from "../constants.js";
5
- import { _applyWrites, _prepareNextTasks, increment, shouldInterrupt, } from "./algo.js";
3
+ import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, PUSH, CONFIG_KEY_SCRATCHPAD, } from "../constants.js";
4
+ import { _applyWrites, _prepareNextTasks, _prepareSingleTask, increment, shouldInterrupt, } from "./algo.js";
6
5
  import { gatherIterator, gatherIteratorSync, prefixGenerator, } from "../utils.js";
7
6
  import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels, } from "./io.js";
8
7
  import { getSubgraphsSeenSet, EmptyInputError, GraphInterrupt, isGraphInterrupt, MultipleSubgraphsError, } from "../errors.js";
9
8
  import { getNewChannelVersions, patchConfigurable } from "./utils/index.js";
10
- import { mapDebugTasks, mapDebugCheckpoint, mapDebugTaskResults, } from "./debug.js";
9
+ import { mapDebugTasks, mapDebugCheckpoint, mapDebugTaskResults, printStepTasks, } from "./debug.js";
10
+ import { IterableReadableWritableStream } from "./stream.js";
11
11
  const INPUT_DONE = Symbol.for("INPUT_DONE");
12
12
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
13
13
  const DEFAULT_LOOP_LIMIT = 25;
14
- export class IterableReadableWritableStream extends IterableReadableStream {
15
- constructor(params) {
16
- let streamControllerPromiseResolver;
17
- const streamControllerPromise = new Promise((resolve) => {
18
- streamControllerPromiseResolver = resolve;
19
- });
20
- super({
21
- start: (controller) => {
22
- streamControllerPromiseResolver(controller);
23
- },
24
- });
25
- Object.defineProperty(this, "modes", {
26
- enumerable: true,
27
- configurable: true,
28
- writable: true,
29
- value: void 0
30
- });
31
- Object.defineProperty(this, "controller", {
32
- enumerable: true,
33
- configurable: true,
34
- writable: true,
35
- value: void 0
36
- });
37
- Object.defineProperty(this, "passthroughFn", {
38
- enumerable: true,
39
- configurable: true,
40
- writable: true,
41
- value: void 0
42
- });
43
- // .start() will always be called before the stream can be interacted
44
- // with anyway
45
- void streamControllerPromise.then((controller) => {
46
- this.controller = controller;
47
- });
48
- this.passthroughFn = params.passthroughFn;
49
- this.modes = params.modes;
50
- }
51
- push(chunk) {
52
- this.passthroughFn?.(chunk);
53
- this.controller.enqueue(chunk);
54
- }
55
- close() {
56
- try {
57
- this.controller.close();
58
- }
59
- catch (e) {
60
- // pass
61
- }
62
- }
63
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- error(e) {
65
- this.controller.error(e);
66
- }
67
- }
68
14
  function createDuplexStream(...streams) {
69
15
  return new IterableReadableWritableStream({
70
16
  passthroughFn: (value) => {
@@ -245,6 +191,36 @@ export class PregelLoop {
245
191
  writable: true,
246
192
  value: void 0
247
193
  });
194
+ Object.defineProperty(this, "manager", {
195
+ enumerable: true,
196
+ configurable: true,
197
+ writable: true,
198
+ value: void 0
199
+ });
200
+ Object.defineProperty(this, "interruptAfter", {
201
+ enumerable: true,
202
+ configurable: true,
203
+ writable: true,
204
+ value: void 0
205
+ });
206
+ Object.defineProperty(this, "interruptBefore", {
207
+ enumerable: true,
208
+ configurable: true,
209
+ writable: true,
210
+ value: void 0
211
+ });
212
+ Object.defineProperty(this, "toInterrupt", {
213
+ enumerable: true,
214
+ configurable: true,
215
+ writable: true,
216
+ value: []
217
+ });
218
+ Object.defineProperty(this, "debug", {
219
+ enumerable: true,
220
+ configurable: true,
221
+ writable: true,
222
+ value: false
223
+ });
248
224
  this.input = params.input;
249
225
  this.checkpointer = params.checkpointer;
250
226
  // TODO: if managed values no longer needs graph we can replace with
@@ -266,6 +242,7 @@ export class PregelLoop {
266
242
  this.config = params.config;
267
243
  this.checkpointConfig = params.checkpointConfig;
268
244
  this.isNested = params.isNested;
245
+ this.manager = params.manager;
269
246
  this.outputKeys = params.outputKeys;
270
247
  this.streamKeys = params.streamKeys;
271
248
  this.nodes = params.nodes;
@@ -274,6 +251,9 @@ export class PregelLoop {
274
251
  this.stream = params.stream;
275
252
  this.checkpointNamespace = params.checkpointNamespace;
276
253
  this.prevCheckpointConfig = params.prevCheckpointConfig;
254
+ this.interruptAfter = params.interruptAfter;
255
+ this.interruptBefore = params.interruptBefore;
256
+ this.debug = params.debug;
277
257
  }
278
258
  static async initialize(params) {
279
259
  let { config, stream } = params;
@@ -364,6 +344,7 @@ export class PregelLoop {
364
344
  channels,
365
345
  managed: params.managed,
366
346
  isNested,
347
+ manager: params.manager,
367
348
  skipDoneTasks,
368
349
  step,
369
350
  stop,
@@ -374,6 +355,9 @@ export class PregelLoop {
374
355
  nodes: params.nodes,
375
356
  stream,
376
357
  store,
358
+ interruptAfter: params.interruptAfter,
359
+ interruptBefore: params.interruptBefore,
360
+ debug: params.debug,
377
361
  });
378
362
  }
379
363
  _checkpointerPutAfterPrevious(input) {
@@ -454,7 +438,7 @@ export class PregelLoop {
454
438
  if (this.store && !this.store.isRunning) {
455
439
  this.store?.start();
456
440
  }
457
- const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
441
+ const { inputKeys = [] } = params;
458
442
  if (this.status !== "pending") {
459
443
  throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
460
444
  }
@@ -478,7 +462,7 @@ export class PregelLoop {
478
462
  writes: mapOutputUpdates(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
479
463
  });
480
464
  // after execution, check if we should interrupt
481
- if (shouldInterrupt(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
465
+ if (shouldInterrupt(this.checkpoint, this.interruptAfter, Object.values(this.tasks))) {
482
466
  this.status = "interrupt_after";
483
467
  throw new GraphInterrupt();
484
468
  }
@@ -494,8 +478,9 @@ export class PregelLoop {
494
478
  step: this.step,
495
479
  checkpointer: this.checkpointer,
496
480
  isResuming: this.input === INPUT_RESUMING,
497
- manager,
481
+ manager: this.manager,
498
482
  store: this.store,
483
+ stream: this.stream,
499
484
  });
500
485
  this.tasks = nextTasks;
501
486
  // Produce debug output
@@ -526,15 +511,10 @@ export class PregelLoop {
526
511
  }
527
512
  // if all tasks have finished, re-tick
528
513
  if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
529
- return this.tick({
530
- inputKeys,
531
- interruptAfter,
532
- interruptBefore,
533
- manager,
534
- });
514
+ return this.tick({ inputKeys });
535
515
  }
536
516
  // Before execution, check if we should interrupt
537
- if (shouldInterrupt(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
517
+ if (shouldInterrupt(this.checkpoint, this.interruptBefore, Object.values(this.tasks))) {
538
518
  this.status = "interrupt_before";
539
519
  throw new GraphInterrupt();
540
520
  }
@@ -571,20 +551,59 @@ export class PregelLoop {
571
551
  }
572
552
  return suppress;
573
553
  }
554
+ acceptPush(task, writeIdx, call) {
555
+ if (this.interruptAfter?.length > 0 &&
556
+ shouldInterrupt(this.checkpoint, this.interruptAfter, [task])) {
557
+ this.toInterrupt.push(task);
558
+ return;
559
+ }
560
+ const pushed = _prepareSingleTask([PUSH, task.path ?? [], writeIdx, task.id, call], this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, {
561
+ step: this.step,
562
+ checkpointer: this.checkpointer,
563
+ manager: this.manager,
564
+ store: this.store,
565
+ stream: this.stream,
566
+ });
567
+ if (pushed) {
568
+ if (this.interruptBefore?.length > 0 &&
569
+ shouldInterrupt(this.checkpoint, this.interruptBefore, [pushed])) {
570
+ this.toInterrupt.push(pushed);
571
+ return;
572
+ }
573
+ this._emit(gatherIteratorSync(prefixGenerator(mapDebugTasks(this.step, [pushed]), "debug")));
574
+ if (this.debug) {
575
+ printStepTasks(this.step, [pushed]);
576
+ }
577
+ this.tasks[pushed.id] = pushed;
578
+ if (this.skipDoneTasks) {
579
+ this._matchWrites({ [pushed.id]: pushed });
580
+ }
581
+ return pushed;
582
+ }
583
+ }
574
584
  _suppressInterrupt(e) {
575
585
  return isGraphInterrupt(e) && !this.isNested;
576
586
  }
577
- /**
578
- * Resuming from previous checkpoint requires
579
- * - finding a previous checkpoint
580
- * - receiving null input (outer graph) or RESUMING flag (subgraph)
581
- */
582
587
  async _first(inputKeys) {
588
+ /*
589
+ * Resuming from previous checkpoint requires
590
+ * - finding a previous checkpoint
591
+ * - receiving null input (outer graph) or RESUMING flag (subgraph)
592
+ */
593
+ const { configurable } = this.config;
583
594
  const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
584
595
  (this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined ||
585
596
  this.input === null ||
586
597
  isCommand(this.input));
598
+ // take resume value from parent
599
+ const scratchpad = configurable?.[CONFIG_KEY_SCRATCHPAD];
600
+ if (scratchpad && scratchpad.nullResume !== undefined) {
601
+ this.putWrites(NULL_TASK_ID, [[RESUME, scratchpad.nullResume]]);
602
+ }
587
603
  if (isCommand(this.input)) {
604
+ if (this.input.resume != null && this.checkpointer == null) {
605
+ throw new Error("Cannot use Command(resume=...) without checkpointer");
606
+ }
588
607
  const writes = {};
589
608
  // group writes by task id
590
609
  for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
@@ -649,10 +668,10 @@ export class PregelLoop {
649
668
  });
650
669
  }
651
670
  // done with input
652
- this.input = isResuming ? INPUT_RESUMING : INPUT_DONE;
671
+ this.input = this.input === INPUT_RESUMING ? INPUT_RESUMING : INPUT_DONE;
653
672
  if (!this.isNested) {
654
673
  this.config = patchConfigurable(this.config, {
655
- [CONFIG_KEY_RESUMING]: isResuming,
674
+ [CONFIG_KEY_RESUMING]: this.input === INPUT_RESUMING,
656
675
  });
657
676
  }
658
677
  }
@@ -713,4 +732,20 @@ export class PregelLoop {
713
732
  }
714
733
  this.step += 1;
715
734
  }
735
+ _matchWrites(tasks) {
736
+ for (const [tid, k, v] of this.checkpointPendingWrites) {
737
+ if (k === ERROR || k === INTERRUPT || k === RESUME) {
738
+ continue;
739
+ }
740
+ const task = Object.values(tasks).find((t) => t.id === tid);
741
+ if (task) {
742
+ task.writes.push([k, v]);
743
+ }
744
+ }
745
+ for (const task of Object.values(tasks)) {
746
+ if (task.writes.length > 0) {
747
+ this._outputWrites(task.id, task.writes, true);
748
+ }
749
+ }
750
+ }
716
751
  }
@@ -3,7 +3,7 @@ import { BaseMessage } from "@langchain/core/messages";
3
3
  import { Serialized } from "@langchain/core/load/serializable";
4
4
  import { LLMResult } from "@langchain/core/outputs";
5
5
  import { ChainValues } from "@langchain/core/utils/types";
6
- import { StreamChunk } from "./loop.js";
6
+ import { StreamChunk } from "./stream.js";
7
7
  type Meta = [string[], Record<string, any>];
8
8
  /**
9
9
  * A callback handler that implements stream_mode=messages.
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executeTasksWithRetry = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_MAX_INTERVAL = exports.DEFAULT_BACKOFF_FACTOR = exports.DEFAULT_INITIAL_INTERVAL = void 0;
3
+ exports._runWithRetry = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_MAX_INTERVAL = exports.DEFAULT_BACKOFF_FACTOR = exports.DEFAULT_INITIAL_INTERVAL = void 0;
4
4
  const constants_js_1 = require("../constants.cjs");
5
5
  const errors_js_1 = require("../errors.cjs");
6
+ const index_js_1 = require("./utils/index.cjs");
6
7
  exports.DEFAULT_INITIAL_INTERVAL = 500;
7
8
  exports.DEFAULT_BACKOFF_FACTOR = 2;
8
9
  exports.DEFAULT_MAX_INTERVAL = 128000;
@@ -41,47 +42,9 @@ const DEFAULT_RETRY_ON_HANDLER = (error) => {
41
42
  }
42
43
  return true;
43
44
  };
44
- async function* executeTasksWithRetry(
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- tasks, options) {
47
- const { stepTimeout, retryPolicy } = options ?? {};
48
- let signal = options?.signal;
49
- // Start tasks
50
- const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
51
- return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
52
- }));
53
- if (stepTimeout && signal) {
54
- if ("any" in AbortSignal) {
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- signal = AbortSignal.any([
57
- signal,
58
- AbortSignal.timeout(stepTimeout),
59
- ]);
60
- }
61
- }
62
- else if (stepTimeout) {
63
- signal = AbortSignal.timeout(stepTimeout);
64
- }
65
- // Abort if signal is aborted
66
- signal?.throwIfAborted();
67
- let listener;
68
- const signalPromise = new Promise((_resolve, reject) => {
69
- listener = () => reject(new Error("Abort"));
70
- signal?.addEventListener("abort", listener);
71
- }).finally(() => signal?.removeEventListener("abort", listener));
72
- while (Object.keys(executingTasksMap).length > 0) {
73
- const settledTask = await Promise.race([
74
- ...Object.values(executingTasksMap),
75
- signalPromise,
76
- ]);
77
- yield settledTask;
78
- delete executingTasksMap[settledTask.task.id];
79
- }
80
- }
81
- exports.executeTasksWithRetry = executeTasksWithRetry;
82
45
  async function _runWithRetry(
83
46
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
- pregelTask, retryPolicy) {
47
+ pregelTask, retryPolicy, configurable, signal) {
85
48
  const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
86
49
  let interval = resolvedRetryPolicy !== undefined
87
50
  ? resolvedRetryPolicy.initialInterval ?? exports.DEFAULT_INITIAL_INTERVAL
@@ -89,28 +52,34 @@ pregelTask, retryPolicy) {
89
52
  let attempts = 0;
90
53
  let error;
91
54
  let result;
55
+ let { config } = pregelTask;
56
+ if (configurable) {
57
+ config = (0, index_js_1.patchConfigurable)(config, configurable);
58
+ }
92
59
  // eslint-disable-next-line no-constant-condition
93
60
  while (true) {
94
- // Modify writes in place to clear any previous retries
95
- while (pregelTask.writes.length > 0) {
96
- pregelTask.writes.pop();
61
+ if (signal?.aborted) {
62
+ // no need to throw here - we'll throw from the runner, instead.
63
+ // there's just no point in retrying if the user has requested an abort.
64
+ break;
97
65
  }
66
+ // Clear any writes from previous attempts
67
+ pregelTask.writes.splice(0, pregelTask.writes.length);
98
68
  error = undefined;
99
69
  try {
100
- result = await pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
70
+ result = await pregelTask.proc.invoke(pregelTask.input, config);
101
71
  break;
102
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
72
  }
104
73
  catch (e) {
105
74
  error = e;
106
75
  error.pregelTaskId = pregelTask.id;
107
76
  if ((0, errors_js_1.isParentCommand)(error)) {
108
- const ns = pregelTask.config?.configurable?.checkpoint_ns;
77
+ const ns = config?.configurable?.checkpoint_ns;
109
78
  const cmd = error.command;
110
79
  if (cmd.graph === ns) {
111
80
  // this command is for the current graph, handle it
112
81
  for (const writer of pregelTask.writers) {
113
- await writer.invoke(cmd, pregelTask.config);
82
+ await writer.invoke(cmd, config);
114
83
  }
115
84
  break;
116
85
  }
@@ -153,11 +122,13 @@ pregelTask, retryPolicy) {
153
122
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
123
  error.constructor.unminifiable_name ??
155
124
  error.constructor.name;
156
- console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
125
+ console.log(`Retrying task "${String(pregelTask.name)}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
126
+ // signal subgraphs to resume (if available)
127
+ config = (0, index_js_1.patchConfigurable)(config, { [constants_js_1.CONFIG_KEY_RESUMING]: true });
157
128
  }
158
129
  finally {
159
130
  // Clear checkpoint_ns seen (for subgraph detection)
160
- const checkpointNs = pregelTask.config?.configurable?.checkpoint_ns;
131
+ const checkpointNs = config?.configurable?.checkpoint_ns;
161
132
  if (checkpointNs) {
162
133
  (0, errors_js_1.getSubgraphsSeenSet)().delete(checkpointNs);
163
134
  }
@@ -166,6 +137,7 @@ pregelTask, retryPolicy) {
166
137
  return {
167
138
  task: pregelTask,
168
139
  result,
169
- error,
140
+ error: error,
170
141
  };
171
142
  }
143
+ exports._runWithRetry = _runWithRetry;
@@ -1,5 +1,5 @@
1
1
  import { PregelExecutableTask } from "./types.js";
2
- import type { RetryPolicy } from "./utils/index.js";
2
+ import { type RetryPolicy } from "./utils/index.js";
3
3
  export declare const DEFAULT_INITIAL_INTERVAL = 500;
4
4
  export declare const DEFAULT_BACKOFF_FACTOR = 2;
5
5
  export declare const DEFAULT_MAX_INTERVAL = 128000;
@@ -8,8 +8,8 @@ export type SettledPregelTask = {
8
8
  task: PregelExecutableTask<any, any>;
9
9
  error: Error;
10
10
  };
11
- export declare function executeTasksWithRetry(tasks: PregelExecutableTask<any, any>[], options?: {
12
- stepTimeout?: number;
13
- signal?: AbortSignal;
14
- retryPolicy?: RetryPolicy;
15
- }): AsyncGenerator<SettledPregelTask>;
11
+ export declare function _runWithRetry<N extends PropertyKey, C extends PropertyKey>(pregelTask: PregelExecutableTask<N, C>, retryPolicy?: RetryPolicy, configurable?: Record<string, unknown>, signal?: AbortSignal): Promise<{
12
+ task: PregelExecutableTask<N, C>;
13
+ result: unknown;
14
+ error: Error | undefined;
15
+ }>;
@@ -1,5 +1,6 @@
1
- import { CHECKPOINT_NAMESPACE_SEPARATOR, Command } from "../constants.js";
1
+ import { CHECKPOINT_NAMESPACE_SEPARATOR, Command, CONFIG_KEY_RESUMING, } from "../constants.js";
2
2
  import { getSubgraphsSeenSet, isGraphBubbleUp, isParentCommand, } from "../errors.js";
3
+ import { patchConfigurable } from "./utils/index.js";
3
4
  export const DEFAULT_INITIAL_INTERVAL = 500;
4
5
  export const DEFAULT_BACKOFF_FACTOR = 2;
5
6
  export const DEFAULT_MAX_INTERVAL = 128000;
@@ -38,46 +39,9 @@ const DEFAULT_RETRY_ON_HANDLER = (error) => {
38
39
  }
39
40
  return true;
40
41
  };
41
- export async function* executeTasksWithRetry(
42
+ export async function _runWithRetry(
42
43
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- tasks, options) {
44
- const { stepTimeout, retryPolicy } = options ?? {};
45
- let signal = options?.signal;
46
- // Start tasks
47
- const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
48
- return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
49
- }));
50
- if (stepTimeout && signal) {
51
- if ("any" in AbortSignal) {
52
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
- signal = AbortSignal.any([
54
- signal,
55
- AbortSignal.timeout(stepTimeout),
56
- ]);
57
- }
58
- }
59
- else if (stepTimeout) {
60
- signal = AbortSignal.timeout(stepTimeout);
61
- }
62
- // Abort if signal is aborted
63
- signal?.throwIfAborted();
64
- let listener;
65
- const signalPromise = new Promise((_resolve, reject) => {
66
- listener = () => reject(new Error("Abort"));
67
- signal?.addEventListener("abort", listener);
68
- }).finally(() => signal?.removeEventListener("abort", listener));
69
- while (Object.keys(executingTasksMap).length > 0) {
70
- const settledTask = await Promise.race([
71
- ...Object.values(executingTasksMap),
72
- signalPromise,
73
- ]);
74
- yield settledTask;
75
- delete executingTasksMap[settledTask.task.id];
76
- }
77
- }
78
- async function _runWithRetry(
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- pregelTask, retryPolicy) {
44
+ pregelTask, retryPolicy, configurable, signal) {
81
45
  const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
82
46
  let interval = resolvedRetryPolicy !== undefined
83
47
  ? resolvedRetryPolicy.initialInterval ?? DEFAULT_INITIAL_INTERVAL
@@ -85,28 +49,34 @@ pregelTask, retryPolicy) {
85
49
  let attempts = 0;
86
50
  let error;
87
51
  let result;
52
+ let { config } = pregelTask;
53
+ if (configurable) {
54
+ config = patchConfigurable(config, configurable);
55
+ }
88
56
  // eslint-disable-next-line no-constant-condition
89
57
  while (true) {
90
- // Modify writes in place to clear any previous retries
91
- while (pregelTask.writes.length > 0) {
92
- pregelTask.writes.pop();
58
+ if (signal?.aborted) {
59
+ // no need to throw here - we'll throw from the runner, instead.
60
+ // there's just no point in retrying if the user has requested an abort.
61
+ break;
93
62
  }
63
+ // Clear any writes from previous attempts
64
+ pregelTask.writes.splice(0, pregelTask.writes.length);
94
65
  error = undefined;
95
66
  try {
96
- result = await pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
67
+ result = await pregelTask.proc.invoke(pregelTask.input, config);
97
68
  break;
98
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
69
  }
100
70
  catch (e) {
101
71
  error = e;
102
72
  error.pregelTaskId = pregelTask.id;
103
73
  if (isParentCommand(error)) {
104
- const ns = pregelTask.config?.configurable?.checkpoint_ns;
74
+ const ns = config?.configurable?.checkpoint_ns;
105
75
  const cmd = error.command;
106
76
  if (cmd.graph === ns) {
107
77
  // this command is for the current graph, handle it
108
78
  for (const writer of pregelTask.writers) {
109
- await writer.invoke(cmd, pregelTask.config);
79
+ await writer.invoke(cmd, config);
110
80
  }
111
81
  break;
112
82
  }
@@ -149,11 +119,13 @@ pregelTask, retryPolicy) {
149
119
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
120
  error.constructor.unminifiable_name ??
151
121
  error.constructor.name;
152
- console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
122
+ console.log(`Retrying task "${String(pregelTask.name)}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
123
+ // signal subgraphs to resume (if available)
124
+ config = patchConfigurable(config, { [CONFIG_KEY_RESUMING]: true });
153
125
  }
154
126
  finally {
155
127
  // Clear checkpoint_ns seen (for subgraph detection)
156
- const checkpointNs = pregelTask.config?.configurable?.checkpoint_ns;
128
+ const checkpointNs = config?.configurable?.checkpoint_ns;
157
129
  if (checkpointNs) {
158
130
  getSubgraphsSeenSet().delete(checkpointNs);
159
131
  }
@@ -162,6 +134,6 @@ pregelTask, retryPolicy) {
162
134
  return {
163
135
  task: pregelTask,
164
136
  result,
165
- error,
137
+ error: error,
166
138
  };
167
139
  }