{"version":3,"file":"timeout.cjs","names":["BaseCallbackHandler","#scope","#touch","CONFIG_KEY_SEND","CONFIG_KEY_CALL","patchConfigurable","combineAbortSignals","NodeTimeoutError"],"sources":["../../src/pregel/timeout.ts"],"sourcesContent":["import { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\nimport { PendingWrite } from \"@langchain/langgraph-checkpoint\";\nimport { CONFIG_KEY_CALL, CONFIG_KEY_SEND } from \"../constants.js\";\nimport { NodeTimeoutError } from \"../errors.js\";\nimport { LangGraphRunnableConfig } from \"./runnable_types.js\";\nimport { PregelExecutableTask } from \"./types.js\";\nimport {\n  combineAbortSignals,\n  patchConfigurable,\n  TimeoutPolicy,\n} from \"./utils/index.js\";\n\n/**\n * Tracks the live progress state of a single timed node attempt.\n *\n * The scope guards observable-progress channels (writes, child-task\n * scheduling, the custom stream writer, callbacks) so that:\n *\n * 1. `idleTimeout` is refreshed whenever the node makes progress, and\n * 2. once the attempt is `close()`d after a timeout fires, late writes/calls\n *    from the still-running background task are dropped (Python parity:\n *    buffered writes from the failed attempt must not leak into the\n *    checkpoint).\n *\n * @internal\n */\nclass TimedAttemptScope {\n  active = true;\n\n  lastProgress = Date.now();\n\n  private refreshOn: \"auto\" | \"heartbeat\";\n\n  constructor(refreshOn: \"auto\" | \"heartbeat\") {\n    this.refreshOn = refreshOn;\n  }\n\n  /** Record progress now. Always honored (used by `runtime.heartbeat()`). */\n  touch(): void {\n    this.lastProgress = Date.now();\n  }\n\n  /**\n   * Record progress for an automatic signal (write/call/stream/callback).\n   * No-op when `refreshOn === \"heartbeat\"`, where only explicit heartbeats\n   * count as progress.\n   */\n  autoTouch(): void {\n    if (this.refreshOn === \"auto\") {\n      this.lastProgress = Date.now();\n    }\n  }\n\n  close(): void {\n    this.active = false;\n  }\n}\n\n/**\n * Callback handler that refreshes a {@link TimedAttemptScope} on any LangChain\n * callback event emitted under the node's run. Because it is attached via\n * `config.callbacks`, it only observes events from runs descended from this\n * node's attempt, not from sibling nodes.\n *\n * @internal\n */\nclass IdleProgressCallbackHandler extends BaseCallbackHandler {\n  name = \"IdleProgressCallbackHandler\";\n\n  awaitHandlers = false;\n\n  #scope: TimedAttemptScope;\n\n  constructor(scope: TimedAttemptScope) {\n    super();\n    this.#scope = scope;\n  }\n\n  #touch = () => {\n    this.#scope.autoTouch();\n  };\n\n  handleLLMStart = this.#touch;\n\n  handleChatModelStart = this.#touch;\n\n  handleLLMNewToken = this.#touch;\n\n  handleLLMEnd = this.#touch;\n\n  handleLLMError = this.#touch;\n\n  handleChainStart = this.#touch;\n\n  handleChainEnd = this.#touch;\n\n  handleChainError = this.#touch;\n\n  handleToolStart = this.#touch;\n\n  handleToolEnd = this.#touch;\n\n  handleToolError = this.#touch;\n\n  handleText = this.#touch;\n\n  handleRetrieverStart = this.#touch;\n\n  handleRetrieverEnd = this.#touch;\n\n  handleRetrieverError = this.#touch;\n\n  handleCustomEvent = this.#touch;\n}\n\n/**\n * Wrap the node attempt config so observable-progress signals refresh the idle\n * clock and are dropped once the scope is closed. Also injects\n * {@link LangGraphRunnableConfig.heartbeat}.\n */\nfunction wrapConfig(\n  config: LangGraphRunnableConfig,\n  scope: TimedAttemptScope,\n  policy: TimeoutPolicy,\n  taskName: string\n): LangGraphRunnableConfig {\n  const configurable = config.configurable ?? {};\n  const patch: Record<string, unknown> = {};\n\n  const send = configurable[CONFIG_KEY_SEND];\n  if (typeof send === \"function\") {\n    patch[CONFIG_KEY_SEND] = (writes: PendingWrite[]) => {\n      if (!scope.active) return undefined;\n      if (writes && writes.length) scope.autoTouch();\n      return send(writes);\n    };\n  }\n\n  const callFn = configurable[CONFIG_KEY_CALL];\n  if (typeof callFn === \"function\") {\n    patch[CONFIG_KEY_CALL] = (...args: unknown[]) => {\n      if (!scope.active) {\n        throw new Error(\n          `Node \"${taskName}\" attempt was cancelled after its timeout fired`\n        );\n      }\n      scope.autoTouch();\n      return (callFn as (...a: unknown[]) => unknown)(...args);\n    };\n  }\n\n  const out: LangGraphRunnableConfig =\n    Object.keys(patch).length > 0 ? patchConfigurable(config, patch) : config;\n  const wrapped: LangGraphRunnableConfig = { ...out };\n\n  // `heartbeat` always resets the idle clock, even under `refreshOn:\n  // \"heartbeat\"`. It is a no-op when no idle timeout is configured.\n  wrapped.heartbeat = () => {\n    if (policy.idleTimeout !== undefined) scope.touch();\n  };\n\n  if (typeof wrapped.writer === \"function\") {\n    const writer = wrapped.writer;\n    wrapped.writer = ((chunk: unknown) => {\n      if (!scope.active) return undefined;\n      scope.autoTouch();\n      return (writer as (c: unknown) => unknown)(chunk);\n    }) as typeof wrapped.writer;\n  }\n\n  if (\n    (policy.refreshOn ?? \"auto\") === \"auto\" &&\n    policy.idleTimeout !== undefined\n  ) {\n    const handler = new IdleProgressCallbackHandler(scope);\n    const cb = wrapped.callbacks;\n    if (cb === undefined) {\n      wrapped.callbacks = [handler];\n    } else if (Array.isArray(cb)) {\n      wrapped.callbacks = [...cb, handler];\n    } else {\n      const copied = cb.copy();\n      copied.addHandler(handler, true);\n      wrapped.callbacks = copied;\n    }\n  }\n\n  return wrapped;\n}\n\ntype AttemptOutcome<T> =\n  | { type: \"ok\"; value: T }\n  | { type: \"err\"; error: unknown }\n  | { type: \"timeout\"; kind: \"run\" | \"idle\" };\n\n/**\n * Run a single node attempt under a {@link TimeoutPolicy}.\n *\n * Races the node invocation against per-attempt run/idle watchdogs. On\n * successful completion (or node error), returns/rethrows normally. When a\n * watchdog fires first, the scope is closed, the task's buffered writes are\n * dropped, the attempt's {@link AbortSignal} is aborted, and a\n * {@link NodeTimeoutError} is thrown.\n *\n * @internal\n */\nexport async function runAttemptWithTimeout<T>(\n  task: PregelExecutableTask<string, string>,\n  config: LangGraphRunnableConfig,\n  policy: TimeoutPolicy,\n  invoke: (scopedConfig: LangGraphRunnableConfig) => Promise<T>\n): Promise<T> {\n  const refreshOn = policy.refreshOn ?? \"auto\";\n  const scope = new TimedAttemptScope(refreshOn);\n\n  const timeoutController = new AbortController();\n  const { signal: composedSignal, dispose } = combineAbortSignals(\n    config.signal,\n    timeoutController.signal\n  );\n\n  const scopedConfig = wrapConfig(\n    { ...config, signal: composedSignal },\n    scope,\n    policy,\n    String(task.name)\n  );\n\n  const start = Date.now();\n  const bg = invoke(scopedConfig);\n\n  // Normalize the node result into an outcome. Catching the rejection here\n  // (rather than via a separate `bg.catch`) keeps `bg` handled even when a\n  // watchdog wins the race below, so its late settlement can never surface as\n  // an unhandled rejection.\n  const nodeOutcome: Promise<AttemptOutcome<T>> = bg.then(\n    (value) => ({ type: \"ok\", value }),\n    (error) => ({ type: \"err\", error })\n  );\n\n  let runTimer: ReturnType<typeof setTimeout> | undefined;\n  let idleTimer: ReturnType<typeof setTimeout> | undefined;\n  const clearTimers = () => {\n    if (runTimer !== undefined) clearTimeout(runTimer);\n    if (idleTimer !== undefined) clearTimeout(idleTimer);\n  };\n\n  // The watchdog never rejects: it either resolves with a timeout outcome or\n  // stays pending forever (and is dropped) when the node wins the race. The\n  // idle timer re-arms itself off `scope.lastProgress`, so it can't be a single\n  // fixed `setTimeout`.\n  const watchdog = new Promise<AttemptOutcome<T>>((resolve) => {\n    if (policy.runTimeout !== undefined) {\n      runTimer = setTimeout(\n        () => resolve({ type: \"timeout\", kind: \"run\" }),\n        policy.runTimeout\n      );\n    }\n\n    if (policy.idleTimeout !== undefined) {\n      const idleMs = policy.idleTimeout;\n      const checkIdle = () => {\n        const remaining = scope.lastProgress + idleMs - Date.now();\n        if (remaining <= 0) {\n          resolve({ type: \"timeout\", kind: \"idle\" });\n        } else {\n          idleTimer = setTimeout(checkIdle, remaining);\n        }\n      };\n      idleTimer = setTimeout(checkIdle, idleMs);\n    }\n  });\n\n  let outcome: AttemptOutcome<T>;\n  try {\n    outcome = await Promise.race([nodeOutcome, watchdog]);\n  } finally {\n    clearTimers();\n  }\n\n  // Watchdog timers are macrotasks and cannot fire while a synchronous\n  // (CPU-bound) node blocks the event loop. Such a node — or one with a long\n  // synchronous prefix — can therefore settle the race as \"ok\"/\"err\" before an\n  // already-expired timer ever runs, bypassing the documented hard wall-clock\n  // cap. Re-check the budget against wall-clock time here so the caps hold for\n  // synchronous nodes too. (For genuinely async work the watchdog already\n  // wins the race, so this only catches what timers structurally cannot.)\n  if (outcome.type !== \"timeout\") {\n    const now = Date.now();\n    if (policy.runTimeout !== undefined && now - start >= policy.runTimeout) {\n      outcome = { type: \"timeout\", kind: \"run\" };\n    } else if (\n      policy.idleTimeout !== undefined &&\n      now - scope.lastProgress >= policy.idleTimeout\n    ) {\n      outcome = { type: \"timeout\", kind: \"idle\" };\n    }\n  }\n\n  if (outcome.type === \"ok\") {\n    dispose?.();\n    return outcome.value;\n  }\n  if (outcome.type === \"err\") {\n    dispose?.();\n    throw outcome.error;\n  }\n\n  // A watchdog fired: close the scope (drop late writes/calls), discard the\n  // attempt's buffered writes, abort the node, and surface a NodeTimeoutError.\n  const elapsed = Date.now() - start;\n  scope.close();\n  task.writes.splice(0, task.writes.length);\n  // Abort BEFORE disposing the combined signal so the abort actually\n  // propagates to the node's signal (dispose removes the relay listeners).\n  timeoutController.abort();\n  dispose?.();\n\n  throw new NodeTimeoutError({\n    node: String(task.name),\n    elapsed,\n    kind: outcome.kind,\n    runTimeout: policy.runTimeout,\n    idleTimeout: policy.idleTimeout,\n  });\n}\n"],"mappings":"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"}