@librechat/agents 3.1.85 → 3.1.87

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