@n8n/agents 0.26.3 → 0.27.1

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 (70) hide show
  1. package/dist/build.tsbuildinfo +1 -1
  2. package/dist/runtime/loop/agent-runtime.d.ts +24 -51
  3. package/dist/runtime/loop/agent-runtime.js +321 -318
  4. package/dist/runtime/loop/agent-runtime.js.map +1 -1
  5. package/dist/runtime/loop/checkpoint-data.d.ts +12 -0
  6. package/dist/runtime/loop/checkpoint-data.js +65 -0
  7. package/dist/runtime/loop/checkpoint-data.js.map +1 -0
  8. package/dist/runtime/loop/execution-counter.js +1 -5
  9. package/dist/runtime/loop/execution-counter.js.map +1 -1
  10. package/dist/runtime/loop/generate-sink.d.ts +2 -2
  11. package/dist/runtime/loop/generate-sink.js +3 -5
  12. package/dist/runtime/loop/generate-sink.js.map +1 -1
  13. package/dist/runtime/loop/run-output-sink.d.ts +3 -78
  14. package/dist/runtime/loop/run-output-sink.js +7 -0
  15. package/dist/runtime/loop/run-output-sink.js.map +1 -1
  16. package/dist/runtime/loop/runtime-context.d.ts +17 -1
  17. package/dist/runtime/loop/runtime-context.js +49 -20
  18. package/dist/runtime/loop/runtime-context.js.map +1 -1
  19. package/dist/runtime/loop/runtime-helpers.d.ts +1 -1
  20. package/dist/runtime/loop/stream-sink.d.ts +9 -2
  21. package/dist/runtime/loop/stream-sink.js +78 -56
  22. package/dist/runtime/loop/stream-sink.js.map +1 -1
  23. package/dist/runtime/memory/memory-orchestrator.d.ts +1 -1
  24. package/dist/runtime/memory/memory-orchestrator.js +8 -8
  25. package/dist/runtime/memory/memory-orchestrator.js.map +1 -1
  26. package/dist/runtime/memory/memory-store.d.ts +1 -1
  27. package/dist/runtime/memory/memory-store.js +7 -2
  28. package/dist/runtime/memory/memory-store.js.map +1 -1
  29. package/dist/runtime/memory/tool-result-memory.d.ts +7 -0
  30. package/dist/runtime/memory/tool-result-memory.js +65 -0
  31. package/dist/runtime/memory/tool-result-memory.js.map +1 -0
  32. package/dist/runtime/model/messages.js +20 -9
  33. package/dist/runtime/model/messages.js.map +1 -1
  34. package/dist/runtime/model/raw-error.d.ts +1 -1
  35. package/dist/runtime/state/run-state.d.ts +2 -2
  36. package/dist/runtime/state/run-state.js +4 -4
  37. package/dist/runtime/state/run-state.js.map +1 -1
  38. package/dist/runtime/telemetry/runtime-telemetry.d.ts +1 -1
  39. package/dist/runtime/tools/delegate-sub-agent-runner.d.ts +13 -0
  40. package/dist/runtime/tools/delegate-sub-agent-runner.js +361 -0
  41. package/dist/runtime/tools/delegate-sub-agent-runner.js.map +1 -0
  42. package/dist/runtime/tools/delegate-sub-agent-tool.d.ts +5 -241
  43. package/dist/runtime/tools/delegate-sub-agent-tool.js +94 -487
  44. package/dist/runtime/tools/delegate-sub-agent-tool.js.map +1 -1
  45. package/dist/runtime/tools/tool-adapter.js +28 -28
  46. package/dist/runtime/tools/tool-adapter.js.map +1 -1
  47. package/dist/runtime/tools/tool-call-executor.d.ts +19 -86
  48. package/dist/runtime/tools/tool-call-executor.js +236 -579
  49. package/dist/runtime/tools/tool-call-executor.js.map +1 -1
  50. package/dist/runtime/tools/tool-call-runner.d.ts +25 -0
  51. package/dist/runtime/tools/tool-call-runner.js +328 -0
  52. package/dist/runtime/tools/tool-call-runner.js.map +1 -0
  53. package/dist/runtime/tools/tool-result-guard.d.ts +2 -7
  54. package/dist/runtime/tools/tool-result-guard.js +13 -70
  55. package/dist/runtime/tools/tool-result-guard.js.map +1 -1
  56. package/dist/types/runtime/agent-loop.d.ts +73 -0
  57. package/dist/types/runtime/agent-loop.js +3 -0
  58. package/dist/types/runtime/agent-loop.js.map +1 -0
  59. package/dist/types/runtime/agent-runtime.d.ts +51 -0
  60. package/dist/types/runtime/agent-runtime.js +3 -0
  61. package/dist/types/runtime/agent-runtime.js.map +1 -0
  62. package/dist/types/runtime/delegation.d.ts +234 -0
  63. package/dist/types/runtime/delegation.js +82 -0
  64. package/dist/types/runtime/delegation.js.map +1 -0
  65. package/dist/types/runtime/tool-execution.d.ts +103 -0
  66. package/dist/types/runtime/tool-execution.js +3 -0
  67. package/dist/types/runtime/tool-execution.js.map +1 -0
  68. package/dist/types/sdk/agent-builder.d.ts +1 -1
  69. package/dist/types/sdk/memory.d.ts +2 -1
  70. package/package.json +6 -6
@@ -1,51 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ToolCallExecutor = void 0;
4
- const json_schema_1 = require("@n8n/ai-utilities/json-schema");
5
4
  const delegate_sub_agent_tool_1 = require("./delegate-sub-agent-tool");
6
5
  const sub_agent_task_path_1 = require("./sub-agent-task-path");
7
- const tool_adapter_1 = require("./tool-adapter");
8
- const tool_result_guard_1 = require("./tool-result-guard");
9
- const untrusted_tool_output_1 = require("./untrusted-tool-output");
10
- const abort_1 = require("../../sdk/abort");
11
- const cancellation_1 = require("../../sdk/cancellation");
12
- const message_1 = require("../../sdk/message");
13
- const parse_1 = require("../../utils/parse");
14
- const zod_1 = require("../../utils/zod");
6
+ const tool_call_runner_1 = require("./tool-call-runner");
15
7
  const execution_counter_1 = require("../loop/execution-counter");
16
- const runtime_helpers_1 = require("../loop/runtime-helpers");
17
8
  const messages_1 = require("../model/messages");
18
- function isDeniedApprovalResumeData(value) {
19
- return value !== null && typeof value === 'object' && Reflect.get(value, 'approved') === false;
20
- }
21
- function shouldEmitToolExecutionStart(tool, resumeData) {
22
- if (!tool.approval)
23
- return true;
24
- if (!tool.approval.required && tool.approval.conditional !== true)
25
- return true;
26
- if (resumeData === undefined)
27
- return false;
28
- return !isDeniedApprovalResumeData(resumeData);
29
- }
30
- function getToolResumeJsonSchema(tool, resumeSchemaOverride) {
31
- const resolvedSchema = resumeSchemaOverride ?? tool.resumeSchema;
32
- if (!resolvedSchema)
33
- return undefined;
34
- return (0, zod_1.isZodSchema)(resolvedSchema) ? (0, json_schema_1.zodSchemaToJsonSchema)(resolvedSchema) : resolvedSchema;
35
- }
36
9
  class ToolCallExecutor {
37
10
  constructor(deps) {
38
11
  this.deps = deps;
39
- this.offloadedToolResults = false;
12
+ this.runner = new tool_call_runner_1.ToolCallRunner(deps);
40
13
  }
41
14
  get hasOffloadedToolResults() {
42
- return this.offloadedToolResults;
43
- }
44
- get telemetry() {
45
- return this.deps.telemetry;
46
- }
47
- get eventBus() {
48
- return this.deps.eventBus;
15
+ return this.runner.hasOffloadedToolResults;
49
16
  }
50
17
  get concurrency() {
51
18
  return this.deps.concurrency;
@@ -83,425 +50,277 @@ class ToolCallExecutor {
83
50
  return batch;
84
51
  }
85
52
  async iterateToolCallsConcurrent(ctx) {
86
- const { toolCalls, toolMap, list, runId, telemetry: resolvedTelemetry, executionCounter, abortSignal, } = ctx;
87
53
  const errors = [];
88
- const runtimeToolCalls = [];
89
- for (const toolCall of toolCalls) {
90
- const normalizedInput = (0, messages_1.normalizeToolInputForModel)(toolCall.input);
91
- if (!normalizedInput.ok) {
92
- const error = new Error(normalizedInput.error);
93
- (0, execution_counter_1.incrementToolCallCount)(executionCounter);
94
- list.setToolCallError(toolCall.toolCallId, error);
95
- errors.push({
96
- toolCallId: toolCall.toolCallId,
97
- toolName: toolCall.toolName,
98
- input: normalizedInput.input,
99
- error,
100
- });
101
- continue;
102
- }
103
- runtimeToolCalls.push({ ...toolCall, input: normalizedInput.input });
104
- }
54
+ const runtimeToolCalls = this.normalizeToolCalls(ctx.toolCalls, ctx, errors);
105
55
  const executableCalls = runtimeToolCalls.filter((tc) => !tc.providerExecuted);
106
56
  const providerExecutedCount = runtimeToolCalls.length - executableCalls.length;
107
57
  for (let i = 0; i < providerExecutedCount; i++) {
108
- (0, execution_counter_1.incrementToolCallCount)(executionCounter);
58
+ (0, execution_counter_1.incrementToolCallCount)(ctx.executionCounter);
109
59
  }
110
60
  const executableCallsById = new Map(executableCalls.map((tc) => [tc.toolCallId, tc]));
111
61
  const unexecutedIds = new Set(executableCalls.map((tc) => tc.toolCallId));
112
- const results = [];
113
- const suspensions = [];
114
- const pending = {};
62
+ const result = { results: [], suspensions: [], errors, pending: {} };
115
63
  for (let batchStart = 0; batchStart < executableCalls.length;) {
116
64
  if (ctx.isAborted()) {
117
- this.deps.onCancelled();
118
- for (const id of unexecutedIds) {
119
- const tc = executableCallsById.get(id);
120
- const modelOutput = '[Skipped: run was aborted]';
121
- list.setToolCallResult(tc.toolCallId, modelOutput, { canceled: true });
122
- results.push({
123
- toolCallId: tc.toolCallId,
124
- toolName: tc.toolName,
125
- input: tc.input,
126
- toolEntry: {
127
- tool: tc.toolName,
128
- input: tc.input,
129
- output: modelOutput,
130
- transformed: false,
131
- canceled: true,
132
- },
133
- modelOutput,
134
- });
135
- }
136
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
65
+ this.skipAbortedToolCalls(unexecutedIds, executableCallsById, ctx.list, result.results);
66
+ return await this.finalizeBatch(result, ctx);
137
67
  }
138
- const batch = this.takeNextToolCallBatch(executableCalls, batchStart, toolMap);
68
+ const batch = this.takeNextToolCallBatch(executableCalls, batchStart, ctx.toolMap);
139
69
  batchStart += batch.length;
140
- const settledResults = await Promise.allSettled(batch.map(async (tc) => await this.processToolCall({
141
- toolCallId: tc.toolCallId,
142
- toolName: tc.toolName,
143
- input: tc.input,
144
- toolMap,
145
- list,
146
- runId,
147
- persistence: ctx.persistence,
148
- resolvedTelemetry,
149
- executionCounter,
150
- abortSignal,
151
- countToolCall: true,
152
- })));
153
- for (const tc of batch) {
70
+ const settled = await this.executeToolCallBatch(batch, ctx);
71
+ for (const tc of batch)
154
72
  unexecutedIds.delete(tc.toolCallId);
155
- }
156
- let hasSuspension = false;
157
- for (let i = 0; i < settledResults.length; i++) {
158
- const result = settledResults[i];
159
- const tc = batch[i];
160
- const toolInput = tc.input;
161
- if (result.status === 'rejected') {
162
- list.setToolCallError(tc.toolCallId, result.reason);
163
- errors.push({
164
- toolCallId: tc.toolCallId,
165
- toolName: tc.toolName,
166
- input: toolInput,
167
- error: result.reason,
168
- });
169
- }
170
- else if (result.value.outcome === 'suspended') {
171
- hasSuspension = true;
172
- suspensions.push({
173
- toolCallId: tc.toolCallId,
174
- toolName: tc.toolName,
175
- input: toolInput,
176
- payload: result.value.payload,
177
- resumeSchema: result.value.resumeSchema,
178
- });
179
- pending[tc.toolCallId] = {
180
- suspended: true,
181
- toolCallId: tc.toolCallId,
182
- toolName: tc.toolName,
183
- input: toolInput,
184
- suspendPayload: result.value.payload,
185
- resumeSchema: result.value.resumeSchema,
186
- ...(result.value.continuation !== undefined
187
- ? { continuation: result.value.continuation }
188
- : {}),
189
- runId,
190
- };
191
- }
192
- else if (result.value.outcome === 'success') {
193
- results.push({
194
- toolCallId: tc.toolCallId,
195
- toolName: tc.toolName,
196
- input: toolInput,
197
- toolEntry: result.value.toolEntry,
198
- modelOutput: result.value.modelOutput,
199
- customMessage: result.value.customMessage,
200
- ...(result.value.mcpServerName !== undefined
201
- ? { mcpServerName: result.value.mcpServerName }
202
- : {}),
203
- });
204
- }
205
- else if (result.value.outcome === 'cancelled') {
206
- results.push({
207
- toolCallId: tc.toolCallId,
208
- toolName: tc.toolName,
209
- input: toolInput,
210
- toolEntry: result.value.toolEntry,
211
- modelOutput: result.value.modelOutput,
212
- });
213
- }
214
- else if (result.value.outcome === 'error') {
215
- errors.push({
216
- toolCallId: tc.toolCallId,
217
- toolName: tc.toolName,
218
- input: toolInput,
219
- error: result.value.error,
220
- });
221
- }
222
- else if (result.value.outcome === 'noop') {
223
- }
224
- }
73
+ const hasSuspension = this.collectBatchResults(batch, settled, result, ctx);
225
74
  if (ctx.isAborted()) {
226
- this.deps.onCancelled();
227
- for (const id of unexecutedIds) {
228
- const tc = executableCallsById.get(id);
229
- const modelOutput = '[Skipped: run was aborted]';
230
- list.setToolCallResult(tc.toolCallId, modelOutput, { canceled: true });
231
- results.push({
232
- toolCallId: tc.toolCallId,
233
- toolName: tc.toolName,
234
- input: tc.input,
235
- toolEntry: {
236
- tool: tc.toolName,
237
- input: tc.input,
238
- output: modelOutput,
239
- transformed: false,
240
- canceled: true,
241
- },
242
- modelOutput,
243
- });
244
- }
245
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
75
+ this.skipAbortedToolCalls(unexecutedIds, executableCallsById, ctx.list, result.results);
76
+ return await this.finalizeBatch(result, ctx);
246
77
  }
247
78
  if (hasSuspension) {
248
- for (const id of unexecutedIds) {
249
- const tc = executableCallsById.get(id);
250
- pending[tc.toolCallId] = {
251
- suspended: false,
252
- toolCallId: tc.toolCallId,
253
- toolName: tc.toolName,
254
- input: tc.input,
255
- };
256
- }
79
+ this.retainUnexecutedToolCalls(unexecutedIds, executableCallsById, result.pending);
257
80
  break;
258
81
  }
259
82
  }
260
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
83
+ return await this.finalizeBatch(result, ctx);
261
84
  }
262
- async iteratePendingToolCallsConcurrent(ctx) {
263
- const { pendingResume, toolMap, list, runId, persistence, telemetry: resolvedTelemetry, executionCounter, abortSignal, } = ctx;
264
- const resumedId = pendingResume.resumeToolCallId;
265
- const resumedEntry = pendingResume.pendingToolCalls[resumedId];
266
- if (!resumedEntry) {
267
- throw new Error(`No pending tool call found for toolCallId: ${resumedId}`);
268
- }
269
- const resumedToolName = resumedEntry.toolName;
270
- const results = [];
271
- const suspensions = [];
272
- const errors = [];
273
- const pending = {};
274
- const processResult = await this.processToolCall(this.pendingToolCallParams(resumedEntry, ctx, pendingResume.resumeData));
275
- if (processResult.outcome === 'suspended') {
276
- pending[resumedId] = {
277
- suspended: true,
278
- toolCallId: resumedEntry.toolCallId,
279
- toolName: resumedToolName,
280
- input: resumedEntry.input,
281
- suspendPayload: processResult.payload,
282
- resumeSchema: processResult.resumeSchema,
283
- ...(processResult.continuation !== undefined
284
- ? { continuation: processResult.continuation }
285
- : {}),
286
- runId,
287
- };
288
- suspensions.push({
289
- toolCallId: resumedId,
290
- toolName: resumedToolName,
291
- input: resumedEntry.input,
292
- payload: processResult.payload,
293
- resumeSchema: processResult.resumeSchema,
294
- });
295
- }
296
- else if (processResult.outcome === 'success') {
297
- results.push({
298
- toolCallId: resumedEntry.toolCallId,
299
- toolName: resumedToolName,
300
- input: resumedEntry.input,
301
- toolEntry: processResult.toolEntry,
302
- modelOutput: processResult.modelOutput,
303
- customMessage: processResult.customMessage,
304
- ...(processResult.mcpServerName !== undefined
305
- ? { mcpServerName: processResult.mcpServerName }
306
- : {}),
307
- });
308
- }
309
- else if (processResult.outcome === 'cancelled') {
310
- results.push({
311
- toolCallId: resumedEntry.toolCallId,
312
- toolName: resumedToolName,
313
- input: resumedEntry.input,
314
- toolEntry: processResult.toolEntry,
315
- modelOutput: processResult.modelOutput,
316
- });
317
- list.addInput([
318
- { role: 'user', content: [{ type: 'text', text: processResult.userMessage }] },
319
- ]);
320
- for (const id of Object.keys(pendingResume.pendingToolCalls)) {
321
- if (id !== resumedId) {
322
- const siblingEntry = pendingResume.pendingToolCalls[id];
323
- const siblingTool = toolMap.get(siblingEntry.toolName);
324
- const siblingParams = this.pendingToolCallParams(siblingEntry, ctx, pendingResume.resumeData);
325
- if (siblingEntry.suspended && siblingTool?.onCancellation) {
326
- try {
327
- await this.runCancellationCleanup(siblingParams, siblingTool, processResult.userMessage);
328
- }
329
- catch {
330
- this.retainPendingToolCall(id, siblingEntry, pending, suspensions);
331
- continue;
332
- }
333
- }
334
- const modelOutput = '[Skipped: a sibling tool call was cancelled]';
335
- list.setToolCallResult(id, modelOutput, {
336
- canceled: true,
337
- });
338
- results.push({
339
- toolCallId: siblingEntry.toolCallId,
340
- toolName: siblingEntry.toolName,
341
- input: siblingEntry.input,
342
- toolEntry: {
343
- tool: siblingEntry.toolName,
344
- input: siblingEntry.input,
345
- output: modelOutput,
346
- transformed: false,
347
- canceled: true,
348
- },
349
- modelOutput,
350
- });
351
- }
85
+ normalizeToolCalls(toolCalls, ctx, errors) {
86
+ const calls = [];
87
+ for (const toolCall of toolCalls) {
88
+ const normalizedInput = (0, messages_1.normalizeToolInputForModel)(toolCall.input);
89
+ if (!normalizedInput.ok) {
90
+ const error = new Error(normalizedInput.error);
91
+ (0, execution_counter_1.incrementToolCallCount)(ctx.executionCounter);
92
+ ctx.list.setToolCallError(toolCall.toolCallId, error);
93
+ errors.push({
94
+ toolCallId: toolCall.toolCallId,
95
+ toolName: toolCall.toolName,
96
+ input: normalizedInput.input,
97
+ error,
98
+ });
99
+ continue;
352
100
  }
353
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
101
+ calls.push({ ...toolCall, input: normalizedInput.input });
354
102
  }
355
- else if (processResult.outcome === 'retryable-error') {
356
- for (const [id, entry] of Object.entries(pendingResume.pendingToolCalls)) {
357
- this.retainPendingToolCall(id, entry, pending, suspensions);
358
- }
359
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
103
+ return calls;
104
+ }
105
+ async executeToolCallBatch(batch, ctx) {
106
+ return await Promise.allSettled(batch.map(async (tc) => await this.runner.processToolCall({
107
+ toolCallId: tc.toolCallId,
108
+ toolName: tc.toolName,
109
+ input: tc.input,
110
+ toolMap: ctx.toolMap,
111
+ list: ctx.list,
112
+ runId: ctx.runId,
113
+ persistence: ctx.persistence,
114
+ resolvedTelemetry: ctx.telemetry,
115
+ executionCounter: ctx.executionCounter,
116
+ abortSignal: ctx.abortSignal,
117
+ countToolCall: true,
118
+ })));
119
+ }
120
+ collectBatchResults(calls, settled, batch, ctx) {
121
+ let hasSuspension = false;
122
+ for (let i = 0; i < settled.length; i++) {
123
+ if (this.collectSettledToolCall(calls[i], settled[i], batch, ctx))
124
+ hasSuspension = true;
360
125
  }
361
- else if (processResult.outcome === 'error') {
362
- errors.push({
363
- toolCallId: resumedEntry.toolCallId,
364
- toolName: resumedToolName,
365
- input: resumedEntry.input,
366
- error: processResult.error,
126
+ return hasSuspension;
127
+ }
128
+ collectSettledToolCall(call, result, batch, ctx) {
129
+ if (result.status === 'rejected') {
130
+ ctx.list.setToolCallError(call.toolCallId, result.reason);
131
+ batch.errors.push({
132
+ toolCallId: call.toolCallId,
133
+ toolName: call.toolName,
134
+ input: call.input,
135
+ error: result.reason,
367
136
  });
368
- }
369
- else if (processResult.outcome === 'noop') {
370
- }
371
- const unexecuted = [];
372
- for (const [id, entry] of Object.entries(pendingResume.pendingToolCalls)) {
373
- if (id === resumedId)
374
- continue;
375
- if (entry.suspended) {
376
- this.retainPendingToolCall(id, entry, pending, suspensions);
377
- }
378
- else {
379
- unexecuted.push({
380
- toolCallId: id,
381
- toolName: entry.toolName,
382
- input: entry.input,
137
+ return false;
138
+ }
139
+ const outcome = result.value;
140
+ switch (outcome.outcome) {
141
+ case 'suspended':
142
+ batch.suspensions.push(this.buildToolCallSuspension(call, outcome));
143
+ batch.pending[call.toolCallId] = this.buildPendingToolCall(call, outcome, ctx.runId);
144
+ return true;
145
+ case 'success':
146
+ case 'cancelled':
147
+ this.appendCompletedToolCall(call, outcome, batch.results);
148
+ break;
149
+ case 'error':
150
+ batch.errors.push({
151
+ toolCallId: call.toolCallId,
152
+ toolName: call.toolName,
153
+ input: call.input,
154
+ error: outcome.error,
383
155
  });
384
- }
156
+ break;
385
157
  }
386
- if (unexecuted.length > 0) {
387
- const batch = await this.iterateToolCallsConcurrent({
388
- toolCalls: unexecuted,
389
- toolMap,
390
- list,
391
- runId,
392
- persistence,
393
- telemetry: resolvedTelemetry,
394
- executionCounter,
395
- abortSignal,
396
- isAborted: ctx.isAborted,
397
- });
398
- results.push(...batch.results);
399
- suspensions.push(...batch.suspensions);
400
- errors.push(...batch.errors);
401
- Object.assign(pending, batch.pending);
158
+ return false;
159
+ }
160
+ buildToolCallSuspension(call, outcome) {
161
+ return {
162
+ toolCallId: call.toolCallId,
163
+ toolName: call.toolName,
164
+ input: call.input,
165
+ payload: outcome.payload,
166
+ resumeSchema: outcome.resumeSchema,
167
+ };
168
+ }
169
+ buildPendingToolCall(call, outcome, runId) {
170
+ return {
171
+ suspended: true,
172
+ toolCallId: call.toolCallId,
173
+ toolName: call.toolName,
174
+ input: call.input,
175
+ suspendPayload: outcome.payload,
176
+ resumeSchema: outcome.resumeSchema,
177
+ ...(outcome.continuation !== undefined ? { continuation: outcome.continuation } : {}),
178
+ runId,
179
+ };
180
+ }
181
+ appendCompletedToolCall(call, outcome, results) {
182
+ const result = {
183
+ toolCallId: call.toolCallId,
184
+ toolName: call.toolName,
185
+ input: call.input,
186
+ toolEntry: outcome.toolEntry,
187
+ modelOutput: outcome.modelOutput,
188
+ };
189
+ if (outcome.outcome === 'success') {
190
+ result.customMessage = outcome.customMessage;
191
+ if (outcome.mcpServerName !== undefined)
192
+ result.mcpServerName = outcome.mcpServerName;
402
193
  }
403
- return await this.finalizeBatch({ results, suspensions, errors, pending }, ctx);
194
+ results.push(result);
404
195
  }
405
- async processToolCall(params) {
406
- const { toolName, toolMap, list, toolCallId, resumeData } = params;
407
- const builtTool = toolMap.get(toolName);
408
- if (!builtTool) {
409
- return await this.toolError(params, new Error(`Tool ${toolName} not found`));
196
+ skipAbortedToolCalls(ids, calls, list, results) {
197
+ this.deps.onCancelled();
198
+ for (const id of ids) {
199
+ const call = calls.get(id);
200
+ const modelOutput = '[Skipped: run was aborted]';
201
+ list.setToolCallResult(call.toolCallId, modelOutput, { canceled: true });
202
+ results.push(this.buildSkippedToolResult(call, modelOutput));
410
203
  }
411
- const settledBlock = this.findSettledToolCall(list, toolCallId);
412
- if (settledBlock) {
413
- return this.completeSettledToolCall(params, settledBlock);
204
+ }
205
+ buildSkippedToolResult(call, modelOutput) {
206
+ return {
207
+ toolCallId: call.toolCallId,
208
+ toolName: call.toolName,
209
+ input: call.input,
210
+ toolEntry: {
211
+ tool: call.toolName,
212
+ input: call.input,
213
+ output: modelOutput,
214
+ transformed: false,
215
+ canceled: true,
216
+ },
217
+ modelOutput,
218
+ };
219
+ }
220
+ retainUnexecutedToolCalls(ids, calls, pending) {
221
+ for (const id of ids) {
222
+ const call = calls.get(id);
223
+ pending[call.toolCallId] = {
224
+ suspended: false,
225
+ toolCallId: call.toolCallId,
226
+ toolName: call.toolName,
227
+ input: call.input,
228
+ };
414
229
  }
415
- if ((0, cancellation_1.isCancellation)(resumeData) && !builtTool.handleCancellation) {
416
- try {
417
- await this.runCancellationCleanup(params, builtTool, resumeData.message);
418
- }
419
- catch {
420
- return { outcome: 'retryable-error' };
230
+ }
231
+ async iteratePendingToolCallsConcurrent(ctx) {
232
+ const { pendingResume } = ctx;
233
+ const resumedId = pendingResume.resumeToolCallId;
234
+ const resumedEntry = pendingResume.pendingToolCalls[resumedId];
235
+ if (!resumedEntry)
236
+ throw new Error(`No pending tool call found for toolCallId: ${resumedId}`);
237
+ const batch = { results: [], suspensions: [], errors: [], pending: {} };
238
+ const outcome = await this.runner.processToolCall(this.pendingToolCallParams(resumedEntry, ctx, pendingResume.resumeData));
239
+ if (outcome.outcome === 'cancelled') {
240
+ this.appendCompletedToolCall(resumedEntry, outcome, batch.results);
241
+ ctx.list.addInput([{ role: 'user', content: [{ type: 'text', text: outcome.userMessage }] }]);
242
+ await this.cancelSiblingToolCalls(ctx, batch, outcome.userMessage);
243
+ return await this.finalizeBatch(batch, ctx);
244
+ }
245
+ if (outcome.outcome === 'retryable-error') {
246
+ for (const [id, entry] of Object.entries(pendingResume.pendingToolCalls)) {
247
+ this.retainPendingToolCall(id, entry, batch.pending, batch.suspensions);
421
248
  }
422
- return this.buildCancelledOutcome(params, resumeData.message);
423
- }
424
- if (params.countToolCall ?? true) {
425
- (0, execution_counter_1.incrementToolCallCount)(params.executionCounter);
426
- }
427
- const validation = await this.validateToolInput(params, builtTool);
428
- if (!validation.ok)
429
- return validation.outcome;
430
- const input = validation.input;
431
- if (shouldEmitToolExecutionStart(builtTool, resumeData)) {
432
- this.eventBus.emit({
433
- type: "tool_execution_start",
434
- toolCallId,
435
- toolName,
436
- args: input,
437
- });
249
+ return await this.finalizeBatch(batch, ctx);
438
250
  }
439
- let toolResult;
440
- let interruptedSuspendPayload;
441
- let interruptedSuspendOptions;
442
- let didSuspend = false;
443
- let abortObserved = false;
444
- let suspensionCleanup;
445
- const cleanupInterruptedSuspension = async () => {
446
- suspensionCleanup ??= this.runCancellationCleanup({
447
- ...params,
448
- input,
449
- suspendPayload: interruptedSuspendPayload,
450
- continuation: interruptedSuspendOptions?.continuation,
451
- resumeSchema: getToolResumeJsonSchema(builtTool, interruptedSuspendOptions?.resumeSchema),
452
- }, builtTool, 'Run aborted').catch(() => undefined);
453
- await suspensionCleanup;
454
- };
455
- try {
456
- toolResult = await this.runToolHandler(params, builtTool, input, async (payload, options) => {
457
- didSuspend = true;
458
- interruptedSuspendPayload = payload;
459
- interruptedSuspendOptions = options;
460
- if (abortObserved || params.abortSignal?.aborted) {
461
- await cleanupInterruptedSuspension();
462
- }
463
- });
251
+ this.collectResumedToolCall(resumedId, resumedEntry, outcome, batch, ctx.runId);
252
+ await this.executeRemainingToolCalls(ctx, batch);
253
+ return await this.finalizeBatch(batch, ctx);
254
+ }
255
+ collectResumedToolCall(id, entry, outcome, batch, runId) {
256
+ switch (outcome.outcome) {
257
+ case 'suspended':
258
+ batch.pending[id] = this.buildPendingToolCall(entry, outcome, runId);
259
+ batch.suspensions.push(this.buildToolCallSuspension({ ...entry, toolCallId: id }, outcome));
260
+ break;
261
+ case 'success':
262
+ this.appendCompletedToolCall(entry, outcome, batch.results);
263
+ break;
264
+ case 'error':
265
+ batch.errors.push({
266
+ toolCallId: entry.toolCallId,
267
+ toolName: entry.toolName,
268
+ input: entry.input,
269
+ error: outcome.error,
270
+ });
271
+ break;
464
272
  }
465
- catch (error) {
466
- if ((0, abort_1.isAbortError)(error) || params.abortSignal?.aborted) {
467
- abortObserved = true;
468
- if (didSuspend) {
469
- await cleanupInterruptedSuspension();
273
+ }
274
+ async cancelSiblingToolCalls(ctx, batch, userMessage) {
275
+ for (const id of Object.keys(ctx.pendingResume.pendingToolCalls)) {
276
+ if (id === ctx.pendingResume.resumeToolCallId)
277
+ continue;
278
+ const entry = ctx.pendingResume.pendingToolCalls[id];
279
+ const tool = ctx.toolMap.get(entry.toolName);
280
+ const params = this.pendingToolCallParams(entry, ctx, ctx.pendingResume.resumeData);
281
+ if (entry.suspended && tool?.onCancellation) {
282
+ try {
283
+ await this.runner.runCancellationCleanup(params, tool, userMessage);
470
284
  }
471
- else if (params.suspendPayload !== undefined || params.continuation !== undefined) {
472
- try {
473
- await this.runCancellationCleanup({ ...params, input }, builtTool, 'Run aborted');
474
- }
475
- catch {
476
- }
285
+ catch {
286
+ this.retainPendingToolCall(id, entry, batch.pending, batch.suspensions);
287
+ continue;
477
288
  }
478
- this.deps.onCancelled();
479
- return this.buildCancelledOutcome(params, 'Run aborted');
480
289
  }
481
- return await this.toolError(params, error, builtTool);
482
- }
483
- if ((0, tool_adapter_1.isSuspendedToolResult)(toolResult)) {
484
- return await this.buildSuspendedOutcome(params, builtTool, toolResult);
290
+ const modelOutput = '[Skipped: a sibling tool call was cancelled]';
291
+ ctx.list.setToolCallResult(id, modelOutput, { canceled: true });
292
+ batch.results.push(this.buildSkippedToolResult(entry, modelOutput));
485
293
  }
486
- return await this.buildSuccessOutcome(params, builtTool, input, toolResult);
487
294
  }
488
- async runCancellationCleanup(params, builtTool, message) {
489
- if (!builtTool.onCancellation)
295
+ async executeRemainingToolCalls(ctx, result) {
296
+ const unexecuted = [];
297
+ for (const [id, entry] of Object.entries(ctx.pendingResume.pendingToolCalls)) {
298
+ if (id === ctx.pendingResume.resumeToolCallId)
299
+ continue;
300
+ if (entry.suspended) {
301
+ this.retainPendingToolCall(id, entry, result.pending, result.suspensions);
302
+ }
303
+ else {
304
+ unexecuted.push({ toolCallId: id, toolName: entry.toolName, input: entry.input });
305
+ }
306
+ }
307
+ if (unexecuted.length === 0)
490
308
  return;
491
- await builtTool.onCancellation(params.input, {
492
- cancellation: { message },
493
- toolCallId: params.toolCallId,
494
- toolName: params.toolName,
495
- runId: params.runId,
496
- persistence: params.persistence,
497
- parentTelemetry: params.resolvedTelemetry,
498
- emitEvent: (event) => this.eventBus.emit(event),
499
- abortSignal: params.abortSignal,
500
- executionCounter: params.executionCounter,
501
- suspendPayload: params.suspendPayload,
502
- continuation: params.continuation,
503
- resumeSchema: params.resumeSchema,
309
+ const batch = await this.iterateToolCallsConcurrent({
310
+ toolCalls: unexecuted,
311
+ toolMap: ctx.toolMap,
312
+ list: ctx.list,
313
+ runId: ctx.runId,
314
+ persistence: ctx.persistence,
315
+ telemetry: ctx.telemetry,
316
+ executionCounter: ctx.executionCounter,
317
+ abortSignal: ctx.abortSignal,
318
+ isAborted: ctx.isAborted,
504
319
  });
320
+ result.results.push(...batch.results);
321
+ result.suspensions.push(...batch.suspensions);
322
+ result.errors.push(...batch.errors);
323
+ Object.assign(result.pending, batch.pending);
505
324
  }
506
325
  async cleanupPendingToolCalls(pending, ctx, message = 'Run aborted') {
507
326
  for (const entry of Object.values(pending)) {
@@ -511,7 +330,7 @@ class ToolCallExecutor {
511
330
  if (!tool?.onCancellation)
512
331
  continue;
513
332
  try {
514
- await this.runCancellationCleanup(this.pendingToolCallParams(entry, ctx), tool, message);
333
+ await this.runner.runCancellationCleanup(this.pendingToolCallParams(entry, ctx), tool, message);
515
334
  }
516
335
  catch {
517
336
  }
@@ -558,168 +377,6 @@ class ToolCallExecutor {
558
377
  resumeSchema: entry.resumeSchema,
559
378
  });
560
379
  }
561
- async toolError(params, error, builtTool) {
562
- this.eventBus.emit({
563
- type: "tool_execution_end",
564
- toolCallId: params.toolCallId,
565
- toolName: params.toolName,
566
- result: error,
567
- isError: true,
568
- });
569
- const errorText = (0, runtime_helpers_1.stringifyError)(error);
570
- const guardedError = await (0, tool_result_guard_1.guardToolErrorForModel)(builtTool?.outputTrust === 'untrusted'
571
- ? (0, untrusted_tool_output_1.protectUntrustedToolError)(errorText, builtTool)
572
- : errorText, this.deps.tokenCounter, this.getResultStorage(params));
573
- params.list.setToolCallError(params.toolCallId, guardedError);
574
- return { outcome: 'error', error };
575
- }
576
- findSettledToolCall(list, toolCallId) {
577
- return list
578
- .responseDelta()
579
- .flatMap((m) => ((0, message_1.isLlmMessage)(m) && 'content' in m ? m.content : []))
580
- .find((c) => c.type === 'tool-call' && c.toolCallId === toolCallId && c.state !== 'pending');
581
- }
582
- completeSettledToolCall(params, settledBlock) {
583
- const settledResult = settledBlock.state === 'resolved' ? settledBlock.output : settledBlock.error;
584
- this.eventBus.emit({
585
- type: "tool_execution_end",
586
- toolCallId: params.toolCallId,
587
- toolName: params.toolName,
588
- result: settledResult,
589
- isError: settledBlock.state === 'rejected',
590
- });
591
- return { outcome: 'noop' };
592
- }
593
- buildCancelledOutcome(params, userMessage) {
594
- const { toolCallId, toolName, input, list } = params;
595
- const modelOutput = `[Tool call cancelled. User said: "${userMessage}"]`;
596
- this.eventBus.emit({
597
- type: "tool_execution_end",
598
- toolCallId,
599
- toolName,
600
- result: modelOutput,
601
- isError: false,
602
- });
603
- list.setToolCallResult(toolCallId, modelOutput, { canceled: true });
604
- return {
605
- outcome: 'cancelled',
606
- toolEntry: { tool: toolName, input, output: modelOutput, transformed: false, canceled: true },
607
- modelOutput,
608
- userMessage,
609
- canceled: true,
610
- };
611
- }
612
- async validateToolInput(params, builtTool) {
613
- if (!builtTool.inputSchema)
614
- return { ok: true, input: params.input };
615
- const result = await (0, parse_1.parseWithSchema)(builtTool.inputSchema, params.input);
616
- if (!result.success) {
617
- const reason = result.schemaInvalid
618
- ? `Tool ${params.toolName} has an input schema that could not be compiled: ${result.error}`
619
- : `Invalid tool input: ${result.error}`;
620
- return { ok: false, outcome: await this.toolError(params, new Error(reason)) };
621
- }
622
- return { ok: true, input: result.data };
623
- }
624
- async runToolHandler(params, builtTool, input, onSuspend) {
625
- const { toolCallId, toolName, runId, persistence, resumeData, resolvedTelemetry, executionCounter, abortSignal, suspendPayload, continuation, resumeSchema, } = params;
626
- return await this.telemetry.withToolSpan(toolCallId, toolName, input, resolvedTelemetry, async () => await (0, abort_1.raceWithAbort)(async () => await (0, tool_adapter_1.executeTool)(input, builtTool, resumeData, resolvedTelemetry, toolCallId, {
627
- runId,
628
- persistence,
629
- ...(this.deps.loadSkill ? { loadSkill: this.deps.loadSkill } : {}),
630
- emitEvent: (event) => this.eventBus.emit(event),
631
- abortSignal,
632
- executionCounter,
633
- suspendPayload,
634
- continuation,
635
- resumeSchema,
636
- onSuspend,
637
- }), abortSignal));
638
- }
639
- async buildSuspendedOutcome(params, builtTool, toolResult) {
640
- if (builtTool.suspendSchema) {
641
- const parseResult = await (0, parse_1.parseWithSchema)(builtTool.suspendSchema, toolResult.payload);
642
- if (!parseResult.success) {
643
- return await this.toolError(params, new Error(`Invalid suspend payload: ${parseResult.error}`));
644
- }
645
- toolResult.payload = parseResult.data;
646
- }
647
- const resumeSchema = getToolResumeJsonSchema(builtTool, toolResult.resumeSchema);
648
- if (!resumeSchema) {
649
- return await this.toolError(params, new Error(`Tool ${params.toolName} has no resume schema`));
650
- }
651
- return {
652
- outcome: 'suspended',
653
- payload: toolResult.payload,
654
- resumeSchema,
655
- ...(toolResult.continuation !== undefined ? { continuation: toolResult.continuation } : {}),
656
- };
657
- }
658
- async buildSuccessOutcome(params, builtTool, input, toolResult) {
659
- const { toolCallId, toolName, list } = params;
660
- let modelResult;
661
- try {
662
- modelResult = builtTool.toModelOutput ? builtTool.toModelOutput(toolResult) : toolResult;
663
- if (builtTool.outputTrust === 'untrusted') {
664
- modelResult = (0, untrusted_tool_output_1.protectUntrustedToolResult)(modelResult, builtTool);
665
- }
666
- }
667
- catch (error) {
668
- return await this.toolError(params, error, builtTool);
669
- }
670
- const storage = this.getResultStorage(params);
671
- const guardedResult = await (0, tool_result_guard_1.guardToolResultForModel)(modelResult, this.deps.tokenCounter, storage);
672
- this.eventBus.emit({
673
- type: "tool_execution_end",
674
- toolCallId,
675
- toolName,
676
- result: toolResult,
677
- isError: false,
678
- });
679
- list.setToolCallResult(toolCallId, guardedResult.historyOutput);
680
- let customMessage = await builtTool.toMessage?.(toolResult);
681
- if (customMessage && builtTool.outputTrust === 'untrusted') {
682
- customMessage = (0, untrusted_tool_output_1.protectUntrustedToolMessage)(customMessage, builtTool);
683
- }
684
- let guardedCustomMessage = customMessage
685
- ? await (0, tool_result_guard_1.guardToolMessageForModel)(customMessage, this.deps.tokenCounter, storage)
686
- : undefined;
687
- if (guardedCustomMessage && 'role' in guardedCustomMessage) {
688
- guardedCustomMessage = {
689
- ...guardedCustomMessage,
690
- origin: { kind: 'tool', toolName },
691
- };
692
- }
693
- if (guardedCustomMessage) {
694
- list.addResponse([guardedCustomMessage]);
695
- }
696
- return {
697
- outcome: 'success',
698
- toolEntry: {
699
- tool: toolName,
700
- input,
701
- output: toolResult,
702
- transformed: !!builtTool.toModelOutput,
703
- },
704
- modelOutput: guardedResult.wireOutput,
705
- customMessage: guardedCustomMessage,
706
- ...(builtTool.mcpServerName !== undefined ? { mcpServerName: builtTool.mcpServerName } : {}),
707
- };
708
- }
709
- getResultStorage(params) {
710
- const filesystem = this.deps.workspaceFilesystem;
711
- if (!filesystem)
712
- return undefined;
713
- return {
714
- filesystem,
715
- runId: params.runId,
716
- toolCallId: params.toolCallId,
717
- onOffloaded: () => {
718
- this.offloadedToolResults = true;
719
- },
720
- ...(params.abortSignal ? { abortSignal: params.abortSignal } : {}),
721
- };
722
- }
723
380
  }
724
381
  exports.ToolCallExecutor = ToolCallExecutor;
725
382
  //# sourceMappingURL=tool-call-executor.js.map