@langchain/langgraph 0.2.3 → 0.2.4

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 (54) hide show
  1. package/dist/channels/base.cjs +16 -4
  2. package/dist/channels/base.d.ts +3 -0
  3. package/dist/channels/base.js +14 -3
  4. package/dist/constants.cjs +4 -1
  5. package/dist/constants.d.ts +3 -0
  6. package/dist/constants.js +3 -0
  7. package/dist/graph/annotation.cjs +5 -1
  8. package/dist/graph/annotation.d.ts +5 -4
  9. package/dist/graph/annotation.js +5 -1
  10. package/dist/graph/state.cjs +7 -6
  11. package/dist/graph/state.d.ts +5 -2
  12. package/dist/graph/state.js +6 -5
  13. package/dist/managed/base.cjs +163 -0
  14. package/dist/managed/base.d.ts +30 -0
  15. package/dist/managed/base.js +155 -0
  16. package/dist/managed/index.cjs +19 -0
  17. package/dist/managed/index.d.ts +3 -0
  18. package/dist/managed/index.js +3 -0
  19. package/dist/managed/is_last_step.cjs +11 -0
  20. package/dist/managed/is_last_step.d.ts +4 -0
  21. package/dist/managed/is_last_step.js +7 -0
  22. package/dist/managed/shared_value.cjs +109 -0
  23. package/dist/managed/shared_value.d.ts +23 -0
  24. package/dist/managed/shared_value.js +105 -0
  25. package/dist/pregel/algo.cjs +111 -50
  26. package/dist/pregel/algo.d.ts +7 -6
  27. package/dist/pregel/algo.js +112 -51
  28. package/dist/pregel/index.cjs +65 -6
  29. package/dist/pregel/index.d.ts +9 -2
  30. package/dist/pregel/index.js +67 -8
  31. package/dist/pregel/loop.cjs +40 -3
  32. package/dist/pregel/loop.d.ts +10 -0
  33. package/dist/pregel/loop.js +40 -3
  34. package/dist/pregel/types.d.ts +8 -2
  35. package/dist/pregel/validate.d.ts +3 -2
  36. package/dist/store/base.cjs +12 -0
  37. package/dist/store/base.d.ts +7 -0
  38. package/dist/store/base.js +8 -0
  39. package/dist/store/batch.cjs +126 -0
  40. package/dist/store/batch.d.ts +55 -0
  41. package/dist/store/batch.js +122 -0
  42. package/dist/store/index.cjs +19 -0
  43. package/dist/store/index.d.ts +3 -0
  44. package/dist/store/index.js +3 -0
  45. package/dist/store/memory.cjs +43 -0
  46. package/dist/store/memory.d.ts +6 -0
  47. package/dist/store/memory.js +39 -0
  48. package/dist/utils.cjs +26 -1
  49. package/dist/utils.d.ts +1 -0
  50. package/dist/utils.js +24 -0
  51. package/dist/web.cjs +2 -0
  52. package/dist/web.d.ts +2 -0
  53. package/dist/web.js +2 -0
  54. package/package.json +1 -1
@@ -33,23 +33,49 @@ function shouldInterrupt(checkpoint, interruptNodes, tasks) {
33
33
  return anyChannelUpdated && anyTriggeredNodeInInterruptNodes;
34
34
  }
35
35
  exports.shouldInterrupt = shouldInterrupt;
36
- function _localRead(checkpoint, channels, task, select, fresh = false) {
37
- if (fresh) {
38
- const newCheckpoint = (0, base_js_1.createCheckpoint)(checkpoint, channels, -1);
39
- // create a new copy of channels
40
- const newChannels = (0, base_js_1.emptyChannels)(channels, newCheckpoint);
41
- // Note: _applyWrites contains side effects
36
+ function _localRead(step, checkpoint, channels, managed, task, select, fresh = false) {
37
+ let managedKeys = [];
38
+ let updated = new Set();
39
+ if (!Array.isArray(select)) {
40
+ for (const [c] of task.writes) {
41
+ if (c === select) {
42
+ updated = new Set([c]);
43
+ break;
44
+ }
45
+ }
46
+ updated = updated || new Set();
47
+ }
48
+ else {
49
+ managedKeys = select.filter((k) => managed.get(k));
50
+ select = select.filter((k) => !managed.get(k));
51
+ updated = new Set(select.filter((c) => task.writes.some(([key, _]) => key === c)));
52
+ }
53
+ let values;
54
+ if (fresh && updated.size > 0) {
55
+ const localChannels = Object.fromEntries(Object.entries(channels).filter(([k, _]) => updated.has(k)));
56
+ const newCheckpoint = (0, base_js_1.createCheckpoint)(checkpoint, localChannels, -1);
57
+ const newChannels = (0, base_js_1.emptyChannels)(localChannels, newCheckpoint);
42
58
  _applyWrites((0, langgraph_checkpoint_1.copyCheckpoint)(newCheckpoint), newChannels, [task]);
43
- return (0, io_js_1.readChannels)(newChannels, select);
59
+ values = (0, io_js_1.readChannels)({ ...channels, ...newChannels }, select);
44
60
  }
45
61
  else {
46
- return (0, io_js_1.readChannels)(channels, select);
62
+ values = (0, io_js_1.readChannels)(channels, select);
47
63
  }
64
+ if (managedKeys.length > 0) {
65
+ for (const k of managedKeys) {
66
+ const managedValue = managed.get(k);
67
+ if (managedValue) {
68
+ const resultOfManagedCall = managedValue.call(step);
69
+ values[k] = resultOfManagedCall;
70
+ }
71
+ }
72
+ }
73
+ return values;
48
74
  }
49
75
  exports._localRead = _localRead;
50
- function _localWrite(
76
+ function _localWrite(step,
51
77
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- commit, processes, channels,
78
+ commit, processes, channels, managed,
53
79
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
80
  writes) {
55
81
  for (const [chan, value] of writes) {
@@ -60,8 +86,10 @@ writes) {
60
86
  if (!(value.node in processes)) {
61
87
  throw new errors_js_1.InvalidUpdateError(`Invalid node name ${value.node} in packet`);
62
88
  }
89
+ // replace any runtime values with placeholders
90
+ managed.replaceRuntimeValues(step, value.args);
63
91
  }
64
- else if (!(chan in channels)) {
92
+ else if (!(chan in channels) && !managed.get(chan)) {
65
93
  console.warn(`Skipping write for channel '${chan}' which has no readers`);
66
94
  }
67
95
  }
@@ -71,6 +99,8 @@ exports._localWrite = _localWrite;
71
99
  function _applyWrites(checkpoint, channels, tasks,
72
100
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
101
  getNextVersion) {
102
+ // Filter out non instances of BaseChannel
103
+ const onlyChannels = Object.fromEntries(Object.entries(channels).filter(([_, value]) => (0, base_js_1.isBaseChannel)(value)));
74
104
  // Update seen versions
75
105
  for (const task of tasks) {
76
106
  if (checkpoint.versions_seen[task.name] === undefined) {
@@ -93,9 +123,9 @@ getNextVersion) {
93
123
  .flatMap((task) => task.triggers)
94
124
  .filter((chan) => !constants_js_1.RESERVED.includes(chan)));
95
125
  for (const chan of channelsToConsume) {
96
- if (channels[chan].consume()) {
126
+ if (chan in onlyChannels && onlyChannels[chan].consume()) {
97
127
  if (getNextVersion !== undefined) {
98
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
128
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
99
129
  }
100
130
  }
101
131
  }
@@ -105,6 +135,7 @@ getNextVersion) {
105
135
  }
106
136
  // Group writes by channel
107
137
  const pendingWriteValuesByChannel = {};
138
+ const pendingWritesByManaged = {};
108
139
  for (const task of tasks) {
109
140
  for (const [chan, val] of task.writes) {
110
141
  if (chan === constants_js_1.TASKS) {
@@ -113,7 +144,7 @@ getNextVersion) {
113
144
  args: val.args,
114
145
  });
115
146
  }
116
- else {
147
+ else if (chan in onlyChannels) {
117
148
  if (chan in pendingWriteValuesByChannel) {
118
149
  pendingWriteValuesByChannel[chan].push(val);
119
150
  }
@@ -121,6 +152,14 @@ getNextVersion) {
121
152
  pendingWriteValuesByChannel[chan] = [val];
122
153
  }
123
154
  }
155
+ else {
156
+ if (chan in pendingWritesByManaged) {
157
+ pendingWritesByManaged[chan].push(val);
158
+ }
159
+ else {
160
+ pendingWritesByManaged[chan] = [val];
161
+ }
162
+ }
124
163
  }
125
164
  }
126
165
  // find the highest version of all channels
@@ -131,10 +170,10 @@ getNextVersion) {
131
170
  const updatedChannels = new Set();
132
171
  // Apply writes to channels
133
172
  for (const [chan, vals] of Object.entries(pendingWriteValuesByChannel)) {
134
- if (chan in channels) {
173
+ if (chan in onlyChannels) {
135
174
  let updated;
136
175
  try {
137
- updated = channels[chan].update(vals);
176
+ updated = onlyChannels[chan].update(vals);
138
177
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
178
  }
140
179
  catch (e) {
@@ -146,23 +185,25 @@ getNextVersion) {
146
185
  }
147
186
  }
148
187
  if (updated && getNextVersion !== undefined) {
149
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
188
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
150
189
  }
151
190
  updatedChannels.add(chan);
152
191
  }
153
192
  }
154
193
  // Channels that weren't updated in this step are notified of a new step
155
- for (const chan of Object.keys(channels)) {
194
+ for (const chan of Object.keys(onlyChannels)) {
156
195
  if (!updatedChannels.has(chan)) {
157
- const updated = channels[chan].update([]);
196
+ const updated = onlyChannels[chan].update([]);
158
197
  if (updated && getNextVersion !== undefined) {
159
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
198
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
160
199
  }
161
200
  }
162
201
  }
202
+ // Return managed values writes to be applied externally
203
+ return pendingWritesByManaged;
163
204
  }
164
205
  exports._applyWrites = _applyWrites;
165
- function _prepareNextTasks(checkpoint, processes, channels, config, forExecution, extra) {
206
+ function _prepareNextTasks(checkpoint, processes, channels, managed, config, forExecution, extra) {
166
207
  const parentNamespace = config.configurable?.checkpoint_ns ?? "";
167
208
  const tasks = [];
168
209
  const taskDescriptions = [];
@@ -192,6 +233,7 @@ function _prepareNextTasks(checkpoint, processes, channels, config, forExecution
192
233
  const node = proc.getNode();
193
234
  if (node !== undefined) {
194
235
  const writes = [];
236
+ managed.replaceRuntimePlaceholders(step, packet.args);
195
237
  tasks.push({
196
238
  name: packet.node,
197
239
  input: packet.args,
@@ -204,12 +246,13 @@ function _prepareNextTasks(checkpoint, processes, channels, config, forExecution
204
246
  runName: packet.node,
205
247
  callbacks: manager?.getChild(`graph:step:${step}`),
206
248
  configurable: {
207
- [constants_js_1.CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
208
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, {
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ [constants_js_1.CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, channels, managed, writes_),
251
+ [constants_js_1.CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
209
252
  name: packet.node,
210
253
  writes: writes,
211
254
  triggers,
212
- }),
255
+ }, select_, fresh_),
213
256
  },
214
257
  }),
215
258
  id: taskId,
@@ -243,7 +286,7 @@ function _prepareNextTasks(checkpoint, processes, channels, config, forExecution
243
286
  .sort();
244
287
  // If any of the channels read by this process were updated
245
288
  if (triggers.length > 0) {
246
- const val = _procInput(proc, channels, forExecution);
289
+ const val = _procInput(step, proc, managed, channels, forExecution);
247
290
  if (val === undefined) {
248
291
  continue;
249
292
  }
@@ -273,12 +316,13 @@ function _prepareNextTasks(checkpoint, processes, channels, config, forExecution
273
316
  runName: name,
274
317
  callbacks: manager?.getChild(`graph:step:${step}`),
275
318
  configurable: {
276
- [constants_js_1.CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
277
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, {
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ [constants_js_1.CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, channels, managed, writes_),
321
+ [constants_js_1.CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
278
322
  name,
279
323
  writes: writes,
280
324
  triggers,
281
- }),
325
+ }, select_, fresh_),
282
326
  [constants_js_1.CONFIG_KEY_CHECKPOINTER]: checkpointer,
283
327
  [constants_js_1.CONFIG_KEY_RESUMING]: isResuming,
284
328
  checkpoint_id: checkpoint.id,
@@ -298,12 +342,46 @@ function _prepareNextTasks(checkpoint, processes, channels, config, forExecution
298
342
  return forExecution ? tasks : taskDescriptions;
299
343
  }
300
344
  exports._prepareNextTasks = _prepareNextTasks;
301
- function _procInput(proc, channels, forExecution) {
345
+ function _procInput(step, proc, managed, channels, forExecution) {
302
346
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
347
  let val;
304
- // If all trigger channels subscribed by this process are not empty
305
- // then invoke the process with the values of all non-empty channels
306
- if (Array.isArray(proc.channels)) {
348
+ if (typeof proc.channels === "object" && !Array.isArray(proc.channels)) {
349
+ val = {};
350
+ for (const [k, chan] of Object.entries(proc.channels)) {
351
+ if (proc.triggers.includes(chan)) {
352
+ try {
353
+ val[k] = (0, io_js_1.readChannel)(channels, chan, false);
354
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355
+ }
356
+ catch (e) {
357
+ if (e.name === errors_js_1.EmptyChannelError.unminifiable_name) {
358
+ return undefined;
359
+ }
360
+ else {
361
+ throw e;
362
+ }
363
+ }
364
+ }
365
+ else if (chan in channels) {
366
+ try {
367
+ val[k] = (0, io_js_1.readChannel)(channels, chan, true);
368
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
369
+ }
370
+ catch (e) {
371
+ if (e.name === errors_js_1.EmptyChannelError.unminifiable_name) {
372
+ continue;
373
+ }
374
+ else {
375
+ throw e;
376
+ }
377
+ }
378
+ }
379
+ else {
380
+ val[k] = managed.get(k)?.call(step);
381
+ }
382
+ }
383
+ }
384
+ else if (Array.isArray(proc.channels)) {
307
385
  let successfulRead = false;
308
386
  for (const chan of proc.channels) {
309
387
  try {
@@ -322,24 +400,7 @@ function _procInput(proc, channels, forExecution) {
322
400
  }
323
401
  }
324
402
  if (!successfulRead) {
325
- return;
326
- }
327
- }
328
- else if (typeof proc.channels === "object") {
329
- val = {};
330
- for (const [k, chan] of Object.entries(proc.channels)) {
331
- try {
332
- val[k] = (0, io_js_1.readChannel)(channels, chan, !proc.triggers.includes(chan));
333
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
334
- }
335
- catch (e) {
336
- if (e.name === errors_js_1.EmptyChannelError.unminifiable_name) {
337
- continue;
338
- }
339
- else {
340
- throw e;
341
- }
342
- }
403
+ return undefined;
343
404
  }
344
405
  }
345
406
  else {
@@ -1,9 +1,10 @@
1
1
  import { RunnableConfig } from "@langchain/core/runnables";
2
2
  import { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
3
- import { All, BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint, type PendingWrite } from "@langchain/langgraph-checkpoint";
3
+ import { All, BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint, type PendingWrite, type PendingWriteValue } from "@langchain/langgraph-checkpoint";
4
4
  import { BaseChannel } from "../channels/base.js";
5
5
  import { PregelNode } from "./read.js";
6
6
  import { PregelExecutableTask, PregelTaskDescription } from "./types.js";
7
+ import { ManagedValueMapping } from "../managed/base.js";
7
8
  /**
8
9
  * Construct a type with a set of properties K of type T
9
10
  */
@@ -17,14 +18,14 @@ export type WritesProtocol<C = string> = {
17
18
  };
18
19
  export declare const increment: (current?: number) => number;
19
20
  export declare function shouldInterrupt<N extends PropertyKey, C extends PropertyKey>(checkpoint: Checkpoint, interruptNodes: All | N[], tasks: PregelExecutableTask<N, C>[]): boolean;
20
- export declare function _localRead<Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, channels: Cc, task: WritesProtocol<keyof Cc>, select: Array<keyof Cc> | keyof Cc, fresh?: boolean): Record<string, unknown> | unknown;
21
- export declare function _localWrite(commit: (writes: [string, any][]) => void, processes: Record<string, PregelNode>, channels: Record<string, BaseChannel>, writes: [string, any][]): void;
22
- export declare function _applyWrites<Cc extends Record<string, BaseChannel>>(checkpoint: Checkpoint, channels: Cc, tasks: WritesProtocol<keyof Cc>[], getNextVersion?: (version: any, channel: BaseChannel) => any): void;
21
+ export declare function _localRead<Cc extends Record<string, BaseChannel>>(step: number, checkpoint: ReadonlyCheckpoint, channels: Cc, managed: ManagedValueMapping, task: WritesProtocol<keyof Cc>, select: Array<keyof Cc> | keyof Cc, fresh?: boolean): Record<string, unknown> | unknown;
22
+ export declare function _localWrite(step: number, commit: (writes: [string, any][]) => any, processes: Record<string, PregelNode>, channels: Record<string, BaseChannel>, managed: ManagedValueMapping, writes: [string, any][]): void;
23
+ export declare function _applyWrites<Cc extends Record<string, BaseChannel>>(checkpoint: Checkpoint, channels: Cc, tasks: WritesProtocol<keyof Cc>[], getNextVersion?: (version: any, channel: BaseChannel) => any): Record<string, PendingWriteValue[]>;
23
24
  export type NextTaskExtraFields = {
24
25
  step: number;
25
26
  isResuming?: boolean;
26
27
  checkpointer?: BaseCheckpointSaver;
27
28
  manager?: CallbackManagerForChainRun;
28
29
  };
29
- export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, processes: Nn, channels: Cc, config: RunnableConfig, forExecution: false, extra: NextTaskExtraFields): PregelTaskDescription[];
30
- export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, processes: Nn, channels: Cc, config: RunnableConfig, forExecution: true, extra: NextTaskExtraFields): PregelExecutableTask<keyof Nn, keyof Cc>[];
30
+ export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: false, extra: NextTaskExtraFields): PregelTaskDescription[];
31
+ export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: true, extra: NextTaskExtraFields): PregelExecutableTask<keyof Nn, keyof Cc>[];
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import { mergeConfigs, patchConfig, } from "@langchain/core/runnables";
3
3
  import { copyCheckpoint, uuid5, maxChannelVersion, } from "@langchain/langgraph-checkpoint";
4
- import { createCheckpoint, emptyChannels, } from "../channels/base.js";
4
+ import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
5
5
  import { readChannel, readChannels } from "./io.js";
6
6
  import { _isSend, _isSendInterface, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_SEND, INTERRUPT, RESERVED, TAG_HIDDEN, TASKS, } from "../constants.js";
7
7
  import { EmptyChannelError, InvalidUpdateError } from "../errors.js";
@@ -28,22 +28,48 @@ export function shouldInterrupt(checkpoint, interruptNodes, tasks) {
28
28
  : interruptNodes.includes(task.name));
29
29
  return anyChannelUpdated && anyTriggeredNodeInInterruptNodes;
30
30
  }
31
- export function _localRead(checkpoint, channels, task, select, fresh = false) {
32
- if (fresh) {
33
- const newCheckpoint = createCheckpoint(checkpoint, channels, -1);
34
- // create a new copy of channels
35
- const newChannels = emptyChannels(channels, newCheckpoint);
36
- // Note: _applyWrites contains side effects
31
+ export function _localRead(step, checkpoint, channels, managed, task, select, fresh = false) {
32
+ let managedKeys = [];
33
+ let updated = new Set();
34
+ if (!Array.isArray(select)) {
35
+ for (const [c] of task.writes) {
36
+ if (c === select) {
37
+ updated = new Set([c]);
38
+ break;
39
+ }
40
+ }
41
+ updated = updated || new Set();
42
+ }
43
+ else {
44
+ managedKeys = select.filter((k) => managed.get(k));
45
+ select = select.filter((k) => !managed.get(k));
46
+ updated = new Set(select.filter((c) => task.writes.some(([key, _]) => key === c)));
47
+ }
48
+ let values;
49
+ if (fresh && updated.size > 0) {
50
+ const localChannels = Object.fromEntries(Object.entries(channels).filter(([k, _]) => updated.has(k)));
51
+ const newCheckpoint = createCheckpoint(checkpoint, localChannels, -1);
52
+ const newChannels = emptyChannels(localChannels, newCheckpoint);
37
53
  _applyWrites(copyCheckpoint(newCheckpoint), newChannels, [task]);
38
- return readChannels(newChannels, select);
54
+ values = readChannels({ ...channels, ...newChannels }, select);
39
55
  }
40
56
  else {
41
- return readChannels(channels, select);
57
+ values = readChannels(channels, select);
42
58
  }
59
+ if (managedKeys.length > 0) {
60
+ for (const k of managedKeys) {
61
+ const managedValue = managed.get(k);
62
+ if (managedValue) {
63
+ const resultOfManagedCall = managedValue.call(step);
64
+ values[k] = resultOfManagedCall;
65
+ }
66
+ }
67
+ }
68
+ return values;
43
69
  }
44
- export function _localWrite(
70
+ export function _localWrite(step,
45
71
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
- commit, processes, channels,
72
+ commit, processes, channels, managed,
47
73
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
74
  writes) {
49
75
  for (const [chan, value] of writes) {
@@ -54,8 +80,10 @@ writes) {
54
80
  if (!(value.node in processes)) {
55
81
  throw new InvalidUpdateError(`Invalid node name ${value.node} in packet`);
56
82
  }
83
+ // replace any runtime values with placeholders
84
+ managed.replaceRuntimeValues(step, value.args);
57
85
  }
58
- else if (!(chan in channels)) {
86
+ else if (!(chan in channels) && !managed.get(chan)) {
59
87
  console.warn(`Skipping write for channel '${chan}' which has no readers`);
60
88
  }
61
89
  }
@@ -64,6 +92,8 @@ writes) {
64
92
  export function _applyWrites(checkpoint, channels, tasks,
65
93
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
94
  getNextVersion) {
95
+ // Filter out non instances of BaseChannel
96
+ const onlyChannels = Object.fromEntries(Object.entries(channels).filter(([_, value]) => isBaseChannel(value)));
67
97
  // Update seen versions
68
98
  for (const task of tasks) {
69
99
  if (checkpoint.versions_seen[task.name] === undefined) {
@@ -86,9 +116,9 @@ getNextVersion) {
86
116
  .flatMap((task) => task.triggers)
87
117
  .filter((chan) => !RESERVED.includes(chan)));
88
118
  for (const chan of channelsToConsume) {
89
- if (channels[chan].consume()) {
119
+ if (chan in onlyChannels && onlyChannels[chan].consume()) {
90
120
  if (getNextVersion !== undefined) {
91
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
121
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
92
122
  }
93
123
  }
94
124
  }
@@ -98,6 +128,7 @@ getNextVersion) {
98
128
  }
99
129
  // Group writes by channel
100
130
  const pendingWriteValuesByChannel = {};
131
+ const pendingWritesByManaged = {};
101
132
  for (const task of tasks) {
102
133
  for (const [chan, val] of task.writes) {
103
134
  if (chan === TASKS) {
@@ -106,7 +137,7 @@ getNextVersion) {
106
137
  args: val.args,
107
138
  });
108
139
  }
109
- else {
140
+ else if (chan in onlyChannels) {
110
141
  if (chan in pendingWriteValuesByChannel) {
111
142
  pendingWriteValuesByChannel[chan].push(val);
112
143
  }
@@ -114,6 +145,14 @@ getNextVersion) {
114
145
  pendingWriteValuesByChannel[chan] = [val];
115
146
  }
116
147
  }
148
+ else {
149
+ if (chan in pendingWritesByManaged) {
150
+ pendingWritesByManaged[chan].push(val);
151
+ }
152
+ else {
153
+ pendingWritesByManaged[chan] = [val];
154
+ }
155
+ }
117
156
  }
118
157
  }
119
158
  // find the highest version of all channels
@@ -124,10 +163,10 @@ getNextVersion) {
124
163
  const updatedChannels = new Set();
125
164
  // Apply writes to channels
126
165
  for (const [chan, vals] of Object.entries(pendingWriteValuesByChannel)) {
127
- if (chan in channels) {
166
+ if (chan in onlyChannels) {
128
167
  let updated;
129
168
  try {
130
- updated = channels[chan].update(vals);
169
+ updated = onlyChannels[chan].update(vals);
131
170
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
171
  }
133
172
  catch (e) {
@@ -139,22 +178,24 @@ getNextVersion) {
139
178
  }
140
179
  }
141
180
  if (updated && getNextVersion !== undefined) {
142
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
181
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
143
182
  }
144
183
  updatedChannels.add(chan);
145
184
  }
146
185
  }
147
186
  // Channels that weren't updated in this step are notified of a new step
148
- for (const chan of Object.keys(channels)) {
187
+ for (const chan of Object.keys(onlyChannels)) {
149
188
  if (!updatedChannels.has(chan)) {
150
- const updated = channels[chan].update([]);
189
+ const updated = onlyChannels[chan].update([]);
151
190
  if (updated && getNextVersion !== undefined) {
152
- checkpoint.channel_versions[chan] = getNextVersion(maxVersion, channels[chan]);
191
+ checkpoint.channel_versions[chan] = getNextVersion(maxVersion, onlyChannels[chan]);
153
192
  }
154
193
  }
155
194
  }
195
+ // Return managed values writes to be applied externally
196
+ return pendingWritesByManaged;
156
197
  }
157
- export function _prepareNextTasks(checkpoint, processes, channels, config, forExecution, extra) {
198
+ export function _prepareNextTasks(checkpoint, processes, channels, managed, config, forExecution, extra) {
158
199
  const parentNamespace = config.configurable?.checkpoint_ns ?? "";
159
200
  const tasks = [];
160
201
  const taskDescriptions = [];
@@ -184,6 +225,7 @@ export function _prepareNextTasks(checkpoint, processes, channels, config, forEx
184
225
  const node = proc.getNode();
185
226
  if (node !== undefined) {
186
227
  const writes = [];
228
+ managed.replaceRuntimePlaceholders(step, packet.args);
187
229
  tasks.push({
188
230
  name: packet.node,
189
231
  input: packet.args,
@@ -196,12 +238,13 @@ export function _prepareNextTasks(checkpoint, processes, channels, config, forEx
196
238
  runName: packet.node,
197
239
  callbacks: manager?.getChild(`graph:step:${step}`),
198
240
  configurable: {
199
- [CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
200
- [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, {
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
+ [CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, channels, managed, writes_),
243
+ [CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
201
244
  name: packet.node,
202
245
  writes: writes,
203
246
  triggers,
204
- }),
247
+ }, select_, fresh_),
205
248
  },
206
249
  }),
207
250
  id: taskId,
@@ -235,7 +278,7 @@ export function _prepareNextTasks(checkpoint, processes, channels, config, forEx
235
278
  .sort();
236
279
  // If any of the channels read by this process were updated
237
280
  if (triggers.length > 0) {
238
- const val = _procInput(proc, channels, forExecution);
281
+ const val = _procInput(step, proc, managed, channels, forExecution);
239
282
  if (val === undefined) {
240
283
  continue;
241
284
  }
@@ -265,12 +308,13 @@ export function _prepareNextTasks(checkpoint, processes, channels, config, forEx
265
308
  runName: name,
266
309
  callbacks: manager?.getChild(`graph:step:${step}`),
267
310
  configurable: {
268
- [CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
269
- [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, {
311
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
312
+ [CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, channels, managed, writes_),
313
+ [CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
270
314
  name,
271
315
  writes: writes,
272
316
  triggers,
273
- }),
317
+ }, select_, fresh_),
274
318
  [CONFIG_KEY_CHECKPOINTER]: checkpointer,
275
319
  [CONFIG_KEY_RESUMING]: isResuming,
276
320
  checkpoint_id: checkpoint.id,
@@ -289,12 +333,46 @@ export function _prepareNextTasks(checkpoint, processes, channels, config, forEx
289
333
  }
290
334
  return forExecution ? tasks : taskDescriptions;
291
335
  }
292
- function _procInput(proc, channels, forExecution) {
336
+ function _procInput(step, proc, managed, channels, forExecution) {
293
337
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
294
338
  let val;
295
- // If all trigger channels subscribed by this process are not empty
296
- // then invoke the process with the values of all non-empty channels
297
- if (Array.isArray(proc.channels)) {
339
+ if (typeof proc.channels === "object" && !Array.isArray(proc.channels)) {
340
+ val = {};
341
+ for (const [k, chan] of Object.entries(proc.channels)) {
342
+ if (proc.triggers.includes(chan)) {
343
+ try {
344
+ val[k] = readChannel(channels, chan, false);
345
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
346
+ }
347
+ catch (e) {
348
+ if (e.name === EmptyChannelError.unminifiable_name) {
349
+ return undefined;
350
+ }
351
+ else {
352
+ throw e;
353
+ }
354
+ }
355
+ }
356
+ else if (chan in channels) {
357
+ try {
358
+ val[k] = readChannel(channels, chan, true);
359
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
360
+ }
361
+ catch (e) {
362
+ if (e.name === EmptyChannelError.unminifiable_name) {
363
+ continue;
364
+ }
365
+ else {
366
+ throw e;
367
+ }
368
+ }
369
+ }
370
+ else {
371
+ val[k] = managed.get(k)?.call(step);
372
+ }
373
+ }
374
+ }
375
+ else if (Array.isArray(proc.channels)) {
298
376
  let successfulRead = false;
299
377
  for (const chan of proc.channels) {
300
378
  try {
@@ -313,24 +391,7 @@ function _procInput(proc, channels, forExecution) {
313
391
  }
314
392
  }
315
393
  if (!successfulRead) {
316
- return;
317
- }
318
- }
319
- else if (typeof proc.channels === "object") {
320
- val = {};
321
- for (const [k, chan] of Object.entries(proc.channels)) {
322
- try {
323
- val[k] = readChannel(channels, chan, !proc.triggers.includes(chan));
324
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
- }
326
- catch (e) {
327
- if (e.name === EmptyChannelError.unminifiable_name) {
328
- continue;
329
- }
330
- else {
331
- throw e;
332
- }
333
- }
394
+ return undefined;
334
395
  }
335
396
  }
336
397
  else {