@langchain/langgraph
Version:
1 lines • 85.6 kB
Source Map (JSON)
{"version":3,"file":"state.cjs","names":["Graph","schemaMetaRegistry","StateGraphInputError","StateSchema","coerceTimeoutPolicy","RunnableCallable","CONFIG_KEY_NODE_ERROR","isStateGraphInit","AnnotationRoot","isStateDefinitionInit","Runnable","isPregelLike","START","EphemeralValue","SELF","getChannel","CompiledGraph","isCommand","Command","_getOverwriteValue","OVERWRITE","InvalidUpdateError","PASSTHROUGH","PregelNode","TAG_HIDDEN","ChannelWrite","LastValueAfterFinish","NamedBarrierValueAfterFinish","NamedBarrierValue","END","_isSend","ChannelRead","isInterrupted","ParentCommand","Branch"],"sources":["../../src/graph/state.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-use-before-define */\nimport { _coerceToRunnable, Runnable } from \"@langchain/core/runnables\";\nimport {\n All,\n type BaseCache,\n BaseCheckpointSaver,\n BaseStore,\n} from \"@langchain/langgraph-checkpoint\";\nimport {\n getInteropZodObjectShape,\n type InteropZodObject,\n interopParse,\n interopZodObjectPartial,\n isInteropZodObject,\n} from \"@langchain/core/utils/types\";\nimport type {\n RunnableLike,\n LangGraphRunnableConfig,\n Runtime,\n} from \"../pregel/runnable_types.js\";\nimport { BaseChannel } from \"../channels/base.js\";\nimport {\n CompiledGraph,\n Graph,\n Branch,\n AddNodeOptions,\n NodeSpec,\n NodeErrorHandler,\n} from \"./graph.js\";\nimport {\n ChannelWrite,\n ChannelWriteEntry,\n ChannelWriteTupleEntry,\n PASSTHROUGH,\n} from \"../pregel/write.js\";\nimport { ChannelRead, PregelNode } from \"../pregel/read.js\";\nimport {\n NamedBarrierValue,\n NamedBarrierValueAfterFinish,\n} from \"../channels/named_barrier_value.js\";\nimport { EphemeralValue } from \"../channels/ephemeral_value.js\";\nimport { RunnableCallable } from \"../utils.js\";\nimport {\n isCommand,\n _isSend,\n CHECKPOINT_NAMESPACE_END,\n CHECKPOINT_NAMESPACE_SEPARATOR,\n Command,\n SELF,\n Send,\n START,\n END,\n TAG_HIDDEN,\n CommandInstance,\n isInterrupted,\n Interrupt,\n INTERRUPT,\n CONFIG_KEY_NODE_ERROR,\n _getOverwriteValue,\n OVERWRITE,\n} from \"../constants.js\";\nimport {\n InvalidUpdateError,\n NodeError,\n ParentCommand,\n StateGraphInputError,\n} from \"../errors.js\";\nimport {\n AnnotationRoot,\n getChannel,\n SingleReducer,\n StateDefinition,\n StateType,\n} from \"./annotation.js\";\nimport { StateSchema } from \"../state/index.js\";\nimport type {\n CachePolicy,\n RetryPolicy,\n TimeoutPolicy,\n} from \"../pregel/utils/index.js\";\nimport { coerceTimeoutPolicy } from \"../pregel/utils/index.js\";\nimport { isPregelLike } from \"../pregel/utils/subgraph.js\";\nimport { LastValueAfterFinish } from \"../channels/last_value.js\";\nimport { type SchemaMetaRegistry, schemaMetaRegistry } from \"./zod/meta.js\";\nimport type {\n InferInterruptResumeType,\n InferInterruptInputType,\n} from \"../interrupt.js\";\nimport type { InferWriterType } from \"../writer.js\";\nimport type { AnyStateSchema } from \"../state/schema.js\";\nimport {\n ContextSchemaInit,\n ExtractStateType,\n ExtractUpdateType,\n isStateDefinitionInit,\n isStateGraphInit,\n StateGraphInit,\n StateGraphOptions,\n ToStateDefinition,\n type StateDefinitionInit,\n} from \"./types.js\";\nimport type { StreamTransformer } from \"../stream/types.js\";\nimport type { Pregel } from \"../pregel/index.js\";\n\nconst ROOT = \"__root__\";\n\n/**\n * Reserved node name for the single shared error handler that is materialized\n * when a graph-wide default error handler is set via\n * {@link StateGraph.setNodeDefaults}. Every regular node that lacks its own\n * `errorHandler` routes failures to this node. Mirrors Python's\n * `__default_error_handler__`.\n */\nconst DEFAULT_ERROR_HANDLER_NODE = \"__default_error_handler__\";\n\nexport type ChannelReducers<Channels extends object> = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in keyof Channels]: SingleReducer<Channels[K], any>;\n};\n\nexport interface StateGraphArgs<Channels extends object | unknown> {\n channels: Channels extends object\n ? Channels extends unknown[]\n ? ChannelReducers<{ __root__: Channels }>\n : ChannelReducers<Channels>\n : ChannelReducers<{ __root__: Channels }>;\n}\n\n/**\n * Retry and cache policies configurable on graph nodes.\n *\n * Use with {@link StateGraph.addNode} for per-node overrides, or with\n * {@link StateGraph.setNodeDefaults} for graph-wide defaults applied at\n * `compile()` time. Per-node values always take precedence over defaults.\n * `setNodeDefaults` may be called before or after `addNode`, including as the\n * last step before `compile()`.\n */\nexport type NodePolicyOptions = {\n /**\n * Retry policy controlling backoff, max attempts, and which errors trigger\n * a retry.\n *\n * @see {@link RetryPolicy}\n */\n retryPolicy?: RetryPolicy;\n /**\n * Cache policy controlling how node results are keyed and how long they\n * persist.\n *\n * - Pass a {@link CachePolicy} object for fine-grained control (e.g. custom\n * `keyFunc`, `ttl`).\n * - Pass `true` to enable caching with default settings.\n * - Pass `false` to disable caching.\n *\n * @see {@link CachePolicy}\n */\n cachePolicy?: CachePolicy | boolean;\n /**\n * Maximum duration for a single attempt of this node. Accepts a number of\n * milliseconds (a hard wall-clock cap) or a {@link TimeoutPolicy} for finer\n * control over run / idle timeouts. When exceeded, a {@link NodeTimeoutError}\n * is raised and the node's retry policy (if any) decides whether to retry.\n *\n * @see {@link TimeoutPolicy}\n */\n timeout?: number | TimeoutPolicy;\n};\n\n/**\n * Resolved retry and cache policies stored on a node after boolean\n * `cachePolicy` shorthand is normalized.\n *\n * @internal\n */\nexport type NodePolicies = {\n retryPolicy?: RetryPolicy;\n /** `false` opts out of graph defaults set via {@link StateGraph.setNodeDefaults}. */\n cachePolicy?: CachePolicy | false;\n /** Resolved timeout policy after the `number` shorthand is normalized. */\n timeout?: TimeoutPolicy;\n};\n\n/**\n * Graph-wide node defaults captured by {@link StateGraph.setNodeDefaults} and\n * resolved at {@link StateGraph.compile} time.\n *\n * @internal\n */\ntype ResolvedNodeDefaults = NodePolicies & {\n /**\n * Default error handler applied to every regular node that does not set its\n * own via `addNode(..., { errorHandler })`. Never applied to error-handler\n * nodes themselves — handlers must not catch their own failures.\n */\n errorHandler?: NodeErrorHandler;\n};\n\nexport type StateGraphNodeSpec<RunInput, RunOutput> = NodeSpec<\n RunInput,\n RunOutput\n> &\n NodePolicies & {\n input?: StateDefinition;\n };\n\n/**\n * Options for StateGraph.addNode() method.\n *\n * @template Nodes - Node name constraints\n * @template InputSchema - Per-node input schema type (inferred from options.input)\n * @template HandlerState - State type passed to the node-level error handler\n * @template Update - The update type the error handler may return\n */\nexport type StateGraphAddNodeOptions<\n Nodes extends string = string,\n InputSchema extends StateDefinitionInit | undefined =\n | StateDefinitionInit\n | undefined,\n HandlerState = InputSchema extends StateDefinitionInit\n ? ExtractStateType<InputSchema>\n : unknown,\n Update = unknown,\n> = {\n input?: InputSchema;\n /**\n * Optional node-level error handler. Runs only after this node's\n * {@link RetryPolicy} is exhausted. Receives a {@link NodeError} with the\n * failed node name and error, and may return a state update or `Command`.\n */\n errorHandler?: NodeErrorHandler<HandlerState, Update, Nodes>;\n} & NodePolicyOptions &\n AddNodeOptions<Nodes>;\n\ntype StateGraphAddNodeOptionsWithNodeInput<\n Nodes extends string,\n NodeInput,\n Update = unknown,\n> = StateGraphAddNodeOptions<\n Nodes,\n StateDefinitionInit | undefined,\n NodeInput,\n Update\n>;\n\nexport type StateGraphArgsWithStateSchema<\n SD extends StateDefinition,\n I extends StateDefinition,\n O extends StateDefinition,\n> = {\n stateSchema: AnnotationRoot<SD>;\n input?: AnnotationRoot<I>;\n output?: AnnotationRoot<O>;\n};\n\nexport type StateGraphArgsWithInputOutputSchemas<\n SD extends StateDefinition,\n O extends StateDefinition = SD,\n> = {\n input: AnnotationRoot<SD>;\n output: AnnotationRoot<O>;\n};\n\ntype ExtractStateDefinition<T> = T extends AnyStateSchema\n ? T // Keep StateSchema as-is to preserve type information\n : T extends StateDefinitionInit\n ? ToStateDefinition<T>\n : StateDefinition;\n\ntype NodeAction<\n S,\n U,\n C extends StateDefinitionInit,\n InterruptType,\n WriterType,\n> = RunnableLike<\n S,\n U extends object ? U & Record<string, any> : U, // eslint-disable-line @typescript-eslint/no-explicit-any\n Runtime<StateType<ToStateDefinition<C>>, InterruptType, WriterType>\n>;\n\ntype StrictNodeAction<\n S,\n U,\n C extends StateDefinitionInit,\n Nodes extends string,\n InterruptType,\n WriterType,\n> = RunnableLike<\n Prettify<S>,\n | U\n | Command<\n InferInterruptResumeType<InterruptType>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n U & Record<string, any>,\n Nodes\n >,\n Runtime<StateType<ToStateDefinition<C>>, InterruptType, WriterType>\n>;\n\nconst PartialStateSchema = Symbol.for(\"langgraph.state.partial\");\ntype PartialStateSchema = typeof PartialStateSchema;\n\ntype MergeReturnType<Prev, Curr> = Prev & Curr extends infer T\n ? { [K in keyof T]: T[K] } & unknown\n : never;\n\ntype Prettify<T> = {\n [K in keyof T]: T[K];\n // eslint-disable-next-line @typescript-eslint/ban-types\n} & {};\n\n/**\n * A graph whose nodes communicate by reading and writing to a shared state.\n * Each node takes a defined `State` as input and returns a `Partial<State>`.\n *\n * Each state key can optionally be annotated with a reducer function that\n * will be used to aggregate the values of that key received from multiple nodes.\n * The signature of a reducer function is (left: Value, right: UpdateValue) => Value.\n *\n * See {@link Annotation} for more on defining state.\n *\n * After adding nodes and edges to your graph, you must call `.compile()` on it before\n * you can use it.\n *\n * @typeParam SD - The state definition used to construct the graph. Can be an\n * {@link AnnotationRoot}, {@link StateSchema}, or Zod object schema. This is the\n * primary generic from which `S` and `U` are derived.\n *\n * @typeParam S - The full state type representing the complete shape of your graph's\n * state after all reducers have been applied. Automatically inferred from `SD`.\n *\n * @typeParam U - The update type representing what nodes can return to modify state.\n * Typically a partial of the state type. Automatically inferred from `SD`.\n *\n * @typeParam N - Union of all node names in the graph (e.g., `\"agent\" | \"tool\"`).\n * Accumulated as you call `.addNode()`. Used for type-safe routing.\n *\n * @typeParam I - The input schema definition. Set via the `input` option in the\n * constructor to restrict what data the graph accepts when invoked.\n *\n * @typeParam O - The output schema definition. Set via the `output` option in the\n * constructor to restrict what data the graph returns after execution.\n *\n * @typeParam C - The config/context schema definition. Set via the `context` option\n * to define additional configuration passed at runtime.\n *\n * @typeParam NodeReturnType - Constrains what types nodes in this graph can return.\n *\n * @typeParam InterruptType - The type for {@link interrupt} resume values. Set via\n * the `interrupt` option for typed human-in-the-loop patterns.\n *\n * @typeParam WriterType - The type for custom stream writers. Set via the `writer`\n * option to enable typed custom streaming from within nodes.\n *\n * @example\n * ```ts\n * import {\n * type BaseMessage,\n * AIMessage,\n * HumanMessage,\n * } from \"@langchain/core/messages\";\n * import { StateGraph, Annotation } from \"@langchain/langgraph\";\n *\n * // Define a state with a single key named \"messages\" that will\n * // combine a returned BaseMessage or arrays of BaseMessages\n * const StateAnnotation = Annotation.Root({\n * sentiment: Annotation<string>,\n * messages: Annotation<BaseMessage[]>({\n * reducer: (left: BaseMessage[], right: BaseMessage | BaseMessage[]) => {\n * if (Array.isArray(right)) {\n * return left.concat(right);\n * }\n * return left.concat([right]);\n * },\n * default: () => [],\n * }),\n * });\n *\n * const graphBuilder = new StateGraph(StateAnnotation);\n *\n * // A node in the graph that returns an object with a \"messages\" key\n * // will update the state by combining the existing value with the returned one.\n * const myNode = (state: typeof StateAnnotation.State) => {\n * return {\n * messages: [new AIMessage(\"Some new response\")],\n * sentiment: \"positive\",\n * };\n * };\n *\n * const graph = graphBuilder\n * .addNode(\"myNode\", myNode)\n * .addEdge(\"__start__\", \"myNode\")\n * .addEdge(\"myNode\", \"__end__\")\n * .compile();\n *\n * await graph.invoke({ messages: [new HumanMessage(\"how are you?\")] });\n *\n * // {\n * // messages: [HumanMessage(\"how are you?\"), AIMessage(\"Some new response\")],\n * // sentiment: \"positive\",\n * // }\n * ```\n */\nexport class StateGraph<\n SD extends StateDefinitionInit | unknown,\n S = ExtractStateType<SD>,\n U = ExtractUpdateType<SD, S>,\n N extends string = typeof START,\n I extends StateDefinitionInit = ExtractStateDefinition<SD>,\n O extends StateDefinitionInit = ExtractStateDefinition<SD>,\n C extends StateDefinitionInit = StateDefinition,\n NodeReturnType = unknown,\n InterruptType = unknown,\n WriterType = unknown,\n> extends Graph<N, S, U, StateGraphNodeSpec<S, U>, ToStateDefinition<C>> {\n channels: Record<string, BaseChannel> = {};\n\n // TODO: this doesn't dedupe edges as in py, so worth fixing at some point\n waitingEdges: Set<[N[], N]> = new Set();\n\n /** @internal */\n _schemaDefinition: StateDefinition;\n\n /** @internal */\n _schemaRuntimeDefinition: InteropZodObject | AnyStateSchema | undefined;\n\n /** @internal */\n _inputDefinition: I;\n\n /** @internal */\n _inputRuntimeDefinition:\n | InteropZodObject\n | AnyStateSchema\n | PartialStateSchema\n | undefined;\n\n /** @internal */\n _outputDefinition: O;\n\n /** @internal */\n _outputRuntimeDefinition: InteropZodObject | AnyStateSchema | undefined;\n\n /**\n * Map schemas to managed values\n * @internal\n */\n _schemaDefinitions = new Map();\n\n /** @internal */\n _metaRegistry: SchemaMetaRegistry = schemaMetaRegistry;\n\n /** @internal Used only for typing. */\n _configSchema: ToStateDefinition<C> | undefined;\n\n /** @internal */\n _configRuntimeSchema: InteropZodObject | undefined;\n\n /** @internal */\n _interrupt: InterruptType;\n\n /** @internal */\n _writer: WriterType;\n\n /**\n * Graph-wide default node policies, resolved at `compile()` time.\n * @internal\n */\n _nodeDefaults: ResolvedNodeDefaults = {};\n\n declare Node: StrictNodeAction<S, U, C, N, InterruptType, WriterType>;\n\n /**\n * Create a new StateGraph for building stateful, multi-step workflows.\n *\n * Accepts state definitions via `Annotation.Root`, `StateSchema`, or Zod schemas.\n *\n * @example Direct schema\n * ```ts\n * const StateAnnotation = Annotation.Root({\n * messages: Annotation<string[]>({ reducer: (a, b) => [...a, ...b] }),\n * });\n * const graph = new StateGraph(StateAnnotation);\n * ```\n *\n * @example Direct schema with input/output filtering\n * ```ts\n * const graph = new StateGraph(StateAnnotation, {\n * input: InputSchema,\n * output: OutputSchema,\n * });\n * ```\n *\n * @example Object pattern with state, input, output\n * ```ts\n * const graph = new StateGraph({\n * state: FullStateSchema,\n * input: InputSchema,\n * output: OutputSchema,\n * });\n * ```\n *\n * @example Input/output only (state inferred from input)\n * ```ts\n * const graph = new StateGraph({\n * input: InputAnnotation,\n * output: OutputAnnotation,\n * });\n * ```\n */\n constructor(\n state: SD extends StateDefinitionInit ? SD : never,\n options?:\n | C\n | AnnotationRoot<ToStateDefinition<C>>\n | StateGraphOptions<I, O, C, N, InterruptType, WriterType>\n );\n\n constructor(\n fields: SD extends StateDefinition\n ? StateGraphArgsWithInputOutputSchemas<SD, ToStateDefinition<O>>\n : never,\n contextSchema?: C | AnnotationRoot<ToStateDefinition<C>>\n );\n\n constructor(\n fields: SD extends StateDefinition\n ?\n | AnnotationRoot<SD>\n | StateGraphArgsWithStateSchema<\n SD,\n ToStateDefinition<I>,\n ToStateDefinition<O>\n >\n : never,\n contextSchema?: C | AnnotationRoot<ToStateDefinition<C>>\n );\n\n constructor(\n init: Omit<\n StateGraphInit<\n SD extends StateDefinitionInit ? SD : StateDefinitionInit,\n SD extends StateDefinitionInit ? SD : StateDefinitionInit,\n O,\n C extends ContextSchemaInit ? C : undefined,\n N,\n InterruptType,\n WriterType\n >,\n \"state\" | \"stateSchema\" | \"input\"\n > & {\n input: SD extends StateDefinitionInit ? SD : never;\n state?: never;\n stateSchema?: never;\n },\n contextSchema?: C | AnnotationRoot<ToStateDefinition<C>>\n );\n\n constructor(\n init: StateGraphInit<\n SD extends StateDefinitionInit ? SD : StateDefinitionInit,\n I,\n O,\n C extends ContextSchemaInit ? C : undefined,\n N,\n InterruptType,\n WriterType\n >\n );\n\n /** @deprecated Use `Annotation.Root`, `StateSchema`, or Zod schemas instead. */\n constructor(\n fields: StateGraphArgs<S>,\n contextSchema?: C | AnnotationRoot<ToStateDefinition<C>>\n );\n\n constructor(\n stateOrInit:\n | StateDefinitionInit\n | StateGraphInit<StateDefinitionInit, I, O>\n | StateGraphArgs<S>,\n options?:\n | C\n | AnnotationRoot<ToStateDefinition<C>>\n | StateGraphOptions<\n I,\n O,\n C extends ContextSchemaInit ? C : undefined,\n N,\n InterruptType,\n WriterType\n >\n ) {\n super();\n\n // Normalize all input patterns to StateGraphInit format\n const init = this._normalizeToStateGraphInit(stateOrInit, options);\n\n // Resolve state schema: state > stateSchema (deprecated) > input\n const stateSchema = init.state ?? init.stateSchema ?? init.input;\n if (!stateSchema) {\n throw new StateGraphInputError();\n }\n\n // Get channel definitions from the schema (may contain channel factories)\n const stateChannelDef = this._getChannelsFromSchema(stateSchema);\n\n // Set schema definitions (these may contain channel factories)\n this._schemaDefinition = stateChannelDef;\n\n // Set runtime definitions for validation\n if (StateSchema.isInstance(stateSchema)) {\n this._schemaRuntimeDefinition = stateSchema;\n } else if (isInteropZodObject(stateSchema)) {\n this._schemaRuntimeDefinition = stateSchema;\n }\n\n // Set input runtime definition\n if (init.input) {\n if (StateSchema.isInstance(init.input)) {\n this._inputRuntimeDefinition = init.input;\n } else if (isInteropZodObject(init.input)) {\n this._inputRuntimeDefinition = init.input;\n } else {\n this._inputRuntimeDefinition = PartialStateSchema;\n }\n } else {\n this._inputRuntimeDefinition = PartialStateSchema;\n }\n\n // Set output runtime definition\n if (init.output) {\n if (StateSchema.isInstance(init.output)) {\n this._outputRuntimeDefinition = init.output;\n } else if (isInteropZodObject(init.output)) {\n this._outputRuntimeDefinition = init.output;\n } else {\n this._outputRuntimeDefinition = this._schemaRuntimeDefinition;\n }\n } else {\n this._outputRuntimeDefinition = this._schemaRuntimeDefinition;\n }\n\n // Set input/output definitions (default to state)\n const inputChannelDef = init.input\n ? this._getChannelsFromSchema(init.input)\n : stateChannelDef;\n const outputChannelDef = init.output\n ? (this._getChannelsFromSchema(init.output) as O)\n : stateChannelDef;\n this._inputDefinition = inputChannelDef as I;\n this._outputDefinition = outputChannelDef as O;\n\n // Add all schemas (_addSchema instantiates channel factories and populates this.channels)\n this._addSchema(this._schemaDefinition);\n this._addSchema(this._inputDefinition);\n this._addSchema(this._outputDefinition);\n\n // Handle context schema\n if (init.context) {\n if (isInteropZodObject(init.context)) {\n this._configRuntimeSchema = init.context;\n }\n }\n\n // Handle interrupt and writer\n this._interrupt = init.interrupt as InterruptType;\n this._writer = init.writer as WriterType;\n }\n\n /**\n * Set graph-wide default node policies that apply to every node in this\n * graph.\n *\n * Per-node values passed to {@link addNode} always take precedence over these\n * defaults. Defaults are resolved at {@link compile} time, so call order does\n * not matter — you may call this before or after `addNode`, including as the\n * last step before `compile()`. Calling it multiple times merges the provided\n * fields, with later calls overriding earlier ones on a per-field basis.\n *\n * Policies set here are **not** inherited by subgraphs.\n *\n * `retryPolicy` and `timeout` defaults apply to **all** nodes, including\n * auto-generated error-handler nodes. `cachePolicy` and `errorHandler`\n * defaults apply to **regular nodes only** — caching an error-handler result\n * is unsafe, and a handler must never catch its own (or another handler's)\n * failure.\n *\n * @param defaults - The default node policies to apply.\n * @returns The builder instance, for chaining.\n *\n * @example Call before `addNode`\n * ```ts\n * const graph = new StateGraph(State)\n * .setNodeDefaults({\n * retryPolicy: { maxAttempts: 3 },\n * cachePolicy: { ttl: 60 },\n * timeout: 60_000,\n * errorHandler: (state, { node, error }) => ({ lastError: error.message }),\n * })\n * .addNode(\"a\", nodeA)\n * .addNode(\"b\", nodeB, { retryPolicy: { maxAttempts: 5 } }) // overrides default\n * .addEdge(START, \"a\")\n * .compile();\n * ```\n *\n * @example Call after `addNode`, immediately before `compile()`\n * ```ts\n * const graph = new StateGraph(State)\n * .addNode(\"a\", nodeA)\n * .addNode(\"b\", nodeB, { retryPolicy: { maxAttempts: 5 } }) // overrides default\n * .addEdge(START, \"a\")\n * .setNodeDefaults({\n * retryPolicy: { maxAttempts: 3 },\n * cachePolicy: { ttl: 60 },\n * })\n * .compile();\n * ```\n */\n setNodeDefaults(\n defaults: NodePolicyOptions & {\n /**\n * Default node-level error handler invoked when any **regular** node\n * raises and does not have its own handler set via\n * `addNode(..., { errorHandler })`. Runs only after the failing node's\n * retry policy is exhausted. It is never invoked when an error-handler\n * node itself raises — handler failures fail the run.\n *\n * Because a single shared handler serves every node, its `state`\n * argument is typed as `unknown`: at runtime it receives the **failing\n * node's input** (see `addNode(..., { input })`), which may be a subset\n * of the graph state and differs per node. Narrow it yourself before\n * reading fields. The handler may still return a graph-level update (`U`)\n * or route via `new Command({ goto })` to any node (`N`).\n */\n errorHandler?: NodeErrorHandler<unknown, U, N>;\n }\n ): this {\n if (defaults.retryPolicy !== undefined) {\n this._nodeDefaults.retryPolicy = defaults.retryPolicy;\n }\n if (defaults.cachePolicy !== undefined) {\n this._nodeDefaults.cachePolicy =\n typeof defaults.cachePolicy === \"boolean\"\n ? defaults.cachePolicy\n ? {}\n : undefined\n : defaults.cachePolicy;\n }\n if (defaults.timeout !== undefined) {\n this._nodeDefaults.timeout = coerceTimeoutPolicy(defaults.timeout);\n }\n if (defaults.errorHandler !== undefined) {\n this._nodeDefaults.errorHandler =\n defaults.errorHandler as NodeErrorHandler;\n }\n return this;\n }\n\n /**\n * Build the shared spec for a graph-wide default error handler, or\n * `undefined` when {@link setNodeDefaults} did not configure one. The spec is\n * installed under {@link DEFAULT_ERROR_HANDLER_NODE} for the duration of a\n * single {@link compile} call and routes failures from every regular node\n * that lacks its own handler.\n * @internal\n */\n protected _createDefaultErrorHandlerSpec():\n | StateGraphNodeSpec<S, U>\n | undefined {\n const userHandler = this._nodeDefaults.errorHandler;\n if (userHandler === undefined) {\n return undefined;\n }\n const handlerRunnable = new RunnableCallable({\n func: (state: unknown, config: LangGraphRunnableConfig) => {\n // Per-task failure context, injected when the handler task is prepared\n // (see _prepareNodeErrorHandlerTask). `state` is the failing node's\n // input, which may be a per-node subset of the graph state — hence the\n // handler's `state` parameter is typed `unknown`.\n const nodeError = config?.configurable?.[CONFIG_KEY_NODE_ERROR] as\n | NodeError\n | undefined;\n return userHandler(state, nodeError as NodeError, config);\n },\n name: DEFAULT_ERROR_HANDLER_NODE,\n trace: false,\n });\n return {\n runnable: handlerRunnable as unknown as Runnable<S, U>,\n metadata: undefined,\n input: this._schemaDefinition,\n retryPolicy: undefined,\n cachePolicy: undefined,\n isErrorHandler: true,\n };\n }\n\n /**\n * Normalize all constructor input patterns to a unified StateGraphInit object.\n * @internal\n */\n private _normalizeToStateGraphInit(\n stateOrInit: unknown,\n options?: unknown\n ): StateGraphInit<StateDefinitionInit, I, O, C> {\n // Check if already StateGraphInit format\n if (isStateGraphInit(stateOrInit)) {\n // Second arg can be either a direct context schema or an options object\n if (isInteropZodObject(options) || AnnotationRoot.isInstance(options)) {\n return {\n ...stateOrInit,\n context: options as C,\n };\n }\n // Merge any 2nd arg options\n const opts = options as StateGraphOptions<I, O> | undefined;\n return {\n ...stateOrInit,\n input: stateOrInit.input ?? opts?.input,\n output: stateOrInit.output ?? opts?.output,\n context: stateOrInit.context ?? opts?.context,\n interrupt: stateOrInit.interrupt ?? opts?.interrupt,\n writer: stateOrInit.writer ?? opts?.writer,\n nodes: stateOrInit.nodes ?? opts?.nodes,\n } as StateGraphInit<StateDefinitionInit, I, O, C>;\n }\n\n // Check if direct schema (StateSchema, Zod, Annotation, StateDefinition)\n if (isStateDefinitionInit(stateOrInit)) {\n // Second arg can be either a direct context schema or an options object\n if (isInteropZodObject(options) || AnnotationRoot.isInstance(options)) {\n return {\n state: stateOrInit,\n context: options as C,\n };\n }\n const opts = options as StateGraphOptions<I, O> | undefined;\n return {\n state: stateOrInit as StateDefinitionInit,\n input: opts?.input as I,\n output: opts?.output as O,\n context: opts?.context,\n interrupt: opts?.interrupt,\n writer: opts?.writer,\n nodes: opts?.nodes,\n };\n }\n\n // Check for legacy { channels } format\n if (isStateGraphArgs(stateOrInit as StateGraphArgs<S>)) {\n const legacyArgs = stateOrInit as StateGraphArgs<S>;\n const spec = _getChannels(legacyArgs.channels);\n return {\n state: spec as StateDefinitionInit,\n };\n }\n\n throw new StateGraphInputError();\n }\n\n /**\n * Convert any supported schema type to a StateDefinition (channel map).\n * @internal\n */\n private _getChannelsFromSchema(schema: StateDefinitionInit): StateDefinition {\n if (StateSchema.isInstance(schema)) {\n return schema.getChannels();\n }\n\n if (isInteropZodObject(schema)) {\n return this._metaRegistry.getChannelsForSchema(schema);\n }\n\n // AnnotationRoot - has .spec property that is the StateDefinition\n if (\n typeof schema === \"object\" &&\n \"lc_graph_name\" in schema &&\n (schema as { lc_graph_name: unknown }).lc_graph_name === \"AnnotationRoot\"\n ) {\n return (schema as AnnotationRoot<StateDefinition>).spec;\n }\n\n // StateDefinition (raw channel map) - return as-is\n if (\n typeof schema === \"object\" &&\n !Array.isArray(schema) &&\n Object.keys(schema).length > 0\n ) {\n return schema as StateDefinition;\n }\n\n throw new StateGraphInputError(\n \"Invalid schema type. Expected StateSchema, Zod object, AnnotationRoot, or StateDefinition.\"\n );\n }\n\n get allEdges(): Set<[string, string]> {\n return new Set([\n ...this.edges,\n ...Array.from(this.waitingEdges).flatMap(([starts, end]) =>\n starts.map((start) => [start, end] as [string, string])\n ),\n ]);\n }\n\n _addSchema(stateDefinition: StateDefinitionInit) {\n if (this._schemaDefinitions.has(stateDefinition)) {\n return;\n }\n // TODO: Support managed values\n this._schemaDefinitions.set(stateDefinition, stateDefinition);\n for (const [key, val] of Object.entries(stateDefinition)) {\n let channel;\n if (typeof val === \"function\") {\n channel = val();\n } else {\n channel = val;\n }\n if (this.channels[key] !== undefined) {\n if (!this.channels[key].equals(channel)) {\n if (channel.lc_graph_name !== \"LastValue\") {\n throw new Error(\n `Channel \"${key}\" already exists with a different type.`\n );\n }\n }\n } else {\n this.channels[key] = channel;\n }\n }\n }\n\n override addNode<\n K extends string,\n NodeMap extends Record<K, NodeAction<S, U, C, InterruptType, WriterType>>,\n >(\n nodes: NodeMap\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<\n NodeReturnType,\n {\n [key in keyof NodeMap]: NodeMap[key] extends NodeAction<\n S,\n infer U,\n C,\n InterruptType,\n WriterType\n >\n ? U\n : never;\n }\n >\n >;\n\n override addNode<K extends string, NodeInput = S, NodeOutput extends U = U>(\n nodes: [\n key: K,\n action: NodeAction<NodeInput, NodeOutput, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>,\n ][]\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n\n override addNode<\n K extends string,\n InputSchema extends StateDefinitionInit,\n NodeOutput extends U = U,\n >(\n key: K,\n action: NodeAction<\n ExtractStateType<InputSchema>,\n NodeOutput,\n C,\n InterruptType,\n WriterType\n >,\n options: StateGraphAddNodeOptions<\n N | K,\n InputSchema,\n ExtractStateType<InputSchema>,\n U\n >\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n\n override addNode<\n K extends string,\n InputSchema extends StateDefinitionInit,\n NodeOutput extends U = U,\n >(\n key: K,\n action: NodeAction<\n ExtractStateType<InputSchema>,\n NodeOutput,\n C,\n InterruptType,\n WriterType\n >,\n options: StateGraphAddNodeOptions<\n N | K,\n InputSchema,\n ExtractStateType<InputSchema>,\n U\n >\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n\n override addNode<K extends string, NodeInput = S, NodeOutput extends U = U>(\n key: K,\n action: NodeAction<NodeInput, NodeOutput, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n\n override addNode<K extends string, NodeInput = S>(\n key: K,\n action: NodeAction<NodeInput, U, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>\n ): StateGraph<SD, S, U, N | K, I, O, C, NodeReturnType>;\n\n override addNode<K extends string, NodeInput = S, NodeOutput extends U = U>(\n ...args:\n | [\n key: K,\n action: NodeAction<\n NodeInput,\n NodeOutput,\n C,\n InterruptType,\n WriterType\n >,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>,\n ]\n | [\n nodes:\n | Record<K, NodeAction<NodeInput, U, C, InterruptType, WriterType>>\n | [\n key: K,\n action: NodeAction<NodeInput, U, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<\n N | K,\n NodeInput,\n U\n >,\n ][],\n ]\n ): StateGraph<SD, S, U, N | K, I, O, C> {\n function isMultipleNodes(\n args: unknown[]\n ): args is [\n nodes:\n | Record<K, NodeAction<NodeInput, U, C, InterruptType, WriterType>>\n | [\n key: K,\n action: NodeAction<NodeInput, U, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<\n N | K,\n NodeInput,\n U\n >,\n ][],\n ] {\n return args.length >= 1 && typeof args[0] !== \"string\";\n }\n\n const nodes = (\n isMultipleNodes(args) // eslint-disable-line no-nested-ternary\n ? Array.isArray(args[0])\n ? args[0]\n : Object.entries(args[0]).map(([key, action]) => [key, action])\n : [[args[0], args[1], args[2]]]\n ) as [\n K,\n NodeAction<NodeInput, U, C, InterruptType, WriterType>,\n StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U> | undefined,\n ][];\n\n if (nodes.length === 0) {\n throw new Error(\"No nodes provided in `addNode`\");\n }\n\n for (const [key, action, options] of nodes) {\n if (key in this.channels) {\n throw new Error(\n `${key} is already being used as a state attribute (a.k.a. a channel), cannot also be used as a node name.`\n );\n }\n\n for (const reservedChar of [\n CHECKPOINT_NAMESPACE_SEPARATOR,\n CHECKPOINT_NAMESPACE_END,\n ]) {\n if (key.includes(reservedChar)) {\n throw new Error(\n `\"${reservedChar}\" is a reserved character and is not allowed in node names.`\n );\n }\n }\n this.warnIfCompiled(\n `Adding a node to a graph that has already been compiled. This will not be reflected in the compiled graph.`\n );\n\n if (key in this.nodes) {\n throw new Error(`Node \\`${key}\\` already present.`);\n }\n if (key === END || key === START) {\n throw new Error(`Node \\`${key}\\` is reserved.`);\n }\n\n let inputSpec: StateDefinition = this._schemaDefinition;\n if (options?.input !== undefined) {\n inputSpec = this._getChannelsFromSchema(options.input);\n }\n this._addSchema(inputSpec);\n\n let runnable;\n if (Runnable.isRunnable(action)) {\n runnable = action;\n } else if (typeof action === \"function\") {\n runnable = new RunnableCallable({\n func: action,\n name: key,\n trace: false,\n });\n } else {\n runnable = _coerceToRunnable(action);\n }\n\n const rawCachePolicy = options?.cachePolicy;\n let cachePolicy: CachePolicy | false | undefined;\n if (rawCachePolicy !== undefined) {\n cachePolicy =\n typeof rawCachePolicy === \"boolean\"\n ? rawCachePolicy\n ? {}\n : false\n : rawCachePolicy;\n }\n\n // If an error handler is provided, register an auto-generated handler\n // node that runs only after this node's retry policy is exhausted.\n let errorHandlerNode: string | undefined;\n if (options?.errorHandler !== undefined) {\n errorHandlerNode = `__error_handler__${key}`;\n if (errorHandlerNode in this.nodes) {\n throw new Error(\n `Cannot add error handler to node \\`${key}\\`: the reserved name \\`${errorHandlerNode}\\` is already in use. ` +\n `StateGraph registers \\`__error_handler__<nodeName>\\` when you pass \\`errorHandler\\` in addNode options. ` +\n `Remove or rename the existing node with that name (for example, you may have added it manually).`\n );\n }\n const userHandler = options.errorHandler;\n const handlerRunnable = new RunnableCallable({\n func: (state: unknown, config: LangGraphRunnableConfig) => {\n // Per-task failure context, injected when the handler task is\n // prepared (see _prepareNodeErrorHandlerTask).\n const nodeError = config?.configurable?.[CONFIG_KEY_NODE_ERROR] as\n | NodeError\n | undefined;\n return userHandler(\n state as NodeInput,\n nodeError as NodeError,\n config\n );\n },\n name: errorHandlerNode,\n trace: false,\n });\n const handlerSpec: StateGraphNodeSpec<S, U> = {\n runnable: handlerRunnable as unknown as Runnable<S, U>,\n metadata: undefined,\n input: inputSpec ?? this._schemaDefinition,\n retryPolicy: undefined,\n cachePolicy: undefined,\n isErrorHandler: true,\n };\n this.nodes[errorHandlerNode as N] = handlerSpec;\n }\n\n const nodeSpec: StateGraphNodeSpec<S, U> = {\n runnable: runnable as unknown as Runnable<S, U>,\n retryPolicy: options?.retryPolicy,\n cachePolicy,\n timeout: coerceTimeoutPolicy(options?.timeout),\n metadata: options?.metadata,\n input: inputSpec ?? this._schemaDefinition,\n subgraphs: isPregelLike(runnable)\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [runnable as Pregel<any, any>]\n : options?.subgraphs,\n ends: options?.ends,\n defer: options?.defer,\n errorHandlerNode,\n };\n\n this.nodes[key as unknown as N] = nodeSpec;\n }\n\n return this as StateGraph<SD, S, U, N | K, I, O, C>;\n }\n\n override addEdge(\n startKey: typeof START | N | N[],\n endKey: N | typeof END\n ): this {\n if (typeof startKey === \"string\") {\n return super.addEdge(startKey, endKey);\n }\n\n if (this.compiled) {\n console.warn(\n \"Adding an edge to a graph that has already been compiled. This will \" +\n \"not be reflected in the compiled graph.\"\n );\n }\n\n for (const start of startKey) {\n if (start === END) {\n throw new Error(\"END cannot be a start node\");\n }\n if (!Object.keys(this.nodes).some((node) => node === start)) {\n throw new Error(`Need to add a node named \"${start}\" first`);\n }\n }\n if (endKey === END) {\n throw new Error(\"END cannot be an end node\");\n }\n if (!Object.keys(this.nodes).some((node) => node === endKey)) {\n throw new Error(`Need to add a node named \"${endKey}\" first`);\n }\n\n this.waitingEdges.add([startKey, endKey]);\n\n return this;\n }\n\n addSequence<K extends string, NodeInput = S, NodeOutput extends U = U>(\n nodes: [\n key: K,\n action: NodeAction<NodeInput, NodeOutput, C, InterruptType, WriterType>,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>,\n ][]\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n\n addSequence<\n K extends string,\n NodeMap extends Record<K, NodeAction<S, U, C, InterruptType, WriterType>>,\n >(\n nodes: NodeMap\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<\n NodeReturnType,\n {\n [key in keyof NodeMap]: NodeMap[key] extends NodeAction<\n S,\n infer U,\n C,\n InterruptType,\n WriterType\n >\n ? U\n : never;\n }\n >\n >;\n\n addSequence<K extends string, NodeInput = S, NodeOutput extends U = U>(\n nodes:\n | [\n key: K,\n action: NodeAction<\n NodeInput,\n NodeOutput,\n C,\n InterruptType,\n WriterType\n >,\n options?: StateGraphAddNodeOptionsWithNodeInput<N | K, NodeInput, U>,\n ][]\n | Record<\n K,\n NodeAction<NodeInput, NodeOutput, C, InterruptType, WriterType>\n >\n ): StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n > {\n const parsedNodes = Array.isArray(nodes) ? nodes : Object.entries(nodes);\n\n if (parsedNodes.length === 0) {\n throw new Error(\"Sequence requires at least one node.\");\n }\n\n let previousNode: N | undefined;\n for (const [key, action, options] of parsedNodes) {\n if (key in this.nodes) {\n throw new Error(\n `Node names must be unique: node with the name \"${key}\" already exists.`\n );\n }\n\n const validKey = key as unknown as N;\n this.addNode(\n key as K,\n action as NodeAction<\n NodeInput,\n NodeOutput,\n C,\n InterruptType,\n WriterType\n >,\n options\n );\n if (previousNode != null) {\n this.addEdge(previousNode, validKey);\n }\n\n previousNode = validKey;\n }\n\n return this as StateGraph<\n SD,\n S,\n U,\n N | K,\n I,\n O,\n C,\n MergeReturnType<NodeReturnType, { [key in K]: NodeOutput }>\n >;\n }\n\n override compile<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const TTransformers extends ReadonlyArray<() => StreamTransformer<any>> =\n [],\n >({\n checkpointer,\n store,\n cache,\n interruptBefore,\n interruptAfter,\n name,\n description,\n transformers,\n }: {\n checkpointer?: BaseCheckpointSaver | boolean;\n store?: BaseStore;\n cache?: BaseCache;\n interruptBefore?: N[] | All;\n interruptAfter?: N[] | All;\n name?: string;\n description?: string;\n /**\n * Stream transformer factories baked into the compiled graph. These run\n * automatically for every `streamEvents(..., { version: \"v3\" })` call,\n * before any call-site transformers.\n */\n transformers?: TTransformers;\n } = {}): CompiledStateGraph<\n Prettify<S>,\n Prettify<U>,\n N,\n I,\n O,\n C,\n NodeReturnType,\n InterruptType,\n WriterType,\n TTransformers\n > {\n // Materialize the single shared default error-handler node (when\n // `setNodeDefaults({ errorHandler })` configured one) BEFORE validation so\n // reachability and `Command({ goto })` target checks treat it like any\n // other error-handler node. It is removed in `finally` so the builder stays\n // immutable across repeated `compile()` calls.\n const defaultErrorHandlerSpec = this._createDefaultErrorHandlerSpec();\n if (defaultErrorHandlerSpec !== undefined) {\n if (DEFAULT_ERROR_HANDLER_NODE in this.nodes) {\n throw new Error(\n `Cannot apply a default error handler: the reserved node name ` +\n `\\`${DEFAULT_ERROR_HANDLER_NODE}\\` is already in use. ` +\n `setNodeDefaults({ errorHandler }) registers a node with that name; ` +\n `rename the conflicting node.`\n );\n }\n this.nodes[DEFAULT_ERROR_HANDLER_NODE as N] = defaultErrorHandlerSpec;\n }\n\n try {\n return this._compileResolved({\n checkpointer,\n store,\n cache,\n interruptBefore,\n interruptAfter,\n name,\n description,\n transformers,\n defaultErrorHandlerNode:\n defaultErrorHandlerSpec !== undefined\n ? DEFAULT_ERROR_HANDLER_NODE\n : undefined,\n });\n } finally {\n if (defaultErrorHandlerSpec !== undefined) {\n delete this.nodes[DEFAULT_ERROR_HANDLER_NODE as N];\n }\n }\n }\n\n /** @internal */\n protected _compileResolved<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const TTransformers extends ReadonlyArray<() => StreamTransformer<any>> =\n [],\n >({\n checkpointer,\n store,\n cache,\n interruptBefore,\n interruptAfter,\n name,\n description,\n transformers,\n defaultErrorHandlerNode,\n }: {\n checkpointer?: BaseCheckpointSaver | boolean;\n store?: BaseStore;\n cache?: BaseCache;\n interruptBefore?: N[] | All;\n interruptAfter?: N[] | All;\n name?: string;\n description?: string;\n transformers?: TTransformers;\n defaultErrorHandlerNode?: string;\n }): CompiledStateGraph<\n Prettify<S>,\n Prettify<U>,\n N,\n I,\n O,\n C,\n NodeReturnType,\n InterruptType,\n WriterType,\n TTransformers\n > {\n // validate the graph\n this.validate([\n ...(Array.isArray(interruptBefore) ? interruptBefore : []),\n ...(Array.isArray(interruptAfter) ? interruptAfter : []),\n ]);\n\n // prepare output channels\n const outputKeys = Object.keys(\n this._schemaDefinitions.get(this._outputDefinition)\n );\n const outputChannels =\n outputKeys.length === 1 && outputKeys[0] === ROOT ? ROOT : outputKeys;\n\n const streamKeys = Object.keys(this.channels);\n const streamChannels =\n streamKeys.length === 1 && streamKeys[0] === ROOT ? ROOT : streamKeys;\n\n const userInterrupt = this._interrupt;\n // create empty compiled graph\n const compiled = new CompiledStateGraph<\n S,\n U,\n N,\n I,\n O,\n C,\n NodeReturnType,\n InterruptType,\n WriterType,\n TTransformers\n >({\n builder: this,\n checkpointer,\n interruptAfter,\n interruptBefore,\n autoValidate: false,\n nodes: {} as Record<N | typeof START, PregelNode<S, U>>,\n channels: {\n ...this.channels,\n [START]: new EphemeralValue(),\n } as Record<N | typeof START | typeof END | string, BaseChannel>,\n inputChannels: START,\n outputChannels,\n streamChannels,\n streamMode: \"updates\",\n store,\n cache,\n name,\n description,\n userInterrupt,\n streamTransformers: transformers,\n });\n\n // attach nodes, edges and branches\n compiled.attachNode(START);\n // Resolve graph-wide node defaults. Per-node values always win. Defaults\n // are merged into a copy of each spec so repeated `compile()` calls remain\n // stable (the builder's node specs are never mutated).\n //\n // - `retryPolicy` / `timeout` apply to all nodes, including error-handler\n // nodes (a stuck or transiently-failing handler is treated like any\n // other node).\n // - `cachePolicy` and the default `errorHandler` apply to regular nodes\n // only — caching a handler result is unsafe, and a handler must never\n // catch its own (or another handler's) failure.\n const nodeDefaults = this._nodeDefaults;\n const hasNodeDefaults =\n nodeDefaults.retryPolicy !== undefined ||\n nodeDefaults.cachePolicy !== undefined ||\n nodeDefaults.timeout !== undefined ||\n defaultErrorHandlerNode !== undefined;\n for (const [key, node] of Object.entries<StateGraphNodeSpec<S, U>>(\n this.nodes\n )) {\n const isErrorHandlerNode = node.isErrorHandler === true;\n const resolvedNode = hasNodeDefaults\n ? {\n ...node,\n retryPolicy: node.retryPolicy ?? nodeDefaults.retryPolicy,\n cachePolicy: isErrorHandlerNode\n ? undefined\n : node.cachePolicy === false\n ? undefined\n : (node.cachePolicy ?? nodeDefaults.cachePolicy),\n timeout: node.timeout ?? nodeDefaults.timeout,\n errorHandlerNode:\n !isErrorHandlerNode