UNPKG

@langchain/langgraph

Version:

LangGraph

1 lines 95.6 kB
{"version":3,"file":"index.cjs","names":["channelMappingOrArray: string[] | Record<string, string>","triggers: string[]","PregelNode","channelWriteEntries: Array<ChannelWriteEntry>","PASSTHROUGH","Runnable","ChannelWrite","TASKS","Topic","findSubgraphPregel","CHECKPOINT_NAMESPACE_SEPARATOR","emptyChannels","NULL_TASK_ID","INPUT","_prepareNextTasks","gatherIterator","taskStates: Record<string, RunnableConfig | StateSnapshot>","CHECKPOINT_NAMESPACE_END","config: RunnableConfig","config","subgraphConfig: RunnableConfig","CONFIG_KEY_CHECKPOINTER","ERROR","INTERRUPT","SCHEDULED","tasksWithWrites","readChannels","patchCheckpointMap","GraphValueError","checkpointNamespace: string","recastCheckpointNamespace","patchConfigurable","checkpointer: BaseCheckpointSaver","checkpointer: BaseCheckpointSaver | undefined","InvalidUpdateError","nextConfig","createCheckpoint","END","getNewChannelVersions","COPY","values","asNode","mapInput","tasks","validUpdates: Array<{\n values: Record<string, unknown> | unknown;\n asNode: keyof Nodes | string;\n taskId?: string;\n }>","tasks: PregelExecutableTask<keyof Nodes, keyof Channels>[]","RunnableSequence","CONFIG_KEY_SEND","CONFIG_KEY_READ","_localRead","PUSH","defaultStreamMode: StreamMode[]","CONFIG_KEY_TASK_ID","defaultCheckpointer: BaseCheckpointSaver | undefined","defaultStore: BaseStore | undefined","defaultCache: BaseCache | undefined","checkpointDuringDurability: Durability | undefined","defaultDurability: Durability","CONFIG_KEY_DURABILITY","combineAbortSignals","IterableReadableStreamWithAbortSignal","toEventStream","combineCallbacks","ensureLangGraphConfig","IterableReadableWritableStream","ns: string","CONFIG_KEY_CHECKPOINT_NS","StreamMessagesHandler","getConfig","interrupt","_coerceToDict","getOnlyChannels","loop: PregelLoop | undefined","loopError: unknown","PregelLoop","PregelRunner","CONFIG_KEY_NODE_FINISHED","CONFIG_KEY_STREAM","interruptChunks: Interrupt[][]","latest: OutputType | undefined","isInterrupted","GraphRecursionError"],"sources":["../../src/pregel/index.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport {\n _coerceToRunnable,\n getCallbackManagerForConfig,\n mergeConfigs,\n patchConfig,\n Runnable,\n RunnableConfig,\n RunnableFunc,\n RunnableLike,\n RunnableSequence,\n} from \"@langchain/core/runnables\";\nimport type { StreamEvent } from \"@langchain/core/tracers/log_stream\";\nimport { IterableReadableStream } from \"@langchain/core/utils/stream\";\nimport {\n All,\n BaseCache,\n BaseCheckpointSaver,\n BaseStore,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n compareChannelVersions,\n copyCheckpoint,\n emptyCheckpoint,\n PendingWrite,\n SCHEDULED,\n SendProtocol,\n uuid5,\n} from \"@langchain/langgraph-checkpoint\";\nimport {\n BaseChannel,\n createCheckpoint,\n emptyChannels,\n getOnlyChannels,\n} from \"../channels/base.js\";\nimport {\n CHECKPOINT_NAMESPACE_END,\n CHECKPOINT_NAMESPACE_SEPARATOR,\n Command,\n CONFIG_KEY_CHECKPOINTER,\n CONFIG_KEY_NODE_FINISHED,\n CONFIG_KEY_READ,\n CONFIG_KEY_SEND,\n CONFIG_KEY_STREAM,\n CONFIG_KEY_TASK_ID,\n COPY,\n END,\n ERROR,\n INPUT,\n INTERRUPT,\n Interrupt,\n isInterrupted,\n NULL_TASK_ID,\n PUSH,\n CONFIG_KEY_DURABILITY,\n CONFIG_KEY_CHECKPOINT_NS,\n type CommandInstance,\n TASKS,\n} from \"../constants.js\";\nimport {\n GraphRecursionError,\n GraphValueError,\n InvalidUpdateError,\n} from \"../errors.js\";\nimport { gatherIterator, patchConfigurable } from \"../utils.js\";\nimport {\n _applyWrites,\n _localRead,\n _prepareNextTasks,\n StrRecord,\n WritesProtocol,\n} from \"./algo.js\";\nimport {\n printStepCheckpoint,\n printStepTasks,\n printStepWrites,\n tasksWithWrites,\n} from \"./debug.js\";\nimport { mapInput, readChannels } from \"./io.js\";\nimport { PregelLoop } from \"./loop.js\";\nimport { StreamMessagesHandler } from \"./messages.js\";\nimport { PregelNode } from \"./read.js\";\nimport { LangGraphRunnableConfig } from \"./runnable_types.js\";\nimport { PregelRunner } from \"./runner.js\";\nimport {\n IterableReadableStreamWithAbortSignal,\n IterableReadableWritableStream,\n toEventStream,\n} from \"./stream.js\";\nimport type {\n Durability,\n GetStateOptions,\n MultipleChannelSubscriptionOptions,\n PregelExecutableTask,\n PregelInputType,\n PregelInterface,\n PregelOptions,\n PregelOutputType,\n PregelParams,\n SingleChannelSubscriptionOptions,\n StateSnapshot,\n StreamMode,\n StreamOutputMap,\n} from \"./types.js\";\nimport {\n ensureLangGraphConfig,\n getConfig,\n recastCheckpointNamespace,\n} from \"./utils/config.js\";\nimport {\n _coerceToDict,\n combineAbortSignals,\n combineCallbacks,\n getNewChannelVersions,\n patchCheckpointMap,\n RetryPolicy,\n} from \"./utils/index.js\";\nimport { findSubgraphPregel } from \"./utils/subgraph.js\";\nimport { validateGraph, validateKeys } from \"./validate.js\";\nimport { ChannelWrite, ChannelWriteEntry, PASSTHROUGH } from \"./write.js\";\nimport { Topic } from \"../channels/topic.js\";\nimport { interrupt } from \"../interrupt.js\";\n\ntype WriteValue = Runnable | RunnableFunc<unknown, unknown> | unknown;\ntype StreamEventsOptions = Parameters<Runnable[\"streamEvents\"]>[2];\n\n/**\n * Utility class for working with channels in the Pregel system.\n * Provides static methods for subscribing to channels and writing to them.\n *\n * Channels are the communication pathways between nodes in a Pregel graph.\n * They enable message passing and state updates between different parts of the graph.\n */\nexport class Channel {\n /**\n * Creates a PregelNode that subscribes to a single channel.\n * This is used to define how nodes receive input from channels.\n *\n * @example\n * ```typescript\n * // Subscribe to a single channel\n * const node = Channel.subscribeTo(\"messages\");\n *\n * // Subscribe to multiple channels\n * const node = Channel.subscribeTo([\"messages\", \"state\"]);\n *\n * // Subscribe with a custom key\n * const node = Channel.subscribeTo(\"messages\", { key: \"chat\" });\n * ```\n *\n * @param channel Single channel name to subscribe to\n * @param options Subscription options\n * @returns A PregelNode configured to receive from the specified channels\n * @throws {Error} If a key is specified when subscribing to multiple channels\n */\n static subscribeTo(\n channel: string,\n options?: SingleChannelSubscriptionOptions\n ): PregelNode;\n\n /**\n * Creates a PregelNode that subscribes to multiple channels.\n * This is used to define how nodes receive input from channels.\n *\n * @example\n * ```typescript\n * // Subscribe to a single channel\n * const node = Channel.subscribeTo(\"messages\");\n *\n * // Subscribe to multiple channels\n * const node = Channel.subscribeTo([\"messages\", \"state\"]);\n *\n * // Subscribe with a custom key\n * const node = Channel.subscribeTo(\"messages\", { key: \"chat\" });\n * ```\n *\n * @param channels Single channel name to subscribe to\n * @param options Subscription options\n * @returns A PregelNode configured to receive from the specified channels\n * @throws {Error} If a key is specified when subscribing to multiple channels\n */\n static subscribeTo(\n channels: string[],\n options?: MultipleChannelSubscriptionOptions\n ): PregelNode;\n\n static subscribeTo(\n channels: string | string[],\n options?:\n | SingleChannelSubscriptionOptions\n | MultipleChannelSubscriptionOptions\n ): PregelNode {\n const { key, tags } = {\n key: undefined,\n tags: undefined,\n ...(options ?? {}),\n };\n if (Array.isArray(channels) && key !== undefined) {\n throw new Error(\n \"Can't specify a key when subscribing to multiple channels\"\n );\n }\n\n let channelMappingOrArray: string[] | Record<string, string>;\n\n if (typeof channels === \"string\") {\n if (key) {\n channelMappingOrArray = { [key]: channels };\n } else {\n channelMappingOrArray = [channels];\n }\n } else {\n channelMappingOrArray = Object.fromEntries(\n channels.map((chan) => [chan, chan])\n );\n }\n\n const triggers: string[] = Array.isArray(channels) ? channels : [channels];\n\n return new PregelNode({\n channels: channelMappingOrArray,\n triggers,\n tags,\n });\n }\n\n /**\n * Creates a ChannelWrite that specifies how to write values to channels.\n * This is used to define how nodes send output to channels.\n *\n * @example\n * ```typescript\n * // Write to multiple channels\n * const write = Channel.writeTo([\"output\", \"state\"]);\n *\n * // Write with specific values\n * const write = Channel.writeTo([\"output\"], {\n * state: \"completed\",\n * result: calculateResult()\n * });\n *\n * // Write with a transformation function\n * const write = Channel.writeTo([\"output\"], {\n * result: (x) => processResult(x)\n * });\n * ```\n *\n * @param channels - Array of channel names to write to\n * @param writes - Optional map of channel names to values or transformations\n * @returns A ChannelWrite object that can be used to write to the specified channels\n */\n static writeTo(\n channels: string[],\n writes?: Record<string, WriteValue>\n ): ChannelWrite {\n const channelWriteEntries: Array<ChannelWriteEntry> = [];\n\n for (const channel of channels) {\n channelWriteEntries.push({\n channel,\n value: PASSTHROUGH,\n skipNone: false,\n });\n }\n\n for (const [key, value] of Object.entries(writes ?? {})) {\n if (Runnable.isRunnable(value) || typeof value === \"function\") {\n channelWriteEntries.push({\n channel: key,\n value: PASSTHROUGH,\n skipNone: true,\n mapper: _coerceToRunnable(value as RunnableLike),\n });\n } else {\n channelWriteEntries.push({\n channel: key,\n value,\n skipNone: false,\n });\n }\n }\n\n return new ChannelWrite(channelWriteEntries);\n }\n}\n\nexport type { PregelInputType, PregelOptions, PregelOutputType };\n\n// This is a workaround to allow Pregel to override `invoke` / `stream` and `withConfig`\n// without having to adhere to the types in the `Runnable` class (thanks to `any`).\n// Alternatively we could mark those methods with @ts-ignore / @ts-expect-error,\n// but these do not get carried over when building via `tsc`.\nclass PartialRunnable<\n RunInput,\n RunOutput,\n CallOptions extends RunnableConfig\n> extends Runnable<RunInput, RunOutput, CallOptions> {\n lc_namespace = [\"langgraph\", \"pregel\"];\n\n override invoke(\n _input: RunInput,\n _options?: Partial<CallOptions>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n throw new Error(\"Not implemented\");\n }\n\n // Overriden by `Pregel`\n override withConfig(_config: CallOptions): typeof this {\n return super.withConfig(_config) as typeof this;\n }\n\n // Overriden by `Pregel`\n override stream(\n input: RunInput,\n options?: Partial<CallOptions>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<IterableReadableStream<any>> {\n return super.stream(input, options);\n }\n}\n\n/**\n * The Pregel class is the core runtime engine of LangGraph, implementing a message-passing graph computation model\n * inspired by [Google's Pregel system](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/).\n * It provides the foundation for building reliable, controllable agent workflows that can evolve state over time.\n *\n * Key features:\n * - Message passing between nodes in discrete \"supersteps\"\n * - Built-in persistence layer through checkpointers\n * - First-class streaming support for values, updates, and events\n * - Human-in-the-loop capabilities via interrupts\n * - Support for parallel node execution within supersteps\n *\n * The Pregel class is not intended to be instantiated directly by consumers. Instead, use the following higher-level APIs:\n * - {@link StateGraph}: The main graph class for building agent workflows\n * - Compiling a {@link StateGraph} will return a {@link CompiledGraph} instance, which extends `Pregel`\n * - Functional API: A declarative approach using tasks and entrypoints\n * - A `Pregel` instance is returned by the {@link entrypoint} function\n *\n * @example\n * ```typescript\n * // Using StateGraph API\n * const graph = new StateGraph(annotation)\n * .addNode(\"nodeA\", myNodeFunction)\n * .addEdge(\"nodeA\", \"nodeB\")\n * .compile();\n *\n * // The compiled graph is a Pregel instance\n * const result = await graph.invoke(input);\n * ```\n *\n * @example\n * ```typescript\n * // Using Functional API\n * import { task, entrypoint } from \"@langchain/langgraph\";\n * import { MemorySaver } from \"@langchain/langgraph-checkpoint\";\n *\n * // Define tasks that can be composed\n * const addOne = task(\"add\", async (x: number) => x + 1);\n *\n * // Create a workflow using the entrypoint function\n * const workflow = entrypoint({\n * name: \"workflow\",\n * checkpointer: new MemorySaver()\n * }, async (numbers: number[]) => {\n * // Tasks can be run in parallel\n * const results = await Promise.all(numbers.map(n => addOne(n)));\n * return results;\n * });\n *\n * // The workflow is a Pregel instance\n * const result = await workflow.invoke([1, 2, 3]); // Returns [2, 3, 4]\n * ```\n *\n * @typeParam Nodes - Mapping of node names to their {@link PregelNode} implementations\n * @typeParam Channels - Mapping of channel names to their {@link BaseChannel} or {@link ManagedValueSpec} implementations\n * @typeParam ContextType - Type of context that can be passed to the graph\n * @typeParam InputType - Type of input values accepted by the graph\n * @typeParam OutputType - Type of output values produced by the graph\n */\nexport class Pregel<\n Nodes extends StrRecord<string, PregelNode>,\n Channels extends StrRecord<string, BaseChannel>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ContextType extends Record<string, any> = StrRecord<string, any>,\n InputType = PregelInputType,\n OutputType = PregelOutputType,\n StreamUpdatesType = InputType,\n StreamValuesType = OutputType,\n NodeReturnType = unknown,\n CommandType = CommandInstance,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n StreamCustom = any\n >\n extends PartialRunnable<\n InputType | CommandType | null,\n OutputType,\n PregelOptions<Nodes, Channels, ContextType>\n >\n implements PregelInterface<Nodes, Channels, ContextType>\n{\n /**\n * Name of the class when serialized\n * @internal\n */\n static lc_name() {\n return \"LangGraph\";\n }\n\n /** @internal Used for type inference */\n declare \"~InputType\": InputType;\n\n /** @internal Used for type inference */\n declare \"~OutputType\": OutputType;\n\n /** @internal LangChain namespace for serialization necessary because Pregel extends Runnable */\n lc_namespace = [\"langgraph\", \"pregel\"];\n\n /** @internal Flag indicating this is a Pregel instance - necessary for serialization */\n lg_is_pregel = true;\n\n /** The nodes in the graph, mapping node names to their PregelNode instances */\n nodes: Nodes;\n\n /** The channels in the graph, mapping channel names to their BaseChannel or ManagedValueSpec instances */\n channels: Channels;\n\n /**\n * The input channels for the graph. These channels receive the initial input when the graph is invoked.\n * Can be a single channel key or an array of channel keys.\n */\n inputChannels: keyof Channels | Array<keyof Channels>;\n\n /**\n * The output channels for the graph. These channels contain the final output when the graph completes.\n * Can be a single channel key or an array of channel keys.\n */\n outputChannels: keyof Channels | Array<keyof Channels>;\n\n /** Whether to automatically validate the graph structure when it is compiled. Defaults to true. */\n autoValidate: boolean = true;\n\n /**\n * The streaming modes enabled for this graph. Defaults to [\"values\"].\n * Supported modes:\n * - \"values\": Streams the full state after each step\n * - \"updates\": Streams state updates after each step\n * - \"messages\": Streams messages from within nodes\n * - \"custom\": Streams custom events from within nodes\n * - \"debug\": Streams events related to the execution of the graph - useful for tracing & debugging graph execution\n */\n streamMode: StreamMode[] = [\"values\"];\n\n /**\n * Optional channels to stream. If not specified, all channels will be streamed.\n * Can be a single channel key or an array of channel keys.\n */\n streamChannels?: keyof Channels | Array<keyof Channels>;\n\n /**\n * Optional array of node names or \"all\" to interrupt after executing these nodes.\n * Used for implementing human-in-the-loop workflows.\n */\n interruptAfter?: Array<keyof Nodes> | All;\n\n /**\n * Optional array of node names or \"all\" to interrupt before executing these nodes.\n * Used for implementing human-in-the-loop workflows.\n */\n interruptBefore?: Array<keyof Nodes> | All;\n\n /** Optional timeout in milliseconds for the execution of each superstep */\n stepTimeout?: number;\n\n /** Whether to enable debug logging. Defaults to false. */\n debug: boolean = false;\n\n /**\n * Optional checkpointer for persisting graph state.\n * When provided, saves a checkpoint of the graph state at every superstep.\n * When false or undefined, checkpointing is disabled, and the graph will not be able to save or restore state.\n */\n checkpointer?: BaseCheckpointSaver | boolean;\n\n /** Optional retry policy for handling failures in node execution */\n retryPolicy?: RetryPolicy;\n\n /** The default configuration for graph execution, can be overridden on a per-invocation basis */\n config?: LangGraphRunnableConfig;\n\n /**\n * Optional long-term memory store for the graph, allows for persistence & retrieval of data across threads\n */\n store?: BaseStore;\n\n /**\n * Optional cache for the graph, useful for caching tasks.\n */\n cache?: BaseCache;\n\n /**\n * Optional interrupt helper function.\n * @internal\n */\n private userInterrupt?: unknown;\n\n /**\n * The trigger to node mapping for the graph run.\n * @internal\n */\n private triggerToNodes: Record<string, string[]> = {};\n\n /**\n * Constructor for Pregel - meant for internal use only.\n *\n * @internal\n */\n constructor(fields: PregelParams<Nodes, Channels>) {\n super(fields);\n\n let { streamMode } = fields;\n if (streamMode != null && !Array.isArray(streamMode)) {\n streamMode = [streamMode];\n }\n\n this.nodes = fields.nodes;\n this.channels = fields.channels;\n\n if (\n TASKS in this.channels &&\n \"lc_graph_name\" in this.channels[TASKS] &&\n this.channels[TASKS].lc_graph_name !== \"Topic\"\n ) {\n throw new Error(\n `Channel '${TASKS}' is reserved and cannot be used in the graph.`\n );\n } else {\n (this.channels as Record<string, BaseChannel>)[TASKS] =\n new Topic<SendProtocol>({ accumulate: false });\n }\n\n this.autoValidate = fields.autoValidate ?? this.autoValidate;\n this.streamMode = streamMode ?? this.streamMode;\n this.inputChannels = fields.inputChannels;\n this.outputChannels = fields.outputChannels;\n this.streamChannels = fields.streamChannels ?? this.streamChannels;\n this.interruptAfter = fields.interruptAfter;\n this.interruptBefore = fields.interruptBefore;\n this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;\n this.debug = fields.debug ?? this.debug;\n this.checkpointer = fields.checkpointer;\n this.retryPolicy = fields.retryPolicy;\n this.config = fields.config;\n this.store = fields.store;\n this.cache = fields.cache;\n this.name = fields.name;\n this.triggerToNodes = fields.triggerToNodes ?? this.triggerToNodes;\n this.userInterrupt = fields.userInterrupt;\n\n if (this.autoValidate) {\n this.validate();\n }\n }\n\n /**\n * Creates a new instance of the Pregel graph with updated configuration.\n * This method follows the immutable pattern - instead of modifying the current instance,\n * it returns a new instance with the merged configuration.\n *\n * @example\n * ```typescript\n * // Create a new instance with debug enabled\n * const debugGraph = graph.withConfig({ debug: true });\n *\n * // Create a new instance with a specific thread ID\n * const threadGraph = graph.withConfig({\n * configurable: { thread_id: \"123\" }\n * });\n * ```\n *\n * @param config - The configuration to merge with the current configuration\n * @returns A new Pregel instance with the merged configuration\n */\n override withConfig(\n config: Omit<LangGraphRunnableConfig, \"store\" | \"writer\" | \"interrupt\">\n ): typeof this {\n const mergedConfig = mergeConfigs(this.config, config);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new (this.constructor as any)({ ...this, config: mergedConfig });\n }\n\n /**\n * Validates the graph structure to ensure it is well-formed.\n * Checks for:\n * - No orphaned nodes\n * - Valid input/output channel configurations\n * - Valid interrupt configurations\n *\n * @returns this - The Pregel instance for method chaining\n * @throws {GraphValidationError} If the graph structure is invalid\n */\n validate(): this {\n validateGraph<Nodes, Channels>({\n nodes: this.nodes,\n channels: this.channels,\n outputChannels: this.outputChannels,\n inputChannels: this.inputChannels,\n streamChannels: this.streamChannels,\n interruptAfterNodes: this.interruptAfter,\n interruptBeforeNodes: this.interruptBefore,\n });\n\n for (const [name, node] of Object.entries(this.nodes)) {\n for (const trigger of node.triggers) {\n this.triggerToNodes[trigger] ??= [];\n this.triggerToNodes[trigger].push(name);\n }\n }\n\n return this;\n }\n\n /**\n * Gets a list of all channels that should be streamed.\n * If streamChannels is specified, returns those channels.\n * Otherwise, returns all channels in the graph.\n *\n * @returns Array of channel keys to stream\n */\n get streamChannelsList(): Array<keyof Channels> {\n if (Array.isArray(this.streamChannels)) {\n return this.streamChannels;\n } else if (this.streamChannels) {\n return [this.streamChannels];\n } else {\n return Object.keys(this.channels);\n }\n }\n\n /**\n * Gets the channels to stream in their original format.\n * If streamChannels is specified, returns it as-is (either single key or array).\n * Otherwise, returns all channels in the graph as an array.\n *\n * @returns Channel keys to stream, either as a single key or array\n */\n get streamChannelsAsIs(): keyof Channels | Array<keyof Channels> {\n if (this.streamChannels) {\n return this.streamChannels;\n } else {\n return Object.keys(this.channels);\n }\n }\n\n /**\n * Gets a drawable representation of the graph structure.\n * This is an async version of getGraph() and is the preferred method to use.\n *\n * @param config - Configuration for generating the graph visualization\n * @returns A representation of the graph that can be visualized\n */\n async getGraphAsync(config: RunnableConfig) {\n return this.getGraph(config);\n }\n\n /**\n * Gets all subgraphs within this graph.\n * A subgraph is a Pregel instance that is nested within a node of this graph.\n *\n * @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release.\n * @param namespace - Optional namespace to filter subgraphs\n * @param recurse - Whether to recursively get subgraphs of subgraphs\n * @returns Generator yielding tuples of [name, subgraph]\n */\n *getSubgraphs(\n namespace?: string,\n recurse?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Generator<[string, Pregel<any, any>]> {\n for (const [name, node] of Object.entries(this.nodes)) {\n // filter by prefix\n if (namespace !== undefined) {\n if (!namespace.startsWith(name)) {\n continue;\n }\n }\n // find the subgraph if any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type SubgraphPregelType = Pregel<any, any> | undefined;\n\n const candidates = node.subgraphs?.length ? node.subgraphs : [node.bound];\n\n for (const candidate of candidates) {\n const graph = findSubgraphPregel(candidate) as SubgraphPregelType;\n\n if (graph !== undefined) {\n if (name === namespace) {\n yield [name, graph];\n return;\n }\n\n if (namespace === undefined) {\n yield [name, graph];\n }\n\n if (recurse) {\n let newNamespace = namespace;\n if (namespace !== undefined) {\n newNamespace = namespace.slice(name.length + 1);\n }\n for (const [subgraphName, subgraph] of graph.getSubgraphs(\n newNamespace,\n recurse\n )) {\n yield [\n `${name}${CHECKPOINT_NAMESPACE_SEPARATOR}${subgraphName}`,\n subgraph,\n ];\n }\n }\n }\n }\n }\n }\n\n /**\n * Gets all subgraphs within this graph asynchronously.\n * A subgraph is a Pregel instance that is nested within a node of this graph.\n *\n * @param namespace - Optional namespace to filter subgraphs\n * @param recurse - Whether to recursively get subgraphs of subgraphs\n * @returns AsyncGenerator yielding tuples of [name, subgraph]\n */\n async *getSubgraphsAsync(\n namespace?: string,\n recurse?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): AsyncGenerator<[string, Pregel<any, any>]> {\n yield* this.getSubgraphs(namespace, recurse);\n }\n\n /**\n * Prepares a state snapshot from saved checkpoint data.\n * This is an internal method used by getState and getStateHistory.\n *\n * @param config - Configuration for preparing the snapshot\n * @param saved - Optional saved checkpoint data\n * @param subgraphCheckpointer - Optional checkpointer for subgraphs\n * @param applyPendingWrites - Whether to apply pending writes to tasks and then to channels\n * @returns A snapshot of the graph state\n * @internal\n */\n protected async _prepareStateSnapshot({\n config,\n saved,\n subgraphCheckpointer,\n applyPendingWrites = false,\n }: {\n config: RunnableConfig;\n saved?: CheckpointTuple;\n subgraphCheckpointer?: BaseCheckpointSaver;\n applyPendingWrites?: boolean;\n }): Promise<StateSnapshot> {\n if (saved === undefined) {\n return {\n values: {},\n next: [],\n config,\n tasks: [],\n };\n }\n\n // Create all channels\n const channels = emptyChannels(\n this.channels as Record<string, BaseChannel>,\n saved.checkpoint\n );\n\n // Apply null writes first (from NULL_TASK_ID)\n if (saved.pendingWrites?.length) {\n const nullWrites = saved.pendingWrites\n .filter(([taskId, _]) => taskId === NULL_TASK_ID)\n .map(\n ([_, channel, value]) => [String(channel), value] as [string, unknown]\n );\n\n if (nullWrites.length > 0) {\n _applyWrites(\n saved.checkpoint,\n channels,\n [\n {\n name: INPUT,\n writes: nullWrites as PendingWrite[],\n triggers: [],\n },\n ],\n undefined,\n this.triggerToNodes\n );\n }\n }\n\n // Prepare next tasks\n const nextTasks = Object.values(\n _prepareNextTasks(\n saved.checkpoint,\n saved.pendingWrites,\n this.nodes,\n channels,\n saved.config,\n true,\n { step: (saved.metadata?.step ?? -1) + 1, store: this.store }\n )\n );\n\n // Find subgraphs\n const subgraphs = await gatherIterator(this.getSubgraphsAsync());\n const parentNamespace = saved.config.configurable?.checkpoint_ns ?? \"\";\n const taskStates: Record<string, RunnableConfig | StateSnapshot> = {};\n\n // Prepare task states for subgraphs\n for (const task of nextTasks) {\n const matchingSubgraph = subgraphs.find(([name]) => name === task.name);\n if (!matchingSubgraph) {\n continue;\n }\n // assemble checkpoint_ns for this task\n let taskNs = `${String(task.name)}${CHECKPOINT_NAMESPACE_END}${task.id}`;\n if (parentNamespace) {\n taskNs = `${parentNamespace}${CHECKPOINT_NAMESPACE_SEPARATOR}${taskNs}`;\n }\n if (subgraphCheckpointer === undefined) {\n // set config as signal that subgraph checkpoints exist\n const config: RunnableConfig = {\n configurable: {\n thread_id: saved.config.configurable?.thread_id,\n checkpoint_ns: taskNs,\n },\n };\n taskStates[task.id] = config;\n } else {\n // get the state of the subgraph\n const subgraphConfig: RunnableConfig = {\n configurable: {\n [CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,\n thread_id: saved.config.configurable?.thread_id,\n checkpoint_ns: taskNs,\n },\n };\n const pregel = matchingSubgraph[1];\n taskStates[task.id] = await pregel.getState(subgraphConfig, {\n subgraphs: true,\n });\n }\n }\n\n // Apply pending writes to tasks and then to channels if applyPendingWrites is true\n if (applyPendingWrites && saved.pendingWrites?.length) {\n // Map task IDs to task objects for easy lookup\n const nextTaskById = Object.fromEntries(\n nextTasks.map((task) => [task.id, task])\n );\n\n // Apply pending writes to the appropriate tasks\n for (const [taskId, channel, value] of saved.pendingWrites) {\n // Skip special channels and tasks not in nextTasks\n if ([ERROR, INTERRUPT, SCHEDULED].includes(channel)) {\n continue;\n }\n if (!(taskId in nextTaskById)) {\n continue;\n }\n // Add the write to the task\n nextTaskById[taskId].writes.push([String(channel), value]);\n }\n\n // Apply writes from tasks that have writes\n const tasksWithWrites = nextTasks.filter(\n (task) => task.writes.length > 0\n );\n if (tasksWithWrites.length > 0) {\n _applyWrites(\n saved.checkpoint,\n channels,\n tasksWithWrites as unknown as WritesProtocol[],\n undefined,\n this.triggerToNodes\n );\n }\n }\n\n // Preserve thread_id from the config in metadata\n let metadata = saved?.metadata;\n if (metadata && saved?.config?.configurable?.thread_id) {\n metadata = {\n ...metadata,\n thread_id: saved.config.configurable.thread_id as string,\n } as CheckpointMetadata;\n }\n\n // Filter next tasks - only include tasks without writes\n const nextList = nextTasks\n .filter((task) => task.writes.length === 0)\n .map((task) => task.name as string);\n\n // assemble the state snapshot\n return {\n values: readChannels(\n channels,\n this.streamChannelsAsIs as string | string[]\n ),\n next: nextList,\n tasks: tasksWithWrites(\n nextTasks,\n saved?.pendingWrites ?? [],\n taskStates,\n this.streamChannelsAsIs\n ),\n metadata,\n config: patchCheckpointMap(saved.config, saved.metadata),\n createdAt: saved.checkpoint.ts,\n parentConfig: saved.parentConfig,\n };\n }\n\n /**\n * Gets the current state of the graph.\n * Requires a checkpointer to be configured.\n *\n * @param config - Configuration for retrieving the state\n * @param options - Additional options\n * @returns A snapshot of the current graph state\n * @throws {GraphValueError} If no checkpointer is configured\n */\n async getState(\n config: RunnableConfig,\n options?: GetStateOptions\n ): Promise<StateSnapshot> {\n const checkpointer =\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n\n const checkpointNamespace: string =\n config.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n // remove task_ids from checkpoint_ns\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n for await (const [name, subgraph] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n if (name === recastNamespace) {\n return await subgraph.getState(\n patchConfigurable(config, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n { subgraphs: options?.subgraphs }\n );\n }\n }\n throw new Error(\n `Subgraph with namespace \"${recastNamespace}\" not found.`\n );\n }\n\n const mergedConfig = mergeConfigs(this.config, config);\n const saved = await checkpointer.getTuple(config);\n const snapshot = await this._prepareStateSnapshot({\n config: mergedConfig,\n saved,\n subgraphCheckpointer: options?.subgraphs ? checkpointer : undefined,\n applyPendingWrites: !config.configurable?.checkpoint_id,\n });\n return snapshot;\n }\n\n /**\n * Gets the history of graph states.\n * Requires a checkpointer to be configured.\n * Useful for:\n * - Debugging execution history\n * - Implementing time travel\n * - Analyzing graph behavior\n *\n * @param config - Configuration for retrieving the history\n * @param options - Options for filtering the history\n * @returns An async iterator of state snapshots\n * @throws {Error} If no checkpointer is configured\n */\n async *getStateHistory(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncIterableIterator<StateSnapshot> {\n const checkpointer: BaseCheckpointSaver =\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n\n const checkpointNamespace: string =\n config.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n\n // find the subgraph with the matching name\n for await (const [name, pregel] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n if (name === recastNamespace) {\n yield* pregel.getStateHistory(\n patchConfigurable(config, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n options\n );\n return;\n }\n }\n throw new Error(\n `Subgraph with namespace \"${recastNamespace}\" not found.`\n );\n }\n\n const mergedConfig = mergeConfigs(this.config, config, {\n configurable: { checkpoint_ns: checkpointNamespace },\n });\n\n for await (const checkpointTuple of checkpointer.list(\n mergedConfig,\n options\n )) {\n yield this._prepareStateSnapshot({\n config: checkpointTuple.config,\n saved: checkpointTuple,\n });\n }\n }\n\n /**\n * Apply updates to the graph state in bulk.\n * Requires a checkpointer to be configured.\n *\n * This method is useful for recreating a thread\n * from a list of updates, especially if a checkpoint\n * is created as a result of multiple tasks.\n *\n * @internal The API might change in the future.\n *\n * @param startConfig - Configuration for the update\n * @param updates - The list of updates to apply to graph state\n * @returns Updated configuration\n * @throws {GraphValueError} If no checkpointer is configured\n * @throws {InvalidUpdateError} If the update cannot be attributed to a node or an update can be only applied in sequence.\n */\n async bulkUpdateState(\n startConfig: LangGraphRunnableConfig,\n supersteps: Array<{\n updates: Array<{\n values?: Record<string, unknown> | unknown;\n asNode?: keyof Nodes | string;\n }>;\n }>\n ): Promise<RunnableConfig> {\n const checkpointer: BaseCheckpointSaver | undefined =\n startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n if (supersteps.length === 0) {\n throw new Error(\"No supersteps provided\");\n }\n\n if (supersteps.some((s) => s.updates.length === 0)) {\n throw new Error(\"No updates provided\");\n }\n\n // delegate to subgraph\n const checkpointNamespace: string =\n startConfig.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n // remove task_ids from checkpoint_ns\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n // find the subgraph with the matching name\n // eslint-disable-next-line no-unreachable-loop\n for await (const [, pregel] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n return await pregel.bulkUpdateState(\n patchConfigurable(startConfig, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n supersteps\n );\n }\n throw new Error(`Subgraph \"${recastNamespace}\" not found`);\n }\n\n const updateSuperStep = async (\n inputConfig: LangGraphRunnableConfig,\n updates: {\n values?: Record<string, unknown> | unknown;\n asNode?: keyof Nodes | string;\n taskId?: string;\n }[]\n ) => {\n // get last checkpoint\n const config = this.config\n ? mergeConfigs(this.config, inputConfig)\n : inputConfig;\n const saved = await checkpointer.getTuple(config);\n const checkpoint =\n saved !== undefined\n ? copyCheckpoint(saved.checkpoint)\n : emptyCheckpoint();\n const checkpointPreviousVersions = {\n ...saved?.checkpoint.channel_versions,\n };\n const step = saved?.metadata?.step ?? -1;\n\n // merge configurable fields with previous checkpoint config\n let checkpointConfig = patchConfigurable(config, {\n checkpoint_ns: config.configurable?.checkpoint_ns ?? \"\",\n });\n let checkpointMetadata = config.metadata ?? {};\n if (saved?.config.configurable) {\n checkpointConfig = patchConfigurable(config, saved.config.configurable);\n checkpointMetadata = {\n ...saved.metadata,\n ...checkpointMetadata,\n };\n }\n\n // Find last node that updated the state, if not provided\n const { values, asNode } = updates[0];\n if (values == null && asNode === undefined) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot create empty checkpoint with multiple updates`\n );\n }\n\n const nextConfig = await checkpointer.put(\n checkpointConfig,\n createCheckpoint(checkpoint, undefined, step),\n {\n source: \"update\",\n step: step + 1,\n parents: saved?.metadata?.parents ?? {},\n },\n {}\n );\n return patchCheckpointMap(\n nextConfig,\n saved ? saved.metadata : undefined\n );\n }\n\n // update channels\n const channels = emptyChannels(\n this.channels as Record<string, BaseChannel>,\n checkpoint\n );\n\n if (values === null && asNode === END) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot apply multiple updates when clearing state`\n );\n }\n\n if (saved) {\n // tasks for this checkpoint\n const nextTasks = _prepareNextTasks(\n checkpoint,\n saved.pendingWrites || [],\n this.nodes,\n channels,\n saved.config,\n true,\n {\n step: (saved.metadata?.step ?? -1) + 1,\n checkpointer,\n store: this.store,\n }\n );\n\n // apply null writes\n const nullWrites = (saved.pendingWrites || [])\n .filter((w) => w[0] === NULL_TASK_ID)\n .map((w) => w.slice(1)) as PendingWrite<string>[];\n if (nullWrites.length > 0) {\n _applyWrites(\n checkpoint,\n channels,\n [\n {\n name: INPUT,\n writes: nullWrites,\n triggers: [],\n },\n ],\n checkpointer.getNextVersion.bind(checkpointer),\n this.triggerToNodes\n );\n }\n // apply writes from tasks that already ran\n for (const [taskId, k, v] of saved.pendingWrites || []) {\n if ([ERROR, INTERRUPT, SCHEDULED].includes(k)) {\n continue;\n }\n if (!(taskId in nextTasks)) {\n continue;\n }\n nextTasks[taskId].writes.push([k, v]);\n }\n // clear all current tasks\n _applyWrites(\n checkpoint,\n channels,\n Object.values(nextTasks) as WritesProtocol<string>[],\n checkpointer.getNextVersion.bind(checkpointer),\n this.triggerToNodes\n );\n }\n // save checkpoint\n const nextConfig = await checkpointer.put(\n checkpointConfig,\n createCheckpoint(checkpoint, channels, step),\n {\n ...checkpointMetadata,\n source: \"update\",\n step: step + 1,\n parents: saved?.metadata?.parents ?? {},\n },\n getNewChannelVersions(\n checkpointPreviousVersions,\n checkpoint.channel_versions\n )\n );\n return patchCheckpointMap(\n nextConfig,\n saved ? saved.metadata : undefined\n );\n }\n\n if (asNode === COPY) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot copy checkpoint with multiple updates`\n );\n }\n\n if (saved == null) {\n throw new InvalidUpdateError(`Cannot copy a non-existent checkpoint`);\n }\n\n const isCopyWithUpdates = (\n values: unknown\n ): values is [values: unknown, asNode: string][] => {\n if (!Array.isArray(values)) return false;\n if (values.length === 0) return false;\n return values.every((v) => Array.isArray(v) && v.length === 2);\n };\n\n const nextCheckpoint = createCheckpoint(checkpoint, undefined, step);\n const nextConfig = await checkpointer.put(\n saved.parentConfig ??\n patchConfigurable(saved.config, { checkpoint_id: undefined }),\n nextCheckpoint,\n {\n source: \"fork\",\n step: step + 1,\n parents: saved.metadata?.parents ?? {},\n },\n {}\n );\n\n // We want to both clone a checkpoint and update state in one go.\n // Reuse the same task ID if possible.\n if (isCopyWithUpdates(values)) {\n // figure out the task IDs for the next update checkpoint\n const nextTasks = _prepareNextTasks(\n nextCheckpoint,\n saved.pendingWrites,\n this.nodes,\n channels,\n nextConfig,\n false,\n { step: step + 2 }\n );\n\n const tasksGroupBy = Object.values(nextTasks).reduce<\n Record<string, { id: string }[]>\n >((acc, { name, id }) => {\n acc[name] ??= [];\n acc[name].push({ id });\n return acc;\n }, {});\n\n const userGroupBy = values.reduce<\n Record<\n string,\n { values: unknown; asNode: string; taskId?: string }[]\n >\n >((acc, item) => {\n const [values, asNode] = item;\n acc[asNode] ??= [];\n\n const targetIdx = acc[asNode].length;\n const taskId = tasksGroupBy[asNode]?.[targetIdx]?.id;\n acc[asNode].push({ values, asNode, taskId });\n\n return acc;\n }, {});\n\n return updateSuperStep(\n patchCheckpointMap(nextConfig, saved.metadata),\n Object.values(userGroupBy).flat()\n );\n }\n\n return patchCheckpointMap(nextConfig, saved.metadata);\n }\n\n if (asNode === INPUT) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot apply multiple updates when updating as input`\n );\n }\n\n const inputWrites = await gatherIterator(\n mapInput(this.inputChannels, values)\n );\n if (inputWrites.length === 0) {\n throw new InvalidUpdateError(\n `Received no input writes for ${JSON.stringify(\n this.inputChannels,\n null,\n 2\n )}`\n );\n }\n\n // apply to checkpoint\n _applyWrites(\n checkpoint,\n channels,\n [\n {\n name: INPUT,\n writes: inputWrites as PendingWrite[],\n triggers: [],\n },\n ],\n checkpointer.getNextVersion.bind(this.checkpointer),\n this.triggerToNodes\n );\n\n // apply input write to channels\n const nextStep =\n saved?.metadata?.step != null ? saved.metadata.step + 1 : -1;\n const nextConfig = await checkpointer.put(\n checkpointConfig,\n createCheckpoint(checkpoint, channels, nextStep),\n {\n source: \"input\",\n step: nextStep,\n parents: saved?.metadata?.parents ?? {},\n },\n getNewChannelVersions(\n checkpointPreviousVersions,\n checkpoint.channel_versions\n )\n );\n\n // Store the writes\n await checkpointer.putWrites(\n nextConfig,\n inputWrites as PendingWrite[],\n uuid5(INPUT, checkpoint.id)\n );\n\n return patchCheckpointMap(\n nextConfig,\n saved ? saved.metadata : undefined\n );\n }\n\n // apply pending writes, if not on specific checkpoint\n if (\n config.configurable?.checkpoint_id === undefined &&\n saved?.pendingWrites !== undefined &&\n saved.pendingWrites.length > 0\n ) {\n // tasks for this checkpoint\n const nextTasks = _prepareNextTasks(\n checkpoint,\n saved.pendingWrites,\n this.nodes,\n channels,\n saved.config,\n true,\n {\n store: this.store,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n checkpointer: this.checkpointer as any,\n step: (saved.metadata?.step ?? -1) + 1,\n }\n );\n // apply null writes\n const nullWrites = (saved.pendingWrites ?? [])\n .filter((w) => w[0] === NULL_TASK_ID)\n .map((w) => w.slice(1)) as PendingWrite<string>[];\n if (nullWrites.length > 0) {\n _applyWrites(\n saved.checkpoint,\n channels,\n [{ name: INPUT, writes: nullWrites, triggers: [] }],\n undefined,\n this.triggerToNodes\n );\n }\n // apply writes\n for (const [tid, k, v] of saved.pendingWrites) {\n if (\n [ERROR, INTERRUPT, SCHEDULED].includes(k) ||\n nextTasks[tid] === undefined\n ) {\n continue;\n }\n nextTasks[tid].writes.push([k, v]);\n }\n const tasks = Object.values(nextTasks).filter((task) => {\n return task.writes.length > 0;\n });\n if (tasks.length > 0) {\n _applyWrites(\n checkpoint,\n channels,\n tasks as WritesProtocol[],\n undefined,\n this.triggerToNodes\n );\n }\n }\n const nonNullVersion = Object.values(checkpoint.versions_seen)\n .map((seenVersions) => {\n return Object.values(seenVersions);\n })\n .flat()\n .find((v) => !!v);\n\n const validUpdates: Array<{\n values: Record<string, unknown> | unknown;\n asNode: keyof