@strands-agents/sdk 1.9.0 → 1.11.0

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 (169) hide show
  1. package/README.md +1 -1
  2. package/dist/src/agent/agent.d.ts +22 -42
  3. package/dist/src/agent/agent.d.ts.map +1 -1
  4. package/dist/src/agent/agent.js +73 -426
  5. package/dist/src/agent/agent.js.map +1 -1
  6. package/dist/src/context-manager/modes/agentic/agentic-context.d.ts +2 -2
  7. package/dist/src/errors.d.ts +15 -0
  8. package/dist/src/errors.d.ts.map +1 -1
  9. package/dist/src/errors.js +18 -0
  10. package/dist/src/errors.js.map +1 -1
  11. package/dist/src/hooks/events.d.ts +5 -4
  12. package/dist/src/hooks/events.d.ts.map +1 -1
  13. package/dist/src/hooks/events.js +3 -3
  14. package/dist/src/hooks/events.js.map +1 -1
  15. package/dist/src/index.d.ts +4 -1
  16. package/dist/src/index.d.ts.map +1 -1
  17. package/dist/src/index.js +4 -1
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/index.node.d.ts.map +1 -1
  20. package/dist/src/index.node.js +2 -6
  21. package/dist/src/index.node.js.map +1 -1
  22. package/dist/src/middleware/interrupt.d.ts +18 -0
  23. package/dist/src/middleware/interrupt.d.ts.map +1 -0
  24. package/dist/src/middleware/interrupt.js +33 -0
  25. package/dist/src/middleware/interrupt.js.map +1 -0
  26. package/dist/src/models/bedrock.d.ts.map +1 -1
  27. package/dist/src/models/bedrock.js +23 -1
  28. package/dist/src/models/bedrock.js.map +1 -1
  29. package/dist/src/models/openai/errors.d.ts.map +1 -1
  30. package/dist/src/models/openai/errors.js +1 -0
  31. package/dist/src/models/openai/errors.js.map +1 -1
  32. package/dist/src/models/openai/mantle.d.ts +8 -3
  33. package/dist/src/models/openai/mantle.d.ts.map +1 -1
  34. package/dist/src/models/openai/mantle.js +41 -6
  35. package/dist/src/models/openai/mantle.js.map +1 -1
  36. package/dist/src/models/openai/model.d.ts.map +1 -1
  37. package/dist/src/models/openai/model.js +4 -3
  38. package/dist/src/models/openai/model.js.map +1 -1
  39. package/dist/src/models/openai/responses-adapter.d.ts.map +1 -1
  40. package/dist/src/models/openai/responses-adapter.js +41 -9
  41. package/dist/src/models/openai/responses-adapter.js.map +1 -1
  42. package/dist/src/models/vercel.d.ts.map +1 -1
  43. package/dist/src/models/vercel.js +28 -1
  44. package/dist/src/models/vercel.js.map +1 -1
  45. package/dist/src/multiagent/graph.d.ts.map +1 -1
  46. package/dist/src/multiagent/graph.js +1 -1
  47. package/dist/src/multiagent/graph.js.map +1 -1
  48. package/dist/src/{multiagent/queue.d.ts → queue.d.ts} +12 -27
  49. package/dist/src/queue.d.ts.map +1 -0
  50. package/dist/src/{multiagent/queue.js → queue.js} +4 -2
  51. package/dist/src/queue.js.map +1 -0
  52. package/dist/src/register-node-defaults.d.ts +3 -0
  53. package/dist/src/register-node-defaults.d.ts.map +1 -0
  54. package/dist/src/register-node-defaults.js +10 -0
  55. package/dist/src/register-node-defaults.js.map +1 -0
  56. package/dist/src/sandbox/register-node-defaults.d.ts +3 -0
  57. package/dist/src/sandbox/register-node-defaults.d.ts.map +1 -0
  58. package/dist/src/sandbox/register-node-defaults.js +8 -0
  59. package/dist/src/sandbox/register-node-defaults.js.map +1 -0
  60. package/dist/src/session/file-storage.d.ts +3 -0
  61. package/dist/src/session/file-storage.d.ts.map +1 -1
  62. package/dist/src/session/file-storage.js +3 -0
  63. package/dist/src/session/file-storage.js.map +1 -1
  64. package/dist/src/session/s3-storage.d.ts +3 -0
  65. package/dist/src/session/s3-storage.d.ts.map +1 -1
  66. package/dist/src/session/s3-storage.js +3 -0
  67. package/dist/src/session/s3-storage.js.map +1 -1
  68. package/dist/src/session/session-manager.d.ts +11 -2
  69. package/dist/src/session/session-manager.d.ts.map +1 -1
  70. package/dist/src/session/session-manager.js +19 -8
  71. package/dist/src/session/session-manager.js.map +1 -1
  72. package/dist/src/session/snapshot-storage-adapter.d.ts +93 -0
  73. package/dist/src/session/snapshot-storage-adapter.d.ts.map +1 -0
  74. package/dist/src/session/snapshot-storage-adapter.js +157 -0
  75. package/dist/src/session/snapshot-storage-adapter.js.map +1 -0
  76. package/dist/src/session/storage.d.ts +3 -6
  77. package/dist/src/session/storage.d.ts.map +1 -1
  78. package/dist/src/storage/in-memory-storage.d.ts +64 -0
  79. package/dist/src/storage/in-memory-storage.d.ts.map +1 -0
  80. package/dist/src/storage/in-memory-storage.js +85 -0
  81. package/dist/src/storage/in-memory-storage.js.map +1 -0
  82. package/dist/src/storage/index.d.ts +20 -0
  83. package/dist/src/storage/index.d.ts.map +1 -0
  84. package/dist/src/storage/index.js +18 -0
  85. package/dist/src/storage/index.js.map +1 -0
  86. package/dist/src/storage/local-file-storage.d.ts +81 -0
  87. package/dist/src/storage/local-file-storage.d.ts.map +1 -0
  88. package/dist/src/storage/local-file-storage.js +244 -0
  89. package/dist/src/storage/local-file-storage.js.map +1 -0
  90. package/dist/src/storage/s3-storage.d.ts +73 -0
  91. package/dist/src/storage/s3-storage.d.ts.map +1 -0
  92. package/dist/src/storage/s3-storage.js +146 -0
  93. package/dist/src/storage/s3-storage.js.map +1 -0
  94. package/dist/src/storage/storage.d.ts +102 -0
  95. package/dist/src/storage/storage.d.ts.map +1 -0
  96. package/dist/src/storage/storage.js +68 -0
  97. package/dist/src/storage/storage.js.map +1 -0
  98. package/dist/src/telemetry/tracer.d.ts +18 -0
  99. package/dist/src/telemetry/tracer.d.ts.map +1 -1
  100. package/dist/src/telemetry/tracer.js +31 -11
  101. package/dist/src/telemetry/tracer.js.map +1 -1
  102. package/dist/src/tools/executors/concurrent.d.ts +32 -0
  103. package/dist/src/tools/executors/concurrent.d.ts.map +1 -0
  104. package/dist/src/tools/executors/concurrent.js +128 -0
  105. package/dist/src/tools/executors/concurrent.js.map +1 -0
  106. package/dist/src/tools/executors/executor.d.ts +61 -0
  107. package/dist/src/tools/executors/executor.d.ts.map +1 -0
  108. package/dist/src/tools/executors/executor.js +190 -0
  109. package/dist/src/tools/executors/executor.js.map +1 -0
  110. package/dist/src/tools/executors/sequential.d.ts +30 -0
  111. package/dist/src/tools/executors/sequential.d.ts.map +1 -0
  112. package/dist/src/tools/executors/sequential.js +67 -0
  113. package/dist/src/tools/executors/sequential.js.map +1 -0
  114. package/dist/src/tsconfig.tsbuildinfo +1 -1
  115. package/dist/src/types/agent.d.ts +5 -4
  116. package/dist/src/types/agent.d.ts.map +1 -1
  117. package/dist/src/types/agent.js.map +1 -1
  118. package/dist/src/vended-memory-stores/test-memory-store/store.d.ts +32 -24
  119. package/dist/src/vended-memory-stores/test-memory-store/store.d.ts.map +1 -1
  120. package/dist/src/vended-memory-stores/test-memory-store/store.js +84 -91
  121. package/dist/src/vended-memory-stores/test-memory-store/store.js.map +1 -1
  122. package/dist/src/vended-plugins/context-offloader/plugin.d.ts +37 -5
  123. package/dist/src/vended-plugins/context-offloader/plugin.d.ts.map +1 -1
  124. package/dist/src/vended-plugins/context-offloader/plugin.js +116 -17
  125. package/dist/src/vended-plugins/context-offloader/plugin.js.map +1 -1
  126. package/dist/src/vended-plugins/context-offloader/storage.d.ts +23 -9
  127. package/dist/src/vended-plugins/context-offloader/storage.d.ts.map +1 -1
  128. package/dist/src/vended-plugins/context-offloader/storage.js +21 -6
  129. package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -1
  130. package/dist/src/vended-tools/index.d.ts +3 -1
  131. package/dist/src/vended-tools/index.d.ts.map +1 -1
  132. package/dist/src/vended-tools/index.js +3 -1
  133. package/dist/src/vended-tools/index.js.map +1 -1
  134. package/dist/src/vended-tools/notebook/notebook.d.ts +1 -1
  135. package/dist/src/vended-tools/sleep/index.d.ts +9 -0
  136. package/dist/src/vended-tools/sleep/index.d.ts.map +1 -0
  137. package/dist/src/vended-tools/sleep/index.js +7 -0
  138. package/dist/src/vended-tools/sleep/index.js.map +1 -0
  139. package/dist/src/vended-tools/sleep/make-sleep.d.ts +48 -0
  140. package/dist/src/vended-tools/sleep/make-sleep.d.ts.map +1 -0
  141. package/dist/src/vended-tools/sleep/make-sleep.js +81 -0
  142. package/dist/src/vended-tools/sleep/make-sleep.js.map +1 -0
  143. package/dist/src/vended-tools/sleep/sleep.d.ts +19 -0
  144. package/dist/src/vended-tools/sleep/sleep.d.ts.map +1 -0
  145. package/dist/src/vended-tools/sleep/sleep.js +20 -0
  146. package/dist/src/vended-tools/sleep/sleep.js.map +1 -0
  147. package/dist/src/vended-tools/sleep/types.d.ts +31 -0
  148. package/dist/src/vended-tools/sleep/types.d.ts.map +1 -0
  149. package/dist/src/vended-tools/sleep/types.js +22 -0
  150. package/dist/src/vended-tools/sleep/types.js.map +1 -0
  151. package/dist/src/vended-tools/stop/index.d.ts +8 -0
  152. package/dist/src/vended-tools/stop/index.d.ts.map +1 -0
  153. package/dist/src/vended-tools/stop/index.js +6 -0
  154. package/dist/src/vended-tools/stop/index.js.map +1 -0
  155. package/dist/src/vended-tools/stop/stop.d.ts +47 -0
  156. package/dist/src/vended-tools/stop/stop.d.ts.map +1 -0
  157. package/dist/src/vended-tools/stop/stop.js +85 -0
  158. package/dist/src/vended-tools/stop/stop.js.map +1 -0
  159. package/dist/src/vended-tools/stop/types.d.ts +54 -0
  160. package/dist/src/vended-tools/stop/types.d.ts.map +1 -0
  161. package/dist/src/vended-tools/stop/types.js +58 -0
  162. package/dist/src/vended-tools/stop/types.js.map +1 -0
  163. package/package.json +13 -2
  164. package/dist/src/conversation-manager/index.d.ts +0 -10
  165. package/dist/src/conversation-manager/index.d.ts.map +0 -1
  166. package/dist/src/conversation-manager/index.js +0 -10
  167. package/dist/src/conversation-manager/index.js.map +0 -1
  168. package/dist/src/multiagent/queue.d.ts.map +0 -1
  169. package/dist/src/multiagent/queue.js.map +0 -1
@@ -20,11 +20,14 @@ import { SummarizingConversationManager } from '../conversation-manager/summariz
20
20
  import { NullConversationManager } from '../conversation-manager/null-conversation-manager.js';
21
21
  import { ConversationManager } from '../conversation-manager/conversation-manager.js';
22
22
  import { ContextOffloader } from '../vended-plugins/context-offloader/plugin.js';
23
- import { InMemoryStorage } from '../vended-plugins/context-offloader/storage.js';
23
+ import { InMemoryStorage } from '../storage/in-memory-storage.js';
24
24
  import { HookRegistryImplementation } from '../hooks/registry.js';
25
- import { MiddlewareRegistry, InvokeModelStage, ExecuteToolStage, AgentStreamStage } from '../middleware/index.js';
26
- import { InitializedEvent, AfterInvocationEvent, AfterModelCallEvent, AfterToolCallEvent, AfterToolsEvent, BeforeInvocationEvent, BeforeModelCallEvent, BeforeToolCallEvent, BeforeToolsEvent, HookableEvent, MessageAddedEvent, ModelStreamUpdateEvent, ContentBlockEvent, ModelMessageEvent, ToolResultEvent, AgentResultEvent, ToolStreamUpdateEvent, InterruptEvent, } from '../hooks/events.js';
25
+ import { createMiddlewareInterrupt } from '../middleware/interrupt.js';
26
+ import { MiddlewareRegistry, InvokeModelStage, AgentStreamStage } from '../middleware/index.js';
27
+ import { InitializedEvent, AfterInvocationEvent, AfterModelCallEvent, AfterToolsEvent, BeforeInvocationEvent, BeforeModelCallEvent, BeforeToolsEvent, HookableEvent, MessageAddedEvent, ModelStreamUpdateEvent, ContentBlockEvent, ModelMessageEvent, ToolResultEvent, AgentResultEvent, InterruptEvent, } from '../hooks/events.js';
27
28
  import { StructuredOutputTool, STRUCTURED_OUTPUT_TOOL_NAME } from '../tools/structured-output-tool.js';
29
+ import { ConcurrentToolExecutor } from '../tools/executors/concurrent.js';
30
+ import { SequentialToolExecutor } from '../tools/executors/sequential.js';
28
31
  import { AgentAsTool } from './agent-as-tool.js';
29
32
  import { ToolCaller } from './tool-caller.js';
30
33
  import { MemoryManager } from '../memory/memory-manager.js';
@@ -35,7 +38,7 @@ import { logger } from '../logging/logger.js';
35
38
  import { CancelledError, CheckpointError } from '../errors.js';
36
39
  import { DefaultModelRetryStrategy } from '../retry/default-model-retry-strategy.js';
37
40
  import { warnOnDuplicateRetryStrategyTypes } from '../retry/retry-strategy.js';
38
- import { Interrupt, InterruptError, InterruptState, interruptFromAgent } from '../interrupt.js';
41
+ import { InterruptError, InterruptState } from '../interrupt.js';
39
42
  import { Checkpoint } from '../experimental/checkpoint.js';
40
43
  import { isInterruptResponseContent } from '../types/interrupt.js';
41
44
  import { takeSnapshot as takeSnapshotInternal, loadSnapshot as loadSnapshotInternal } from './snapshot.js';
@@ -82,37 +85,27 @@ function resolveConversationManager(contextManager, conversationManager) {
82
85
  }
83
86
  return conversationManager ?? new SlidingWindowConversationManager({ windowSize: 40 });
84
87
  }
88
+ /**
89
+ * Resolves a tool executor instance from an executor or string shorthand.
90
+ */
91
+ function resolveToolExecutor(toolExecutor) {
92
+ switch (toolExecutor) {
93
+ case 'sequential':
94
+ return new SequentialToolExecutor();
95
+ case 'concurrent':
96
+ case undefined:
97
+ return new ConcurrentToolExecutor();
98
+ default:
99
+ if (typeof toolExecutor === 'string') {
100
+ throw new Error(`Unknown toolExecutor: ${toolExecutor}`);
101
+ }
102
+ return toolExecutor;
103
+ }
104
+ }
85
105
  /** Default name assigned to agents when none is provided. */
86
106
  const DEFAULT_AGENT_NAME = 'Strands Agent';
87
107
  /** Default identifier assigned to agents when none is provided. */
88
108
  const DEFAULT_AGENT_ID = 'agent';
89
- /**
90
- * Creates a non-mutating interrupt function for middleware contexts.
91
- * Reads existing responses from state (resume case) but never writes to it.
92
- * On first run (no response), throws InterruptError with a locally-created Interrupt.
93
- *
94
- * @param interruptState - The agent's interrupt state (read-only access)
95
- * @param idPrefix - Prefix for the interrupt ID (e.g., 'middleware:agentStream')
96
- */
97
- function createMiddlewareInterrupt(interruptState, idPrefix) {
98
- return (params) => {
99
- const interruptId = `${idPrefix}:${params.name}`;
100
- const existing = interruptState.interrupts[interruptId];
101
- if (existing?.response !== undefined) {
102
- return { response: existing.response };
103
- }
104
- if (params.response !== undefined) {
105
- return { response: params.response };
106
- }
107
- const interrupt = new Interrupt({
108
- id: interruptId,
109
- name: params.name,
110
- ...(params.reason !== undefined && { reason: params.reason }),
111
- source: 'middleware',
112
- });
113
- throw new InterruptError(interrupt);
114
- };
115
- }
116
109
  /**
117
110
  * Orchestrates the interaction between a model, a set of tools, and MCP clients.
118
111
  * The Agent is responsible for managing the lifecycle of tools and clients
@@ -191,7 +184,7 @@ export class Agent {
191
184
  _meter;
192
185
  /** Interrupt state for human-in-the-loop workflows. */
193
186
  _interruptState;
194
- /** Strategy for executing tool calls from a single assistant turn. */
187
+ /** Executor for tool calls from a single assistant turn. */
195
188
  _toolExecutor;
196
189
  /** When true, the agent loop pauses at cycle boundaries for durable execution. */
197
190
  _checkpointing;
@@ -301,7 +294,7 @@ export class Agent {
301
294
  this._meter = new Meter();
302
295
  // Initialize interrupt state for human-in-the-loop workflows
303
296
  this._interruptState = new InterruptState();
304
- this._toolExecutor = config?.toolExecutor ?? 'concurrent';
297
+ this._toolExecutor = resolveToolExecutor(config?.toolExecutor);
305
298
  this._checkpointing = config?.checkpointing ?? false;
306
299
  // Pass a private helper into ToolCaller so message append + hook firing
307
300
  // remains an internal concern of Agent (not exposed as a public method).
@@ -481,6 +474,21 @@ export class Agent {
481
474
  get toolRegistry() {
482
475
  return this._toolRegistry;
483
476
  }
477
+ /**
478
+ * Executor for tool calls from a single assistant turn.
479
+ *
480
+ * Reading always yields the resolved executor instance. Assigning accepts an
481
+ * executor instance or a {@link ToolExecutorStrategy} string shorthand, which is
482
+ * resolved to the matching instance on write.
483
+ *
484
+ * @throws Error if assigned an unrecognized string shorthand.
485
+ */
486
+ get toolExecutor() {
487
+ return this._toolExecutor;
488
+ }
489
+ set toolExecutor(toolExecutor) {
490
+ this._toolExecutor = resolveToolExecutor(toolExecutor);
491
+ }
484
492
  /**
485
493
  * Read-only snapshot of accumulated agent metrics (cycles, token usage, tool stats).
486
494
  */
@@ -1084,7 +1092,7 @@ export class Agent {
1084
1092
  assistantMessage = modelResult.message;
1085
1093
  }
1086
1094
  // Execute tools
1087
- const toolsResult = yield* this.executeTools(assistantMessage, this._toolRegistry, invocationState, completedToolResults);
1095
+ const toolsResult = yield* this.executeTools(assistantMessage, invocationState, completedToolResults);
1088
1096
  // When the consumer breaks the stream (e.g. agent.cancel() + break),
1089
1097
  // yield* returns undefined because the inner generator was closed.
1090
1098
  if (!toolsResult) {
@@ -1591,15 +1599,14 @@ export class Agent {
1591
1599
  }
1592
1600
  /**
1593
1601
  * Emits `BeforeToolsEvent`, handles the pre-launch cancel paths, then
1594
- * delegates per-tool execution to the configured {@link ToolExecutorStrategy}.
1602
+ * delegates per-tool execution to the configured executor.
1595
1603
  * Always pairs `BeforeToolsEvent` with a terminal `AfterToolsEvent`, even on
1596
1604
  * the invariant-violation throw path.
1597
1605
  *
1598
1606
  * @param assistantMessage - The assistant message containing tool use blocks
1599
- * @param toolRegistry - Registry containing available tools
1600
1607
  * @returns Tool-result message and the dispatched AfterToolsEvent
1601
1608
  */
1602
- async *executeTools(assistantMessage, toolRegistry, invocationState, completedToolResults) {
1609
+ async *executeTools(assistantMessage, invocationState, completedToolResults) {
1603
1610
  const beforeToolsEvent = new BeforeToolsEvent({ agent: this, message: assistantMessage, invocationState });
1604
1611
  try {
1605
1612
  yield beforeToolsEvent;
@@ -1616,105 +1623,41 @@ export class Agent {
1616
1623
  throw error;
1617
1624
  }
1618
1625
  const toolUseBlocks = assistantMessage.content.filter((block) => block.type === 'toolUseBlock');
1619
- if (toolUseBlocks.length === 0) {
1620
- // Preserve BeforeToolsEvent/AfterToolsEvent bracket symmetry even on
1621
- // this invariant-violation branch.
1622
- yield new AfterToolsEvent({
1623
- agent: this,
1624
- message: new Message({ role: 'user', content: [] }),
1625
- invocationState,
1626
- });
1627
- throw new Error('Model indicated toolUse but no tool use blocks found in message');
1628
- }
1629
- // Pre-launch cancel paths are strategy-independent.
1630
- if (beforeToolsEvent.cancel) {
1631
- const message = typeof beforeToolsEvent.cancel === 'string' ? beforeToolsEvent.cancel : 'Tool cancelled by hook';
1632
- return yield* this._yieldCancelledToolResults(toolUseBlocks, message, invocationState);
1633
- }
1634
- if (this.isCancelled) {
1635
- return yield* this._yieldCancelledToolResults(toolUseBlocks, 'Tool execution cancelled', invocationState);
1636
- }
1637
- switch (this._toolExecutor) {
1638
- case 'sequential':
1639
- return yield* this._executeToolsSequential(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage);
1640
- case 'concurrent':
1641
- return yield* this._executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage);
1642
- default: {
1643
- const _exhaustive = this._toolExecutor;
1644
- throw new Error(`Unknown toolExecutor: ${_exhaustive}`);
1645
- }
1646
- }
1647
- }
1648
- /**
1649
- * Emits a `ToolResultEvent` for every block plus an `AfterToolsEvent`, and
1650
- * returns the resulting tool-result message and dispatched event. Used by the pre-launch cancel
1651
- * paths shared across executors.
1652
- */
1653
- async *_yieldCancelledToolResults(toolUseBlocks, message, invocationState) {
1654
- const cancelBlocks = this._cancelAllAsResults(toolUseBlocks, message);
1655
- for (const result of cancelBlocks) {
1656
- yield new ToolResultEvent({ agent: this, result, invocationState });
1657
- }
1658
- const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
1659
- const afterToolsEvent = new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
1660
- yield afterToolsEvent;
1661
- return { message: toolResultMessage, afterToolsEvent };
1662
- }
1663
- /**
1664
- * Executes tools one at a time, honoring `agent.cancelSignal` between
1665
- * iterations to short-circuit not-yet-started tools.
1666
- */
1667
- async *_executeToolsSequential(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage) {
1668
1626
  const toolResultBlocks = [];
1669
1627
  let toolResultMessage;
1670
1628
  let afterToolsEvent;
1671
1629
  try {
1672
- for (const toolUseBlock of toolUseBlocks) {
1673
- // Skip tools that were already completed before the interrupt
1674
- if (completedToolResults?.has(toolUseBlock.toolUseId)) {
1675
- const completedResult = completedToolResults.get(toolUseBlock.toolUseId);
1676
- // No events emitted for already-completed tools.
1677
- // The result is included in the final tool result message.
1678
- toolResultBlocks.push(completedResult);
1679
- continue;
1680
- }
1681
- if (this.isCancelled) {
1682
- const cancelBlock = new ToolResultBlock({
1683
- toolUseId: toolUseBlock.toolUseId,
1684
- status: 'error',
1685
- content: [new TextBlock('Tool execution cancelled')],
1686
- });
1687
- toolResultBlocks.push(cancelBlock);
1688
- yield new ToolResultEvent({ agent: this, result: cancelBlock, invocationState });
1689
- continue;
1690
- }
1691
- try {
1692
- const toolResultBlock = yield* this.executeTool(toolUseBlock, toolRegistry, invocationState);
1693
- toolResultBlocks.push(toolResultBlock);
1694
- yield new ToolResultEvent({ agent: this, result: toolResultBlock, invocationState });
1695
- }
1696
- catch (error) {
1697
- if (error instanceof InterruptError) {
1698
- // Store pending state with completed results so far
1699
- const completedSoFar = {};
1700
- for (const block of toolResultBlocks) {
1701
- completedSoFar[block.toolUseId] = block.toJSON();
1702
- }
1703
- // Also include any previously completed results
1704
- if (completedToolResults) {
1705
- for (const [id, block] of completedToolResults) {
1706
- completedSoFar[id] = block.toJSON();
1707
- }
1708
- }
1709
- this._interruptState.setPendingToolExecution({
1710
- assistantMessageData: assistantMessage.toJSON(),
1711
- completedToolResults: completedSoFar,
1712
- });
1713
- throw error;
1714
- }
1715
- throw error;
1630
+ if (toolUseBlocks.length === 0) {
1631
+ throw new Error('Model indicated toolUse but no tool use blocks found in message');
1632
+ }
1633
+ // Pre-launch cancellation is executor-independent.
1634
+ const cancelMessage = beforeToolsEvent.cancel
1635
+ ? typeof beforeToolsEvent.cancel === 'string'
1636
+ ? beforeToolsEvent.cancel
1637
+ : 'Tool cancelled by hook'
1638
+ : this.isCancelled
1639
+ ? 'Tool execution cancelled'
1640
+ : undefined;
1641
+ if (cancelMessage) {
1642
+ toolResultBlocks.push(...this._cancelAllAsResults(toolUseBlocks, cancelMessage));
1643
+ for (const result of toolResultBlocks) {
1644
+ yield new ToolResultEvent({ agent: this, result, invocationState });
1716
1645
  }
1717
1646
  }
1647
+ else {
1648
+ yield* this._toolExecutor.execute({
1649
+ agent: this,
1650
+ middlewareRegistry: this._middlewareRegistry,
1651
+ tracer: this._tracer,
1652
+ meter: this._meter,
1653
+ }, {
1654
+ toolUseBlocks,
1655
+ toolResultBlocks,
1656
+ invocationState,
1657
+ assistantMessage,
1658
+ ...(completedToolResults && { completedToolResults }),
1659
+ });
1660
+ }
1718
1661
  }
1719
1662
  finally {
1720
1663
  toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
@@ -1734,302 +1677,6 @@ export class Agent {
1734
1677
  content: [new TextBlock(message)],
1735
1678
  }));
1736
1679
  }
1737
- /**
1738
- * Executes tools concurrently by merging N per-tool {@link executeTool}
1739
- * async generators via `Promise.race`. Per-tool event order is preserved
1740
- * (because each generator is iterated serially); cross-tool events may
1741
- * interleave at race resolution boundaries.
1742
- *
1743
- * Per-tool retry (`AfterToolCallEvent.retry`) is isolated — it lives inside
1744
- * `executeTool`'s own `while(true)` loop, so one tool retrying does not
1745
- * disturb its siblings.
1746
- */
1747
- async *_executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage) {
1748
- let toolResultMessage;
1749
- let afterToolsEvent;
1750
- const gens = toolUseBlocks.map((block) => ({
1751
- block,
1752
- gen: completedToolResults?.has(block.toolUseId)
1753
- ? undefined // Skip already-completed tools
1754
- : this.executeTool(block, toolRegistry, invocationState),
1755
- }));
1756
- const step = (idx) => gens[idx].gen.next().then((res) => ({ idx, kind: 'next', res }), (error) => ({ idx, kind: 'throw', error }));
1757
- // Seed completed results from resume state
1758
- const resultsByToolUseId = new Map();
1759
- if (completedToolResults) {
1760
- for (const [id, result] of completedToolResults) {
1761
- resultsByToolUseId.set(id, result);
1762
- }
1763
- }
1764
- // Only race tools that need execution
1765
- const pendingNext = new Map();
1766
- for (let idx = 0; idx < gens.length; idx++) {
1767
- if (gens[idx].gen) {
1768
- pendingNext.set(idx, step(idx));
1769
- }
1770
- }
1771
- // Track interrupts — let all other tools finish before propagating
1772
- let interruptError;
1773
- try {
1774
- while (pendingNext.size > 0) {
1775
- const winner = await Promise.race(pendingNext.values());
1776
- const { idx } = winner;
1777
- const block = gens[idx].block;
1778
- if (winner.kind === 'throw') {
1779
- pendingNext.delete(idx);
1780
- // Detect InterruptError — don't convert to error result, track it
1781
- if (winner.error instanceof InterruptError) {
1782
- interruptError = winner.error;
1783
- continue;
1784
- }
1785
- const err = normalizeError(winner.error);
1786
- const result = new ToolResultBlock({
1787
- toolUseId: block.toolUseId,
1788
- status: 'error',
1789
- content: [new TextBlock(err.message)],
1790
- error: err,
1791
- });
1792
- resultsByToolUseId.set(block.toolUseId, result);
1793
- yield new ToolResultEvent({ agent: this, result, invocationState });
1794
- continue;
1795
- }
1796
- if (winner.res.done) {
1797
- pendingNext.delete(idx);
1798
- resultsByToolUseId.set(block.toolUseId, winner.res.value);
1799
- yield new ToolResultEvent({ agent: this, result: winner.res.value, invocationState });
1800
- }
1801
- else {
1802
- try {
1803
- yield winner.res.value;
1804
- }
1805
- catch (e) {
1806
- // InterruptError thrown back into generator from stream() error injection
1807
- if (e instanceof InterruptError) {
1808
- interruptError = e;
1809
- pendingNext.delete(idx);
1810
- continue;
1811
- }
1812
- throw e;
1813
- }
1814
- pendingNext.set(idx, step(idx));
1815
- }
1816
- }
1817
- // After all tools finish, propagate interrupt if one was raised
1818
- if (interruptError) {
1819
- const completedSoFar = {};
1820
- for (const [id, result] of resultsByToolUseId) {
1821
- completedSoFar[id] = result.toJSON();
1822
- }
1823
- this._interruptState.setPendingToolExecution({
1824
- assistantMessageData: assistantMessage.toJSON(),
1825
- completedToolResults: completedSoFar,
1826
- });
1827
- throw interruptError;
1828
- }
1829
- }
1830
- finally {
1831
- // Close any generators still in-flight (e.g. consumer broke out of stream).
1832
- await Promise.allSettled(Array.from(pendingNext.keys(), (idx) => gens[idx].gen.return(undefined)));
1833
- // Build the result message from whatever completed, in source order.
1834
- // Missing entries get a fallback error block so the message always
1835
- // accounts for every toolUseBlock the model emitted.
1836
- const toolResultBlocks = [];
1837
- for (const block of toolUseBlocks) {
1838
- const result = resultsByToolUseId.get(block.toolUseId);
1839
- if (result) {
1840
- toolResultBlocks.push(result);
1841
- }
1842
- else {
1843
- toolResultBlocks.push(new ToolResultBlock({
1844
- toolUseId: block.toolUseId,
1845
- status: 'error',
1846
- content: [new TextBlock('Tool execution interrupted')],
1847
- }));
1848
- }
1849
- }
1850
- toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
1851
- afterToolsEvent = new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
1852
- yield afterToolsEvent;
1853
- }
1854
- return { message: toolResultMessage, afterToolsEvent };
1855
- }
1856
- /**
1857
- * Executes a single tool and returns the result.
1858
- * If the tool is not found or fails to return a result, returns an error ToolResult
1859
- * instead of throwing an exception. This allows the agent loop to continue and
1860
- * let the model handle the error gracefully.
1861
- *
1862
- * @param toolUseBlock - Tool use block to execute
1863
- * @param toolRegistry - Registry containing available tools
1864
- * @returns Tool result block
1865
- */
1866
- async *executeTool(toolUseBlock, toolRegistry, invocationState) {
1867
- const registryTool = toolRegistry.get(toolUseBlock.name);
1868
- // Create toolUse object for hook events and telemetry. Callbacks may mutate
1869
- // this object's fields (input/name/toolUseId) inside BeforeToolCallEvent.
1870
- const toolUse = {
1871
- name: toolUseBlock.name,
1872
- toolUseId: toolUseBlock.toolUseId,
1873
- input: toolUseBlock.input,
1874
- };
1875
- // Retry loop for tool execution
1876
- while (true) {
1877
- const beforeToolCallEvent = new BeforeToolCallEvent({
1878
- agent: this,
1879
- toolUse,
1880
- tool: registryTool,
1881
- invocationState,
1882
- });
1883
- yield beforeToolCallEvent;
1884
- // Resolve the tool that would actually execute. selectedTool wins;
1885
- // otherwise if the hook renamed toolUse.name, re-resolve from the
1886
- // registry under the new name; otherwise use the original registry
1887
- // lookup. Resolved before the cancel check so AfterToolCallEvent.tool
1888
- // is consistent whether the cancel or execution branch runs.
1889
- const effectiveTool = beforeToolCallEvent.selectedTool ??
1890
- (toolUse.name !== toolUseBlock.name ? toolRegistry.get(toolUse.name) : registryTool);
1891
- // Cancel individual tool if hook requested it
1892
- if (beforeToolCallEvent.cancel) {
1893
- const cancelMessage = typeof beforeToolCallEvent.cancel === 'string' ? beforeToolCallEvent.cancel : 'Tool cancelled by hook';
1894
- const cancelResult = new ToolResultBlock({
1895
- toolUseId: toolUse.toolUseId,
1896
- status: 'error',
1897
- content: [new TextBlock(cancelMessage)],
1898
- });
1899
- const afterToolCallEvent = new AfterToolCallEvent({
1900
- agent: this,
1901
- toolUse,
1902
- tool: effectiveTool,
1903
- result: cancelResult,
1904
- invocationState,
1905
- });
1906
- yield afterToolCallEvent;
1907
- if (afterToolCallEvent.retry) {
1908
- continue;
1909
- }
1910
- return afterToolCallEvent.result;
1911
- }
1912
- // Execute tool core logic through middleware chain
1913
- const toolResult = yield* this._executeToolWithMiddleware(effectiveTool, toolUse, invocationState);
1914
- const error = toolResult.error;
1915
- // Single point for AfterToolCallEvent
1916
- const afterToolCallEvent = new AfterToolCallEvent({
1917
- agent: this,
1918
- toolUse,
1919
- tool: effectiveTool,
1920
- result: toolResult,
1921
- invocationState,
1922
- ...(error !== undefined && { error }),
1923
- });
1924
- yield afterToolCallEvent;
1925
- if (afterToolCallEvent.retry) {
1926
- continue;
1927
- }
1928
- // Return the (possibly mutated) result so hook transformations propagate
1929
- // to ToolResultEvent and the conversation message the model will see.
1930
- return afterToolCallEvent.result;
1931
- }
1932
- }
1933
- async *_executeToolWithMiddleware(tool, toolUse, invocationState) {
1934
- const context = {
1935
- agent: this,
1936
- tool,
1937
- toolUse: deepCopy(toolUse),
1938
- invocationState,
1939
- interrupt: createMiddlewareInterrupt(this._interruptState, `middleware:executeTool:${toolUse.toolUseId}`),
1940
- };
1941
- // async function* doesn't bind lexical `this`; capture for the terminal callback.
1942
- // eslint-disable-next-line @typescript-eslint/no-this-alias
1943
- const self = this;
1944
- const middlewareResult = yield* this._middlewareRegistry.invoke(ExecuteToolStage, context, async function* (ctx) {
1945
- const result = yield* self._executeToolCore(ctx.tool, ctx.toolUse, ctx.invocationState);
1946
- return { result };
1947
- });
1948
- return middlewareResult.result;
1949
- }
1950
- async *_executeToolCore(effectiveTool, toolUse, invocationState) {
1951
- // Start tool span within loop span context
1952
- const toolSpan = this._tracer.startToolCallSpan({
1953
- tool: toolUse,
1954
- });
1955
- // Track tool execution time for metrics
1956
- const toolStartTime = Date.now();
1957
- let toolResult;
1958
- let error;
1959
- if (!effectiveTool) {
1960
- // Tool not found
1961
- toolResult = new ToolResultBlock({
1962
- toolUseId: toolUse.toolUseId,
1963
- status: 'error',
1964
- content: [new TextBlock(`Tool '${toolUse.name}' not found in registry`)],
1965
- });
1966
- }
1967
- else {
1968
- // Execute tool within the tool span context
1969
- const toolContext = {
1970
- toolUse: {
1971
- name: toolUse.name,
1972
- toolUseId: toolUse.toolUseId,
1973
- input: toolUse.input,
1974
- },
1975
- agent: this,
1976
- invocationState,
1977
- interrupt: (params) => {
1978
- return interruptFromAgent(this, `tool:${toolUse.toolUseId}:${params.name}`, params, 'tool');
1979
- },
1980
- };
1981
- try {
1982
- // Manually iterate tool stream to wrap each ToolStreamEvent in ToolStreamUpdateEvent.
1983
- // This keeps the tool authoring interface unchanged — tools construct ToolStreamEvent
1984
- // without knowledge of agents or hooks, and we wrap at the boundary.
1985
- // Tool execution is ran within the tool span's context so that
1986
- // downstream calls (e.g., MCP clients) can propagate trace context
1987
- const toolGenerator = this._tracer.withSpanContext(toolSpan, () => effectiveTool.stream(toolContext));
1988
- let toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
1989
- while (!toolNext.done) {
1990
- yield new ToolStreamUpdateEvent({ agent: this, event: toolNext.value, invocationState });
1991
- toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
1992
- }
1993
- const result = toolNext.value;
1994
- if (!result) {
1995
- // Tool didn't return a result
1996
- toolResult = new ToolResultBlock({
1997
- toolUseId: toolUse.toolUseId,
1998
- status: 'error',
1999
- content: [new TextBlock(`Tool '${toolUse.name}' did not return a result`)],
2000
- });
2001
- }
2002
- else {
2003
- toolResult = result;
2004
- error = result.error;
2005
- }
2006
- }
2007
- catch (e) {
2008
- // Re-throw InterruptError to allow interrupt handling
2009
- if (e instanceof InterruptError) {
2010
- throw e;
2011
- }
2012
- // Tool execution failed with error
2013
- error = normalizeError(e);
2014
- toolResult = new ToolResultBlock({
2015
- toolUseId: toolUse.toolUseId,
2016
- status: 'error',
2017
- content: [new TextBlock(error.message)],
2018
- error,
2019
- });
2020
- }
2021
- }
2022
- // End tool span with the raw tool result — telemetry reflects what the
2023
- // tool actually returned, independent of AfterToolCallEvent mutations.
2024
- this._tracer.endToolCallSpan(toolSpan, { toolResult, ...(error && { error }) });
2025
- // End tool metrics tracking
2026
- this._meter.endToolCall({
2027
- tool: toolUse,
2028
- duration: Date.now() - toolStartTime,
2029
- success: toolResult.status === 'success',
2030
- });
2031
- return toolResult;
2032
- }
2033
1680
  /**
2034
1681
  * Redacts the last message in the conversation history.
2035
1682
  * Called when guardrails block user input and redaction is enabled.