@librechat/agents 3.8.0 → 3.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +13 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +176 -55
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +16 -6
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/hitl/approvalReview.cjs +89 -0
  8. package/dist/cjs/hitl/approvalReview.cjs.map +1 -0
  9. package/dist/cjs/langfuse.cjs +42 -0
  10. package/dist/cjs/langfuse.cjs.map +1 -1
  11. package/dist/cjs/langfuseToolOutputTracing.cjs +42 -0
  12. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  13. package/dist/cjs/langfuseTraceShaping.cjs +7 -1
  14. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  15. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +6 -10
  16. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  17. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +2 -7
  18. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  19. package/dist/cjs/llm/contextPressureMeter.cjs +51 -10
  20. package/dist/cjs/llm/contextPressureMeter.cjs.map +1 -1
  21. package/dist/cjs/llm/init.cjs +1 -1
  22. package/dist/cjs/llm/invoke.cjs +2 -2
  23. package/dist/cjs/llm/openai/index.cjs +4 -3
  24. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  25. package/dist/cjs/llm/openai/utils/index.cjs +5 -4
  26. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  27. package/dist/cjs/llm/preempt.cjs +3 -2
  28. package/dist/cjs/llm/preempt.cjs.map +1 -1
  29. package/dist/cjs/llm/prepareProviderRequest.cjs +9 -7
  30. package/dist/cjs/llm/prepareProviderRequest.cjs.map +1 -1
  31. package/dist/cjs/llm/providers.cjs +1 -1
  32. package/dist/cjs/main.cjs +14 -8
  33. package/dist/cjs/messages/alternation.cjs +1 -5
  34. package/dist/cjs/messages/alternation.cjs.map +1 -1
  35. package/dist/cjs/messages/budget.cjs +206 -7
  36. package/dist/cjs/messages/budget.cjs.map +1 -1
  37. package/dist/cjs/messages/cache.cjs +3 -9
  38. package/dist/cjs/messages/cache.cjs.map +1 -1
  39. package/dist/cjs/messages/core.cjs +26 -38
  40. package/dist/cjs/messages/core.cjs.map +1 -1
  41. package/dist/cjs/messages/format.cjs +132 -43
  42. package/dist/cjs/messages/format.cjs.map +1 -1
  43. package/dist/cjs/messages/index.cjs +2 -0
  44. package/dist/cjs/messages/prune.cjs +14 -9
  45. package/dist/cjs/messages/prune.cjs.map +1 -1
  46. package/dist/cjs/messages/reasoningTypes.cjs +21 -0
  47. package/dist/cjs/messages/reasoningTypes.cjs.map +1 -0
  48. package/dist/cjs/messages/recency.cjs +15 -94
  49. package/dist/cjs/messages/recency.cjs.map +1 -1
  50. package/dist/cjs/messages/toolHistoryProjection.cjs +243 -0
  51. package/dist/cjs/messages/toolHistoryProjection.cjs.map +1 -0
  52. package/dist/cjs/messages/toolResultTypes.cjs +145 -4
  53. package/dist/cjs/messages/toolResultTypes.cjs.map +1 -1
  54. package/dist/cjs/run.cjs +22 -10
  55. package/dist/cjs/run.cjs.map +1 -1
  56. package/dist/cjs/session/AgentSession.cjs +7 -5
  57. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  58. package/dist/cjs/session/JsonlSessionStore.cjs +3 -0
  59. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  60. package/dist/cjs/session/index.cjs +1 -1
  61. package/dist/cjs/session/sessionProjection.cjs +75 -0
  62. package/dist/cjs/session/sessionProjection.cjs.map +1 -0
  63. package/dist/cjs/stream.cjs +20 -19
  64. package/dist/cjs/stream.cjs.map +1 -1
  65. package/dist/cjs/summarization/index.cjs.map +1 -1
  66. package/dist/cjs/summarization/node.cjs +19 -9
  67. package/dist/cjs/summarization/node.cjs.map +1 -1
  68. package/dist/cjs/summarization/shared.cjs +9 -0
  69. package/dist/cjs/summarization/shared.cjs.map +1 -1
  70. package/dist/cjs/tools/ToolNode.cjs +256 -84
  71. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  72. package/dist/cjs/tools/local/CompileCheckTool.cjs +1 -1
  73. package/dist/cjs/tools/local/LocalCodingTools.cjs +1 -1
  74. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +24 -9
  75. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  76. package/dist/cjs/tools/subagent/SubagentReplay.cjs +2 -0
  77. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  78. package/dist/cjs/tools/toolBatchReplay.cjs +187 -0
  79. package/dist/cjs/tools/toolBatchReplay.cjs.map +1 -0
  80. package/dist/cjs/tools/toolOutputReferences.cjs +12 -0
  81. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  82. package/dist/cjs/types/hitl.cjs +4 -0
  83. package/dist/cjs/types/hitl.cjs.map +1 -1
  84. package/dist/cjs/utils/events.cjs +13 -0
  85. package/dist/cjs/utils/events.cjs.map +1 -1
  86. package/dist/cjs/utils/tokens.cjs +105 -0
  87. package/dist/cjs/utils/tokens.cjs.map +1 -1
  88. package/dist/esm/agents/AgentContext.mjs +13 -3
  89. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  90. package/dist/esm/graphs/Graph.mjs +178 -57
  91. package/dist/esm/graphs/Graph.mjs.map +1 -1
  92. package/dist/esm/graphs/MultiAgentGraph.mjs +16 -6
  93. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  94. package/dist/esm/hitl/approvalReview.mjs +83 -0
  95. package/dist/esm/hitl/approvalReview.mjs.map +1 -0
  96. package/dist/esm/langfuse.mjs +43 -2
  97. package/dist/esm/langfuse.mjs.map +1 -1
  98. package/dist/esm/langfuseToolOutputTracing.mjs +41 -1
  99. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  100. package/dist/esm/langfuseTraceShaping.mjs +7 -1
  101. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  102. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +6 -10
  103. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  104. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +2 -7
  105. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  106. package/dist/esm/llm/contextPressureMeter.mjs +52 -11
  107. package/dist/esm/llm/contextPressureMeter.mjs.map +1 -1
  108. package/dist/esm/llm/init.mjs +1 -1
  109. package/dist/esm/llm/invoke.mjs +2 -2
  110. package/dist/esm/llm/openai/index.mjs +2 -1
  111. package/dist/esm/llm/openai/index.mjs.map +1 -1
  112. package/dist/esm/llm/openai/utils/index.mjs +5 -4
  113. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  114. package/dist/esm/llm/preempt.mjs +2 -1
  115. package/dist/esm/llm/preempt.mjs.map +1 -1
  116. package/dist/esm/llm/prepareProviderRequest.mjs +9 -7
  117. package/dist/esm/llm/prepareProviderRequest.mjs.map +1 -1
  118. package/dist/esm/llm/providers.mjs +1 -1
  119. package/dist/esm/main.mjs +13 -10
  120. package/dist/esm/messages/alternation.mjs +2 -6
  121. package/dist/esm/messages/alternation.mjs.map +1 -1
  122. package/dist/esm/messages/budget.mjs +206 -8
  123. package/dist/esm/messages/budget.mjs.map +1 -1
  124. package/dist/esm/messages/cache.mjs +3 -9
  125. package/dist/esm/messages/cache.mjs.map +1 -1
  126. package/dist/esm/messages/core.mjs +24 -34
  127. package/dist/esm/messages/core.mjs.map +1 -1
  128. package/dist/esm/messages/format.mjs +133 -44
  129. package/dist/esm/messages/format.mjs.map +1 -1
  130. package/dist/esm/messages/index.mjs +2 -0
  131. package/dist/esm/messages/prune.mjs +14 -9
  132. package/dist/esm/messages/prune.mjs.map +1 -1
  133. package/dist/esm/messages/reasoningTypes.mjs +20 -0
  134. package/dist/esm/messages/reasoningTypes.mjs.map +1 -0
  135. package/dist/esm/messages/recency.mjs +16 -95
  136. package/dist/esm/messages/recency.mjs.map +1 -1
  137. package/dist/esm/messages/toolHistoryProjection.mjs +237 -0
  138. package/dist/esm/messages/toolHistoryProjection.mjs.map +1 -0
  139. package/dist/esm/messages/toolResultTypes.mjs +141 -4
  140. package/dist/esm/messages/toolResultTypes.mjs.map +1 -1
  141. package/dist/esm/run.mjs +23 -11
  142. package/dist/esm/run.mjs.map +1 -1
  143. package/dist/esm/session/AgentSession.mjs +7 -5
  144. package/dist/esm/session/AgentSession.mjs.map +1 -1
  145. package/dist/esm/session/JsonlSessionStore.mjs +3 -0
  146. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  147. package/dist/esm/session/index.mjs +1 -1
  148. package/dist/esm/session/sessionProjection.mjs +72 -0
  149. package/dist/esm/session/sessionProjection.mjs.map +1 -0
  150. package/dist/esm/stream.mjs +17 -16
  151. package/dist/esm/stream.mjs.map +1 -1
  152. package/dist/esm/summarization/index.mjs.map +1 -1
  153. package/dist/esm/summarization/node.mjs +20 -10
  154. package/dist/esm/summarization/node.mjs.map +1 -1
  155. package/dist/esm/summarization/shared.mjs +9 -1
  156. package/dist/esm/summarization/shared.mjs.map +1 -1
  157. package/dist/esm/tools/ToolNode.mjs +256 -84
  158. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  159. package/dist/esm/tools/local/CompileCheckTool.mjs +1 -1
  160. package/dist/esm/tools/local/LocalCodingTools.mjs +1 -1
  161. package/dist/esm/tools/subagent/SubagentExecutor.mjs +24 -9
  162. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  163. package/dist/esm/tools/subagent/SubagentReplay.mjs +1 -1
  164. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  165. package/dist/esm/tools/toolBatchReplay.mjs +176 -0
  166. package/dist/esm/tools/toolBatchReplay.mjs.map +1 -0
  167. package/dist/esm/tools/toolOutputReferences.mjs +12 -0
  168. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  169. package/dist/esm/types/hitl.mjs +4 -1
  170. package/dist/esm/types/hitl.mjs.map +1 -1
  171. package/dist/esm/utils/events.mjs +13 -0
  172. package/dist/esm/utils/events.mjs.map +1 -1
  173. package/dist/esm/utils/tokens.mjs +105 -1
  174. package/dist/esm/utils/tokens.mjs.map +1 -1
  175. package/dist/types/agents/AgentContext.d.ts +13 -1
  176. package/dist/types/graphs/Graph.d.ts +27 -0
  177. package/dist/types/hitl/approvalReview.d.ts +32 -0
  178. package/dist/types/hooks/types.d.ts +4 -2
  179. package/dist/types/index.d.ts +1 -0
  180. package/dist/types/langfuse.d.ts +5 -0
  181. package/dist/types/langfuseToolOutputTracing.d.ts +2 -0
  182. package/dist/types/llm/contextPressureMeter.d.ts +2 -1
  183. package/dist/types/llm/prepareProviderRequest.d.ts +7 -1
  184. package/dist/types/messages/budget.d.ts +15 -10
  185. package/dist/types/messages/core.d.ts +8 -17
  186. package/dist/types/messages/format.d.ts +3 -2
  187. package/dist/types/messages/index.d.ts +1 -1
  188. package/dist/types/messages/prune.d.ts +1 -1
  189. package/dist/types/messages/reasoningTypes.d.ts +7 -0
  190. package/dist/types/messages/recency.d.ts +3 -0
  191. package/dist/types/messages/toolHistoryProjection.d.ts +65 -0
  192. package/dist/types/messages/toolResultTypes.d.ts +31 -1
  193. package/dist/types/session/sessionProjection.d.ts +10 -0
  194. package/dist/types/summarization/index.d.ts +2 -1
  195. package/dist/types/summarization/node.d.ts +1 -0
  196. package/dist/types/summarization/shared.d.ts +12 -0
  197. package/dist/types/tools/ToolNode.d.ts +12 -6
  198. package/dist/types/tools/subagent/SubagentReplay.d.ts +2 -0
  199. package/dist/types/tools/toolBatchReplay.d.ts +54 -0
  200. package/dist/types/tools/toolOutputReferences.d.ts +2 -0
  201. package/dist/types/types/graph.d.ts +20 -0
  202. package/dist/types/types/run.d.ts +4 -0
  203. package/dist/types/types/summarize.d.ts +4 -1
  204. package/dist/types/utils/tokens.d.ts +2 -1
  205. package/package.json +1 -1
  206. package/src/agents/AgentContext.ts +34 -3
  207. package/src/graphs/Graph.ts +465 -147
  208. package/src/graphs/MultiAgentGraph.ts +28 -6
  209. package/src/hitl/approvalReview.ts +209 -0
  210. package/src/hooks/types.ts +4 -1
  211. package/src/index.ts +1 -0
  212. package/src/langfuse.ts +67 -9
  213. package/src/langfuseToolOutputTracing.ts +91 -0
  214. package/src/langfuseTraceShaping.ts +17 -4
  215. package/src/llm/anthropic/utils/message_inputs.ts +5 -10
  216. package/src/llm/bedrock/utils/message_inputs.ts +2 -8
  217. package/src/llm/contextPressureMeter.ts +80 -12
  218. package/src/llm/openai/utils/index.ts +5 -4
  219. package/src/llm/prepareProviderRequest.ts +21 -16
  220. package/src/messages/alternation.ts +2 -15
  221. package/src/messages/budget.ts +439 -23
  222. package/src/messages/cache.ts +8 -9
  223. package/src/messages/core.ts +65 -95
  224. package/src/messages/format.ts +262 -78
  225. package/src/messages/index.ts +1 -1
  226. package/src/messages/prune.ts +31 -12
  227. package/src/messages/reasoningTypes.ts +23 -0
  228. package/src/messages/recency.ts +35 -179
  229. package/src/messages/toolHistoryProjection.ts +460 -0
  230. package/src/messages/toolResultTypes.ts +331 -100
  231. package/src/run.ts +44 -22
  232. package/src/session/AgentSession.ts +33 -16
  233. package/src/session/JsonlSessionStore.ts +6 -0
  234. package/src/session/sessionProjection.ts +126 -0
  235. package/src/stream.ts +14 -19
  236. package/src/summarization/index.ts +2 -0
  237. package/src/summarization/node.ts +62 -13
  238. package/src/summarization/shared.ts +23 -0
  239. package/src/tools/ToolNode.ts +518 -184
  240. package/src/tools/subagent/SubagentExecutor.ts +35 -6
  241. package/src/tools/subagent/SubagentReplay.ts +2 -2
  242. package/src/tools/toolBatchReplay.ts +395 -0
  243. package/src/tools/toolOutputReferences.ts +16 -0
  244. package/src/types/graph.ts +20 -0
  245. package/src/types/run.ts +4 -0
  246. package/src/types/summarize.ts +4 -1
  247. package/src/utils/events.ts +19 -0
  248. package/src/utils/tokens.ts +183 -0
@@ -1,19 +1,21 @@
1
- import { calculateMaxToolResultChars, truncateToolResultContent } from "../utils/truncation.mjs";
2
1
  import { CODE_EXECUTION_TOOLS } from "../common/enum.mjs";
3
2
  import "../common/index.mjs";
3
+ import { calculateMaxToolResultChars, truncateToolResultContent } from "../utils/truncation.mjs";
4
4
  import { attachRunStepResumeState } from "./runStepResume.mjs";
5
5
  import { SUBAGENT_PARENT_BATCH_CONFIG_KEY, SUBAGENT_REPLAY_CONTROLLER, attachSubagentResumeManifest } from "./subagent/SubagentReplay.mjs";
6
6
  import { composeAbortSignals } from "../utils/misc.mjs";
7
7
  import { resolveLangfuseRuntimeScope, withLangfuseRuntimeScope } from "../langfuseRuntimeScope.mjs";
8
8
  import { cloneToolMessageWithContent, compactToolContent, hasComputerCallOutputMarker, isComputerCallOutputContent, isComputerCallOutputMessage, serializeStructuredValueBounded, serializeToolContentBounded } from "../utils/toolContent.mjs";
9
9
  import { stampSyntheticProviderMessage } from "../messages/provenance.mjs";
10
+ import { buildToolExecutionRequestPlan, coerceArgsForSchema, coerceRecordArgs, recordArgsEqual, resolveRuntimeSessionHint, stableStringify } from "./eagerEventExecution.mjs";
11
+ import { TOOL_APPROVAL_REVIEW_CONFIG_KEY, getReviewedToolApproval, getToolApprovalReviewEvidence, toolApprovalPayloadMatches, toolApprovalProposalMatches } from "../hitl/approvalReview.mjs";
10
12
  import { TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY } from "../hooks/types.mjs";
13
+ import { TOOL_BATCH_REPLAY_KEY, attachToolBatchReplayState, getToolBatchReplayOwner, getToolBatchReplayScope, getToolBatchReplayState, isChildToolApprovalOwner, restoreToolBatchReplayState } from "./toolBatchReplay.mjs";
11
14
  import { executeHooks } from "../hooks/executeHooks.mjs";
12
15
  import "../hooks/index.mjs";
13
16
  import { convertInjectedMessages } from "../messages/injected.mjs";
14
17
  import { safeDispatchCustomEvent } from "../utils/events.mjs";
15
18
  import { RUN_BREAKER_SCOPE_CONFIG_KEY, StreamLimitExceededError } from "../llm/streamLimits.mjs";
16
- import { buildToolExecutionRequestPlan, coerceArgsForSchema, coerceRecordArgs, recordArgsEqual, resolveRuntimeSessionHint } from "./eagerEventExecution.mjs";
17
19
  import { INTENT_ARG, isIntentLabelProperty, outcomeFieldsFromResult, readOutcomeFields, resolveToolOutcome } from "./intentArg.mjs";
18
20
  import { ToolOutputReferenceRegistry, buildReferenceKey } from "./toolOutputReferences.mjs";
19
21
  import { PreparedSubagentError } from "./preparedSubagents.mjs";
@@ -28,10 +30,17 @@ import { RunnableLambda } from "@langchain/core/runnables";
28
30
  import { AIMessage, HumanMessage, ToolMessage, isAIMessage, isBaseMessage } from "@langchain/core/messages";
29
31
  import { Command, END, GraphInterrupt, Send, interrupt, isCommand, isGraphInterrupt } from "@langchain/langgraph";
30
32
  import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons";
33
+ import { createHash } from "crypto";
31
34
  //#region src/tools/ToolNode.ts
32
- function stripRunBreakerScope(configurable) {
33
- if (configurable == null || !("lc_run_breaker_scope" in configurable)) return configurable;
34
- const { [RUN_BREAKER_SCOPE_CONFIG_KEY]: _scope, ...rest } = configurable;
35
+ function stripToolApprovalReviewConfig(configurable) {
36
+ if (configurable == null || !("__librechat_tool_approval_review" in configurable) && !("__librechat_tool_batch_replay" in configurable)) return configurable;
37
+ const { [TOOL_APPROVAL_REVIEW_CONFIG_KEY]: _review, [TOOL_BATCH_REPLAY_KEY]: _replay, ...rest } = configurable;
38
+ return rest;
39
+ }
40
+ function stripHostExecutionConfig(configurable) {
41
+ const withoutReview = stripToolApprovalReviewConfig(configurable);
42
+ if (withoutReview == null || !("lc_run_breaker_scope" in withoutReview)) return withoutReview;
43
+ const { [RUN_BREAKER_SCOPE_CONFIG_KEY]: _scope, ...rest } = withoutReview;
35
44
  return rest;
36
45
  }
37
46
  function createToolApprovalReplayKey(config, agentId, toolUseId) {
@@ -60,6 +69,9 @@ function withSubagentReplayBatch(config, replayBatchKey) {
60
69
  }
61
70
  };
62
71
  }
72
+ function directReplayKey(call, batchIndex) {
73
+ return call.id == null || call.id === "" ? JSON.stringify(["position", batchIndex]) : JSON.stringify(["id", call.id]);
74
+ }
63
75
  function createToolErrorOwnership() {
64
76
  return {
65
77
  handlerOwned: /* @__PURE__ */ new Set(),
@@ -113,20 +125,14 @@ function describeOfferedShape(value) {
113
125
  function findAssistantBatch(messages) {
114
126
  for (let i = messages.length - 1; i >= 0; i--) {
115
127
  const message = messages[i];
116
- if (isAIMessage(message)) return {
117
- message,
118
- index: i,
119
- messageCount: messages.length
120
- };
128
+ if (isAIMessage(message)) return { message };
121
129
  }
122
130
  }
123
- function getAssistantBatchReplayKey(batch, runId, threadId) {
131
+ function getAssistantBatchReplayKey(batch, threadId) {
124
132
  return JSON.stringify([
125
- runId ?? null,
126
133
  threadId ?? null,
127
- batch.message.id ?? null,
128
- batch.index,
129
- batch.messageCount
134
+ batch.message.id == null || batch.message.id === "" ? null : batch.message.id,
135
+ createHash("sha256").update(stableStringify(batch.message.tool_calls ?? [])).digest("hex")
130
136
  ]);
131
137
  }
132
138
  function buildToolApprovalInterruptPayload(askEntries, hookSessionId) {
@@ -244,7 +250,7 @@ var ToolNode = class extends RunnableCallable {
244
250
  toolUsageCount;
245
251
  toolCallTurns = /* @__PURE__ */ new Map();
246
252
  directPathTurns = /* @__PURE__ */ new Map();
247
- settledInterruptingResults = /* @__PURE__ */ new Map();
253
+ settledDirectResultsByBatch = /* @__PURE__ */ new Map();
248
254
  toolRegistry;
249
255
  toolDefinitions;
250
256
  localImplementationNames = /* @__PURE__ */ new Set();
@@ -279,16 +285,44 @@ var ToolNode = class extends RunnableCallable {
279
285
  name: name ?? TOOL_NODE_RUN_NAME,
280
286
  tags,
281
287
  func: async (input, config) => {
288
+ const messages = Array.isArray(input) ? input : input.messages;
289
+ const assistantBatch = messages == null ? void 0 : findAssistantBatch(messages);
290
+ const activeReplayKey = assistantBatch == null || getToolBatchReplayScope(config) == null ? void 0 : getAssistantBatchReplayKey(assistantBatch, getToolBatchReplayScope(config));
291
+ const replayOwner = getToolBatchReplayOwner(config, this.executingAgentId ?? this.agentId ?? "");
292
+ const reviewEvidence = getToolApprovalReviewEvidence(config);
293
+ if (reviewEvidence?.owner != null && reviewEvidence.owner !== replayOwner) {
294
+ const calls = this.isSendInput(input) ? [input.lg_tool_call] : assistantBatch?.message.tool_calls ?? [];
295
+ const trustedParentCallIds = new Set(calls.filter((call) => call.name === "subagent" && this.toolMap.get(call.name)?.[SUBAGENT_REPLAY_CONTROLLER] != null).map((call) => call.id ?? ""));
296
+ if (!(getToolBatchReplayState(config.configurable)?.records.some((record) => record.owner === replayOwner && record.batch === activeReplayKey) === true) || !isChildToolApprovalOwner(config, reviewEvidence.owner, trustedParentCallIds)) throw new Error("Tool approval execution owner changed before resume — failing closed");
297
+ }
298
+ const referenceReplay = {};
299
+ const restoredBatches = await restoreToolBatchReplayState(config, replayOwner);
300
+ if (restoredBatches.length > 0 && !restoredBatches.some(({ batch }) => batch === activeReplayKey)) throw new Error("Tool batch proposal changed before approval replay — failing closed");
301
+ if (activeReplayKey != null && getToolBatchReplayState(config.configurable) != null) this.settledDirectResultsByBatch.delete(activeReplayKey);
302
+ for (const { batch, results, turnState, referenceState } of restoredBatches) {
303
+ if (batch !== activeReplayKey) continue;
304
+ this.settledDirectResultsByBatch.set(batch, new Map(results));
305
+ if (turnState != null) this.restoreSubagentResumeState(turnState);
306
+ referenceReplay.state = referenceState;
307
+ }
282
308
  restoreRunStepResumeState?.(input.runStepState, config);
283
309
  let result;
284
310
  try {
285
- result = await this.run(input, config);
311
+ result = await this.run(input, config, referenceReplay);
286
312
  } catch (error) {
287
- if (!isGraphInterrupt(error) || createRunStepResumeState == null) throw error;
288
- const resumeState = createRunStepResumeState();
289
- throw new GraphInterrupt(error.interrupts.map((pendingInterrupt) => ({
290
- ...pendingInterrupt,
291
- value: attachRunStepResumeState(pendingInterrupt.value, resumeState)
313
+ if (!isGraphInterrupt(error)) throw error;
314
+ const resumeState = createRunStepResumeState?.();
315
+ const activeResults = activeReplayKey == null ? void 0 : this.settledDirectResultsByBatch.get(activeReplayKey);
316
+ const activeBatches = activeReplayKey == null ? /* @__PURE__ */ new Map() : new Map([[activeReplayKey, activeResults ?? /* @__PURE__ */ new Map()]]);
317
+ const turnState = this.createSubagentResumeState();
318
+ const activeCallIds = new Set(assistantBatch?.message.tool_calls?.map((call) => call.id));
319
+ turnState.directPathTurns = turnState.directPathTurns.filter(({ toolCallId }) => activeCallIds.has(toolCallId));
320
+ throw new GraphInterrupt(await Promise.all(error.interrupts.map(async (pendingInterrupt) => {
321
+ const value = await attachToolBatchReplayState(pendingInterrupt.value, replayOwner, activeBatches, turnState, referenceReplay.state);
322
+ return {
323
+ ...pendingInterrupt,
324
+ value: resumeState == null ? value : attachRunStepResumeState(value, resumeState)
325
+ };
292
326
  })));
293
327
  }
294
328
  if (createRunStepResumeState == null) return result;
@@ -416,7 +450,7 @@ var ToolNode = class extends RunnableCallable {
416
450
  }
417
451
  clearDirectPathTurns() {
418
452
  this.directPathTurns.clear();
419
- this.settledInterruptingResults.clear();
453
+ this.settledDirectResultsByBatch.clear();
420
454
  }
421
455
  getCallerCapabilityProjection() {
422
456
  const discoveredToolNames = new Set(this.getDiscoveredToolNames?.() ?? []);
@@ -486,11 +520,16 @@ var ToolNode = class extends RunnableCallable {
486
520
  }
487
521
  invokeWithRuntime(tool, invokeParams, call, config, runInput) {
488
522
  const lgConfig = config;
489
- const runtime = {
523
+ const isTrustedSubagentHop = call.name === "subagent" && tool[SUBAGENT_REPLAY_CONTROLLER] != null;
524
+ const toolConfig = {
490
525
  ...config,
526
+ configurable: isTrustedSubagentHop ? config.configurable : stripToolApprovalReviewConfig(config.configurable)
527
+ };
528
+ const runtime = {
529
+ ...toolConfig,
491
530
  state: runInput,
492
531
  toolCallId: call.id ?? "",
493
- config,
532
+ config: toolConfig,
494
533
  context: lgConfig.context,
495
534
  store: lgConfig.store ?? null,
496
535
  writer: lgConfig.writer ?? config.configurable?.writer ?? null
@@ -576,6 +615,8 @@ var ToolNode = class extends RunnableCallable {
576
615
  if (call.id != null && call.id !== "") this.toolCallTurns.set(call.id, next);
577
616
  return next;
578
617
  })();
618
+ this.recordToolUsageTurn(call.name, usageCount, call.id);
619
+ if (batchContext.replayBatchKey != null && call.id != null && call.id !== "") this.directPathTurns.set(call.id, usageCount);
579
620
  let args = call.args;
580
621
  if (resolveFn != null) {
581
622
  const { resolved, unresolved } = resolveFn(runId, args);
@@ -724,6 +765,7 @@ var ToolNode = class extends RunnableCallable {
724
765
  throw new PreparedSubagentError("Tool hooks changed after a subagent started.");
725
766
  }
726
767
  const replayController = this.toolMap.get(call.name)?.[SUBAGENT_REPLAY_CONTROLLER];
768
+ const isTrustedSubagentReviewHop = call.name === "subagent" && replayController != null;
727
769
  const replayConfig = replayController == null ? config : withSubagentReplayBatch(config, batchContext.replayBatchKey);
728
770
  const settledOutput = await replayController?.getSettledOutput(call, replayConfig);
729
771
  if (settledOutput != null) {
@@ -754,9 +796,12 @@ var ToolNode = class extends RunnableCallable {
754
796
  const approvalReplayKey = call.id == null || call.id === "" ? void 0 : createToolApprovalReplayKey(config, this.executingAgentId ?? this.agentId ?? "", call.id);
755
797
  const approvalReplaySessionId = getToolApprovalReplaySessionId(config, runId);
756
798
  const pendingApproval = approvalReplayKey == null ? void 0 : hookRegistry?.getPendingToolApproval(approvalReplaySessionId, approvalReplayKey);
799
+ const approvalReviewEvidence = getToolApprovalReviewEvidence(config, getToolBatchReplayOwner(config, this.executingAgentId ?? this.agentId ?? ""));
800
+ const reviewedApproval = getReviewedToolApproval(approvalReviewEvidence?.payload, call.id);
801
+ const approvalEvidenceAppliesToBatch = approvalReviewEvidence?.payload.action_requests.some((request) => batchContext.approvalScopeCallIds == null ? request.tool_call_id === call.id : batchContext.approvalScopeCallIds.has(request.tool_call_id)) === true;
757
802
  const hasPostHook = hookRegistry?.hasHookFor("PostToolUse", runId) === true;
758
803
  const hasFailureHook = hookRegistry?.hasHookFor("PostToolUseFailure", runId) === true;
759
- if (hookRegistry == null || !hasPreHook && pendingApproval == null && !hasPostHook && !hasFailureHook) {
804
+ if (!hasPreHook && pendingApproval == null && reviewedApproval == null && approvalReviewEvidence == null && !hasPostHook && !hasFailureHook) {
760
805
  const output = await this.runTool(call, replayConfig, batchContext);
761
806
  return output instanceof ToolMessage ? persistOutput(output) : output;
762
807
  }
@@ -774,18 +819,21 @@ var ToolNode = class extends RunnableCallable {
774
819
  }
775
820
  const turn = usageCount;
776
821
  const stepId = this.toolCallStepIds?.get(call.id ?? "") ?? "";
777
- let resolvedArgs = call.args;
778
822
  const hookResolveOptions = { substituteIntentKey: this.toolDeclaresBusinessIntent(call.name) };
779
- if (batchContext.preBatchSnapshot != null) {
780
- const { resolved } = batchContext.preBatchSnapshot.resolve(call.args, hookResolveOptions);
781
- resolvedArgs = resolved;
782
- } else if (this.toolOutputRegistry != null) {
783
- const { resolved } = this.toolOutputRegistry.resolve(registryRunId, call.args, hookResolveOptions);
784
- resolvedArgs = resolved;
785
- }
823
+ const resolveDirectArgs = (args) => {
824
+ if (batchContext.preBatchSnapshot != null) return batchContext.preBatchSnapshot.resolve(args, hookResolveOptions).resolved;
825
+ if (this.toolOutputRegistry != null) return this.toolOutputRegistry.resolve(registryRunId, args, hookResolveOptions).resolved;
826
+ return args;
827
+ };
828
+ const originalResolvedArgs = resolveDirectArgs(call.args);
829
+ let resolvedArgs = hasPreHook || pendingApproval != null ? originalResolvedArgs : reviewedApproval?.request.arguments ?? originalResolvedArgs;
786
830
  let effectiveCall = call;
787
- if (hasPreHook || pendingApproval != null) {
788
- const preResult = await executeHooks({
831
+ if (hasPreHook || pendingApproval != null || reviewedApproval != null || approvalReviewEvidence != null) {
832
+ const preResult = hookRegistry == null ? {
833
+ additionalContexts: [],
834
+ injectedMessages: [],
835
+ errors: []
836
+ } : await executeHooks({
789
837
  registry: hookRegistry,
790
838
  input: {
791
839
  hook_event_name: "PreToolUse",
@@ -803,16 +851,31 @@ var ToolNode = class extends RunnableCallable {
803
851
  matchQuery: call.name,
804
852
  onceReplayKey: approvalReplayKey,
805
853
  onceReplaySessionId: approvalReplaySessionId
806
- }).catch(() => void 0);
854
+ }).catch(() => approvalReviewEvidence == null ? void 0 : {
855
+ decision: "deny",
856
+ reason: "Approval policy could not be evaluated on resume",
857
+ additionalContexts: [],
858
+ injectedMessages: [],
859
+ errors: []
860
+ });
807
861
  if (preResult != null) {
808
862
  if (preResult.additionalContexts.length > 0) {
809
863
  replayAdditionalContexts.push(...preResult.additionalContexts);
810
864
  batchContext.additionalContextsSink?.push(...preResult.additionalContexts);
811
865
  }
812
- if (preResult.updatedInput != null) effectiveCall = {
813
- ...call,
814
- args: preResult.updatedInput
815
- };
866
+ if (preResult.updatedInput != null) {
867
+ resolvedArgs = resolveDirectArgs(preResult.updatedInput);
868
+ effectiveCall = {
869
+ ...call,
870
+ args: resolvedArgs
871
+ };
872
+ } else if (reviewedApproval != null) {
873
+ resolvedArgs = reviewedApproval.request.arguments;
874
+ effectiveCall = {
875
+ ...call,
876
+ args: resolvedArgs
877
+ };
878
+ }
816
879
  if (preResult.decision === "deny") return persistOutput(this.blockDirectCall({
817
880
  call,
818
881
  resolvedArgs,
@@ -821,7 +884,15 @@ var ToolNode = class extends RunnableCallable {
821
884
  runId,
822
885
  threadId
823
886
  }), effectiveCall.args);
824
- if (preResult.decision === "ask") {
887
+ if (approvalEvidenceAppliesToBatch && reviewedApproval == null && !isTrustedSubagentReviewHop && preResult.decision !== "ask") return persistOutput(this.blockDirectCall({
888
+ call,
889
+ resolvedArgs,
890
+ reason: "Tool call was not included in the restored approval batch — failing closed",
891
+ hookRegistry,
892
+ runId,
893
+ threadId
894
+ }), resolvedArgs);
895
+ if (preResult.decision === "ask" || reviewedApproval != null) {
825
896
  if (this.humanInTheLoop?.enabled !== true) {
826
897
  const reason = this.resolveAskDecisionForDirectTool(preResult.reason, call.name);
827
898
  return persistOutput(this.blockDirectCall({
@@ -842,26 +913,41 @@ var ToolNode = class extends RunnableCallable {
842
913
  runId,
843
914
  threadId
844
915
  }), effectiveCall.args);
916
+ const allowedDecisions = preResult.decision === "ask" ? preResult.allowedDecisions : reviewedApproval?.reviewConfig.allowed_decisions;
917
+ const askReason = preResult.decision === "ask" ? preResult.reason : reviewedApproval?.request.description;
918
+ if (effectiveCall === call) effectiveCall = {
919
+ ...call,
920
+ args: resolvedArgs
921
+ };
845
922
  const payload = buildToolApprovalInterruptPayload([{
846
923
  entry: {
847
924
  call: effectiveCall,
848
925
  args: effectiveCall.args,
849
926
  stepId
850
927
  },
851
- reason: preResult.reason,
852
- allowedDecisions: preResult.allowedDecisions
928
+ reason: askReason,
929
+ allowedDecisions
853
930
  }], runId);
854
931
  const resumeValue = AsyncLocalStorageProviderSingleton.runWithConfig(config, () => interrupt(payload));
855
- hookRegistry.clearPendingToolApproval(approvalReplaySessionId, approvalReplayKey);
932
+ hookRegistry?.clearPendingToolApproval(approvalReplaySessionId, approvalReplayKey);
933
+ const currentApproval = getReviewedToolApproval(payload, toolCallId);
934
+ if (reviewedApproval != null && (currentApproval == null || !toolApprovalProposalMatches(currentApproval, reviewedApproval))) return persistOutput(this.blockDirectCall({
935
+ call,
936
+ resolvedArgs,
937
+ reason: "Reviewed tool proposal changed before resume; retry approval",
938
+ hookRegistry,
939
+ runId,
940
+ threadId
941
+ }), effectiveCall.args);
856
942
  const decision = normalizeApprovalDecisions([toolCallId], resumeValue).get(toolCallId) ?? {
857
943
  type: "reject",
858
944
  reason: "No decision provided for tool approval"
859
945
  };
860
946
  const declaredType = decision.type;
861
- if (preResult.allowedDecisions != null && (typeof declaredType !== "string" || !preResult.allowedDecisions.includes(declaredType))) return persistOutput(this.blockDirectCall({
947
+ if (allowedDecisions != null && (typeof declaredType !== "string" || !allowedDecisions.includes(declaredType))) return persistOutput(this.blockDirectCall({
862
948
  call,
863
949
  resolvedArgs,
864
- reason: `Decision "${typeof declaredType === "string" ? declaredType : "<missing>"}" not in allowedDecisions [${preResult.allowedDecisions.join(", ")}] — failing closed`,
950
+ reason: `Decision "${typeof declaredType === "string" ? declaredType : "<missing>"}" not in allowedDecisions [${allowedDecisions.join(", ")}] — failing closed`,
865
951
  hookRegistry,
866
952
  runId,
867
953
  threadId
@@ -869,7 +955,7 @@ var ToolNode = class extends RunnableCallable {
869
955
  if (decision.type === "reject") return persistOutput(this.blockDirectCall({
870
956
  call,
871
957
  resolvedArgs,
872
- reason: decision.reason ?? preResult.reason ?? "Rejected by user",
958
+ reason: decision.reason ?? askReason ?? "Rejected by user",
873
959
  hookRegistry,
874
960
  runId,
875
961
  threadId
@@ -997,7 +1083,7 @@ var ToolNode = class extends RunnableCallable {
997
1083
  }
998
1084
  blockDirectCall(args) {
999
1085
  const { call, resolvedArgs, reason, hookRegistry, runId, threadId } = args;
1000
- if (hookRegistry.hasHookFor("PermissionDenied", runId) === true) executeHooks({
1086
+ if (hookRegistry?.hasHookFor("PermissionDenied", runId) === true) executeHooks({
1001
1087
  registry: hookRegistry,
1002
1088
  input: {
1003
1089
  hook_event_name: "PermissionDenied",
@@ -1125,7 +1211,8 @@ var ToolNode = class extends RunnableCallable {
1125
1211
  resolvedArgs = pre.resolved;
1126
1212
  if (pre.unresolved.length > 0 && call.id != null) unresolvedByCallId.set(call.id, pre.unresolved);
1127
1213
  } else if (registry != null) {
1128
- const { resolved, unresolved } = registry.resolve(registryRunId, originalArgs, { substituteIntentKey: this.toolDeclaresBusinessIntent(call.name) });
1214
+ const options = { substituteIntentKey: this.toolDeclaresBusinessIntent(call.name) };
1215
+ const { resolved, unresolved } = preBatchSnapshot != null ? preBatchSnapshot.resolve(originalArgs, options) : registry.resolve(registryRunId, originalArgs, options);
1129
1216
  resolvedArgs = resolved;
1130
1217
  if (unresolved.length > 0 && call.id != null) unresolvedByCallId.set(call.id, unresolved);
1131
1218
  }
@@ -1147,8 +1234,9 @@ var ToolNode = class extends RunnableCallable {
1147
1234
  });
1148
1235
  const hookRegistry = this.hookRegistry;
1149
1236
  const approvalReplaySessionId = getToolApprovalReplaySessionId(config, runId);
1237
+ const approvalReviewEvidence = getToolApprovalReviewEvidence(config, getToolBatchReplayOwner(config, this.executingAgentId ?? this.agentId ?? ""));
1150
1238
  const hasPendingApproval = preToolCalls.some((entry) => entry.call.id != null && hookRegistry?.getPendingToolApproval(approvalReplaySessionId, createToolApprovalReplayKey(config, this.executingAgentId ?? this.agentId ?? "", entry.call.id)) != null);
1151
- if (hookRegistry != null && (hookRegistry.hasHookFor("PreToolUse", runId) || hasPendingApproval)) {
1239
+ if (hookRegistry?.hasHookFor("PreToolUse", runId) === true || hasPendingApproval || approvalReviewEvidence != null) {
1152
1240
  const preemptedEagerRecords = /* @__PURE__ */ new Map();
1153
1241
  if (this.eagerEventToolExecutions != null) for (const entry of preToolCalls) {
1154
1242
  const callId = entry.call.id;
@@ -1159,7 +1247,7 @@ var ToolNode = class extends RunnableCallable {
1159
1247
  this.eagerEventToolExecutions.delete(callId);
1160
1248
  }
1161
1249
  }
1162
- const preResults = await Promise.all(preToolCalls.map((entry) => {
1250
+ const preResults = hookRegistry == null ? preToolCalls.map(() => HOOK_FALLBACK) : await Promise.all(preToolCalls.map((entry) => {
1163
1251
  const toolUseId = entry.call.id;
1164
1252
  const approvalReplayKey = toolUseId == null ? void 0 : createToolApprovalReplayKey(config, this.executingAgentId ?? this.agentId ?? "", toolUseId);
1165
1253
  return executeHooks({
@@ -1212,7 +1300,7 @@ var ToolNode = class extends RunnableCallable {
1212
1300
  const flushDeferredBlockedSideEffects = async () => {
1213
1301
  for (const item of deferredBlockedSideEffects) {
1214
1302
  await this.dispatchStepCompleted(item.callId, item.toolName, item.args, item.contentString, config);
1215
- if (hookRegistry.hasHookFor("PermissionDenied", runId)) executeHooks({
1303
+ if (hookRegistry?.hasHookFor("PermissionDenied", runId) === true) executeHooks({
1216
1304
  registry: hookRegistry,
1217
1305
  input: {
1218
1306
  hook_event_name: "PermissionDenied",
@@ -1256,6 +1344,13 @@ var ToolNode = class extends RunnableCallable {
1256
1344
  continue;
1257
1345
  }
1258
1346
  if (hookResult.updatedInput != null) applyInputOverride(entry, hookResult.updatedInput);
1347
+ else {
1348
+ const reviewed = getReviewedToolApproval(approvalReviewEvidence?.payload, entry.call.id);
1349
+ if (reviewed != null) {
1350
+ entry.args = this.coerceEventToolArgs(entry.call.name, reviewed.request.arguments);
1351
+ if (entry.call.id != null) unresolvedByCallId.delete(entry.call.id);
1352
+ }
1353
+ }
1259
1354
  askEntries.push({
1260
1355
  entry,
1261
1356
  reason: hookResult.reason,
@@ -1264,6 +1359,19 @@ var ToolNode = class extends RunnableCallable {
1264
1359
  continue;
1265
1360
  }
1266
1361
  if (hookResult.updatedInput != null) applyInputOverride(entry, hookResult.updatedInput);
1362
+ const reviewedApproval = getReviewedToolApproval(approvalReviewEvidence?.payload, entry.call.id);
1363
+ if (reviewedApproval != null) {
1364
+ if (hookResult.updatedInput == null) {
1365
+ entry.args = this.coerceEventToolArgs(entry.call.name, reviewedApproval.request.arguments);
1366
+ if (entry.call.id != null) unresolvedByCallId.delete(entry.call.id);
1367
+ }
1368
+ askEntries.push({
1369
+ entry,
1370
+ reason: reviewedApproval.request.description,
1371
+ allowedDecisions: reviewedApproval.reviewConfig.allowed_decisions
1372
+ });
1373
+ continue;
1374
+ }
1267
1375
  if (entry.call.id != null) {
1268
1376
  const preempted = preemptedEagerRecords.get(entry.call.id);
1269
1377
  if (preempted != null) this.eagerEventToolExecutions?.set(entry.call.id, preempted);
@@ -1273,9 +1381,12 @@ var ToolNode = class extends RunnableCallable {
1273
1381
  if (askEntries.length > 0) {
1274
1382
  const payload = buildToolApprovalInterruptPayload(askEntries, runId);
1275
1383
  const resumeValue = AsyncLocalStorageProviderSingleton.runWithConfig(config, () => interrupt(payload));
1276
- for (const { entry } of askEntries) hookRegistry.clearPendingToolApproval(approvalReplaySessionId, createToolApprovalReplayKey(config, this.executingAgentId ?? this.agentId ?? "", entry.call.id));
1277
- const decisionByCallId = normalizeApprovalDecisions(askEntries.map(({ entry }) => entry.call.id), resumeValue);
1278
- for (const { entry, reason: askReason, allowedDecisions } of askEntries) {
1384
+ for (const { entry } of askEntries) hookRegistry?.clearPendingToolApproval(approvalReplaySessionId, createToolApprovalReplayKey(config, this.executingAgentId ?? this.agentId ?? "", entry.call.id));
1385
+ const proposalMatches = approvalReviewEvidence == null || toolApprovalPayloadMatches(payload, approvalReviewEvidence.payload);
1386
+ if (!proposalMatches) for (const { entry } of askEntries) blockEntry(entry, "Reviewed tool proposal changed before resume; retry approval");
1387
+ const decisionEntries = proposalMatches ? askEntries : [];
1388
+ const decisionByCallId = normalizeApprovalDecisions(decisionEntries.map(({ entry }) => entry.call.id), resumeValue);
1389
+ for (const { entry, reason: askReason, allowedDecisions } of decisionEntries) {
1279
1390
  const decision = decisionByCallId.get(entry.call.id) ?? {
1280
1391
  type: "reject",
1281
1392
  reason: "No decision provided for tool approval"
@@ -1401,7 +1512,7 @@ var ToolNode = class extends RunnableCallable {
1401
1512
  userId: config.configurable?.user_id,
1402
1513
  agentId: this.executingAgentId,
1403
1514
  callerCapabilityProjection: this.getCallerCapabilityProjectionSnapshot(),
1404
- configurable: stripRunBreakerScope(config.configurable),
1515
+ configurable: stripHostExecutionConfig(config.configurable),
1405
1516
  metadata: config.metadata,
1406
1517
  signal: config.signal,
1407
1518
  resolve: (results) => {
@@ -1659,15 +1770,39 @@ var ToolNode = class extends RunnableCallable {
1659
1770
  return this.dispatchStepCompleted(result.toolCallId, request.name, request.args, output, config, request.turn, resolveToolOutcome(request.args, outcomeFieldsFromResult(result), { isError: result.status === "error" }));
1660
1771
  }
1661
1772
  async runDirectBatchInterruptSafe(directCalls, batchIndices, config, baseContext) {
1662
- const settledBatchResults = baseContext.replayBatchKey == null ? void 0 : this.settledInterruptingResults.get(baseContext.replayBatchKey);
1773
+ const settledBatchResults = baseContext.replayBatchKey == null ? void 0 : this.settledDirectResultsByBatch.get(baseContext.replayBatchKey) ?? /* @__PURE__ */ new Map();
1774
+ const cacheResult = (call, position, result) => {
1775
+ if (baseContext.replayBatchKey == null || settledBatchResults == null) return;
1776
+ settledBatchResults.set(directReplayKey(call, baseContext.replayCallPositions?.get(call) ?? batchIndices[position]), result);
1777
+ this.settledDirectResultsByBatch.set(baseContext.replayBatchKey, settledBatchResults);
1778
+ };
1663
1779
  const restoreResult = (call, result) => {
1664
1780
  baseContext.additionalContextsSink?.push(...result.additionalContexts);
1781
+ const turn = result.turn;
1782
+ if (turn != null) this.recordToolUsageTurn(call.name, turn, call.id);
1783
+ const refMeta = isBaseMessage(result.output) ? result.output.additional_kwargs : void 0;
1784
+ if (refMeta?._refKey != null && result.referenceContent != null) {
1785
+ this.toolOutputRegistry?.set(baseContext.batchScopeId, refMeta._refKey, result.referenceContent);
1786
+ if (isBaseMessage(result.output)) result = {
1787
+ ...result,
1788
+ output: new ToolMessage({
1789
+ ...result.output,
1790
+ additional_kwargs: {
1791
+ ...result.output.additional_kwargs,
1792
+ _refScope: baseContext.batchScopeId
1793
+ }
1794
+ })
1795
+ };
1796
+ }
1665
1797
  if (call.id != null && result.resolvedArgs != null && baseContext.resolvedArgsByCallId != null) baseContext.resolvedArgsByCallId.set(call.id, result.resolvedArgs);
1666
1798
  return result;
1667
1799
  };
1668
1800
  const runOne = async (call, position) => {
1669
- const cachedResult = typeof call.id === "string" ? settledBatchResults?.get(call.id) : void 0;
1670
- if (cachedResult != null) return restoreResult(call, cachedResult);
1801
+ const cachedResult = settledBatchResults?.get(directReplayKey(call, baseContext.replayCallPositions?.get(call) ?? batchIndices[position]));
1802
+ if (cachedResult != null) {
1803
+ if (cachedResult.proposal.name !== call.name || !recordArgsEqual(cachedResult.proposal.args, call.args)) throw new Error("Cannot change an already completed tool call during approval replay");
1804
+ return restoreResult(call, cachedResult);
1805
+ }
1671
1806
  const additionalContexts = [];
1672
1807
  const output = await this.runDirectToolWithLifecycleHooks(call, config, {
1673
1808
  ...baseContext,
@@ -1675,14 +1810,32 @@ var ToolNode = class extends RunnableCallable {
1675
1810
  additionalContextsSink: additionalContexts
1676
1811
  });
1677
1812
  const resolvedArgs = call.id == null ? void 0 : baseContext.resolvedArgsByCallId?.get(call.id);
1678
- return restoreResult(call, {
1813
+ const result = {
1814
+ proposal: structuredClone({
1815
+ name: call.name,
1816
+ args: call.args
1817
+ }),
1818
+ turn: call.id == null ? void 0 : this.toolCallTurns.get(call.id) ?? this.directPathTurns.get(call.id),
1679
1819
  output,
1680
1820
  additionalContexts,
1681
1821
  ...resolvedArgs == null ? {} : { resolvedArgs }
1682
- });
1822
+ };
1823
+ const refMeta = isBaseMessage(output) ? output.additional_kwargs : void 0;
1824
+ if (refMeta?._refKey != null) result.referenceContent = this.toolOutputRegistry?.get(refMeta._refScope, refMeta._refKey);
1825
+ cacheResult(call, position, result);
1826
+ return restoreResult(call, result);
1683
1827
  };
1684
1828
  const interrupting = this.interruptingToolNames;
1685
- if (!(interrupting != null && directCalls.some((call) => interrupting.has(call.name)))) return (await Promise.all(directCalls.map((call, i) => runOne(call, i)))).map((result) => result.output);
1829
+ if (!(interrupting != null && directCalls.some((call) => interrupting.has(call.name)))) {
1830
+ const results = await Promise.allSettled(directCalls.map((call, i) => runOne(call, i)));
1831
+ this.throwIfBreakerTripped(config);
1832
+ const failed = results.find((result) => result.status === "rejected" && !isGraphInterrupt(result.reason)) ?? results.find((result) => result.status === "rejected");
1833
+ if (failed?.status === "rejected") throw failed.reason;
1834
+ return results.map((result) => {
1835
+ if (result.status === "rejected") throw result.reason;
1836
+ return result.value.output;
1837
+ });
1838
+ }
1686
1839
  const outputs = new Array(directCalls.length);
1687
1840
  const interruptingPositions = [];
1688
1841
  const regularPositions = [];
@@ -1703,12 +1856,6 @@ var ToolNode = class extends RunnableCallable {
1703
1856
  }
1704
1857
  const position = interruptingPositions[i];
1705
1858
  outputs[position] = result.value.output;
1706
- const callId = directCalls[position].id;
1707
- if (baseContext.replayBatchKey != null && typeof callId === "string" && callId !== "") {
1708
- const batchResults = this.settledInterruptingResults.get(baseContext.replayBatchKey) ?? /* @__PURE__ */ new Map();
1709
- batchResults.set(callId, result.value);
1710
- this.settledInterruptingResults.set(baseContext.replayBatchKey, batchResults);
1711
- }
1712
1859
  }
1713
1860
  if (hasInterruptingError) {
1714
1861
  if (isGraphInterrupt(interruptingError)) {
@@ -1750,7 +1897,7 @@ var ToolNode = class extends RunnableCallable {
1750
1897
  const outputs = [...toolMessages, ...injected];
1751
1898
  return Array.isArray(input) ? outputs : { messages: outputs };
1752
1899
  }
1753
- async run(input, config) {
1900
+ async run(input, config, referenceReplay = {}) {
1754
1901
  const breakerSignal = this.getBreakerSignal?.();
1755
1902
  const composedSignal = composeAbortSignals(config.signal, breakerSignal);
1756
1903
  const batchRunScope = this.getRunScope?.();
@@ -1766,10 +1913,11 @@ var ToolNode = class extends RunnableCallable {
1766
1913
  this.toolCallTurns.clear();
1767
1914
  const resolvedArgsByCallId = /* @__PURE__ */ new Map();
1768
1915
  const errorOwnership = createToolErrorOwnership();
1769
- const incomingRunId = config.configurable?.run_id;
1770
- const incomingThreadId = config.configurable?.thread_id;
1771
- const batchScopeId = incomingRunId ?? `\0anon-${this.anonBatchCounter++}`;
1772
- const turn = this.toolOutputRegistry?.nextTurn(batchScopeId) ?? 0;
1916
+ const batchScopeId = config.configurable?.run_id ?? `\0anon-${this.anonBatchCounter++}`;
1917
+ const resumedReferenceState = referenceReplay.state;
1918
+ const replayInputSnapshot = resumedReferenceState == null ? void 0 : this.toolOutputRegistry?.resumeBatch(batchScopeId, resumedReferenceState);
1919
+ referenceReplay.state = resumedReferenceState ?? this.toolOutputRegistry?.snapshotState(batchScopeId);
1920
+ const turn = resumedReferenceState?.turnCounter ?? this.toolOutputRegistry?.nextTurn(batchScopeId) ?? 0;
1773
1921
  let outputs;
1774
1922
  let replayBatchKey;
1775
1923
  let promotedAiMessage;
@@ -1779,17 +1927,19 @@ var ToolNode = class extends RunnableCallable {
1779
1927
  if (this.eventDrivenMode && !isLocalTool) return this.executeViaEvent([input.lg_tool_call], config, input, {
1780
1928
  batchIndices: [0],
1781
1929
  turn,
1782
- batchScopeId
1930
+ batchScopeId,
1931
+ preBatchSnapshot: replayInputSnapshot
1783
1932
  });
1784
1933
  const directAdditionalContexts = [];
1785
1934
  const { lg_tool_call: _sendToolCall, ...sendState } = input;
1786
1935
  const sendMessages = sendState.messages;
1787
1936
  const sendAssistantBatch = Array.isArray(sendMessages) ? findAssistantBatch(sendMessages) : void 0;
1788
- const sendReplayBatchKey = sendAssistantBatch == null ? void 0 : getAssistantBatchReplayKey(sendAssistantBatch, incomingRunId, incomingThreadId);
1937
+ const sendReplayBatchKey = sendAssistantBatch == null || getToolBatchReplayScope(config) == null ? void 0 : getAssistantBatchReplayKey(sendAssistantBatch, getToolBatchReplayScope(config));
1789
1938
  const sendOutput = await this.runDirectToolWithLifecycleHooks(input.lg_tool_call, config, {
1790
1939
  batchIndex: 0,
1791
1940
  turn,
1792
1941
  batchScopeId,
1942
+ preBatchSnapshot: replayInputSnapshot,
1793
1943
  resolvedArgsByCallId,
1794
1944
  errorOwnership,
1795
1945
  additionalContextsSink: directAdditionalContexts,
@@ -1813,14 +1963,15 @@ var ToolNode = class extends RunnableCallable {
1813
1963
  const assistantBatch = findAssistantBatch(messages);
1814
1964
  if (assistantBatch == null) throw new Error("ToolNode only accepts AIMessages as input.");
1815
1965
  const aiMessage = assistantBatch.message;
1816
- replayBatchKey = getAssistantBatchReplayKey(assistantBatch, incomingRunId, incomingThreadId);
1966
+ replayBatchKey = getToolBatchReplayScope(config) == null ? void 0 : getAssistantBatchReplayKey(assistantBatch, getToolBatchReplayScope(config));
1817
1967
  if (this.loadRuntimeTools) {
1818
1968
  const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
1819
1969
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
1820
1970
  this.applyToolExecutionOverrides();
1821
1971
  }
1972
+ const replayCallPositions = new Map((aiMessage.tool_calls ?? []).map((call, i) => [call, i]));
1822
1973
  const filteredCalls = aiMessage.tool_calls?.filter((call) => {
1823
- return (call.id == null || !toolMessageIds.has(call.id)) && !(call.id?.startsWith("srvtoolu_") ?? false);
1974
+ return (call.id == null || call.id === "" || !toolMessageIds.has(call.id)) && !(call.id?.startsWith("srvtoolu_") ?? false);
1824
1975
  }) ?? [];
1825
1976
  const attributableInvalidCalls = !(!Array.isArray(input) && typeof aiMessage.id === "string" && aiMessage.id.length > 0) ? [] : (aiMessage.invalid_tool_calls ?? []).filter((call) => call.id != null && call.id !== "" && !toolMessageIds.has(call.id) && !call.id.startsWith("srvtoolu_"));
1826
1977
  invalidCallResults = attributableInvalidCalls.map((call) => new ToolMessage({
@@ -1862,14 +2013,16 @@ var ToolNode = class extends RunnableCallable {
1862
2013
  if (directEntries.length === 0 && invalidCallResults.length === 0) return this.executeViaEvent(filteredCalls, config, input, {
1863
2014
  batchIndices: eventEntries.map((entry) => entry.batchIndex),
1864
2015
  turn,
1865
- batchScopeId
2016
+ batchScopeId,
2017
+ preBatchSnapshot: replayInputSnapshot
1866
2018
  });
1867
2019
  const directCalls = directEntries.map((e) => e.call);
1868
2020
  const directIndices = directEntries.map((e) => e.batchIndex);
1869
2021
  const eventCalls = eventEntries.map((e) => e.call);
1870
2022
  const eventIndices = eventEntries.map((e) => e.batchIndex);
2023
+ const approvalScopeCallIds = new Set(filteredCalls.flatMap((call) => call.id == null ? [] : [call.id]));
1871
2024
  const preResolvedEventArgs = /* @__PURE__ */ new Map();
1872
- const preBatchSnapshot = this.toolOutputRegistry?.snapshot(batchScopeId);
2025
+ const preBatchSnapshot = replayInputSnapshot ?? this.toolOutputRegistry?.snapshot(batchScopeId);
1873
2026
  if (preBatchSnapshot != null) {
1874
2027
  for (const entry of eventEntries) if (entry.call.id != null) {
1875
2028
  const { resolved, unresolved } = preBatchSnapshot.resolve(entry.call.args, { substituteIntentKey: this.toolDeclaresBusinessIntent(entry.call.name) });
@@ -1888,9 +2041,25 @@ var ToolNode = class extends RunnableCallable {
1888
2041
  preBatchSnapshot,
1889
2042
  additionalContextsSink: directAdditionalContexts,
1890
2043
  replayBatchKey,
2044
+ replayCallPositions,
2045
+ approvalScopeCallIds,
1891
2046
  runInput: input
1892
2047
  }) : [];
1893
- if (directCalls.length > 0 && directOutputs.length > 0) await this.handleRunToolCompletions(directCalls, directOutputs, config, resolvedArgsByCallId, errorOwnership);
2048
+ const settledDirectResults = replayBatchKey == null ? void 0 : this.settledDirectResultsByBatch.get(replayBatchKey);
2049
+ const unhandledDirectCalls = [];
2050
+ const unhandledDirectOutputs = [];
2051
+ for (let i = 0; i < directCalls.length; i++) {
2052
+ if ((settledDirectResults?.get(directReplayKey(directCalls[i], replayCallPositions.get(directCalls[i]))))?.completionHandled === true) continue;
2053
+ unhandledDirectCalls.push(directCalls[i]);
2054
+ unhandledDirectOutputs.push(directOutputs[i]);
2055
+ }
2056
+ if (unhandledDirectCalls.length > 0) {
2057
+ await this.handleRunToolCompletions(unhandledDirectCalls, unhandledDirectOutputs, config, resolvedArgsByCallId, errorOwnership);
2058
+ for (let i = 0; i < directCalls.length; i++) {
2059
+ const settled = settledDirectResults?.get(directReplayKey(directCalls[i], replayCallPositions.get(directCalls[i])));
2060
+ if (settled != null) settled.completionHandled = true;
2061
+ }
2062
+ }
1894
2063
  const eventResult = eventCalls.length > 0 ? await this.dispatchToolEvents(eventCalls, config, {
1895
2064
  batchIndices: eventIndices,
1896
2065
  turn,
@@ -1917,8 +2086,9 @@ var ToolNode = class extends RunnableCallable {
1917
2086
  ...eventResult.injected
1918
2087
  ];
1919
2088
  } else {
1920
- const preBatchSnapshot = this.toolOutputRegistry?.snapshot(batchScopeId);
2089
+ const preBatchSnapshot = replayInputSnapshot ?? this.toolOutputRegistry?.snapshot(batchScopeId);
1921
2090
  const directAdditionalContexts = [];
2091
+ const approvalScopeCallIds = new Set(filteredCalls.flatMap((call) => call.id == null ? [] : [call.id]));
1922
2092
  const toolOutputs = await this.runDirectBatchInterruptSafe(filteredCalls, filteredCalls.map((_call, i) => i), config, {
1923
2093
  turn,
1924
2094
  batchScopeId,
@@ -1927,6 +2097,8 @@ var ToolNode = class extends RunnableCallable {
1927
2097
  preBatchSnapshot,
1928
2098
  additionalContextsSink: directAdditionalContexts,
1929
2099
  replayBatchKey,
2100
+ replayCallPositions,
2101
+ approvalScopeCallIds,
1930
2102
  runInput: input
1931
2103
  });
1932
2104
  await this.handleRunToolCompletions(filteredCalls, toolOutputs, config, resolvedArgsByCallId, errorOwnership);
@@ -1955,7 +2127,7 @@ var ToolNode = class extends RunnableCallable {
1955
2127
  }
1956
2128
  }
1957
2129
  if (!outputs.some(isCommand)) {
1958
- if (replayBatchKey != null) this.settledInterruptingResults.delete(replayBatchKey);
2130
+ if (replayBatchKey != null) this.settledDirectResultsByBatch.delete(replayBatchKey);
1959
2131
  return Array.isArray(input) ? outputs : { messages: outputs };
1960
2132
  }
1961
2133
  if (promotedAiMessage != null) outputs = outputs.map((output) => isCommand(output) ? patchCommandUpdateForPromotedInvalidCalls(output, promotedAiMessage, invalidCallResults) : output);
@@ -2005,7 +2177,7 @@ var ToolNode = class extends RunnableCallable {
2005
2177
  combinedOutputs.push(parallelCommand);
2006
2178
  } else if (handoffCommands.length === 1) combinedOutputs.push(handoffCommands[0]);
2007
2179
  if (parentCommand) combinedOutputs.push(parentCommand);
2008
- if (replayBatchKey != null) this.settledInterruptingResults.delete(replayBatchKey);
2180
+ if (replayBatchKey != null) this.settledDirectResultsByBatch.delete(replayBatchKey);
2009
2181
  return combinedOutputs;
2010
2182
  }
2011
2183
  isSendInput(input) {