@librechat/agents 3.1.86 → 3.1.88

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 (160) hide show
  1. package/README.md +69 -0
  2. package/dist/cjs/events.cjs +23 -0
  3. package/dist/cjs/events.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +133 -18
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  8. package/dist/cjs/llm/anthropic/index.cjs +251 -53
  9. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  10. package/dist/cjs/llm/init.cjs +1 -5
  11. package/dist/cjs/llm/init.cjs.map +1 -1
  12. package/dist/cjs/llm/openai/index.cjs +113 -24
  13. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  14. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openrouter/index.cjs +3 -1
  16. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  17. package/dist/cjs/main.cjs +18 -5
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/openai/index.cjs +253 -0
  20. package/dist/cjs/openai/index.cjs.map +1 -0
  21. package/dist/cjs/responses/index.cjs +448 -0
  22. package/dist/cjs/responses/index.cjs.map +1 -0
  23. package/dist/cjs/run.cjs +108 -7
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/session/AgentSession.cjs +1057 -0
  26. package/dist/cjs/session/AgentSession.cjs.map +1 -0
  27. package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
  28. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
  29. package/dist/cjs/session/handlers.cjs +221 -0
  30. package/dist/cjs/session/handlers.cjs.map +1 -0
  31. package/dist/cjs/session/ids.cjs +22 -0
  32. package/dist/cjs/session/ids.cjs.map +1 -0
  33. package/dist/cjs/session/messageSerialization.cjs +179 -0
  34. package/dist/cjs/session/messageSerialization.cjs.map +1 -0
  35. package/dist/cjs/stream.cjs +475 -11
  36. package/dist/cjs/stream.cjs.map +1 -1
  37. package/dist/cjs/summarization/node.cjs +1 -1
  38. package/dist/cjs/summarization/node.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +177 -59
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
  42. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
  43. package/dist/cjs/tools/handlers.cjs +1 -1
  44. package/dist/cjs/tools/handlers.cjs.map +1 -1
  45. package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
  46. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
  47. package/dist/esm/events.mjs +23 -1
  48. package/dist/esm/events.mjs.map +1 -1
  49. package/dist/esm/graphs/Graph.mjs +133 -18
  50. package/dist/esm/graphs/Graph.mjs.map +1 -1
  51. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  52. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  53. package/dist/esm/llm/anthropic/index.mjs +251 -53
  54. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  55. package/dist/esm/llm/init.mjs +1 -5
  56. package/dist/esm/llm/init.mjs.map +1 -1
  57. package/dist/esm/llm/openai/index.mjs +113 -25
  58. package/dist/esm/llm/openai/index.mjs.map +1 -1
  59. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  60. package/dist/esm/llm/openrouter/index.mjs +4 -2
  61. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  62. package/dist/esm/main.mjs +5 -1
  63. package/dist/esm/main.mjs.map +1 -1
  64. package/dist/esm/openai/index.mjs +246 -0
  65. package/dist/esm/openai/index.mjs.map +1 -0
  66. package/dist/esm/responses/index.mjs +440 -0
  67. package/dist/esm/responses/index.mjs.map +1 -0
  68. package/dist/esm/run.mjs +108 -7
  69. package/dist/esm/run.mjs.map +1 -1
  70. package/dist/esm/session/AgentSession.mjs +1054 -0
  71. package/dist/esm/session/AgentSession.mjs.map +1 -0
  72. package/dist/esm/session/JsonlSessionStore.mjs +422 -0
  73. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
  74. package/dist/esm/session/handlers.mjs +219 -0
  75. package/dist/esm/session/handlers.mjs.map +1 -0
  76. package/dist/esm/session/ids.mjs +17 -0
  77. package/dist/esm/session/ids.mjs.map +1 -0
  78. package/dist/esm/session/messageSerialization.mjs +173 -0
  79. package/dist/esm/session/messageSerialization.mjs.map +1 -0
  80. package/dist/esm/stream.mjs +476 -12
  81. package/dist/esm/stream.mjs.map +1 -1
  82. package/dist/esm/summarization/node.mjs +1 -1
  83. package/dist/esm/summarization/node.mjs.map +1 -1
  84. package/dist/esm/tools/ToolNode.mjs +177 -59
  85. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  86. package/dist/esm/tools/eagerEventExecution.mjs +107 -0
  87. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
  88. package/dist/esm/tools/handlers.mjs +1 -1
  89. package/dist/esm/tools/handlers.mjs.map +1 -1
  90. package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
  91. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
  92. package/dist/types/events.d.ts +1 -0
  93. package/dist/types/graphs/Graph.d.ts +24 -9
  94. package/dist/types/index.d.ts +1 -0
  95. package/dist/types/llm/openai/index.d.ts +1 -0
  96. package/dist/types/openai/index.d.ts +75 -0
  97. package/dist/types/responses/index.d.ts +97 -0
  98. package/dist/types/run.d.ts +2 -0
  99. package/dist/types/session/AgentSession.d.ts +32 -0
  100. package/dist/types/session/JsonlSessionStore.d.ts +67 -0
  101. package/dist/types/session/handlers.d.ts +8 -0
  102. package/dist/types/session/ids.d.ts +4 -0
  103. package/dist/types/session/index.d.ts +5 -0
  104. package/dist/types/session/messageSerialization.d.ts +7 -0
  105. package/dist/types/session/types.d.ts +191 -0
  106. package/dist/types/tools/ToolNode.d.ts +12 -1
  107. package/dist/types/tools/eagerEventExecution.d.ts +23 -0
  108. package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
  109. package/dist/types/types/hitl.d.ts +4 -0
  110. package/dist/types/types/run.d.ts +11 -1
  111. package/dist/types/types/tools.d.ts +36 -0
  112. package/package.json +19 -2
  113. package/src/__tests__/stream.eagerEventExecution.test.ts +2571 -0
  114. package/src/events.ts +29 -0
  115. package/src/graphs/Graph.ts +224 -50
  116. package/src/graphs/MultiAgentGraph.ts +1 -1
  117. package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
  118. package/src/index.ts +3 -0
  119. package/src/llm/anthropic/index.ts +356 -84
  120. package/src/llm/anthropic/llm.spec.ts +64 -0
  121. package/src/llm/custom-chat-models.smoke.test.ts +175 -4
  122. package/src/llm/openai/contentBlocks.test.ts +35 -0
  123. package/src/llm/openai/deepseek.test.ts +201 -2
  124. package/src/llm/openai/index.ts +171 -26
  125. package/src/llm/openai/utils/index.ts +22 -0
  126. package/src/llm/openrouter/index.ts +4 -2
  127. package/src/openai/__tests__/openai.test.ts +337 -0
  128. package/src/openai/index.ts +404 -0
  129. package/src/responses/__tests__/responses.test.ts +652 -0
  130. package/src/responses/index.ts +677 -0
  131. package/src/run.ts +158 -8
  132. package/src/scripts/compare_pi_vs_ours.ts +592 -173
  133. package/src/scripts/session_live.ts +548 -0
  134. package/src/session/AgentSession.ts +1432 -0
  135. package/src/session/JsonlSessionStore.ts +572 -0
  136. package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
  137. package/src/session/__tests__/handlers.test.ts +161 -0
  138. package/src/session/handlers.ts +272 -0
  139. package/src/session/ids.ts +17 -0
  140. package/src/session/index.ts +44 -0
  141. package/src/session/messageSerialization.ts +207 -0
  142. package/src/session/types.ts +275 -0
  143. package/src/specs/custom-event-await.test.ts +89 -0
  144. package/src/specs/summarization.test.ts +1 -1
  145. package/src/stream.ts +756 -48
  146. package/src/summarization/node.ts +1 -1
  147. package/src/tools/ToolNode.ts +299 -126
  148. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
  149. package/src/tools/__tests__/handlers.test.ts +2 -1
  150. package/src/tools/__tests__/hitl.test.ts +206 -110
  151. package/src/tools/eagerEventExecution.ts +153 -0
  152. package/src/tools/handlers.ts +8 -4
  153. package/src/tools/streamedToolCallSeals.ts +57 -0
  154. package/src/types/hitl.ts +4 -0
  155. package/src/types/run.ts +11 -0
  156. package/src/types/tools.ts +36 -0
  157. package/dist/cjs/llm/text.cjs +0 -69
  158. package/dist/cjs/llm/text.cjs.map +0 -1
  159. package/dist/esm/llm/text.mjs +0 -67
  160. package/dist/esm/llm/text.mjs.map +0 -1
@@ -2,17 +2,15 @@ import { isBaseMessage, ToolMessage, HumanMessage, isAIMessage } from '@langchai
2
2
  import { isCommand, isGraphInterrupt, interrupt, Command, Send, END } from '@langchain/langgraph';
3
3
  import { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';
4
4
  import { Constants, CODE_EXECUTION_TOOLS, GraphEvents } from '../common/enum.mjs';
5
- import 'nanoid';
6
- import '../messages/core.mjs';
7
- import { calculateMaxToolResultChars, truncateToolResultContent } from '../utils/truncation.mjs';
8
- import { toLangChainContent } from '../messages/langchain.mjs';
9
- import { safeDispatchCustomEvent } from '../utils/events.mjs';
10
- import 'uuid';
5
+ import '../stream.mjs';
11
6
  import { RunnableCallable } from '../utils/run.mjs';
12
7
  import 'ai-tokenizer';
13
8
  import 'zod-to-json-schema';
9
+ import { calculateMaxToolResultChars, truncateToolResultContent } from '../utils/truncation.mjs';
10
+ import { safeDispatchCustomEvent } from '../utils/events.mjs';
14
11
  import { executeHooks } from '../hooks/executeHooks.mjs';
15
12
  import '../hooks/createWorkspacePolicyHook.mjs';
13
+ import { toLangChainContent } from '../messages/langchain.mjs';
16
14
  import { ToolOutputReferenceRegistry, buildReferenceKey } from './toolOutputReferences.mjs';
17
15
  import './local/CompileCheckTool.mjs';
18
16
  import 'path';
@@ -28,6 +26,7 @@ import './ProgrammaticToolCalling.mjs';
28
26
  import './BashProgrammaticToolCalling.mjs';
29
27
  import { resolveLocalToolRegistry, resolveLocalExecutionTools } from './local/resolveLocalExecutionTools.mjs';
30
28
  import './local/attachments.mjs';
29
+ import { buildToolExecutionRequestPlan, recordArgsEqual } from './eagerEventExecution.mjs';
31
30
 
32
31
  /**
33
32
  * Helper to check if a value is a Send object
@@ -258,6 +257,12 @@ class ToolNode extends RunnableCallable {
258
257
  sessions;
259
258
  /** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
260
259
  eventDrivenMode = false;
260
+ /** Opt-in stream-layer prestart config for event-driven tools. */
261
+ eagerEventToolExecution;
262
+ /** Shared per-run prestarted tool registry populated by ChatModelStreamHandler. */
263
+ eagerEventToolExecutions;
264
+ /** Shared per-run per-tool turn counter used by eager and normal event dispatch. */
265
+ eagerEventToolUsageCount;
261
266
  /** Agent ID for event-driven mode */
262
267
  agentId;
263
268
  /** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */
@@ -301,7 +306,7 @@ class ToolNode extends RunnableCallable {
301
306
  * other's in-flight state.
302
307
  */
303
308
  anonBatchCounter = 0;
304
- constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer, }) {
309
+ constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, agentId, directToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer, }) {
305
310
  super({ name, tags, func: (input, config) => this.run(input, config) });
306
311
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
307
312
  this.toolCallStepIds = toolCallStepIds;
@@ -315,6 +320,9 @@ class ToolNode extends RunnableCallable {
315
320
  });
316
321
  this.sessions = sessions;
317
322
  this.eventDrivenMode = eventDrivenMode ?? false;
323
+ this.eagerEventToolExecution = eagerEventToolExecution;
324
+ this.eagerEventToolExecutions = eagerEventToolExecutions;
325
+ this.eagerEventToolUsageCount = eagerEventToolUsageCount;
318
326
  this.agentId = agentId;
319
327
  this.directToolNames = directToolNames;
320
328
  this.maxToolResultChars =
@@ -493,6 +501,18 @@ class ToolNode extends RunnableCallable {
493
501
  getToolUsageCounts() {
494
502
  return new Map(this.toolUsageCount); // Return a copy
495
503
  }
504
+ recordToolUsageTurn(toolName, turn, callId) {
505
+ this.toolUsageCount.set(toolName, Math.max(this.toolUsageCount.get(toolName) ?? 0, turn + 1));
506
+ if (callId != null && callId !== '') {
507
+ this.toolCallTurns.set(callId, turn);
508
+ }
509
+ }
510
+ recordEventToolPlanningTurn(toolName, turn, callId) {
511
+ this.recordToolUsageTurn(toolName, turn, callId);
512
+ if (this.canConsumeEagerEventExecution()) {
513
+ this.eagerEventToolUsageCount?.set(toolName, Math.max(this.eagerEventToolUsageCount.get(toolName) ?? 0, turn + 1));
514
+ }
515
+ }
496
516
  /**
497
517
  * Runs a single tool call with error handling.
498
518
  *
@@ -1298,7 +1318,7 @@ class ToolNode extends RunnableCallable {
1298
1318
  * by `runTool`. Threaded as a local map (instead of instance state)
1299
1319
  * so concurrent batches cannot read each other's entries.
1300
1320
  */
1301
- handleRunToolCompletions(calls, outputs, config, resolvedArgsByCallId) {
1321
+ async handleRunToolCompletions(calls, outputs, config, resolvedArgsByCallId) {
1302
1322
  for (let i = 0; i < calls.length; i++) {
1303
1323
  const call = calls[i];
1304
1324
  const output = outputs[i];
@@ -1344,7 +1364,7 @@ class ToolNode extends RunnableCallable {
1344
1364
  output: contentString,
1345
1365
  progress: 1,
1346
1366
  };
1347
- safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, {
1367
+ await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, {
1348
1368
  result: {
1349
1369
  id: stepId,
1350
1370
  index: turn,
@@ -1513,9 +1533,9 @@ class ToolNode extends RunnableCallable {
1513
1533
  reason,
1514
1534
  });
1515
1535
  };
1516
- const flushDeferredBlockedSideEffects = () => {
1536
+ const flushDeferredBlockedSideEffects = async () => {
1517
1537
  for (const item of deferredBlockedSideEffects) {
1518
- this.dispatchStepCompleted(item.callId, item.toolName, item.args, item.contentString, config);
1538
+ await this.dispatchStepCompleted(item.callId, item.toolName, item.args, item.contentString, config);
1519
1539
  if (hookRegistry.hasHookFor('PermissionDenied', runId)) {
1520
1540
  executeHooks({
1521
1541
  registry: hookRegistry,
@@ -1738,7 +1758,7 @@ class ToolNode extends RunnableCallable {
1738
1758
  * no risk of being rolled back by a subsequent throw, so
1739
1759
  * no risk of a duplicate `ON_RUN_STEP_COMPLETED` event.
1740
1760
  */
1741
- this.dispatchStepCompleted(entry.call.id, entry.call.name, entry.args, truncatedResponse, config);
1761
+ await this.dispatchStepCompleted(entry.call.id, entry.call.name, entry.args, truncatedResponse, config);
1742
1762
  continue;
1743
1763
  }
1744
1764
  if (decision.type === 'edit') {
@@ -1804,7 +1824,7 @@ class ToolNode extends RunnableCallable {
1804
1824
  * dispatches in the same relative position as the pre-deferral
1805
1825
  * code did (after hook processing, before tool execution).
1806
1826
  */
1807
- flushDeferredBlockedSideEffects();
1827
+ await flushDeferredBlockedSideEffects();
1808
1828
  }
1809
1829
  else {
1810
1830
  approvedEntries.push(...preToolCalls);
@@ -1812,50 +1832,92 @@ class ToolNode extends RunnableCallable {
1812
1832
  const injected = [];
1813
1833
  const batchIndexByCallId = new Map();
1814
1834
  if (approvedEntries.length > 0) {
1815
- const requests = approvedEntries.map((entry) => {
1816
- const turn = this.toolUsageCount.get(entry.call.name) ?? 0;
1817
- this.toolUsageCount.set(entry.call.name, turn + 1);
1835
+ const plan = buildToolExecutionRequestPlan({
1836
+ toolCalls: approvedEntries.map((entry) => {
1837
+ const codeSessionContext = CODE_EXECUTION_TOOLS.has(entry.call.name) ||
1838
+ entry.call.name === Constants.SKILL_TOOL ||
1839
+ entry.call.name === Constants.READ_FILE
1840
+ ? this.getCodeSessionContext()
1841
+ : undefined;
1842
+ return {
1843
+ id: entry.call.id,
1844
+ name: entry.call.name,
1845
+ args: entry.args,
1846
+ stepId: entry.stepId,
1847
+ codeSessionContext,
1848
+ };
1849
+ }),
1850
+ usageCount: this.toolUsageCount,
1851
+ invalidArgsBehavior: 'error-result',
1852
+ recordTurn: (toolName, reservedTurn, callId) => {
1853
+ this.recordEventToolPlanningTurn(toolName, reservedTurn, callId);
1854
+ },
1855
+ });
1856
+ if (plan == null) {
1857
+ throw new Error('Unable to build event tool execution request plan');
1858
+ }
1859
+ const requests = plan.requests;
1860
+ for (const entry of approvedEntries) {
1818
1861
  if (entry.batchIndex != null && entry.call.id != null) {
1819
1862
  batchIndexByCallId.set(entry.call.id, entry.batchIndex);
1820
1863
  }
1821
- const request = {
1822
- id: entry.call.id,
1823
- name: entry.call.name,
1824
- args: entry.args,
1825
- stepId: entry.stepId,
1826
- turn,
1827
- };
1828
- /**
1829
- * Emit `codeSessionContext` for any tool whose host handler may need
1830
- * to reach into the code-execution sandbox:
1831
- * - `CODE_EXECUTION_TOOLS` direct executors that POST to /exec.
1832
- * - `SKILL_TOOL` — skill files live alongside code-env state.
1833
- * - `READ_FILE` — when the requested path is a code-env artifact
1834
- * (e.g. `/mnt/data/...`) the host falls back to reading via the
1835
- * same sandbox session; without the seeded `session_id` /
1836
- * `_injected_files` here, that fallback can't see prior-turn
1837
- * artifacts on the very first call of a turn.
1838
- */
1839
- if (CODE_EXECUTION_TOOLS.has(entry.call.name) ||
1840
- entry.call.name === Constants.SKILL_TOOL ||
1841
- entry.call.name === Constants.READ_FILE) {
1842
- request.codeSessionContext = this.getCodeSessionContext();
1864
+ }
1865
+ for (const result of plan.rejectedResults) {
1866
+ this.eagerEventToolExecutions?.delete(result.toolCallId);
1867
+ }
1868
+ const requestMap = new Map(plan.allRequests.map((r) => [r.id, r]));
1869
+ const eagerExecutions = [];
1870
+ const dispatchRequests = [];
1871
+ for (const request of requests) {
1872
+ const eagerExecution = this.takeMatchingEagerEventExecution(request);
1873
+ if (eagerExecution != null) {
1874
+ eagerExecutions.push({ request, execution: eagerExecution });
1843
1875
  }
1844
- return request;
1845
- });
1846
- const requestMap = new Map(requests.map((r) => [r.id, r]));
1847
- const results = await new Promise((resolve, reject) => {
1848
- const batchRequest = {
1849
- toolCalls: requests,
1850
- userId: config.configurable?.user_id,
1851
- agentId: this.agentId,
1852
- configurable: config.configurable,
1853
- metadata: config.metadata,
1854
- resolve,
1855
- reject,
1856
- };
1857
- safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, batchRequest, config);
1858
- });
1876
+ else {
1877
+ dispatchRequests.push(request);
1878
+ }
1879
+ }
1880
+ const dispatchPromise = dispatchRequests.length === 0
1881
+ ? Promise.resolve([])
1882
+ : new Promise((resolve, reject) => {
1883
+ let dispatchSettled = false;
1884
+ let resultSettled = false;
1885
+ let settledResults;
1886
+ const maybeResolve = () => {
1887
+ if (dispatchSettled && resultSettled) {
1888
+ resolve(settledResults ?? []);
1889
+ }
1890
+ };
1891
+ const batchRequest = {
1892
+ toolCalls: dispatchRequests,
1893
+ userId: config.configurable?.user_id,
1894
+ agentId: this.agentId,
1895
+ configurable: config.configurable,
1896
+ metadata: config.metadata,
1897
+ resolve: (results) => {
1898
+ resultSettled = true;
1899
+ settledResults = results;
1900
+ maybeResolve();
1901
+ },
1902
+ reject,
1903
+ };
1904
+ void safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, batchRequest, config)
1905
+ .then(() => {
1906
+ dispatchSettled = true;
1907
+ maybeResolve();
1908
+ })
1909
+ .catch(reject);
1910
+ });
1911
+ const eagerResultsPromise = Promise.all(eagerExecutions.map(({ request, execution }) => this.resolveEagerEventExecution(request, execution))).then((results) => results.flat());
1912
+ const [eagerResults, dispatchedResults] = await Promise.all([
1913
+ eagerResultsPromise,
1914
+ dispatchPromise,
1915
+ ]);
1916
+ const results = [
1917
+ ...plan.rejectedResults,
1918
+ ...eagerResults,
1919
+ ...dispatchedResults,
1920
+ ];
1859
1921
  this.storeCodeSessionFromResults(results, requestMap);
1860
1922
  const hasPostHook = this.hookRegistry?.hasHookFor('PostToolUse', runId) === true;
1861
1923
  const hasFailureHook = this.hookRegistry?.hasHookFor('PostToolUseFailure', runId) === true;
@@ -1994,7 +2056,7 @@ class ToolNode extends RunnableCallable {
1994
2056
  }),
1995
2057
  });
1996
2058
  }
1997
- this.dispatchStepCompleted(result.toolCallId, toolName, request?.args ?? {}, contentString, config, request?.turn);
2059
+ await this.dispatchStepCompleted(result.toolCallId, toolName, request?.args ?? {}, contentString, config, request?.turn);
1998
2060
  postToolBatchEntryByCallId.set(result.toolCallId, {
1999
2061
  toolName,
2000
2062
  toolInput: request?.args ?? {},
@@ -2022,6 +2084,62 @@ class ToolNode extends RunnableCallable {
2022
2084
  });
2023
2085
  return { toolMessages, injected };
2024
2086
  }
2087
+ canConsumeEagerEventExecution() {
2088
+ return (this.eventDrivenMode &&
2089
+ this.eagerEventToolExecution?.enabled === true &&
2090
+ this.hookRegistry == null &&
2091
+ this.humanInTheLoop?.enabled !== true &&
2092
+ this.toolOutputRegistry == null);
2093
+ }
2094
+ takeMatchingEagerEventExecution(request) {
2095
+ if (!this.canConsumeEagerEventExecution()) {
2096
+ return undefined;
2097
+ }
2098
+ const execution = this.eagerEventToolExecutions?.get(request.id);
2099
+ if (execution == null) {
2100
+ return undefined;
2101
+ }
2102
+ this.eagerEventToolExecutions?.delete(request.id);
2103
+ if (execution.toolName !== request.name ||
2104
+ !recordArgsEqual(execution.args, request.args) ||
2105
+ execution.request.turn !== request.turn) {
2106
+ return {
2107
+ toolCallId: request.id,
2108
+ toolName: request.name,
2109
+ args: request.args,
2110
+ request,
2111
+ promise: Promise.resolve({
2112
+ results: [
2113
+ {
2114
+ toolCallId: request.id,
2115
+ status: 'error',
2116
+ content: '',
2117
+ errorMessage: 'Tool call changed after eager execution started; refusing to re-run the tool to avoid duplicate side effects.',
2118
+ },
2119
+ ],
2120
+ }),
2121
+ };
2122
+ }
2123
+ return execution;
2124
+ }
2125
+ async resolveEagerEventExecution(request, execution) {
2126
+ const outcome = await execution.promise;
2127
+ if (outcome.error != null) {
2128
+ throw outcome.error;
2129
+ }
2130
+ const results = outcome.results.filter((result) => result.toolCallId === request.id);
2131
+ if (results.length > 0) {
2132
+ return results;
2133
+ }
2134
+ return [
2135
+ {
2136
+ toolCallId: request.id,
2137
+ status: 'error',
2138
+ content: '',
2139
+ errorMessage: 'Tool execution completed without a result for this tool call',
2140
+ },
2141
+ ];
2142
+ }
2025
2143
  /**
2026
2144
  * Fires the `PostToolBatch` hook (if registered) and appends the
2027
2145
  * accumulated batch-level `additionalContext` strings to `injected`
@@ -2087,7 +2205,7 @@ class ToolNode extends RunnableCallable {
2087
2205
  }));
2088
2206
  }
2089
2207
  }
2090
- dispatchStepCompleted(toolCallId, toolName, args, output, config, turn) {
2208
+ async dispatchStepCompleted(toolCallId, toolName, args, output, config, turn) {
2091
2209
  const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
2092
2210
  if (!stepId) {
2093
2211
  // eslint-disable-next-line no-console
@@ -2095,7 +2213,7 @@ class ToolNode extends RunnableCallable {
2095
2213
  'This indicates a race between the stream consumer and graph execution. ' +
2096
2214
  `Map size: ${this.toolCallStepIds?.size ?? 0}`);
2097
2215
  }
2098
- safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, {
2216
+ await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_COMPLETED, {
2099
2217
  result: {
2100
2218
  id: stepId,
2101
2219
  index: turn ?? this.toolUsageCount.get(toolName) ?? 0,
@@ -2214,7 +2332,7 @@ class ToolNode extends RunnableCallable {
2214
2332
  }),
2215
2333
  ]
2216
2334
  : [sendOutput];
2217
- this.handleRunToolCompletions([input.lg_tool_call],
2335
+ await this.handleRunToolCompletions([input.lg_tool_call],
2218
2336
  // Pass only the tool output to completion handling; the
2219
2337
  // HumanMessage isn't a tool result.
2220
2338
  [sendOutput], config, resolvedArgsByCallId);
@@ -2338,7 +2456,7 @@ class ToolNode extends RunnableCallable {
2338
2456
  })))
2339
2457
  : [];
2340
2458
  if (directCalls.length > 0 && directOutputs.length > 0) {
2341
- this.handleRunToolCompletions(directCalls, directOutputs, config, resolvedArgsByCallId);
2459
+ await this.handleRunToolCompletions(directCalls, directOutputs, config, resolvedArgsByCallId);
2342
2460
  }
2343
2461
  const eventResult = eventCalls.length > 0
2344
2462
  ? await this.dispatchToolEvents(eventCalls, config, {
@@ -2386,7 +2504,7 @@ class ToolNode extends RunnableCallable {
2386
2504
  preBatchSnapshot,
2387
2505
  additionalContextsSink: directAdditionalContexts,
2388
2506
  })));
2389
- this.handleRunToolCompletions(filteredCalls, toolOutputs, config, resolvedArgsByCallId);
2507
+ await this.handleRunToolCompletions(filteredCalls, toolOutputs, config, resolvedArgsByCallId);
2390
2508
  // Append accumulated additionalContexts as a single
2391
2509
  // HumanMessage so the next model turn sees them. Codex P2 #39.
2392
2510
  outputs =