@langchain/langgraph 1.4.19 → 1.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/base.cjs +41 -9
- package/dist/channels/base.cjs.map +1 -1
- package/dist/channels/base.d.cts +11 -4
- package/dist/channels/base.d.cts.map +1 -1
- package/dist/channels/base.d.ts +11 -4
- package/dist/channels/base.d.ts.map +1 -1
- package/dist/channels/base.js +40 -10
- package/dist/channels/base.js.map +1 -1
- package/dist/pregel/index.cjs +28 -14
- package/dist/pregel/index.cjs.map +1 -1
- package/dist/pregel/index.d.cts.map +1 -1
- package/dist/pregel/index.d.ts.map +1 -1
- package/dist/pregel/index.js +29 -15
- package/dist/pregel/index.js.map +1 -1
- package/dist/pregel/loop.cjs +16 -4
- package/dist/pregel/loop.cjs.map +1 -1
- package/dist/pregel/loop.js +17 -5
- package/dist/pregel/loop.js.map +1 -1
- package/package.json +4 -4
package/dist/channels/base.cjs
CHANGED
|
@@ -105,15 +105,19 @@ function exitDeltaTaskId(step, taskId) {
|
|
|
105
105
|
*
|
|
106
106
|
* A channel snapshots when EITHER its accumulated update count reaches
|
|
107
107
|
* `snapshotFrequency` OR the total supersteps since its last snapshot reaches
|
|
108
|
-
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`.
|
|
108
|
+
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel
|
|
109
|
+
* without a version is skipped: it was never written on this branch, so it
|
|
110
|
+
* has nothing to snapshot. Pass `undefined` on a branch without
|
|
111
|
+
* {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes
|
|
112
|
+
* and this snapshot is what bounds its walk. Pure predicate, no mutation.
|
|
109
113
|
*/
|
|
110
|
-
function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot) {
|
|
114
|
+
function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot, channelVersions) {
|
|
111
115
|
const result = /* @__PURE__ */ new Set();
|
|
112
116
|
const maxSupersteps = require_constants.getDeltaMaxSuperstepsSinceSnapshot();
|
|
113
117
|
for (const name in channels) {
|
|
114
118
|
if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;
|
|
115
119
|
const ch = channels[name];
|
|
116
|
-
if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;
|
|
120
|
+
if (!isDeltaChannel(ch) || !ch.isAvailable() || channelVersions !== void 0 && channelVersions[name] === void 0) continue;
|
|
117
121
|
const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];
|
|
118
122
|
if (updates >= ch.snapshotFrequency || supersteps >= maxSupersteps) result.add(name);
|
|
119
123
|
}
|
|
@@ -154,6 +158,34 @@ function createCheckpoint(checkpoint, channels, step, options) {
|
|
|
154
158
|
};
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
161
|
+
* Checkpoint metadata key, `true` when every DeltaChannel write on the
|
|
162
|
+
* checkpoint's branch carries a version. A delta channel with no version there
|
|
163
|
+
* was never written, so it needs no walk. Branches started before this key
|
|
164
|
+
* existed never get it: an older `updateState` stored a delta channel's first
|
|
165
|
+
* write without a version, and only the walk finds such a write.
|
|
166
|
+
*/
|
|
167
|
+
const DELTA_WRITES_VERSIONED = "delta_writes_versioned";
|
|
168
|
+
function isDeltaWritesVersioned(metadata) {
|
|
169
|
+
return metadata?.[DELTA_WRITES_VERSIONED] === true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Delta channels whose value at `checkpoint` the ancestor walk rebuilds.
|
|
173
|
+
*
|
|
174
|
+
* A {@link DeltaSnapshot} or a migrated plain value in `channel_values`
|
|
175
|
+
* resolves directly, so only a channel with nothing stored there needs the
|
|
176
|
+
* walk. With `versionedOnly`, a channel with no version was never written, so
|
|
177
|
+
* it is empty without one; a walk for it would find no snapshot to stop at and
|
|
178
|
+
* read every ancestor, every time the thread is loaded.
|
|
179
|
+
*/
|
|
180
|
+
function deltaChannelsToReplay(specs, checkpoint, versionedOnly) {
|
|
181
|
+
const keys = [];
|
|
182
|
+
for (const k in specs) {
|
|
183
|
+
if (!Object.prototype.hasOwnProperty.call(specs, k)) continue;
|
|
184
|
+
if (isDeltaChannel(specs[k]) && (!versionedOnly || checkpoint.channel_versions[k] !== void 0) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) keys.push(k);
|
|
185
|
+
}
|
|
186
|
+
return keys;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
157
189
|
* Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
|
|
158
190
|
* whose value is absent from `channel_values` by replaying ancestor writes.
|
|
159
191
|
*
|
|
@@ -163,17 +195,15 @@ function createCheckpoint(checkpoint, channels, step, options) {
|
|
|
163
195
|
* from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
|
|
164
196
|
* finds the nearest seed and accumulates the writes between it and the
|
|
165
197
|
* target. All delta channels needing replay are batched into a single saver
|
|
166
|
-
* call.
|
|
198
|
+
* call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries
|
|
199
|
+
* {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are
|
|
200
|
+
* left empty instead of walked.
|
|
167
201
|
*/
|
|
168
202
|
async function channelsFromCheckpoint(specs, checkpoint, options) {
|
|
169
203
|
const channels = emptyChannels(specs, checkpoint);
|
|
170
204
|
const { saver, config } = options ?? {};
|
|
171
205
|
const filteredSpecs = getOnlyChannels(specs);
|
|
172
|
-
const deltaKeys =
|
|
173
|
-
for (const k in filteredSpecs) {
|
|
174
|
-
if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
|
|
175
|
-
if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
|
|
176
|
-
}
|
|
206
|
+
const deltaKeys = deltaChannelsToReplay(filteredSpecs, checkpoint, options?.deltaWritesVersioned ?? false);
|
|
177
207
|
const written = deltaKeys.filter((k) => checkpoint.channel_versions[k] !== void 0);
|
|
178
208
|
if ((saver === void 0 || config === void 0) && written.length > 0) throw new Error(`DeltaChannel ${written.join(", ")} has history to replay but no checkpointer or config was passed to read it`);
|
|
179
209
|
if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
|
|
@@ -192,6 +222,7 @@ async function channelsFromCheckpoint(specs, checkpoint, options) {
|
|
|
192
222
|
}
|
|
193
223
|
//#endregion
|
|
194
224
|
exports.BaseChannel = BaseChannel;
|
|
225
|
+
exports.DELTA_WRITES_VERSIONED = DELTA_WRITES_VERSIONED;
|
|
195
226
|
exports.channelsFromCheckpoint = channelsFromCheckpoint;
|
|
196
227
|
exports.createCheckpoint = createCheckpoint;
|
|
197
228
|
exports.deltaChannelsToSnapshot = deltaChannelsToSnapshot;
|
|
@@ -200,5 +231,6 @@ exports.exitDeltaTaskId = exitDeltaTaskId;
|
|
|
200
231
|
exports.getOnlyChannels = getOnlyChannels;
|
|
201
232
|
exports.isBaseChannel = isBaseChannel;
|
|
202
233
|
exports.isDeltaChannel = isDeltaChannel;
|
|
234
|
+
exports.isDeltaWritesVersioned = isDeltaWritesVersioned;
|
|
203
235
|
|
|
204
236
|
//# sourceMappingURL=base.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.cjs","names":["EmptyChannelError","getDeltaMaxSuperstepsSinceSnapshot","DeltaSnapshot"],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/** Matches Postgres `uuid` / Python `uuid.UUID` (128-bit, 8-4-4-4-12 hex). */\nconst STRUCTURED_UUID =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Synthetic task id for exit-mode DeltaChannel writes.\n *\n * Embeds the superstep in the first UUID group so `ORDER BY task_id, idx`\n * preserves chronological order while remaining a valid RFC UUID (required by\n * Postgres `checkpoint_writes.task_id uuid` columns).\n */\nexport function exitDeltaTaskId(step: number, taskId: string): string {\n if (!STRUCTURED_UUID.test(taskId)) {\n throw new TypeError(`Invalid task id for exit delta: ${taskId}`);\n }\n const parts = taskId.toLowerCase().split(\"-\");\n const stepPart = String(step).padStart(8, \"0\");\n return `${stepPart}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: { saver?: BaseCheckpointSaver; config?: RunnableConfig }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys: string[] = [];\n for (const k in filteredSpecs) {\n if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;\n if (\n isDeltaChannel(filteredSpecs[k]) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n deltaKeys.push(k);\n }\n }\n\n const written = deltaKeys.filter(\n (k) => checkpoint.channel_versions[k] !== undefined\n );\n if ((saver === undefined || config === undefined) && written.length > 0) {\n throw new Error(\n `DeltaChannel ${written.join(\", \")} has history to replay but no checkpointer or config was passed to read it`\n );\n }\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base.cjs","names":["EmptyChannelError","getDeltaMaxSuperstepsSinceSnapshot","DeltaSnapshot"],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type CheckpointMetadata,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/** Matches Postgres `uuid` / Python `uuid.UUID` (128-bit, 8-4-4-4-12 hex). */\nconst STRUCTURED_UUID =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Synthetic task id for exit-mode DeltaChannel writes.\n *\n * Embeds the superstep in the first UUID group so `ORDER BY task_id, idx`\n * preserves chronological order while remaining a valid RFC UUID (required by\n * Postgres `checkpoint_writes.task_id uuid` columns).\n */\nexport function exitDeltaTaskId(step: number, taskId: string): string {\n if (!STRUCTURED_UUID.test(taskId)) {\n throw new TypeError(`Invalid task id for exit delta: ${taskId}`);\n }\n const parts = taskId.toLowerCase().split(\"-\");\n const stepPart = String(step).padStart(8, \"0\");\n return `${stepPart}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel\n * without a version is skipped: it was never written on this branch, so it\n * has nothing to snapshot. Pass `undefined` on a branch without\n * {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes\n * and this snapshot is what bounds its walk. Pure predicate, no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>,\n channelVersions: Record<string, number | string> | undefined\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (\n !isDeltaChannel(ch) ||\n !ch.isAvailable() ||\n (channelVersions !== undefined && channelVersions[name] === undefined)\n ) {\n continue;\n }\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Checkpoint metadata key, `true` when every DeltaChannel write on the\n * checkpoint's branch carries a version. A delta channel with no version there\n * was never written, so it needs no walk. Branches started before this key\n * existed never get it: an older `updateState` stored a delta channel's first\n * write without a version, and only the walk finds such a write.\n */\nexport const DELTA_WRITES_VERSIONED = \"delta_writes_versioned\";\n\nexport function isDeltaWritesVersioned(\n metadata: CheckpointMetadata | undefined\n): boolean {\n return (\n (metadata as Record<string, unknown> | undefined)?.[\n DELTA_WRITES_VERSIONED\n ] === true\n );\n}\n\n/**\n * Delta channels whose value at `checkpoint` the ancestor walk rebuilds.\n *\n * A {@link DeltaSnapshot} or a migrated plain value in `channel_values`\n * resolves directly, so only a channel with nothing stored there needs the\n * walk. With `versionedOnly`, a channel with no version was never written, so\n * it is empty without one; a walk for it would find no snapshot to stop at and\n * read every ancestor, every time the thread is loaded.\n */\nfunction deltaChannelsToReplay(\n specs: Record<string, BaseChannel>,\n checkpoint: ReadonlyCheckpoint,\n versionedOnly: boolean\n): string[] {\n const keys: string[] = [];\n for (const k in specs) {\n if (!Object.prototype.hasOwnProperty.call(specs, k)) continue;\n if (\n isDeltaChannel(specs[k]) &&\n (!versionedOnly || checkpoint.channel_versions[k] !== undefined) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n keys.push(k);\n }\n }\n return keys;\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries\n * {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are\n * left empty instead of walked.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: {\n saver?: BaseCheckpointSaver;\n config?: RunnableConfig;\n deltaWritesVersioned?: boolean;\n }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys = deltaChannelsToReplay(\n filteredSpecs,\n checkpoint,\n options?.deltaWritesVersioned ?? false\n );\n\n const written = deltaKeys.filter(\n (k) => checkpoint.channel_versions[k] !== undefined\n );\n if ((saver === undefined || config === undefined) && written.length > 0) {\n throw new Error(\n `DeltaChannel ${written.join(\", \")} has history to replay but no checkpointer or config was passed to read it`\n );\n }\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":"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"}
|
package/dist/channels/base.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
1
|
+
import { BaseCheckpointSaver, Checkpoint, CheckpointMetadata, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
2
2
|
import { RunnableConfig } from "@langchain/core/runnables";
|
|
3
3
|
|
|
4
4
|
//#region src/channels/base.d.ts
|
|
@@ -99,9 +99,13 @@ declare function exitDeltaTaskId(step: number, taskId: string): string;
|
|
|
99
99
|
*
|
|
100
100
|
* A channel snapshots when EITHER its accumulated update count reaches
|
|
101
101
|
* `snapshotFrequency` OR the total supersteps since its last snapshot reaches
|
|
102
|
-
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`.
|
|
102
|
+
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel
|
|
103
|
+
* without a version is skipped: it was never written on this branch, so it
|
|
104
|
+
* has nothing to snapshot. Pass `undefined` on a branch without
|
|
105
|
+
* {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes
|
|
106
|
+
* and this snapshot is what bounds its walk. Pure predicate, no mutation.
|
|
103
107
|
*/
|
|
104
|
-
declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>): Set<string>;
|
|
108
|
+
declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>, channelVersions: Record<string, number | string> | undefined): Set<string>;
|
|
105
109
|
declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, channels: Record<string, BaseChannel<ValueType>> | undefined, step: number, options?: {
|
|
106
110
|
id?: string;
|
|
107
111
|
channelsToSnapshot?: Set<string>;
|
|
@@ -118,11 +122,14 @@ declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, cha
|
|
|
118
122
|
* from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
|
|
119
123
|
* finds the nearest seed and accumulates the writes between it and the
|
|
120
124
|
* target. All delta channels needing replay are batched into a single saver
|
|
121
|
-
* call.
|
|
125
|
+
* call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries
|
|
126
|
+
* {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are
|
|
127
|
+
* left empty instead of walked.
|
|
122
128
|
*/
|
|
123
129
|
declare function channelsFromCheckpoint<Cc extends Record<string, BaseChannel>>(specs: Cc, checkpoint: ReadonlyCheckpoint, options?: {
|
|
124
130
|
saver?: BaseCheckpointSaver;
|
|
125
131
|
config?: RunnableConfig;
|
|
132
|
+
deltaWritesVersioned?: boolean;
|
|
126
133
|
}): Promise<Cc>;
|
|
127
134
|
//#endregion
|
|
128
135
|
export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, exitDeltaTaskId, isDeltaChannel };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"base.d.cts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;AAqBA;;iBAAgB,cAAA,CAAe,OAAA,EAAS,WAAA;;uBASlB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AA8Bd;EArFE,MAAA,CAAA;;;;AAyGF;;EAhGE,WAAA,CAAA;EAiGyB;;;;;;;;EA5EzB,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;;;;;;AA6EH;;iBAhDgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;;;;;;;iBAoB9B,uBAAA,CACd,QAAA,EAAU,MAAA,SAAe,WAAA,GACzB,0BAAA,EAA4B,MAAA,4BAC5B,eAAA,EAAiB,MAAA,wCAChB,GAAA;AAAA,iBAwBa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EACE,EAAA;EACA,kBAAA,GAAqB,GAAA;EACrB,eAAA,GAAkB,GAAA;EAClB,cAAA,IAAkB,OAAA;AAAA,IAEnB,UAAA;;AAsHH;;;;;;;;;;;;;iBAAsB,sBAAA,YACT,MAAA,SAAe,WAAA,EAAA,CAE1B,KAAA,EAAO,EAAA,EACP,UAAA,EAAY,kBAAA,EACZ,OAAA;EACE,KAAA,GAAQ,mBAAA;EACR,MAAA,GAAS,cAAA;EACT,oBAAA;AAAA,IAED,OAAA,CAAQ,EAAA"}
|
package/dist/channels/base.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseCheckpointSaver, Checkpoint, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
1
|
+
import { BaseCheckpointSaver, Checkpoint, CheckpointMetadata, ReadonlyCheckpoint } from "@langchain/langgraph-checkpoint";
|
|
2
2
|
import { RunnableConfig } from "@langchain/core/runnables";
|
|
3
3
|
|
|
4
4
|
//#region src/channels/base.d.ts
|
|
@@ -99,9 +99,13 @@ declare function exitDeltaTaskId(step: number, taskId: string): string;
|
|
|
99
99
|
*
|
|
100
100
|
* A channel snapshots when EITHER its accumulated update count reaches
|
|
101
101
|
* `snapshotFrequency` OR the total supersteps since its last snapshot reaches
|
|
102
|
-
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`.
|
|
102
|
+
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel
|
|
103
|
+
* without a version is skipped: it was never written on this branch, so it
|
|
104
|
+
* has nothing to snapshot. Pass `undefined` on a branch without
|
|
105
|
+
* {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes
|
|
106
|
+
* and this snapshot is what bounds its walk. Pure predicate, no mutation.
|
|
103
107
|
*/
|
|
104
|
-
declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>): Set<string>;
|
|
108
|
+
declare function deltaChannelsToSnapshot(channels: Record<string, BaseChannel>, countersSinceDeltaSnapshot: Record<string, [number, number]>, channelVersions: Record<string, number | string> | undefined): Set<string>;
|
|
105
109
|
declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, channels: Record<string, BaseChannel<ValueType>> | undefined, step: number, options?: {
|
|
106
110
|
id?: string;
|
|
107
111
|
channelsToSnapshot?: Set<string>;
|
|
@@ -118,11 +122,14 @@ declare function createCheckpoint<ValueType>(checkpoint: ReadonlyCheckpoint, cha
|
|
|
118
122
|
* from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
|
|
119
123
|
* finds the nearest seed and accumulates the writes between it and the
|
|
120
124
|
* target. All delta channels needing replay are batched into a single saver
|
|
121
|
-
* call.
|
|
125
|
+
* call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries
|
|
126
|
+
* {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are
|
|
127
|
+
* left empty instead of walked.
|
|
122
128
|
*/
|
|
123
129
|
declare function channelsFromCheckpoint<Cc extends Record<string, BaseChannel>>(specs: Cc, checkpoint: ReadonlyCheckpoint, options?: {
|
|
124
130
|
saver?: BaseCheckpointSaver;
|
|
125
131
|
config?: RunnableConfig;
|
|
132
|
+
deltaWritesVersioned?: boolean;
|
|
126
133
|
}): Promise<Cc>;
|
|
127
134
|
//#endregion
|
|
128
135
|
export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, exitDeltaTaskId, isDeltaChannel };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"base.d.ts","names":[],"sources":["../../src/channels/base.ts"],"mappings":";;;;;;AAqBA;;iBAAgB,cAAA,CAAe,OAAA,EAAS,WAAA;;uBASlB,WAAA;EAKpB,SAAA,EAAW,SAAA;EAEX,UAAA,EAAY,UAAA;EAFD;;;EAAA,SAOF,aAAA;EAoCO;EAjChB,aAAA;EA0Fc;;;;;;;;EAAA,SAhFL,cAAA,CAAe,UAAA,GAAa,cAAA;EAlBzB;;;;;;;;;;;;;EAAA,SAiCH,MAAA,CAAO,MAAA,EAAQ,UAAA;EAmCxB;;;;;;EAAA,SA3BS,GAAA,CAAA,GAAO,SAAA;EAgFF;;;;;;EAAA,SAxEL,UAAA,CAAA,GAAc,cAAA;EA2EtB;;;;;;;EAlED,OAAA,CAAA;EAiEY;;;;;AA8Bd;EArFE,MAAA,CAAA;;;;AAyGF;;EAhGE,WAAA,CAAA;EAiGyB;;;;;;;;EA5EzB,MAAA,CAAO,KAAA,EAAO,WAAA;AAAA;AAAA,iBAuBA,aAAA,YAAyB,MAAA,SAAe,WAAA,EAAA,CACtD,QAAA,EAAU,EAAA,EACV,UAAA,EAAY,kBAAA,GACX,EAAA;;;;;;AA6EH;;iBAhDgB,eAAA,CAAgB,IAAA,UAAc,MAAA;;;;;;;;;;;;iBAoB9B,uBAAA,CACd,QAAA,EAAU,MAAA,SAAe,WAAA,GACzB,0BAAA,EAA4B,MAAA,4BAC5B,eAAA,EAAiB,MAAA,wCAChB,GAAA;AAAA,iBAwBa,gBAAA,WAAA,CACd,UAAA,EAAY,kBAAA,EACZ,QAAA,EAAU,MAAA,SAAe,WAAA,CAAY,SAAA,gBACrC,IAAA,UACA,OAAA;EACE,EAAA;EACA,kBAAA,GAAqB,GAAA;EACrB,eAAA,GAAkB,GAAA;EAClB,cAAA,IAAkB,OAAA;AAAA,IAEnB,UAAA;;AAsHH;;;;;;;;;;;;;iBAAsB,sBAAA,YACT,MAAA,SAAe,WAAA,EAAA,CAE1B,KAAA,EAAO,EAAA,EACP,UAAA,EAAY,kBAAA,EACZ,OAAA;EACE,KAAA,GAAQ,mBAAA;EACR,MAAA,GAAS,cAAA;EACT,oBAAA;AAAA,IAED,OAAA,CAAQ,EAAA"}
|
package/dist/channels/base.js
CHANGED
|
@@ -105,15 +105,19 @@ function exitDeltaTaskId(step, taskId) {
|
|
|
105
105
|
*
|
|
106
106
|
* A channel snapshots when EITHER its accumulated update count reaches
|
|
107
107
|
* `snapshotFrequency` OR the total supersteps since its last snapshot reaches
|
|
108
|
-
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`.
|
|
108
|
+
* `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel
|
|
109
|
+
* without a version is skipped: it was never written on this branch, so it
|
|
110
|
+
* has nothing to snapshot. Pass `undefined` on a branch without
|
|
111
|
+
* {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes
|
|
112
|
+
* and this snapshot is what bounds its walk. Pure predicate, no mutation.
|
|
109
113
|
*/
|
|
110
|
-
function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot) {
|
|
114
|
+
function deltaChannelsToSnapshot(channels, countersSinceDeltaSnapshot, channelVersions) {
|
|
111
115
|
const result = /* @__PURE__ */ new Set();
|
|
112
116
|
const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();
|
|
113
117
|
for (const name in channels) {
|
|
114
118
|
if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;
|
|
115
119
|
const ch = channels[name];
|
|
116
|
-
if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;
|
|
120
|
+
if (!isDeltaChannel(ch) || !ch.isAvailable() || channelVersions !== void 0 && channelVersions[name] === void 0) continue;
|
|
117
121
|
const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];
|
|
118
122
|
if (updates >= ch.snapshotFrequency || supersteps >= maxSupersteps) result.add(name);
|
|
119
123
|
}
|
|
@@ -154,6 +158,34 @@ function createCheckpoint(checkpoint, channels, step, options) {
|
|
|
154
158
|
};
|
|
155
159
|
}
|
|
156
160
|
/**
|
|
161
|
+
* Checkpoint metadata key, `true` when every DeltaChannel write on the
|
|
162
|
+
* checkpoint's branch carries a version. A delta channel with no version there
|
|
163
|
+
* was never written, so it needs no walk. Branches started before this key
|
|
164
|
+
* existed never get it: an older `updateState` stored a delta channel's first
|
|
165
|
+
* write without a version, and only the walk finds such a write.
|
|
166
|
+
*/
|
|
167
|
+
const DELTA_WRITES_VERSIONED = "delta_writes_versioned";
|
|
168
|
+
function isDeltaWritesVersioned(metadata) {
|
|
169
|
+
return metadata?.[DELTA_WRITES_VERSIONED] === true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Delta channels whose value at `checkpoint` the ancestor walk rebuilds.
|
|
173
|
+
*
|
|
174
|
+
* A {@link DeltaSnapshot} or a migrated plain value in `channel_values`
|
|
175
|
+
* resolves directly, so only a channel with nothing stored there needs the
|
|
176
|
+
* walk. With `versionedOnly`, a channel with no version was never written, so
|
|
177
|
+
* it is empty without one; a walk for it would find no snapshot to stop at and
|
|
178
|
+
* read every ancestor, every time the thread is loaded.
|
|
179
|
+
*/
|
|
180
|
+
function deltaChannelsToReplay(specs, checkpoint, versionedOnly) {
|
|
181
|
+
const keys = [];
|
|
182
|
+
for (const k in specs) {
|
|
183
|
+
if (!Object.prototype.hasOwnProperty.call(specs, k)) continue;
|
|
184
|
+
if (isDeltaChannel(specs[k]) && (!versionedOnly || checkpoint.channel_versions[k] !== void 0) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) keys.push(k);
|
|
185
|
+
}
|
|
186
|
+
return keys;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
157
189
|
* Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}
|
|
158
190
|
* whose value is absent from `channel_values` by replaying ancestor writes.
|
|
159
191
|
*
|
|
@@ -163,17 +195,15 @@ function createCheckpoint(checkpoint, channels, step, options) {
|
|
|
163
195
|
* from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`
|
|
164
196
|
* finds the nearest seed and accumulates the writes between it and the
|
|
165
197
|
* target. All delta channels needing replay are batched into a single saver
|
|
166
|
-
* call.
|
|
198
|
+
* call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries
|
|
199
|
+
* {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are
|
|
200
|
+
* left empty instead of walked.
|
|
167
201
|
*/
|
|
168
202
|
async function channelsFromCheckpoint(specs, checkpoint, options) {
|
|
169
203
|
const channels = emptyChannels(specs, checkpoint);
|
|
170
204
|
const { saver, config } = options ?? {};
|
|
171
205
|
const filteredSpecs = getOnlyChannels(specs);
|
|
172
|
-
const deltaKeys =
|
|
173
|
-
for (const k in filteredSpecs) {
|
|
174
|
-
if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
|
|
175
|
-
if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
|
|
176
|
-
}
|
|
206
|
+
const deltaKeys = deltaChannelsToReplay(filteredSpecs, checkpoint, options?.deltaWritesVersioned ?? false);
|
|
177
207
|
const written = deltaKeys.filter((k) => checkpoint.channel_versions[k] !== void 0);
|
|
178
208
|
if ((saver === void 0 || config === void 0) && written.length > 0) throw new Error(`DeltaChannel ${written.join(", ")} has history to replay but no checkpointer or config was passed to read it`);
|
|
179
209
|
if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
|
|
@@ -191,6 +221,6 @@ async function channelsFromCheckpoint(specs, checkpoint, options) {
|
|
|
191
221
|
return channels;
|
|
192
222
|
}
|
|
193
223
|
//#endregion
|
|
194
|
-
export { BaseChannel, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, exitDeltaTaskId, getOnlyChannels, isBaseChannel, isDeltaChannel };
|
|
224
|
+
export { BaseChannel, DELTA_WRITES_VERSIONED, channelsFromCheckpoint, createCheckpoint, deltaChannelsToSnapshot, emptyChannels, exitDeltaTaskId, getOnlyChannels, isBaseChannel, isDeltaChannel, isDeltaWritesVersioned };
|
|
195
225
|
|
|
196
226
|
//# sourceMappingURL=base.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":[],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/** Matches Postgres `uuid` / Python `uuid.UUID` (128-bit, 8-4-4-4-12 hex). */\nconst STRUCTURED_UUID =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Synthetic task id for exit-mode DeltaChannel writes.\n *\n * Embeds the superstep in the first UUID group so `ORDER BY task_id, idx`\n * preserves chronological order while remaining a valid RFC UUID (required by\n * Postgres `checkpoint_writes.task_id uuid` columns).\n */\nexport function exitDeltaTaskId(step: number, taskId: string): string {\n if (!STRUCTURED_UUID.test(taskId)) {\n throw new TypeError(`Invalid task id for exit delta: ${taskId}`);\n }\n const parts = taskId.toLowerCase().split(\"-\");\n const stepPart = String(step).padStart(8, \"0\");\n return `${stepPart}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Pure predicate — no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (!isDeltaChannel(ch) || !ch.isAvailable()) continue;\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: { saver?: BaseCheckpointSaver; config?: RunnableConfig }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys: string[] = [];\n for (const k in filteredSpecs) {\n if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;\n if (\n isDeltaChannel(filteredSpecs[k]) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n deltaKeys.push(k);\n }\n }\n\n const written = deltaKeys.filter(\n (k) => checkpoint.channel_versions[k] !== undefined\n );\n if ((saver === undefined || config === undefined) && written.length > 0) {\n throw new Error(\n `DeltaChannel ${written.join(\", \")} has history to replay but no checkpointer or config was passed to read it`\n );\n }\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":";;;;;AAaA,MAAM,kBACJ;;;;;AAMF,SAAgB,eAAe,SAA+B;AAC5D,QAAO,WAAW,QAAQ,QAAQ,kBAAkB;;AAGtD,SAAgB,cAAc,KAAkC;AAC9D,QAAO,OAAO,QAAS,IAAoB,kBAAkB;;;AAI/D,IAAsB,cAAtB,MAIE;CACA;CAEA;;CAQA,gBAAgB;;;;;;;;CAkDhB,UAAmB;AACjB,SAAO;;;;;;;;CAST,SAAkB;AAChB,SAAO;;;;;;;CAQT,cAAuB;AACrB,MAAI;AACF,QAAK,KAAK;AACV,UAAO;WAEA,OAAY;AACnB,OAAI,MAAM,SAAS,kBAAkB,kBACnC,QAAO;AAET,SAAM;;;;;;;;;;;CAYV,OAAO,OAA6B;AAClC,SAAO,SAAS;;;AAIpB,MAAM,uBAAuB,OAAO,IAAI,0BAA0B;AAClE,SAAgB,gBACd,UAC6B;AAE7B,KAAI,SAAS,0BAA0B,KAAM,QAAO;CAEpD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;EACxD,MAAM,QAAQ,SAAS;AACvB,MAAI,cAAc,MAAM,CAAE,aAAY,KAAK;;AAG7C,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;AAGT,SAAgB,cACd,UACA,YACI;CACJ,MAAM,mBAAmB,gBAAgB,SAAS;CAElD,MAAM,cAAc,EAAE;AACtB,MAAK,MAAM,KAAK,kBAAkB;AAChC,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,kBAAkB,EAAE,CAAE;EAChE,MAAM,eAAe,WAAW,eAAe;AAC/C,cAAY,KAAK,iBAAiB,GAAG,eAAe,aAAa;;AAEnE,QAAO,OAAO,aAAa,GAAG,uBAAuB,MAAM,CAAC;AAC5D,QAAO;;;;;;;;;AAmBT,SAAgB,gBAAgB,MAAc,QAAwB;AACpE,KAAI,CAAC,gBAAgB,KAAK,OAAO,CAC/B,OAAM,IAAI,UAAU,mCAAmC,SAAS;CAElE,MAAM,QAAQ,OAAO,aAAa,CAAC,MAAM,IAAI;AAE7C,QAAO,GADU,OAAO,KAAK,CAAC,SAAS,GAAG,IAAI,CAC3B,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG,GAAG,MAAM;;;;;;;;;AAUlE,SAAgB,wBACd,UACA,4BACa;CACb,MAAM,yBAAS,IAAI,KAAa;CAChC,MAAM,gBAAgB,oCAAoC;AAC1D,MAAK,MAAM,QAAQ,UAAU;AAC3B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,KAAK,CAAE;EAC3D,MAAM,KAAK,SAAS;AACpB,MAAI,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,aAAa,CAAE;EAC9C,MAAM,CAAC,SAAS,cAAc,2BAA2B,SAAS,CAAC,GAAG,EAAE;AACxE,MACE,WAAY,GAAwB,qBACpC,cAAc,cAEd,QAAO,IAAI,KAAK;;AAGpB,QAAO;;AAGT,SAAgB,iBACd,YACA,UACA,MACA,SAMY;CACZ,MAAM,qBAAqB,SAAS,sCAAsB,IAAI,KAAa;CAC3E,MAAM,EAAE,iBAAiB,mBAAmB,WAAW,EAAE;CAEzD,IAAI;CACJ,IAAI,kBACF,WAAW;AACb,KAAI,aAAa,KAAA,EACf,UAAS,WAAW;MACf;AACL,WAAS,EAAE;AACX,oBAAkB,EAAE,GAAG,WAAW,kBAAkB;AACpD,OAAK,MAAM,KAAK,UAAU;AACxB,OAAI,CAAC,OAAO,UAAU,eAAe,KAAK,UAAU,EAAE,CAAE;GACxD,MAAM,UAAU,SAAS;AACzB,OAAI,mBAAmB,IAAI,EAAE,EAAE;AAM7B,QACE,mBAAmB,KAAA,MAClB,oBAAoB,KAAA,KAAa,CAAC,gBAAgB,IAAI,EAAE,EAEzD,iBAAgB,KAAK,eAAe,gBAAgB,GAAG;AAEzD,WAAO,KAAK,IAAI,cAAc,QAAQ,KAAK,CAAC;AAC5C;;AAEF,OAAI,eAAe,QAAQ,CAEzB;AAEF,OAAI;AACF,WAAO,KAAK,QAAQ,YAAY;YAEzB,OAAY;AACnB,QAAI,MAAM,SAAS,kBAAkB,mBAAmB,OAGtD,OAAM;;;;AAMd,QAAO;EACL,GAAG;EACH,IAAI,SAAS,MAAM,MAAM,KAAK;EAC9B,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B,gBAAgB;EAChB,kBAAkB;EAClB,eAAe,WAAW;EAC3B;;;;;;;;;;;;;;AAeH,eAAsB,uBAGpB,OACA,YACA,SACa;CACb,MAAM,WAAW,cAAc,OAAO,WAAW;CACjD,MAAM,EAAE,OAAO,WAAW,WAAW,EAAE;CAEvC,MAAM,gBAAgB,gBAAgB,MAAM;CAC5C,MAAM,YAAsB,EAAE;AAC9B,MAAK,MAAM,KAAK,eAAe;AAC7B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,eAAe,EAAE,CAAE;AAC7D,MACE,eAAe,cAAc,GAAG,IAChC,CAAC,OAAO,UAAU,eAAe,KAAK,WAAW,gBAAgB,EAAE,CAEnE,WAAU,KAAK,EAAE;;CAIrB,MAAM,UAAU,UAAU,QACvB,MAAM,WAAW,iBAAiB,OAAO,KAAA,EAC3C;AACD,MAAK,UAAU,KAAA,KAAa,WAAW,KAAA,MAAc,QAAQ,SAAS,EACpE,OAAM,IAAI,MACR,gBAAgB,QAAQ,KAAK,KAAK,CAAC,4EACpC;AAEH,KAAI,UAAU,WAAW,KAAK,UAAU,KAAA,KAAa,WAAW,KAAA,EAC9D,QAAO;CAGT,MAAM,YAAY,MAAM,MAAM,uBAAuB;EACnD;EACA,UAAU;EACX,CAAC;AACF,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,UAAU,UAAU;AAC1B,MAAI,YAAY,KAAA,EAAW;EAC3B,MAAM,WAAW,cAAc,GAAG,eAChC,QAAQ,KACT;AACD,WAAS,aAAa,QAAQ,OAAO;AACpC,WAAyC,KAAK;;AAEjD,QAAO"}
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../src/channels/base.ts"],"sourcesContent":["import {\n ReadonlyCheckpoint,\n uuid6,\n Checkpoint,\n DeltaSnapshot,\n type BaseCheckpointSaver,\n type CheckpointMetadata,\n type DeltaChannelHistory,\n} from \"@langchain/langgraph-checkpoint\";\nimport type { RunnableConfig } from \"@langchain/core/runnables\";\nimport { EmptyChannelError } from \"../errors.js\";\nimport { getDeltaMaxSuperstepsSinceSnapshot } from \"../constants.js\";\n\n/** Matches Postgres `uuid` / Python `uuid.UUID` (128-bit, 8-4-4-4-12 hex). */\nconst STRUCTURED_UUID =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Structural check for a {@link DeltaChannel} without importing it (avoids an\n * import cycle: `delta.ts` imports `base.ts`).\n */\nexport function isDeltaChannel(channel: BaseChannel): boolean {\n return channel != null && channel.lc_graph_name === \"DeltaChannel\";\n}\n\nexport function isBaseChannel(obj: unknown): obj is BaseChannel {\n return obj != null && (obj as BaseChannel).lg_is_channel === true;\n}\n\n/** @internal */\nexport abstract class BaseChannel<\n ValueType = unknown,\n UpdateType = unknown,\n CheckpointType = unknown,\n> {\n ValueType: ValueType;\n\n UpdateType: UpdateType;\n\n /**\n * The name of the channel.\n */\n abstract lc_graph_name: string;\n\n /** @ignore */\n lg_is_channel = true;\n\n /**\n * Return a new identical channel, optionally initialized from a checkpoint.\n * Can be thought of as a \"restoration\" from a checkpoint which is a \"snapshot\" of the channel's state.\n *\n * @param {CheckpointType | undefined} checkpoint\n * @param {CheckpointType | undefined} initialValue\n * @returns {this}\n */\n abstract fromCheckpoint(checkpoint?: CheckpointType): this;\n\n /**\n * Update the channel's value with the given sequence of updates.\n * The order of the updates in the sequence is arbitrary.\n * This method is called by Pregel for all channels at the end of each step.\n * If there are no updates, it is called with an empty sequence.\n *\n * Raises InvalidUpdateError if the sequence of updates is invalid.\n * Returns True if the channel was updated, False otherwise.\n *\n * @throws {InvalidUpdateError} if the sequence of updates is invalid.\n * @param {Array<UpdateType>} values\n * @returns {void}\n */\n abstract update(values: UpdateType[]): boolean;\n\n /**\n * Return the current value of the channel.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet).\n * @returns {ValueType}\n */\n abstract get(): ValueType;\n\n /**\n * Return a string representation of the channel's current state.\n *\n * @throws {EmptyChannelError} if the channel is empty (never updated yet), or doesn't support checkpoints.\n * @returns {CheckpointType | undefined}\n */\n abstract checkpoint(): CheckpointType | undefined;\n\n /**\n * Mark the current value of the channel as consumed. By default, no-op.\n * A channel can use this method to modify its state, preventing the value\n * from being consumed again.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n consume(): boolean {\n return false;\n }\n\n /**\n * Notify the channel that the Pregel run is finishing. By default, no-op.\n * A channel can use this method to modify its state, preventing finish.\n *\n * Returns True if the channel was updated, False otherwise.\n */\n finish(): boolean {\n return false;\n }\n\n /**\n * Return True if the channel is available (not empty), False otherwise.\n * Subclasses should override this method to provide a more efficient\n * implementation than calling get() and catching EmptyChannelError.\n */\n isAvailable(): boolean {\n try {\n this.get();\n return true;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Compare this channel with another channel for equality.\n * Used to determine if two channels with the same key are semantically equivalent.\n * Subclasses should override this method to provide a meaningful comparison.\n *\n * @param {BaseChannel} other - The other channel to compare with.\n * @returns {boolean} True if the channels are equal, false otherwise.\n */\n equals(other: BaseChannel): boolean {\n return this === other;\n }\n}\n\nconst IS_ONLY_BASE_CHANNEL = Symbol.for(\"LG_IS_ONLY_BASE_CHANNEL\");\nexport function getOnlyChannels(\n channels: Record<string, BaseChannel>\n): Record<string, BaseChannel> {\n // @ts-expect-error - we know it's a record of base channels\n if (channels[IS_ONLY_BASE_CHANNEL] === true) return channels;\n\n const newChannels = {} as Record<string, BaseChannel>;\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const value = channels[k];\n if (isBaseChannel(value)) newChannels[k] = value;\n }\n\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\nexport function emptyChannels<Cc extends Record<string, BaseChannel>>(\n channels: Cc,\n checkpoint: ReadonlyCheckpoint\n): Cc {\n const filteredChannels = getOnlyChannels(channels) as Cc;\n\n const newChannels = {} as Cc;\n for (const k in filteredChannels) {\n if (!Object.prototype.hasOwnProperty.call(filteredChannels, k)) continue;\n const channelValue = checkpoint.channel_values[k];\n newChannels[k] = filteredChannels[k].fromCheckpoint(channelValue);\n }\n Object.assign(newChannels, { [IS_ONLY_BASE_CHANNEL]: true });\n return newChannels;\n}\n\n/**\n * Minimal structural view of a {@link DeltaChannel}, used by helpers in this\n * module that must not import the concrete class (import-cycle avoidance).\n */\ninterface DeltaChannelLike extends BaseChannel {\n snapshotFrequency: number;\n replayWrites(writes: DeltaChannelHistory[\"writes\"]): void;\n}\n\n/**\n * Synthetic task id for exit-mode DeltaChannel writes.\n *\n * Embeds the superstep in the first UUID group so `ORDER BY task_id, idx`\n * preserves chronological order while remaining a valid RFC UUID (required by\n * Postgres `checkpoint_writes.task_id uuid` columns).\n */\nexport function exitDeltaTaskId(step: number, taskId: string): string {\n if (!STRUCTURED_UUID.test(taskId)) {\n throw new TypeError(`Invalid task id for exit delta: ${taskId}`);\n }\n const parts = taskId.toLowerCase().split(\"-\");\n const stepPart = String(step).padStart(8, \"0\");\n return `${stepPart}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;\n}\n\n/**\n * Return the set of {@link DeltaChannel} names that should snapshot now.\n *\n * A channel snapshots when EITHER its accumulated update count reaches\n * `snapshotFrequency` OR the total supersteps since its last snapshot reaches\n * `DELTA_MAX_SUPERSTEPS_SINCE_SNAPSHOT`. Given `channelVersions`, a channel\n * without a version is skipped: it was never written on this branch, so it\n * has nothing to snapshot. Pass `undefined` on a branch without\n * {@link DELTA_WRITES_VERSIONED}, where such a channel can still hold writes\n * and this snapshot is what bounds its walk. Pure predicate, no mutation.\n */\nexport function deltaChannelsToSnapshot(\n channels: Record<string, BaseChannel>,\n countersSinceDeltaSnapshot: Record<string, [number, number]>,\n channelVersions: Record<string, number | string> | undefined\n): Set<string> {\n const result = new Set<string>();\n const maxSupersteps = getDeltaMaxSuperstepsSinceSnapshot();\n for (const name in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, name)) continue;\n const ch = channels[name];\n if (\n !isDeltaChannel(ch) ||\n !ch.isAvailable() ||\n (channelVersions !== undefined && channelVersions[name] === undefined)\n ) {\n continue;\n }\n const [updates, supersteps] = countersSinceDeltaSnapshot[name] ?? [0, 0];\n if (\n updates >= (ch as DeltaChannelLike).snapshotFrequency ||\n supersteps >= maxSupersteps\n ) {\n result.add(name);\n }\n }\n return result;\n}\n\nexport function createCheckpoint<ValueType>(\n checkpoint: ReadonlyCheckpoint,\n channels: Record<string, BaseChannel<ValueType>> | undefined,\n step: number,\n options?: {\n id?: string;\n channelsToSnapshot?: Set<string>;\n updatedChannels?: Set<string>;\n getNextVersion?: (current: number | string | undefined) => number | string;\n }\n): Checkpoint {\n const channelsToSnapshot = options?.channelsToSnapshot ?? new Set<string>();\n const { updatedChannels, getNextVersion } = options ?? {};\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let values: Record<string, any>;\n let channelVersions: Record<string, number | string> =\n checkpoint.channel_versions;\n if (channels === undefined) {\n values = checkpoint.channel_values;\n } else {\n values = {};\n channelVersions = { ...checkpoint.channel_versions };\n for (const k in channels) {\n if (!Object.prototype.hasOwnProperty.call(channels, k)) continue;\n const channel = channels[k];\n if (channelsToSnapshot.has(k)) {\n // Snapshot a DeltaChannel: store the materialized value directly. In\n // exit/deferred modes the channel may have reached its snapshot\n // threshold over several supersteps without the LAST superstep\n // writing to it, so its version wouldn't be bumped by applyWrites —\n // bump it here so the saver includes the snapshot blob.\n if (\n getNextVersion !== undefined &&\n (updatedChannels === undefined || !updatedChannels.has(k))\n ) {\n channelVersions[k] = getNextVersion(channelVersions[k]);\n }\n values[k] = new DeltaSnapshot(channel.get());\n continue;\n }\n if (isDeltaChannel(channel)) {\n // Omitted from channel_values; reconstructed from ancestor writes.\n continue;\n }\n try {\n values[k] = channel.checkpoint();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (error: any) {\n if (error.name === EmptyChannelError.unminifiable_name) {\n // no-op\n } else {\n throw error; // Rethrow unexpected errors\n }\n }\n }\n }\n\n return {\n v: 4,\n id: options?.id ?? uuid6(step),\n ts: new Date().toISOString(),\n channel_values: values,\n channel_versions: channelVersions,\n versions_seen: checkpoint.versions_seen,\n };\n}\n\n/**\n * Checkpoint metadata key, `true` when every DeltaChannel write on the\n * checkpoint's branch carries a version. A delta channel with no version there\n * was never written, so it needs no walk. Branches started before this key\n * existed never get it: an older `updateState` stored a delta channel's first\n * write without a version, and only the walk finds such a write.\n */\nexport const DELTA_WRITES_VERSIONED = \"delta_writes_versioned\";\n\nexport function isDeltaWritesVersioned(\n metadata: CheckpointMetadata | undefined\n): boolean {\n return (\n (metadata as Record<string, unknown> | undefined)?.[\n DELTA_WRITES_VERSIONED\n ] === true\n );\n}\n\n/**\n * Delta channels whose value at `checkpoint` the ancestor walk rebuilds.\n *\n * A {@link DeltaSnapshot} or a migrated plain value in `channel_values`\n * resolves directly, so only a channel with nothing stored there needs the\n * walk. With `versionedOnly`, a channel with no version was never written, so\n * it is empty without one; a walk for it would find no snapshot to stop at and\n * read every ancestor, every time the thread is loaded.\n */\nfunction deltaChannelsToReplay(\n specs: Record<string, BaseChannel>,\n checkpoint: ReadonlyCheckpoint,\n versionedOnly: boolean\n): string[] {\n const keys: string[] = [];\n for (const k in specs) {\n if (!Object.prototype.hasOwnProperty.call(specs, k)) continue;\n if (\n isDeltaChannel(specs[k]) &&\n (!versionedOnly || checkpoint.channel_versions[k] !== undefined) &&\n !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)\n ) {\n keys.push(k);\n }\n }\n return keys;\n}\n\n/**\n * Hydrate channels from a checkpoint, reconstructing any {@link DeltaChannel}\n * whose value is absent from `channel_values` by replaying ancestor writes.\n *\n * For most channels (and for delta channels with a {@link DeltaSnapshot} or a\n * migrated plain value in `channel_values`), {@link emptyChannels} is\n * sufficient and no saver access is required. When a delta channel is absent\n * from `channel_values`, an ancestor walk via `saver.getDeltaChannelHistory`\n * finds the nearest seed and accumulates the writes between it and the\n * target. All delta channels needing replay are batched into a single saver\n * call. Pass `deltaWritesVersioned` for a checkpoint whose metadata carries\n * {@link DELTA_WRITES_VERSIONED}, so delta channels without a version are\n * left empty instead of walked.\n */\nexport async function channelsFromCheckpoint<\n Cc extends Record<string, BaseChannel>,\n>(\n specs: Cc,\n checkpoint: ReadonlyCheckpoint,\n options?: {\n saver?: BaseCheckpointSaver;\n config?: RunnableConfig;\n deltaWritesVersioned?: boolean;\n }\n): Promise<Cc> {\n const channels = emptyChannels(specs, checkpoint);\n const { saver, config } = options ?? {};\n\n const filteredSpecs = getOnlyChannels(specs);\n const deltaKeys = deltaChannelsToReplay(\n filteredSpecs,\n checkpoint,\n options?.deltaWritesVersioned ?? false\n );\n\n const written = deltaKeys.filter(\n (k) => checkpoint.channel_versions[k] !== undefined\n );\n if ((saver === undefined || config === undefined) && written.length > 0) {\n throw new Error(\n `DeltaChannel ${written.join(\", \")} has history to replay but no checkpointer or config was passed to read it`\n );\n }\n if (deltaKeys.length === 0 || saver === undefined || config === undefined) {\n return channels;\n }\n\n const histories = await saver.getDeltaChannelHistory({\n config,\n channels: deltaKeys,\n });\n for (const k of deltaKeys) {\n const history = histories[k];\n if (history === undefined) continue;\n const replayCh = filteredSpecs[k].fromCheckpoint(\n history.seed\n ) as unknown as DeltaChannelLike;\n replayCh.replayWrites(history.writes);\n (channels as Record<string, BaseChannel>)[k] = replayCh;\n }\n return channels;\n}\n"],"mappings":"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"}
|
package/dist/pregel/index.cjs
CHANGED
|
@@ -488,7 +488,8 @@ var Pregel = class extends PartialRunnable {
|
|
|
488
488
|
};
|
|
489
489
|
const channels = await require_base.channelsFromCheckpoint(this.channels, saved.checkpoint, {
|
|
490
490
|
saver,
|
|
491
|
-
config: saved.config ?? config
|
|
491
|
+
config: saved.config ?? config,
|
|
492
|
+
deltaWritesVersioned: require_base.isDeltaWritesVersioned(saved.metadata)
|
|
492
493
|
});
|
|
493
494
|
if (saved.pendingWrites?.length) {
|
|
494
495
|
const nullWrites = saved.pendingWrites.filter(([taskId, _]) => taskId === require_constants.NULL_TASK_ID).map(([_, channel, value]) => [String(channel), value]);
|
|
@@ -653,18 +654,22 @@ var Pregel = class extends PartialRunnable {
|
|
|
653
654
|
...checkpointMetadata
|
|
654
655
|
};
|
|
655
656
|
}
|
|
657
|
+
const deltaWritesVersioned = Object.values(this.channels).some(require_base.isDeltaChannel) && (saved === void 0 || require_base.isDeltaWritesVersioned(saved.metadata));
|
|
658
|
+
const versionedMetadata = deltaWritesVersioned ? { [require_base.DELTA_WRITES_VERSIONED]: true } : {};
|
|
656
659
|
const { values, asNode } = updates[0];
|
|
657
660
|
if (values == null && asNode === void 0) {
|
|
658
661
|
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot create empty checkpoint with multiple updates`);
|
|
659
662
|
return require_index.patchCheckpointMap(await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, void 0, step), {
|
|
660
663
|
source: "update",
|
|
661
664
|
step: step + 1,
|
|
662
|
-
parents: saved?.metadata?.parents ?? {}
|
|
665
|
+
parents: saved?.metadata?.parents ?? {},
|
|
666
|
+
...versionedMetadata
|
|
663
667
|
}, {}), saved ? saved.metadata : void 0);
|
|
664
668
|
}
|
|
665
669
|
const channels = await require_base.channelsFromCheckpoint(this.channels, checkpoint, {
|
|
666
670
|
saver: checkpointer,
|
|
667
|
-
config: saved?.config ?? checkpointConfig
|
|
671
|
+
config: saved?.config ?? checkpointConfig,
|
|
672
|
+
deltaWritesVersioned
|
|
668
673
|
});
|
|
669
674
|
if (values === null && asNode === "__end__") {
|
|
670
675
|
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot apply multiple updates when clearing state`);
|
|
@@ -691,11 +696,13 @@ var Pregel = class extends PartialRunnable {
|
|
|
691
696
|
}
|
|
692
697
|
require_algo._applyWrites(checkpoint, channels, Object.values(nextTasks), checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
693
698
|
}
|
|
699
|
+
const { [require_base.DELTA_WRITES_VERSIONED]: _carried, ...endMetadata } = checkpointMetadata;
|
|
694
700
|
return require_index.patchCheckpointMap(await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step), {
|
|
695
|
-
...
|
|
701
|
+
...endMetadata,
|
|
696
702
|
source: "update",
|
|
697
703
|
step: step + 1,
|
|
698
|
-
parents: saved?.metadata?.parents ?? {}
|
|
704
|
+
parents: saved?.metadata?.parents ?? {},
|
|
705
|
+
...versionedMetadata
|
|
699
706
|
}, require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions)), saved ? saved.metadata : void 0);
|
|
700
707
|
}
|
|
701
708
|
if (asNode === "__copy__") {
|
|
@@ -710,7 +717,8 @@ var Pregel = class extends PartialRunnable {
|
|
|
710
717
|
const nextConfig = await checkpointer.put(saved.parentConfig ?? require_utils.patchConfigurable(saved.config, { checkpoint_id: void 0 }), nextCheckpoint, {
|
|
711
718
|
source: "fork",
|
|
712
719
|
step: step + 1,
|
|
713
|
-
parents: saved.metadata?.parents ?? {}
|
|
720
|
+
parents: saved.metadata?.parents ?? {},
|
|
721
|
+
...versionedMetadata
|
|
714
722
|
}, {});
|
|
715
723
|
if (isCopyWithUpdates(values)) {
|
|
716
724
|
const nextTasks = require_algo._prepareNextTasks(nextCheckpoint, saved.pendingWrites, this.nodes, channels, nextConfig, false, { step: step + 2 });
|
|
@@ -748,7 +756,8 @@ var Pregel = class extends PartialRunnable {
|
|
|
748
756
|
const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, nextStep), {
|
|
749
757
|
source: "input",
|
|
750
758
|
step: nextStep,
|
|
751
|
-
parents: saved?.metadata?.parents ?? {}
|
|
759
|
+
parents: saved?.metadata?.parents ?? {},
|
|
760
|
+
...versionedMetadata
|
|
752
761
|
}, require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions));
|
|
753
762
|
await checkpointer.putWrites(nextConfig, inputWrites, (0, _langchain_langgraph_checkpoint.uuid5)(require_constants.INPUT, checkpoint.id));
|
|
754
763
|
return require_index.patchCheckpointMap(nextConfig, saved ? saved.metadata : void 0);
|
|
@@ -760,11 +769,11 @@ var Pregel = class extends PartialRunnable {
|
|
|
760
769
|
step: (saved.metadata?.step ?? -1) + 1
|
|
761
770
|
});
|
|
762
771
|
const nullWrites = (saved.pendingWrites ?? []).filter((w) => w[0] === require_constants.NULL_TASK_ID).map((w) => w.slice(1));
|
|
763
|
-
if (nullWrites.length > 0) require_algo._applyWrites(
|
|
772
|
+
if (nullWrites.length > 0) require_algo._applyWrites(checkpoint, channels, [{
|
|
764
773
|
name: require_constants.INPUT,
|
|
765
774
|
writes: nullWrites,
|
|
766
775
|
triggers: []
|
|
767
|
-
}],
|
|
776
|
+
}], checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
768
777
|
for (const [tid, k, v] of saved.pendingWrites) {
|
|
769
778
|
if ([
|
|
770
779
|
"__error__",
|
|
@@ -776,7 +785,7 @@ var Pregel = class extends PartialRunnable {
|
|
|
776
785
|
const tasks = Object.values(nextTasks).filter((task) => {
|
|
777
786
|
return task.writes.length > 0;
|
|
778
787
|
});
|
|
779
|
-
if (tasks.length > 0) require_algo._applyWrites(checkpoint, channels, tasks,
|
|
788
|
+
if (tasks.length > 0) require_algo._applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
780
789
|
}
|
|
781
790
|
const nonNullVersion = Object.values(checkpoint.versions_seen).map((seenVersions) => {
|
|
782
791
|
return Object.values(seenVersions);
|
|
@@ -813,7 +822,7 @@ var Pregel = class extends PartialRunnable {
|
|
|
813
822
|
});
|
|
814
823
|
}
|
|
815
824
|
const tasks = [];
|
|
816
|
-
for (const { asNode, values, taskId } of validUpdates) {
|
|
825
|
+
for (const [i, { asNode, values, taskId }] of validUpdates.entries()) {
|
|
817
826
|
if (this.nodes[asNode] === void 0) throw new require_errors.InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
|
|
818
827
|
const writers = this.nodes[asNode].getWriters();
|
|
819
828
|
if (!writers.length) throw new require_errors.InvalidUpdateError(`No writers found for node "${asNode.toString()}"`);
|
|
@@ -823,7 +832,7 @@ var Pregel = class extends PartialRunnable {
|
|
|
823
832
|
proc: writers.length > 1 ? _langchain_core_runnables.RunnableSequence.from(writers, { omitSequenceTags: true }) : writers[0],
|
|
824
833
|
writes: [],
|
|
825
834
|
triggers: [require_constants.INTERRUPT],
|
|
826
|
-
id: taskId ?? (0, _langchain_langgraph_checkpoint.uuid5)("__interrupt__"
|
|
835
|
+
id: taskId ?? (0, _langchain_langgraph_checkpoint.uuid5)(i === 0 ? "__interrupt__" : `__interrupt__:${i}`, checkpoint.id),
|
|
827
836
|
writers: []
|
|
828
837
|
});
|
|
829
838
|
}
|
|
@@ -842,11 +851,16 @@ var Pregel = class extends PartialRunnable {
|
|
|
842
851
|
if (saved !== void 0 && channelWrites.length > 0) await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
|
|
843
852
|
}
|
|
844
853
|
require_algo._applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
854
|
+
const channelsToSnapshot = /* @__PURE__ */ new Set();
|
|
855
|
+
if (saved === void 0) {
|
|
856
|
+
for (const [k, ch] of Object.entries(channels)) if (require_base.isDeltaChannel(ch) && checkpoint.channel_versions[k] !== void 0) channelsToSnapshot.add(k);
|
|
857
|
+
}
|
|
845
858
|
const newVersions = require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
|
|
846
|
-
const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step + 1), {
|
|
859
|
+
const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step + 1, { channelsToSnapshot }), {
|
|
847
860
|
source: "update",
|
|
848
861
|
step: step + 1,
|
|
849
|
-
parents: saved?.metadata?.parents ?? {}
|
|
862
|
+
parents: saved?.metadata?.parents ?? {},
|
|
863
|
+
...versionedMetadata
|
|
850
864
|
}, newVersions);
|
|
851
865
|
for (const task of tasks) {
|
|
852
866
|
const pushWrites = task.writes.filter((w) => w[0] === require_constants.PUSH);
|