@langchain/langgraph 1.4.18-rc.0 → 1.4.19

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.
@@ -174,6 +174,8 @@ async function channelsFromCheckpoint(specs, checkpoint, options) {
174
174
  if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
175
175
  if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
176
176
  }
177
+ const written = deltaKeys.filter((k) => checkpoint.channel_versions[k] !== void 0);
178
+ 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`);
177
179
  if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
178
180
  const histories = await saver.getDeltaChannelHistory({
179
181
  config,
@@ -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 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 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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"}
@@ -174,6 +174,8 @@ async function channelsFromCheckpoint(specs, checkpoint, options) {
174
174
  if (!Object.prototype.hasOwnProperty.call(filteredSpecs, k)) continue;
175
175
  if (isDeltaChannel(filteredSpecs[k]) && !Object.prototype.hasOwnProperty.call(checkpoint.channel_values, k)) deltaKeys.push(k);
176
176
  }
177
+ const written = deltaKeys.filter((k) => checkpoint.channel_versions[k] !== void 0);
178
+ 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`);
177
179
  if (deltaKeys.length === 0 || saver === void 0 || config === void 0) return channels;
178
180
  const histories = await saver.getDeltaChannelHistory({
179
181
  config,
@@ -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 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.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":"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"}
@@ -424,17 +424,62 @@ var Pregel = class extends PartialRunnable {
424
424
  yield* this.getSubgraphs(namespace, recurse);
425
425
  }
426
426
  /**
427
+ * The checkpointer runs and state methods use: none for `checkpointer: false`,
428
+ * else the one a parent lends a subgraph through the config, else this
429
+ * graph's own.
430
+ */
431
+ _resolveCheckpointer(config) {
432
+ if (this.checkpointer === false) return void 0;
433
+ const lent = config?.configurable?.[require_constants.CONFIG_KEY_CHECKPOINTER];
434
+ if (lent !== void 0) return lent;
435
+ if (this.checkpointer === true) throw new Error("checkpointer: true cannot be used for root graphs.");
436
+ return this.checkpointer;
437
+ }
438
+ _stateCheckpointer(config) {
439
+ const checkpointer = this._resolveCheckpointer(config);
440
+ if (typeof checkpointer !== "object" || checkpointer === null) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
441
+ return checkpointer;
442
+ }
443
+ /**
444
+ * A `checkpointer: true` subgraph keeps one history per thread, stored
445
+ * under its namespace with the task ids removed.
446
+ */
447
+ _ownCheckpointConfig(config) {
448
+ if (this.checkpointer !== true) return config;
449
+ const ns = config.configurable?.["checkpoint_ns"] ?? "";
450
+ return {
451
+ ...config,
452
+ configurable: {
453
+ ...config.configurable,
454
+ [require_constants.CONFIG_KEY_CHECKPOINT_NS]: ns.split("|").map((part) => part.split(":")[0]).join("|")
455
+ }
456
+ };
457
+ }
458
+ /**
459
+ * The subgraph a state method's namespaced config addresses, and the config
460
+ * to call it with, lending it `checkpointer`. `undefined` when the config is
461
+ * for this graph, comes from inside a running task, or names no declared
462
+ * subgraph (a dynamically created one, such as a tool-call subgraph).
463
+ */
464
+ async _subgraphForNamespace(config, checkpointer) {
465
+ const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
466
+ if (checkpointNamespace === "" || config.configurable?.["__pregel_read"] !== void 0 || config.configurable?.["__pregel_checkpointer"] !== void 0) return;
467
+ const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
468
+ for await (const [name, subgraph] of this.getSubgraphsAsync(recastNamespace, true)) if (name === recastNamespace) return [subgraph, require_utils.patchConfigurable(config, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer })];
469
+ }
470
+ /**
427
471
  * Prepares a state snapshot from saved checkpoint data.
428
472
  * This is an internal method used by getState and getStateHistory.
429
473
  *
430
474
  * @param config - Configuration for preparing the snapshot
431
475
  * @param saved - Optional saved checkpoint data
432
- * @param subgraphCheckpointer - Optional checkpointer for subgraphs
476
+ * @param saver - Checkpointer the caller resolved; a subgraph's own `checkpointer` is unset or `true`
477
+ * @param recurse - Whether to include each subgraph task's state
433
478
  * @param applyPendingWrites - Whether to apply pending writes to tasks and then to channels
434
479
  * @returns A snapshot of the graph state
435
480
  * @internal
436
481
  */
437
- async _prepareStateSnapshot({ config, saved, subgraphCheckpointer, applyPendingWrites = false }) {
482
+ async _prepareStateSnapshot({ config, saved, saver, recurse = false, applyPendingWrites = false }) {
438
483
  if (saved === void 0) return {
439
484
  values: {},
440
485
  next: [],
@@ -442,7 +487,7 @@ var Pregel = class extends PartialRunnable {
442
487
  tasks: []
443
488
  };
444
489
  const channels = await require_base.channelsFromCheckpoint(this.channels, saved.checkpoint, {
445
- saver: typeof this.checkpointer === "object" ? this.checkpointer : void 0,
490
+ saver,
446
491
  config: saved.config ?? config
447
492
  });
448
493
  if (saved.pendingWrites?.length) {
@@ -462,10 +507,10 @@ var Pregel = class extends PartialRunnable {
462
507
  const taskStates = {};
463
508
  for (const task of nextTasks) {
464
509
  const matchingSubgraph = subgraphs.find(([name]) => name === task.name);
465
- if (!matchingSubgraph) continue;
510
+ if (!matchingSubgraph || matchingSubgraph[1].checkpointer === false) continue;
466
511
  let taskNs = `${String(task.name)}:${task.id}`;
467
512
  if (parentNamespace) taskNs = `${parentNamespace}|${taskNs}`;
468
- if (subgraphCheckpointer === void 0) {
513
+ if (!recurse) {
469
514
  const config = { configurable: {
470
515
  thread_id: saved.config.configurable?.thread_id,
471
516
  checkpoint_ns: taskNs
@@ -473,7 +518,7 @@ var Pregel = class extends PartialRunnable {
473
518
  taskStates[task.id] = config;
474
519
  } else {
475
520
  const subgraphConfig = { configurable: {
476
- [require_constants.CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,
521
+ [require_constants.CONFIG_KEY_CHECKPOINTER]: saver,
477
522
  thread_id: saved.config.configurable?.thread_id,
478
523
  checkpoint_ns: taskNs
479
524
  } };
@@ -521,19 +566,20 @@ var Pregel = class extends PartialRunnable {
521
566
  * @throws {GraphValueError} If no checkpointer is configured
522
567
  */
523
568
  async getState(config, options) {
524
- const checkpointer = config.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
525
- if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
526
- const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
527
- if (checkpointNamespace !== "" && config.configurable?.["__pregel_read"] === void 0 && config.configurable?.["__pregel_checkpointer"] === void 0) {
528
- const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
529
- for await (const [name, subgraph] of this.getSubgraphsAsync(recastNamespace, true)) if (name === recastNamespace) return await subgraph.getState(require_utils.patchConfigurable(config, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), { subgraphs: options?.subgraphs });
569
+ const checkpointer = this._stateCheckpointer(config);
570
+ const subgraph = await this._subgraphForNamespace(config, checkpointer);
571
+ if (subgraph !== void 0) {
572
+ const [pregel, subgraphConfig] = subgraph;
573
+ return await pregel.getState(subgraphConfig, { subgraphs: options?.subgraphs });
530
574
  }
531
- const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, config);
532
- const saved = await checkpointer.getTuple(config);
575
+ const ownConfig = this._ownCheckpointConfig(config);
576
+ const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, ownConfig);
577
+ const saved = await checkpointer.getTuple(ownConfig);
533
578
  return await this._prepareStateSnapshot({
534
579
  config: mergedConfig,
535
580
  saved,
536
- subgraphCheckpointer: options?.subgraphs ? checkpointer : void 0,
581
+ saver: checkpointer,
582
+ recurse: options?.subgraphs,
537
583
  applyPendingWrites: !config.configurable?.checkpoint_id
538
584
  });
539
585
  }
@@ -551,20 +597,18 @@ var Pregel = class extends PartialRunnable {
551
597
  * @throws {Error} If no checkpointer is configured
552
598
  */
553
599
  async *getStateHistory(config, options) {
554
- const checkpointer = config.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
555
- if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
556
- const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
557
- if (checkpointNamespace !== "" && config.configurable?.["__pregel_checkpointer"] === void 0) {
558
- const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
559
- for await (const [name, pregel] of this.getSubgraphsAsync(recastNamespace, true)) if (name === recastNamespace) {
560
- yield* pregel.getStateHistory(require_utils.patchConfigurable(config, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), options);
561
- return;
562
- }
600
+ const checkpointer = this._stateCheckpointer(config);
601
+ const subgraph = await this._subgraphForNamespace(config, checkpointer);
602
+ if (subgraph !== void 0) {
603
+ const [pregel, subgraphConfig] = subgraph;
604
+ yield* pregel.getStateHistory(subgraphConfig, options);
605
+ return;
563
606
  }
564
- const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, config, { configurable: { checkpoint_ns: checkpointNamespace } });
607
+ const mergedConfig = this._ownCheckpointConfig((0, _langchain_core_runnables.mergeConfigs)(this.config, config, { configurable: { checkpoint_ns: config.configurable?.checkpoint_ns ?? "" } }));
565
608
  for await (const checkpointTuple of checkpointer.list(mergedConfig, options)) yield this._prepareStateSnapshot({
566
609
  config: checkpointTuple.config,
567
- saved: checkpointTuple
610
+ saved: checkpointTuple,
611
+ saver: checkpointer
568
612
  });
569
613
  }
570
614
  /**
@@ -584,18 +628,18 @@ var Pregel = class extends PartialRunnable {
584
628
  * @throws {InvalidUpdateError} If the update cannot be attributed to a node or an update can be only applied in sequence.
585
629
  */
586
630
  async bulkUpdateState(startConfig, supersteps) {
587
- const checkpointer = startConfig.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
588
- if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
631
+ const checkpointer = this._stateCheckpointer(startConfig);
589
632
  if (supersteps.length === 0) throw new Error("No supersteps provided");
590
633
  if (supersteps.some((s) => s.updates.length === 0)) throw new Error("No updates provided");
591
- const checkpointNamespace = startConfig.configurable?.checkpoint_ns ?? "";
592
- if (checkpointNamespace !== "" && startConfig.configurable?.["__pregel_checkpointer"] === void 0) {
593
- const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
594
- for await (const [, pregel] of this.getSubgraphsAsync(recastNamespace, true)) return await pregel.bulkUpdateState(require_utils.patchConfigurable(startConfig, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), supersteps);
595
- throw new Error(`Subgraph "${recastNamespace}" not found`);
634
+ const subgraph = await this._subgraphForNamespace(startConfig, checkpointer);
635
+ if (subgraph !== void 0) {
636
+ const [pregel, subgraphConfig] = subgraph;
637
+ return await pregel.bulkUpdateState(subgraphConfig, supersteps);
596
638
  }
639
+ const checkpointNamespace = startConfig.configurable?.checkpoint_ns ?? "";
640
+ if (checkpointNamespace !== "" && startConfig.configurable?.["__pregel_read"] === void 0 && startConfig.configurable?.["__pregel_checkpointer"] === void 0) throw new Error(`Subgraph "${require_config.recastCheckpointNamespace(checkpointNamespace)}" not found`);
597
641
  const updateSuperStep = async (inputConfig, updates) => {
598
- const config = this.config ? (0, _langchain_core_runnables.mergeConfigs)(this.config, inputConfig) : inputConfig;
642
+ const config = this._ownCheckpointConfig(this.config ? (0, _langchain_core_runnables.mergeConfigs)(this.config, inputConfig) : inputConfig);
599
643
  const saved = await checkpointer.getTuple(config);
600
644
  const checkpoint = saved !== void 0 ? (0, _langchain_langgraph_checkpoint.copyCheckpoint)(saved.checkpoint) : (0, _langchain_langgraph_checkpoint.emptyCheckpoint)();
601
645
  const checkpointPreviousVersions = { ...saved?.checkpoint.channel_versions };
@@ -699,7 +743,7 @@ var Pregel = class extends PartialRunnable {
699
743
  name: require_constants.INPUT,
700
744
  writes: inputWrites,
701
745
  triggers: []
702
- }], checkpointer.getNextVersion.bind(this.checkpointer), this.triggerToNodes);
746
+ }], checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
703
747
  const nextStep = saved?.metadata?.step != null ? saved.metadata.step + 1 : -1;
704
748
  const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, nextStep), {
705
749
  source: "input",
@@ -797,7 +841,7 @@ var Pregel = class extends PartialRunnable {
797
841
  const channelWrites = task.writes.filter((w) => w[0] !== require_constants.PUSH);
798
842
  if (saved !== void 0 && channelWrites.length > 0) await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
799
843
  }
800
- require_algo._applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(this.checkpointer), this.triggerToNodes);
844
+ require_algo._applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
801
845
  const newVersions = require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
802
846
  const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step + 1), {
803
847
  source: "update",
@@ -877,11 +921,7 @@ var Pregel = class extends PartialRunnable {
877
921
  else defaultStreamMode = this.streamMode;
878
922
  streamModeSingle = true;
879
923
  }
880
- let defaultCheckpointer;
881
- if (this.checkpointer === false) defaultCheckpointer = void 0;
882
- else if (config !== void 0 && config.configurable?.["__pregel_checkpointer"] !== void 0) defaultCheckpointer = config.configurable[require_constants.CONFIG_KEY_CHECKPOINTER];
883
- else if (this.checkpointer === true) throw new Error("checkpointer: true cannot be used for root graphs.");
884
- else defaultCheckpointer = this.checkpointer;
924
+ const defaultCheckpointer = this._resolveCheckpointer(config);
885
925
  const defaultStore = config.store ?? this.store;
886
926
  const defaultCache = config.cache ?? this.cache;
887
927
  if (config.durability != null && config.checkpointDuring != null) throw new Error("Cannot use both `durability` and `checkpointDuring` at the same time.");
@@ -1021,11 +1061,7 @@ var Pregel = class extends PartialRunnable {
1021
1061
  if (typeof config.context !== "undefined") config.context = await this._validateContext(config.context);
1022
1062
  else config.configurable = await this._validateContext(config.configurable);
1023
1063
  const stream = new require_stream.IterableReadableWritableStream({ modes: new Set(streamMode) });
1024
- if (this.checkpointer === true) {
1025
- config.configurable ??= {};
1026
- const ns = config.configurable["checkpoint_ns"] ?? "";
1027
- config.configurable[require_constants.CONFIG_KEY_CHECKPOINT_NS] = ns.split("|").map((part) => part.split(":")[0]).join("|");
1028
- }
1064
+ config.configurable = this._ownCheckpointConfig(config).configurable;
1029
1065
  if (streamMode.includes("messages")) {
1030
1066
  const messageStreamer = isV3 ? new require_messages_v2.StreamProtocolMessagesHandler((chunk) => stream.push(chunk)) : new require_messages.StreamMessagesHandler((chunk) => stream.push(chunk));
1031
1067
  const { callbacks } = config;