@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
@@ -0,0 +1,373 @@
1
+ import { z } from 'zod';
2
+ import { tool } from '@langchain/core/tools';
3
+ import { AIMessage, ToolMessage } from '@langchain/core/messages';
4
+ import { describe, it, expect, jest, afterEach } from '@jest/globals';
5
+ import type { StructuredToolInterface } from '@langchain/core/tools';
6
+ import type { PreToolUseHookInput, PreToolUseHookOutput } from '@/hooks';
7
+ import type * as t from '@/types';
8
+ import { GraphEvents } from '@/common';
9
+ import { HookRegistry } from '@/hooks';
10
+ import * as events from '@/utils/events';
11
+ import { ToolNode } from '../ToolNode';
12
+
13
+ function createDummyTool(name: string): StructuredToolInterface {
14
+ return tool(async () => 'direct should not run', {
15
+ name,
16
+ description: 'dummy',
17
+ schema: z.object({ city: z.string() }),
18
+ }) as unknown as StructuredToolInterface;
19
+ }
20
+
21
+ function createAIMessage(
22
+ id: string,
23
+ name: string,
24
+ args: Record<string, unknown>
25
+ ): AIMessage {
26
+ return new AIMessage({
27
+ content: '',
28
+ tool_calls: [{ id, name, args }],
29
+ });
30
+ }
31
+
32
+ function createAIMessageWithToolCalls(
33
+ toolCalls: Array<{
34
+ id: string;
35
+ name: string;
36
+ args: unknown;
37
+ }>
38
+ ): AIMessage {
39
+ return new AIMessage({
40
+ content: '',
41
+ tool_calls: toolCalls as AIMessage['tool_calls'],
42
+ });
43
+ }
44
+
45
+ function installToolExecuteResponder(content: string): {
46
+ toolExecuteCalls: t.ToolExecuteBatchRequest[];
47
+ } {
48
+ const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
49
+ jest
50
+ .spyOn(events, 'safeDispatchCustomEvent')
51
+ .mockImplementation(async (event, data): Promise<void> => {
52
+ if (event !== GraphEvents.ON_TOOL_EXECUTE) {
53
+ return;
54
+ }
55
+ const batch = data as t.ToolExecuteBatchRequest;
56
+ toolExecuteCalls.push(batch);
57
+ batch.resolve(
58
+ batch.toolCalls.map((request) => ({
59
+ toolCallId: request.id,
60
+ status: 'success' as const,
61
+ content,
62
+ }))
63
+ );
64
+ });
65
+ return { toolExecuteCalls };
66
+ }
67
+
68
+ describe('ToolNode eager event tool execution', () => {
69
+ afterEach(() => {
70
+ jest.restoreAllMocks();
71
+ });
72
+
73
+ it('uses a matching prestarted event result without redispatching the tool', async () => {
74
+ const { toolExecuteCalls } = installToolExecuteResponder('redispatched');
75
+ const eagerExecutions = new Map<string, t.EagerEventToolExecution>();
76
+ const eagerUsageCount = new Map<string, number>([['weather', 1]]);
77
+ const request: t.ToolCallRequest = {
78
+ id: 'call_weather',
79
+ name: 'weather',
80
+ args: { city: 'NYC' },
81
+ stepId: 'step_weather',
82
+ turn: 0,
83
+ };
84
+ eagerExecutions.set('call_weather', {
85
+ toolCallId: 'call_weather',
86
+ toolName: 'weather',
87
+ args: { city: 'NYC' },
88
+ request,
89
+ promise: Promise.resolve({
90
+ results: [
91
+ {
92
+ toolCallId: 'call_weather',
93
+ status: 'success',
94
+ content: 'eager result',
95
+ },
96
+ ],
97
+ }),
98
+ });
99
+
100
+ const toolNode = new ToolNode({
101
+ tools: [createDummyTool('weather')],
102
+ eventDrivenMode: true,
103
+ eagerEventToolExecution: { enabled: true },
104
+ eagerEventToolExecutions: eagerExecutions,
105
+ eagerEventToolUsageCount: eagerUsageCount,
106
+ toolCallStepIds: new Map([['call_weather', 'step_weather']]),
107
+ });
108
+
109
+ const result = (await toolNode.invoke({
110
+ messages: [createAIMessage('call_weather', 'weather', { city: 'NYC' })],
111
+ })) as { messages: ToolMessage[] };
112
+
113
+ expect(toolExecuteCalls).toHaveLength(0);
114
+ expect(eagerUsageCount.get('weather')).toBe(1);
115
+ expect(eagerExecutions.has('call_weather')).toBe(false);
116
+ expect(result.messages).toHaveLength(1);
117
+ expect(result.messages[0].content).toBe('eager result');
118
+ });
119
+
120
+ it('fails closed without redispatching when final args differ from the prestarted call', async () => {
121
+ const { toolExecuteCalls } = installToolExecuteResponder('fresh result');
122
+ const eagerExecutions = new Map<string, t.EagerEventToolExecution>();
123
+ const request: t.ToolCallRequest = {
124
+ id: 'call_weather',
125
+ name: 'weather',
126
+ args: { city: 'NYC' },
127
+ stepId: 'step_weather',
128
+ turn: 0,
129
+ };
130
+ eagerExecutions.set('call_weather', {
131
+ toolCallId: 'call_weather',
132
+ toolName: 'weather',
133
+ args: { city: 'NYC' },
134
+ request,
135
+ promise: Promise.resolve({
136
+ results: [
137
+ {
138
+ toolCallId: 'call_weather',
139
+ status: 'success',
140
+ content: 'stale eager result',
141
+ },
142
+ ],
143
+ }),
144
+ });
145
+
146
+ const toolNode = new ToolNode({
147
+ tools: [createDummyTool('weather')],
148
+ eventDrivenMode: true,
149
+ eagerEventToolExecution: { enabled: true },
150
+ eagerEventToolExecutions: eagerExecutions,
151
+ toolCallStepIds: new Map([['call_weather', 'step_weather']]),
152
+ });
153
+
154
+ const result = (await toolNode.invoke({
155
+ messages: [
156
+ createAIMessage('call_weather', 'weather', { city: 'Boston' }),
157
+ ],
158
+ })) as { messages: ToolMessage[] };
159
+
160
+ expect(toolExecuteCalls).toHaveLength(0);
161
+ expect(eagerExecutions.has('call_weather')).toBe(false);
162
+ expect(result.messages).toHaveLength(1);
163
+ expect(result.messages[0].status).toBe('error');
164
+ expect(result.messages[0].content).toContain('refusing to re-run');
165
+ });
166
+
167
+ it('increments the shared eager turn counter for normal event dispatch', async () => {
168
+ const { toolExecuteCalls } = installToolExecuteResponder('normal result');
169
+ const eagerUsageCount = new Map<string, number>();
170
+
171
+ const toolNode = new ToolNode({
172
+ tools: [createDummyTool('weather')],
173
+ eventDrivenMode: true,
174
+ eagerEventToolExecution: { enabled: true },
175
+ eagerEventToolExecutions: new Map(),
176
+ eagerEventToolUsageCount: eagerUsageCount,
177
+ toolCallStepIds: new Map([['call_weather', 'step_weather']]),
178
+ });
179
+
180
+ const result = (await toolNode.invoke({
181
+ messages: [createAIMessage('call_weather', 'weather', { city: 'NYC' })],
182
+ })) as { messages: ToolMessage[] };
183
+
184
+ expect(toolExecuteCalls).toHaveLength(1);
185
+ expect(toolExecuteCalls[0].toolCalls[0]).toMatchObject({
186
+ id: 'call_weather',
187
+ name: 'weather',
188
+ turn: 0,
189
+ });
190
+ expect(eagerUsageCount.get('weather')).toBe(1);
191
+ expect(result.messages).toHaveLength(1);
192
+ expect(result.messages[0].content).toBe('normal result');
193
+ });
194
+
195
+ it('preserves final turn order when only part of a batch was prestarted', async () => {
196
+ const { toolExecuteCalls } = installToolExecuteResponder('normal result');
197
+ const eagerExecutions = new Map<string, t.EagerEventToolExecution>();
198
+ const eagerUsageCount = new Map<string, number>([['weather', 1]]);
199
+ const staleRequest: t.ToolCallRequest = {
200
+ id: 'call_weather_2',
201
+ name: 'weather',
202
+ args: { city: 'Boston' },
203
+ stepId: 'step_weather_2',
204
+ turn: 0,
205
+ };
206
+ eagerExecutions.set('call_weather_2', {
207
+ toolCallId: 'call_weather_2',
208
+ toolName: 'weather',
209
+ args: { city: 'Boston' },
210
+ request: staleRequest,
211
+ promise: Promise.resolve({
212
+ results: [
213
+ {
214
+ toolCallId: 'call_weather_2',
215
+ status: 'success',
216
+ content: 'stale eager result',
217
+ },
218
+ ],
219
+ }),
220
+ });
221
+
222
+ const toolNode = new ToolNode({
223
+ tools: [createDummyTool('weather')],
224
+ eventDrivenMode: true,
225
+ eagerEventToolExecution: { enabled: true },
226
+ eagerEventToolExecutions: eagerExecutions,
227
+ eagerEventToolUsageCount: eagerUsageCount,
228
+ toolCallStepIds: new Map([
229
+ ['call_weather_1', 'step_weather_1'],
230
+ ['call_weather_2', 'step_weather_2'],
231
+ ]),
232
+ });
233
+
234
+ const result = (await toolNode.invoke({
235
+ messages: [
236
+ createAIMessageWithToolCalls([
237
+ {
238
+ id: 'call_weather_1',
239
+ name: 'weather',
240
+ args: { city: 'NYC' },
241
+ },
242
+ {
243
+ id: 'call_weather_2',
244
+ name: 'weather',
245
+ args: { city: 'Boston' },
246
+ },
247
+ ]),
248
+ ],
249
+ })) as { messages: ToolMessage[] };
250
+
251
+ expect(toolExecuteCalls).toHaveLength(1);
252
+ expect(toolExecuteCalls[0].toolCalls).toEqual([
253
+ expect.objectContaining({
254
+ id: 'call_weather_1',
255
+ name: 'weather',
256
+ turn: 0,
257
+ }),
258
+ ]);
259
+ expect(eagerExecutions.has('call_weather_2')).toBe(false);
260
+ expect(eagerUsageCount.get('weather')).toBe(2);
261
+ expect(result.messages.map((message) => message.tool_call_id)).toEqual([
262
+ 'call_weather_1',
263
+ 'call_weather_2',
264
+ ]);
265
+ expect(result.messages[0].status).toBe('success');
266
+ expect(result.messages[1].status).toBe('error');
267
+ expect(result.messages[1].content).toContain('refusing to re-run');
268
+ });
269
+
270
+ it('returns a per-call error for malformed event tool args without aborting the batch', async () => {
271
+ const { toolExecuteCalls } = installToolExecuteResponder('normal result');
272
+ const eagerUsageCount = new Map<string, number>();
273
+
274
+ const toolNode = new ToolNode({
275
+ tools: [createDummyTool('weather')],
276
+ eventDrivenMode: true,
277
+ eagerEventToolExecution: { enabled: true },
278
+ eagerEventToolExecutions: new Map(),
279
+ eagerEventToolUsageCount: eagerUsageCount,
280
+ toolCallStepIds: new Map([
281
+ ['call_weather_bad', 'step_weather_bad'],
282
+ ['call_weather_good', 'step_weather_good'],
283
+ ]),
284
+ });
285
+
286
+ const result = (await toolNode.invoke({
287
+ messages: [
288
+ createAIMessageWithToolCalls([
289
+ {
290
+ id: 'call_weather_bad',
291
+ name: 'weather',
292
+ args: ['not', 'an', 'object'],
293
+ },
294
+ {
295
+ id: 'call_weather_good',
296
+ name: 'weather',
297
+ args: { city: 'Boston' },
298
+ },
299
+ ]),
300
+ ],
301
+ })) as { messages: ToolMessage[] };
302
+
303
+ expect(toolExecuteCalls).toHaveLength(1);
304
+ expect(toolExecuteCalls[0].toolCalls).toEqual([
305
+ expect.objectContaining({
306
+ id: 'call_weather_good',
307
+ name: 'weather',
308
+ turn: 1,
309
+ }),
310
+ ]);
311
+ expect(eagerUsageCount.get('weather')).toBe(2);
312
+ expect(result.messages.map((message) => message.tool_call_id)).toEqual([
313
+ 'call_weather_bad',
314
+ 'call_weather_good',
315
+ ]);
316
+ expect(result.messages[0].status).toBe('error');
317
+ expect(result.messages[0].content).toContain(
318
+ 'Invalid tool call arguments'
319
+ );
320
+ expect(result.messages[1].status).toBe('success');
321
+ expect(result.messages[1].content).toBe('normal result');
322
+ });
323
+
324
+ it('uses the legacy turn counter when eager mode cannot be consumed', async () => {
325
+ const { toolExecuteCalls } = installToolExecuteResponder('normal result');
326
+ const eagerUsageCount = new Map<string, number>();
327
+ const preToolTurns: Array<number | undefined> = [];
328
+ const hookRegistry = new HookRegistry();
329
+ hookRegistry.register('PreToolUse', {
330
+ hooks: [
331
+ async (
332
+ input: PreToolUseHookInput
333
+ ): Promise<PreToolUseHookOutput> => {
334
+ preToolTurns.push(input.turn);
335
+ return { decision: 'allow' };
336
+ },
337
+ ],
338
+ });
339
+
340
+ const toolNode = new ToolNode({
341
+ tools: [createDummyTool('weather')],
342
+ eventDrivenMode: true,
343
+ eagerEventToolExecution: { enabled: true },
344
+ eagerEventToolExecutions: new Map(),
345
+ eagerEventToolUsageCount: eagerUsageCount,
346
+ hookRegistry,
347
+ toolCallStepIds: new Map([
348
+ ['call_weather_1', 'step_weather_1'],
349
+ ['call_weather_2', 'step_weather_2'],
350
+ ]),
351
+ });
352
+
353
+ await toolNode.invoke({
354
+ messages: [
355
+ createAIMessage('call_weather_1', 'weather', { city: 'NYC' }),
356
+ ],
357
+ });
358
+
359
+ eagerUsageCount.clear();
360
+
361
+ await toolNode.invoke({
362
+ messages: [
363
+ createAIMessage('call_weather_2', 'weather', { city: 'Boston' }),
364
+ ],
365
+ });
366
+
367
+ expect(preToolTurns).toEqual([0, 1]);
368
+ expect(toolExecuteCalls.map((call) => call.toolCalls[0].turn)).toEqual([
369
+ 0, 1,
370
+ ]);
371
+ expect(eagerUsageCount.size).toBe(0);
372
+ });
373
+ });
@@ -134,7 +134,8 @@ describe('handleToolCallChunks', () => {
134
134
  expect(graph.dispatchRunStepDelta).toHaveBeenCalledTimes(1);
135
135
  expect(graph.dispatchRunStepDelta).toHaveBeenCalledWith(
136
136
  'prev-step-id',
137
- expect.objectContaining({ type: StepTypes.TOOL_CALLS })
137
+ expect.objectContaining({ type: StepTypes.TOOL_CALLS }),
138
+ defaultMetadata
138
139
  );
139
140
  });
140
141