@strands-agents/sdk 1.0.0-rc.4 → 1.0.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 (265) hide show
  1. package/README.md +8 -15
  2. package/dist/src/__fixtures__/agent-helpers.d.ts +6 -0
  3. package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
  4. package/dist/src/__fixtures__/agent-helpers.js +3 -1
  5. package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
  6. package/dist/src/__fixtures__/mock-plugin.d.ts.map +1 -1
  7. package/dist/src/__fixtures__/mock-plugin.js +3 -1
  8. package/dist/src/__fixtures__/mock-plugin.js.map +1 -1
  9. package/dist/src/__fixtures__/tool-helpers.d.ts +3 -1
  10. package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
  11. package/dist/src/__fixtures__/tool-helpers.js +3 -1
  12. package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
  13. package/dist/src/__tests__/mcp.test.js +274 -1
  14. package/dist/src/__tests__/mcp.test.js.map +1 -1
  15. package/dist/src/a2a/__tests__/events.test.js +2 -0
  16. package/dist/src/a2a/__tests__/events.test.js.map +1 -1
  17. package/dist/src/a2a/__tests__/executor.test.js +16 -5
  18. package/dist/src/a2a/__tests__/executor.test.js.map +1 -1
  19. package/dist/src/a2a/a2a-agent.d.ts +8 -3
  20. package/dist/src/a2a/a2a-agent.d.ts.map +1 -1
  21. package/dist/src/a2a/a2a-agent.js +12 -6
  22. package/dist/src/a2a/a2a-agent.js.map +1 -1
  23. package/dist/src/a2a/executor.d.ts +13 -0
  24. package/dist/src/a2a/executor.d.ts.map +1 -1
  25. package/dist/src/a2a/executor.js +19 -1
  26. package/dist/src/a2a/executor.js.map +1 -1
  27. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts +2 -0
  28. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts.map +1 -0
  29. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js +23 -0
  30. package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js.map +1 -0
  31. package/dist/src/agent/__tests__/agent.cancel.test.js +1 -1
  32. package/dist/src/agent/__tests__/agent.cancel.test.js.map +1 -1
  33. package/dist/src/agent/__tests__/agent.concurrent.test.d.ts +2 -0
  34. package/dist/src/agent/__tests__/agent.concurrent.test.d.ts.map +1 -0
  35. package/dist/src/agent/__tests__/agent.concurrent.test.js +488 -0
  36. package/dist/src/agent/__tests__/agent.concurrent.test.js.map +1 -0
  37. package/dist/src/agent/__tests__/agent.hook.test.js +174 -12
  38. package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
  39. package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts +2 -0
  40. package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts.map +1 -0
  41. package/dist/src/agent/__tests__/agent.invocation-state.test.js +219 -0
  42. package/dist/src/agent/__tests__/agent.invocation-state.test.js.map +1 -0
  43. package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts +2 -0
  44. package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts.map +1 -0
  45. package/dist/src/agent/__tests__/agent.stateful-model.test.js +169 -0
  46. package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +1 -0
  47. package/dist/src/agent/__tests__/agent.test.js +99 -2
  48. package/dist/src/agent/__tests__/agent.test.js.map +1 -1
  49. package/dist/src/agent/__tests__/agent.tracer.test.node.js +39 -0
  50. package/dist/src/agent/__tests__/agent.tracer.test.node.js.map +1 -1
  51. package/dist/src/agent/__tests__/snapshot.test.js +5 -4
  52. package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
  53. package/dist/src/agent/agent-as-tool.d.ts.map +1 -1
  54. package/dist/src/agent/agent-as-tool.js +4 -2
  55. package/dist/src/agent/agent-as-tool.js.map +1 -1
  56. package/dist/src/agent/agent.d.ts +75 -1
  57. package/dist/src/agent/agent.d.ts.map +1 -1
  58. package/dist/src/agent/agent.js +323 -83
  59. package/dist/src/agent/agent.js.map +1 -1
  60. package/dist/src/agent/snapshot.d.ts +2 -2
  61. package/dist/src/agent/snapshot.d.ts.map +1 -1
  62. package/dist/src/agent/snapshot.js +8 -2
  63. package/dist/src/agent/snapshot.js.map +1 -1
  64. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +4 -4
  65. package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -1
  66. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +2 -2
  67. package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +1 -1
  68. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +8 -3
  69. package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
  70. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +1 -0
  71. package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
  72. package/dist/src/errors.d.ts +11 -0
  73. package/dist/src/errors.d.ts.map +1 -1
  74. package/dist/src/errors.js +12 -0
  75. package/dist/src/errors.js.map +1 -1
  76. package/dist/src/hooks/__tests__/events.test.js +177 -70
  77. package/dist/src/hooks/__tests__/events.test.js.map +1 -1
  78. package/dist/src/hooks/__tests__/registry.test.js +16 -16
  79. package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
  80. package/dist/src/hooks/events.d.ts +95 -25
  81. package/dist/src/hooks/events.d.ts.map +1 -1
  82. package/dist/src/hooks/events.js +98 -23
  83. package/dist/src/hooks/events.js.map +1 -1
  84. package/dist/src/index.d.ts +6 -5
  85. package/dist/src/index.d.ts.map +1 -1
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/logging/__tests__/warn-once.test.d.ts +2 -0
  88. package/dist/src/logging/__tests__/warn-once.test.d.ts.map +1 -0
  89. package/dist/src/logging/__tests__/warn-once.test.js +30 -0
  90. package/dist/src/logging/__tests__/warn-once.test.js.map +1 -0
  91. package/dist/src/logging/warn-once.d.ts +13 -0
  92. package/dist/src/logging/warn-once.d.ts.map +1 -0
  93. package/dist/src/logging/warn-once.js +18 -0
  94. package/dist/src/logging/warn-once.js.map +1 -0
  95. package/dist/src/mcp.d.ts +20 -1
  96. package/dist/src/mcp.d.ts.map +1 -1
  97. package/dist/src/mcp.js +10 -1
  98. package/dist/src/mcp.js.map +1 -1
  99. package/dist/src/mime.d.ts +2 -1
  100. package/dist/src/mime.d.ts.map +1 -1
  101. package/dist/src/mime.js +1 -0
  102. package/dist/src/mime.js.map +1 -1
  103. package/dist/src/models/__tests__/anthropic.test.js +99 -1
  104. package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
  105. package/dist/src/models/__tests__/bedrock.test.js +123 -2
  106. package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
  107. package/dist/src/models/__tests__/google.test.js +88 -0
  108. package/dist/src/models/__tests__/google.test.js.map +1 -1
  109. package/dist/src/models/__tests__/model.test.js +149 -1
  110. package/dist/src/models/__tests__/model.test.js.map +1 -1
  111. package/dist/src/models/anthropic.d.ts +18 -1
  112. package/dist/src/models/anthropic.d.ts.map +1 -1
  113. package/dist/src/models/anthropic.js +40 -6
  114. package/dist/src/models/anthropic.js.map +1 -1
  115. package/dist/src/models/bedrock.d.ts +12 -1
  116. package/dist/src/models/bedrock.d.ts.map +1 -1
  117. package/dist/src/models/bedrock.js +45 -11
  118. package/dist/src/models/bedrock.js.map +1 -1
  119. package/dist/src/models/defaults.d.ts +37 -0
  120. package/dist/src/models/defaults.d.ts.map +1 -0
  121. package/dist/src/models/defaults.js +41 -0
  122. package/dist/src/models/defaults.js.map +1 -0
  123. package/dist/src/models/google/model.d.ts +14 -1
  124. package/dist/src/models/google/model.d.ts.map +1 -1
  125. package/dist/src/models/google/model.js +50 -6
  126. package/dist/src/models/google/model.js.map +1 -1
  127. package/dist/src/models/model.d.ts +56 -0
  128. package/dist/src/models/model.d.ts.map +1 -1
  129. package/dist/src/models/model.js +120 -0
  130. package/dist/src/models/model.js.map +1 -1
  131. package/dist/src/models/openai/__tests__/chat.test.d.ts +2 -0
  132. package/dist/src/models/openai/__tests__/chat.test.d.ts.map +1 -0
  133. package/dist/src/models/{__tests__/openai.test.js → openai/__tests__/chat.test.js} +84 -7
  134. package/dist/src/models/openai/__tests__/chat.test.js.map +1 -0
  135. package/dist/src/models/openai/__tests__/responses.test.d.ts +2 -0
  136. package/dist/src/models/openai/__tests__/responses.test.d.ts.map +1 -0
  137. package/dist/src/models/openai/__tests__/responses.test.js +668 -0
  138. package/dist/src/models/openai/__tests__/responses.test.js.map +1 -0
  139. package/dist/src/models/openai/chat-adapter.d.ts +33 -0
  140. package/dist/src/models/openai/chat-adapter.d.ts.map +1 -0
  141. package/dist/src/models/openai/chat-adapter.js +383 -0
  142. package/dist/src/models/openai/chat-adapter.js.map +1 -0
  143. package/dist/src/models/openai/errors.d.ts +16 -0
  144. package/dist/src/models/openai/errors.d.ts.map +1 -0
  145. package/dist/src/models/openai/errors.js +40 -0
  146. package/dist/src/models/openai/errors.js.map +1 -0
  147. package/dist/src/models/openai/formatting.d.ts +18 -0
  148. package/dist/src/models/openai/formatting.d.ts.map +1 -0
  149. package/dist/src/models/openai/formatting.js +38 -0
  150. package/dist/src/models/openai/formatting.js.map +1 -0
  151. package/dist/src/models/openai/index.d.ts +19 -0
  152. package/dist/src/models/openai/index.d.ts.map +1 -0
  153. package/dist/src/models/openai/index.js +18 -0
  154. package/dist/src/models/openai/index.js.map +1 -0
  155. package/dist/src/models/openai/model.d.ts +77 -0
  156. package/dist/src/models/openai/model.d.ts.map +1 -0
  157. package/dist/src/models/openai/model.js +211 -0
  158. package/dist/src/models/openai/model.js.map +1 -0
  159. package/dist/src/models/openai/responses-adapter.d.ts +78 -0
  160. package/dist/src/models/openai/responses-adapter.d.ts.map +1 -0
  161. package/dist/src/models/openai/responses-adapter.js +467 -0
  162. package/dist/src/models/openai/responses-adapter.js.map +1 -0
  163. package/dist/src/models/openai/types.d.ts +131 -0
  164. package/dist/src/models/openai/types.d.ts.map +1 -0
  165. package/dist/src/models/openai/types.js +5 -0
  166. package/dist/src/models/openai/types.js.map +1 -0
  167. package/dist/src/multiagent/__tests__/events.test.js +122 -28
  168. package/dist/src/multiagent/__tests__/events.test.js.map +1 -1
  169. package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts +2 -0
  170. package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts.map +1 -0
  171. package/dist/src/multiagent/__tests__/graph.invocation-state.test.js +95 -0
  172. package/dist/src/multiagent/__tests__/graph.invocation-state.test.js.map +1 -0
  173. package/dist/src/multiagent/__tests__/nodes.test.js +5 -2
  174. package/dist/src/multiagent/__tests__/nodes.test.js.map +1 -1
  175. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts +2 -0
  176. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts.map +1 -0
  177. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js +56 -0
  178. package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js.map +1 -0
  179. package/dist/src/multiagent/events.d.ts +19 -1
  180. package/dist/src/multiagent/events.d.ts.map +1 -1
  181. package/dist/src/multiagent/events.js +18 -0
  182. package/dist/src/multiagent/events.js.map +1 -1
  183. package/dist/src/multiagent/graph.d.ts +5 -3
  184. package/dist/src/multiagent/graph.d.ts.map +1 -1
  185. package/dist/src/multiagent/graph.js +22 -15
  186. package/dist/src/multiagent/graph.js.map +1 -1
  187. package/dist/src/multiagent/index.d.ts +1 -1
  188. package/dist/src/multiagent/index.d.ts.map +1 -1
  189. package/dist/src/multiagent/multiagent.d.ts +16 -3
  190. package/dist/src/multiagent/multiagent.d.ts.map +1 -1
  191. package/dist/src/multiagent/nodes.d.ts +10 -3
  192. package/dist/src/multiagent/nodes.d.ts.map +1 -1
  193. package/dist/src/multiagent/nodes.js +28 -6
  194. package/dist/src/multiagent/nodes.js.map +1 -1
  195. package/dist/src/multiagent/swarm.d.ts +5 -3
  196. package/dist/src/multiagent/swarm.d.ts.map +1 -1
  197. package/dist/src/multiagent/swarm.js +22 -16
  198. package/dist/src/multiagent/swarm.js.map +1 -1
  199. package/dist/src/plugins/__tests__/registry.test.js +1 -1
  200. package/dist/src/plugins/__tests__/registry.test.js.map +1 -1
  201. package/dist/src/plugins/model-plugin.d.ts +20 -0
  202. package/dist/src/plugins/model-plugin.d.ts.map +1 -0
  203. package/dist/src/plugins/model-plugin.js +29 -0
  204. package/dist/src/plugins/model-plugin.js.map +1 -0
  205. package/dist/src/session/__tests__/session-manager.test.js +13 -11
  206. package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
  207. package/dist/src/session/session-manager.d.ts.map +1 -1
  208. package/dist/src/session/session-manager.js +9 -0
  209. package/dist/src/session/session-manager.js.map +1 -1
  210. package/dist/src/telemetry/__tests__/config.test.js +6 -6
  211. package/dist/src/telemetry/__tests__/config.test.js.map +1 -1
  212. package/dist/src/telemetry/__tests__/config.test.node.js +16 -11
  213. package/dist/src/telemetry/__tests__/config.test.node.js.map +1 -1
  214. package/dist/src/telemetry/__tests__/meter.test.js +23 -0
  215. package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
  216. package/dist/src/telemetry/config.d.ts +9 -3
  217. package/dist/src/telemetry/config.d.ts.map +1 -1
  218. package/dist/src/telemetry/config.js +44 -69
  219. package/dist/src/telemetry/config.js.map +1 -1
  220. package/dist/src/telemetry/meter.d.ts +15 -0
  221. package/dist/src/telemetry/meter.d.ts.map +1 -1
  222. package/dist/src/telemetry/meter.js +14 -0
  223. package/dist/src/telemetry/meter.js.map +1 -1
  224. package/dist/src/tools/mcp-tool.d.ts +24 -3
  225. package/dist/src/tools/mcp-tool.d.ts.map +1 -1
  226. package/dist/src/tools/mcp-tool.js +105 -14
  227. package/dist/src/tools/mcp-tool.js.map +1 -1
  228. package/dist/src/tools/tool.d.ts +11 -1
  229. package/dist/src/tools/tool.d.ts.map +1 -1
  230. package/dist/src/tools/tool.js.map +1 -1
  231. package/dist/src/tsconfig.tsbuildinfo +1 -1
  232. package/dist/src/types/__tests__/agent.test.js +48 -0
  233. package/dist/src/types/__tests__/agent.test.js.map +1 -1
  234. package/dist/src/types/agent.d.ts +55 -6
  235. package/dist/src/types/agent.d.ts.map +1 -1
  236. package/dist/src/types/agent.js +22 -6
  237. package/dist/src/types/agent.js.map +1 -1
  238. package/dist/src/types/elicitation.d.ts +15 -0
  239. package/dist/src/types/elicitation.d.ts.map +1 -0
  240. package/dist/src/types/elicitation.js +2 -0
  241. package/dist/src/types/elicitation.js.map +1 -0
  242. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +37 -33
  243. package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +1 -1
  244. package/dist/src/vended-plugins/skills/agent-skills.d.ts +8 -8
  245. package/dist/src/vended-plugins/skills/agent-skills.d.ts.map +1 -1
  246. package/dist/src/vended-plugins/skills/agent-skills.js +5 -5
  247. package/dist/src/vended-plugins/skills/agent-skills.js.map +1 -1
  248. package/dist/src/vended-plugins/skills/index.d.ts +5 -5
  249. package/dist/src/vended-plugins/skills/index.d.ts.map +1 -1
  250. package/dist/src/vended-plugins/skills/index.js +4 -4
  251. package/dist/src/vended-plugins/skills/index.js.map +1 -1
  252. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +1 -0
  253. package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
  254. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +1 -0
  255. package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -1
  256. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +1 -0
  257. package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
  258. package/package.json +28 -26
  259. package/dist/src/models/__tests__/openai.test.d.ts +0 -2
  260. package/dist/src/models/__tests__/openai.test.d.ts.map +0 -1
  261. package/dist/src/models/__tests__/openai.test.js.map +0 -1
  262. package/dist/src/models/openai.d.ts +0 -312
  263. package/dist/src/models/openai.d.ts.map +0 -1
  264. package/dist/src/models/openai.js +0 -789
  265. package/dist/src/models/openai.js.map +0 -1
@@ -58,12 +58,14 @@ import {} from '../tools/tool.js';
58
58
  import { systemPromptFromData } from '../types/messages.js';
59
59
  import { normalizeError, ConcurrentInvocationError, StructuredOutputError } from '../errors.js';
60
60
  import { Model } from '../models/model.js';
61
+ import { ModelPlugin } from '../plugins/model-plugin.js';
61
62
  import { isModelStreamEvent } from '../models/streaming.js';
62
63
  import { ToolRegistry } from '../registry/tool-registry.js';
63
64
  import { StateStore } from '../state-store.js';
64
65
  import { AgentPrinter, getDefaultAppender } from './printer.js';
65
66
  import { PluginRegistry } from '../plugins/registry.js';
66
67
  import { SlidingWindowConversationManager } from '../conversation-manager/sliding-window-conversation-manager.js';
68
+ import { NullConversationManager } from '../conversation-manager/null-conversation-manager.js';
67
69
  import { ConversationManager } from '../conversation-manager/conversation-manager.js';
68
70
  import { HookRegistryImplementation } from '../hooks/registry.js';
69
71
  import { InitializedEvent, AfterInvocationEvent, AfterModelCallEvent, AfterToolCallEvent, AfterToolsEvent, BeforeInvocationEvent, BeforeModelCallEvent, BeforeToolCallEvent, BeforeToolsEvent, HookableEvent, MessageAddedEvent, ModelStreamUpdateEvent, ContentBlockEvent, ModelMessageEvent, ToolResultEvent, AgentResultEvent, ToolStreamUpdateEvent, } from '../hooks/events.js';
@@ -93,6 +95,12 @@ export class Agent {
93
95
  * State is not passed to the model during inference.
94
96
  */
95
97
  appState;
98
+ /**
99
+ * Runtime state for the model provider. Used by stateful models to persist
100
+ * provider-specific data (e.g., response IDs for conversation chaining)
101
+ * across invocations.
102
+ */
103
+ modelState;
96
104
  _conversationManager;
97
105
  /**
98
106
  * The model provider used by the agent for inference.
@@ -132,6 +140,8 @@ export class Agent {
132
140
  _tracer;
133
141
  /** Meter instance for accumulating loop metrics during invocation. */
134
142
  _meter;
143
+ /** Strategy for executing tool calls from a single assistant turn. */
144
+ _toolExecutor;
135
145
  /**
136
146
  * Creates an instance of the Agent.
137
147
  * @param config - The configuration for the agent.
@@ -140,7 +150,7 @@ export class Agent {
140
150
  // Initialize public fields
141
151
  this.messages = (config?.messages ?? []).map((msg) => (msg instanceof Message ? msg : Message.fromMessageData(msg)));
142
152
  this.appState = new StateStore(config?.appState);
143
- this._conversationManager = config?.conversationManager ?? new SlidingWindowConversationManager({ windowSize: 40 });
153
+ this.modelState = new StateStore(config?.modelState);
144
154
  this.name = config?.name ?? DEFAULT_AGENT_NAME;
145
155
  this.id = config?.id ?? DEFAULT_AGENT_ID;
146
156
  if (config?.description !== undefined)
@@ -152,16 +162,30 @@ export class Agent {
152
162
  else {
153
163
  this.model = config?.model ?? new BedrockModel();
154
164
  }
165
+ // Validate and assign conversation manager
166
+ if (this.model.stateful) {
167
+ if (config?.conversationManager) {
168
+ throw new Error('Cannot use a conversationManager with a stateful model. The model manages conversation state server-side.');
169
+ }
170
+ this._conversationManager = new NullConversationManager();
171
+ }
172
+ else {
173
+ this._conversationManager =
174
+ config?.conversationManager ?? new SlidingWindowConversationManager({ windowSize: 40 });
175
+ }
155
176
  const { tools, mcpClients } = flattenTools(config?.tools ?? []);
156
177
  this._toolRegistry = new ToolRegistry(tools);
157
178
  this._mcpClients = mcpClients;
158
179
  // Initialize hooks registry
159
180
  this._hooksRegistry = new HookRegistryImplementation();
160
181
  // Initialize plugin registry with all plugins to be initialized during initialize()
182
+ // ModelPlugin is registered last so that on AfterInvocationEvent (which uses reverse
183
+ // callback ordering), it runs first — clearing messages before SessionManager saves.
161
184
  this._pluginRegistry = new PluginRegistry([
162
185
  this._conversationManager,
163
186
  ...(config?.plugins ?? []),
164
187
  ...(config?.sessionManager ? [config.sessionManager] : []),
188
+ new ModelPlugin(this.model),
165
189
  ]);
166
190
  if (config?.systemPrompt !== undefined) {
167
191
  this.systemPrompt = systemPromptFromData(config.systemPrompt);
@@ -177,6 +201,7 @@ export class Agent {
177
201
  this._tracer = new Tracer(config?.traceAttributes);
178
202
  // Initialize meter for local metrics accumulation
179
203
  this._meter = new Meter();
204
+ this._toolExecutor = config?.toolExecutor ?? 'concurrent';
180
205
  this._initialized = false;
181
206
  }
182
207
  /**
@@ -376,7 +401,7 @@ export class Agent {
376
401
  yield await this._invokeCallbacks(result.value);
377
402
  result = await streamGenerator.next();
378
403
  }
379
- yield await this._invokeCallbacks(new AgentResultEvent({ agent: this, result: result.value }));
404
+ yield await this._invokeCallbacks(new AgentResultEvent({ agent: this, result: result.value, invocationState: result.value.invocationState }));
380
405
  return result.value;
381
406
  }
382
407
  catch (error) {
@@ -471,8 +496,26 @@ export class Agent {
471
496
  const structuredOutputSchema = options?.structuredOutputSchema ?? this._structuredOutputSchema;
472
497
  const structuredOutputTool = structuredOutputSchema ? new StructuredOutputTool(structuredOutputSchema) : undefined;
473
498
  let structuredOutputChoice;
474
- // Emit event before the try block
475
- yield new BeforeInvocationEvent({ agent: this });
499
+ // Resolve per-invocation state once. The same object is threaded through
500
+ // every lifecycle hook event, every tool context, and is surfaced on the
501
+ // AgentResult. Mutations by hooks/tools are visible across all recursive
502
+ // agent loop cycles within this invocation.
503
+ const invocationState = options?.invocationState ?? {};
504
+ const beforeInvocationEvent = new BeforeInvocationEvent({ agent: this, invocationState });
505
+ yield beforeInvocationEvent;
506
+ if (beforeInvocationEvent.cancel) {
507
+ const cancelText = typeof beforeInvocationEvent.cancel === 'string' ? beforeInvocationEvent.cancel : 'invocation denied by hook';
508
+ const message = new Message({ role: 'assistant', content: [new TextBlock(cancelText)] });
509
+ yield this._appendMessage(message, invocationState);
510
+ yield new AfterInvocationEvent({ agent: this, invocationState });
511
+ return new AgentResult({
512
+ stopReason: 'endTurn',
513
+ lastMessage: message,
514
+ traces: this._tracer.localTraces,
515
+ metrics: this._meter.metrics,
516
+ invocationState,
517
+ });
518
+ }
476
519
  // Normalize input to get the user messages for telemetry
477
520
  const inputMessages = this._normalizeInput(args);
478
521
  // Start agent trace span
@@ -510,11 +553,11 @@ export class Agent {
510
553
  if (currentArgs !== undefined) {
511
554
  const messagesToAppend = this._normalizeInput(currentArgs);
512
555
  for (const message of messagesToAppend) {
513
- yield this._appendMessage(message);
556
+ yield this._appendMessage(message, invocationState);
514
557
  }
515
558
  currentArgs = undefined;
516
559
  }
517
- const modelResult = yield* this._invokeModel(structuredOutputChoice);
560
+ const modelResult = yield* this._invokeModel(invocationState, structuredOutputChoice);
518
561
  if (modelResult.stopReason !== 'toolUse') {
519
562
  // If structured output is required, force it
520
563
  if (structuredOutputTool) {
@@ -525,7 +568,7 @@ export class Agent {
525
568
  }
526
569
  this._meter.endCycle(cycleStartTime);
527
570
  this._tracer.endAgentLoopSpan(cycleSpan);
528
- yield this._appendMessage(modelResult.message);
571
+ yield this._appendMessage(modelResult.message, invocationState);
529
572
  if (structuredOutputChoice) {
530
573
  continue;
531
574
  }
@@ -534,6 +577,7 @@ export class Agent {
534
577
  lastMessage: modelResult.message,
535
578
  traces: this._tracer.localTraces,
536
579
  metrics: this._meter.metrics,
580
+ invocationState,
537
581
  });
538
582
  return result;
539
583
  }
@@ -546,8 +590,8 @@ export class Agent {
546
590
  content: [new TextBlock('Tool execution cancelled')],
547
591
  }));
548
592
  const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
549
- yield this._appendMessage(modelResult.message);
550
- yield this._appendMessage(toolResultMessage);
593
+ yield this._appendMessage(modelResult.message, invocationState);
594
+ yield this._appendMessage(toolResultMessage, invocationState);
551
595
  this._meter.endCycle(cycleStartTime);
552
596
  this._tracer.endAgentLoopSpan(cycleSpan);
553
597
  result = new AgentResult({
@@ -555,19 +599,20 @@ export class Agent {
555
599
  lastMessage: modelResult.message,
556
600
  traces: this._tracer.localTraces,
557
601
  metrics: this._meter.metrics,
602
+ invocationState,
558
603
  });
559
604
  return result;
560
605
  }
561
606
  // Execute tools
562
- const toolResultMessage = yield* this.executeTools(modelResult.message, this._toolRegistry);
607
+ const toolResultMessage = yield* this.executeTools(modelResult.message, this._toolRegistry, invocationState);
563
608
  /**
564
609
  * Deferred append: both messages are added AFTER tool execution completes.
565
610
  * This keeps agent.messages in a valid, reinvokable state at all times.
566
611
  * If interrupted during tool execution, messages has no dangling toolUse
567
612
  * without a matching toolResult, so the agent can be reinvoked cleanly.
568
613
  */
569
- yield this._appendMessage(modelResult.message);
570
- yield this._appendMessage(toolResultMessage);
614
+ yield this._appendMessage(modelResult.message, invocationState);
615
+ yield this._appendMessage(toolResultMessage, invocationState);
571
616
  this._meter.endCycle(cycleStartTime);
572
617
  this._tracer.endAgentLoopSpan(cycleSpan);
573
618
  // Structured output captured: exit
@@ -581,6 +626,7 @@ export class Agent {
581
626
  traces: this._tracer.localTraces,
582
627
  structuredOutput,
583
628
  metrics: this._meter.metrics,
629
+ invocationState,
584
630
  });
585
631
  return result;
586
632
  }
@@ -600,12 +646,13 @@ export class Agent {
600
646
  role: 'assistant',
601
647
  content: [new TextBlock('Cancelled by user')],
602
648
  });
603
- yield this._appendMessage(cancelMessage);
649
+ yield this._appendMessage(cancelMessage, invocationState);
604
650
  result = new AgentResult({
605
651
  stopReason: 'cancelled',
606
652
  lastMessage: cancelMessage,
607
653
  traces: this._tracer.localTraces,
608
654
  metrics: this._meter.metrics,
655
+ invocationState,
609
656
  });
610
657
  return result;
611
658
  }
@@ -621,7 +668,7 @@ export class Agent {
621
668
  role: 'assistant',
622
669
  content: [new TextBlock('Cancelled by user')],
623
670
  });
624
- yield this._appendMessage(cancelMessage);
671
+ yield this._appendMessage(cancelMessage, invocationState);
625
672
  }
626
673
  this._tracer.endAgentSpan(agentSpan, {
627
674
  ...(caughtError && { error: caughtError }),
@@ -634,7 +681,7 @@ export class Agent {
634
681
  this._toolRegistry.remove(STRUCTURED_OUTPUT_TOOL_NAME);
635
682
  }
636
683
  // Always emit final event
637
- yield new AfterInvocationEvent({ agent: this });
684
+ yield new AfterInvocationEvent({ agent: this, invocationState });
638
685
  }
639
686
  }
640
687
  /**
@@ -716,9 +763,9 @@ export class Agent {
716
763
  * @param toolChoice - Optional tool choice to force specific tool usage
717
764
  * @returns Object containing the assistant message, stop reason, and optional redaction message
718
765
  */
719
- async *_invokeModel(toolChoice) {
766
+ async *_invokeModel(invocationState, toolChoice) {
720
767
  const toolSpecs = this._toolRegistry.list().map((tool) => tool.toolSpec);
721
- const streamOptions = { toolSpecs };
768
+ const streamOptions = { toolSpecs, modelState: this.modelState };
722
769
  if (this.systemPrompt !== undefined) {
723
770
  streamOptions.systemPrompt = this.systemPrompt;
724
771
  }
@@ -726,7 +773,37 @@ export class Agent {
726
773
  if (toolChoice) {
727
774
  streamOptions.toolChoice = toolChoice;
728
775
  }
729
- yield new BeforeModelCallEvent({ agent: this, model: this.model });
776
+ // Estimate input tokens for the upcoming model call (non-fatal if estimation fails)
777
+ let projectedInputTokens;
778
+ try {
779
+ projectedInputTokens = await this._estimateInputTokens(streamOptions);
780
+ }
781
+ catch (e) {
782
+ logger.debug(`error=<${e}> | token estimation failed, proceeding without estimate`);
783
+ }
784
+ const beforeModelCallEvent = new BeforeModelCallEvent({
785
+ agent: this,
786
+ model: this.model,
787
+ invocationState,
788
+ ...(projectedInputTokens !== undefined && { projectedInputTokens }),
789
+ });
790
+ yield beforeModelCallEvent;
791
+ if (beforeModelCallEvent.cancel) {
792
+ const cancelText = typeof beforeModelCallEvent.cancel === 'string' ? beforeModelCallEvent.cancel : 'model call denied by hook';
793
+ const message = new Message({ role: 'assistant', content: [new TextBlock(cancelText)] });
794
+ const stopData = { message, stopReason: 'endTurn' };
795
+ const afterModelCallEvent = new AfterModelCallEvent({
796
+ agent: this,
797
+ model: this.model,
798
+ stopData,
799
+ invocationState,
800
+ });
801
+ yield afterModelCallEvent;
802
+ if (afterModelCallEvent.retry) {
803
+ return yield* this._invokeModel(invocationState, toolChoice);
804
+ }
805
+ return { message, stopReason: 'endTurn' };
806
+ }
730
807
  // Start model span within loop span context
731
808
  const modelId = this.model.modelId;
732
809
  const modelSpan = this._tracer.startModelInvokeSpan({
@@ -735,7 +812,7 @@ export class Agent {
735
812
  ...(this.systemPrompt !== undefined && { systemPrompt: this.systemPrompt }),
736
813
  });
737
814
  try {
738
- const result = yield* this._streamFromModel(this.messages, streamOptions);
815
+ const result = yield* this._streamFromModel(this.messages, streamOptions, invocationState);
739
816
  // Accumulate token usage and model latency metrics
740
817
  this._meter.updateCycle(result.metadata);
741
818
  // End model span with usage
@@ -747,7 +824,12 @@ export class Agent {
747
824
  ...(usage && { usage }),
748
825
  ...(metrics && { metrics }),
749
826
  });
750
- yield new ModelMessageEvent({ agent: this, message: result.message, stopReason: result.stopReason });
827
+ yield new ModelMessageEvent({
828
+ agent: this,
829
+ message: result.message,
830
+ stopReason: result.stopReason,
831
+ invocationState,
832
+ });
751
833
  // Handle user content redaction if guardrails blocked input
752
834
  if (result.redaction?.userMessage) {
753
835
  this._redactLastMessage(result.redaction.userMessage);
@@ -757,10 +839,15 @@ export class Agent {
757
839
  stopReason: result.stopReason,
758
840
  ...(result.redaction && { redaction: result.redaction }),
759
841
  };
760
- const afterModelCallEvent = new AfterModelCallEvent({ agent: this, model: this.model, stopData });
842
+ const afterModelCallEvent = new AfterModelCallEvent({
843
+ agent: this,
844
+ model: this.model,
845
+ stopData,
846
+ invocationState,
847
+ });
761
848
  yield afterModelCallEvent;
762
849
  if (afterModelCallEvent.retry) {
763
- return yield* this._invokeModel(toolChoice);
850
+ return yield* this._invokeModel(invocationState, toolChoice);
764
851
  }
765
852
  return result;
766
853
  }
@@ -769,7 +856,12 @@ export class Agent {
769
856
  // End model span with error
770
857
  this._tracer.endModelInvokeSpan(modelSpan, { error: modelError });
771
858
  // Create error event
772
- const errorEvent = new AfterModelCallEvent({ agent: this, model: this.model, error: modelError });
859
+ const errorEvent = new AfterModelCallEvent({
860
+ agent: this,
861
+ model: this.model,
862
+ error: modelError,
863
+ invocationState,
864
+ });
773
865
  // Yield error event - stream will invoke hooks
774
866
  yield errorEvent;
775
867
  // Let CancelledError propagate directly — no retry
@@ -779,7 +871,7 @@ export class Agent {
779
871
  }
780
872
  // After yielding, hooks have been invoked and may have set retry
781
873
  if (errorEvent.retry) {
782
- return yield* this._invokeModel(toolChoice);
874
+ return yield* this._invokeModel(invocationState, toolChoice);
783
875
  }
784
876
  // Re-throw error
785
877
  throw error;
@@ -801,7 +893,7 @@ export class Agent {
801
893
  * @param streamOptions - Options for streaming
802
894
  * @returns StreamAggregatedResult containing message, stop reason, and optional redaction message
803
895
  */
804
- async *_streamFromModel(messages, streamOptions) {
896
+ async *_streamFromModel(messages, streamOptions, invocationState) {
805
897
  const streamGenerator = this.model.streamAggregated(messages, streamOptions);
806
898
  let result = await streamGenerator.next();
807
899
  while (!result.done) {
@@ -809,11 +901,11 @@ export class Agent {
809
901
  const event = result.value;
810
902
  if (isModelStreamEvent(event)) {
811
903
  // ModelStreamEvent: wrap in ModelStreamUpdateEvent
812
- yield new ModelStreamUpdateEvent({ agent: this, event });
904
+ yield new ModelStreamUpdateEvent({ agent: this, event, invocationState });
813
905
  }
814
906
  else {
815
907
  // ContentBlock: wrap in ContentBlockEvent
816
- yield new ContentBlockEvent({ agent: this, contentBlock: event });
908
+ yield new ContentBlockEvent({ agent: this, contentBlock: event, invocationState });
817
909
  }
818
910
  result = await streamGenerator.next();
819
911
  }
@@ -821,61 +913,173 @@ export class Agent {
821
913
  return result.value;
822
914
  }
823
915
  /**
824
- * Executes tools sequentially and streams all tool events.
916
+ * Emits `BeforeToolsEvent`, handles the pre-launch cancel paths, then
917
+ * delegates per-tool execution to the configured {@link ToolExecutorStrategy}.
918
+ * Always pairs `BeforeToolsEvent` with a terminal `AfterToolsEvent`, even on
919
+ * the invariant-violation throw path.
825
920
  *
826
921
  * @param assistantMessage - The assistant message containing tool use blocks
827
922
  * @param toolRegistry - Registry containing available tools
828
923
  * @returns User message containing tool results
829
924
  */
830
- async *executeTools(assistantMessage, toolRegistry) {
831
- const beforeToolsEvent = new BeforeToolsEvent({ agent: this, message: assistantMessage });
925
+ async *executeTools(assistantMessage, toolRegistry, invocationState) {
926
+ const beforeToolsEvent = new BeforeToolsEvent({ agent: this, message: assistantMessage, invocationState });
832
927
  yield beforeToolsEvent;
928
+ const toolUseBlocks = assistantMessage.content.filter((block) => block.type === 'toolUseBlock');
929
+ if (toolUseBlocks.length === 0) {
930
+ // Preserve BeforeToolsEvent/AfterToolsEvent bracket symmetry even on
931
+ // this invariant-violation branch.
932
+ yield new AfterToolsEvent({
933
+ agent: this,
934
+ message: new Message({ role: 'user', content: [] }),
935
+ invocationState,
936
+ });
937
+ throw new Error('Model indicated toolUse but no tool use blocks found in message');
938
+ }
939
+ // Pre-launch cancel paths are strategy-independent.
940
+ if (beforeToolsEvent.cancel) {
941
+ const message = typeof beforeToolsEvent.cancel === 'string' ? beforeToolsEvent.cancel : 'Tool cancelled by hook';
942
+ return yield* this._yieldCancelledToolResults(toolUseBlocks, message, invocationState);
943
+ }
944
+ if (this.isCancelled) {
945
+ return yield* this._yieldCancelledToolResults(toolUseBlocks, 'Tool execution cancelled', invocationState);
946
+ }
947
+ switch (this._toolExecutor) {
948
+ case 'sequential':
949
+ return yield* this._executeToolsSequential(toolUseBlocks, toolRegistry, invocationState);
950
+ case 'concurrent':
951
+ return yield* this._executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState);
952
+ default: {
953
+ const _exhaustive = this._toolExecutor;
954
+ throw new Error(`Unknown toolExecutor: ${_exhaustive}`);
955
+ }
956
+ }
957
+ }
958
+ /**
959
+ * Emits a `ToolResultEvent` for every block plus an `AfterToolsEvent`, and
960
+ * returns the resulting tool-result message. Used by the pre-launch cancel
961
+ * paths shared across executors.
962
+ */
963
+ async *_yieldCancelledToolResults(toolUseBlocks, message, invocationState) {
964
+ const cancelBlocks = this._cancelAllAsResults(toolUseBlocks, message);
965
+ for (const result of cancelBlocks) {
966
+ yield new ToolResultEvent({ agent: this, result, invocationState });
967
+ }
968
+ const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
969
+ yield new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
970
+ return toolResultMessage;
971
+ }
972
+ /**
973
+ * Executes tools one at a time, honoring `agent.cancelSignal` between
974
+ * iterations to short-circuit not-yet-started tools.
975
+ */
976
+ async *_executeToolsSequential(toolUseBlocks, toolRegistry, invocationState) {
833
977
  const toolResultBlocks = [];
834
978
  let toolResultMessage;
835
979
  try {
836
- // Extract tool use blocks from assistant message
837
- const toolUseBlocks = assistantMessage.content.filter((block) => block.type === 'toolUseBlock');
838
- if (toolUseBlocks.length === 0) {
839
- // No tool use blocks found even though stopReason is toolUse
840
- throw new Error('Model indicated toolUse but no tool use blocks found in message');
841
- }
842
- // Cancel all tools if hook requested it
843
- if (beforeToolsEvent.cancel) {
844
- const cancelMessage = cancelToolMessage(beforeToolsEvent.cancel);
845
- const cancelBlocks = toolUseBlocks.map((block) => new ToolResultBlock({
846
- toolUseId: block.toolUseId,
847
- status: 'error',
848
- content: [new TextBlock(cancelMessage)],
849
- }));
850
- for (const result of cancelBlocks) {
851
- yield new ToolResultEvent({ agent: this, result });
980
+ for (const toolUseBlock of toolUseBlocks) {
981
+ if (this.isCancelled) {
982
+ const cancelBlock = new ToolResultBlock({
983
+ toolUseId: toolUseBlock.toolUseId,
984
+ status: 'error',
985
+ content: [new TextBlock('Tool execution cancelled')],
986
+ });
987
+ toolResultBlocks.push(cancelBlock);
988
+ yield new ToolResultEvent({ agent: this, result: cancelBlock, invocationState });
989
+ continue;
852
990
  }
853
- toolResultBlocks.push(...cancelBlocks);
991
+ const toolResultBlock = yield* this.executeTool(toolUseBlock, toolRegistry, invocationState);
992
+ toolResultBlocks.push(toolResultBlock);
993
+ yield new ToolResultEvent({ agent: this, result: toolResultBlock, invocationState });
854
994
  }
855
- else {
856
- for (const toolUseBlock of toolUseBlocks) {
857
- if (this.isCancelled) {
858
- const cancelBlock = new ToolResultBlock({
859
- toolUseId: toolUseBlock.toolUseId,
860
- status: 'error',
861
- content: [new TextBlock('Tool execution cancelled')],
862
- });
863
- toolResultBlocks.push(cancelBlock);
864
- yield new ToolResultEvent({ agent: this, result: cancelBlock });
865
- continue;
866
- }
867
- const toolResultBlock = yield* this.executeTool(toolUseBlock, toolRegistry);
868
- toolResultBlocks.push(toolResultBlock);
869
- yield new ToolResultEvent({ agent: this, result: toolResultBlock });
995
+ }
996
+ finally {
997
+ toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
998
+ yield new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
999
+ }
1000
+ return toolResultMessage;
1001
+ }
1002
+ /**
1003
+ * Produces one error ToolResultBlock per tool use block, each carrying
1004
+ * `message` as its error text. Shared by pre-launch cancel paths.
1005
+ */
1006
+ _cancelAllAsResults(toolUseBlocks, message) {
1007
+ return toolUseBlocks.map((block) => new ToolResultBlock({
1008
+ toolUseId: block.toolUseId,
1009
+ status: 'error',
1010
+ content: [new TextBlock(message)],
1011
+ }));
1012
+ }
1013
+ /**
1014
+ * Executes tools concurrently by merging N per-tool {@link executeTool}
1015
+ * async generators via `Promise.race`. Per-tool event order is preserved
1016
+ * (because each generator is iterated serially); cross-tool events may
1017
+ * interleave at race resolution boundaries.
1018
+ *
1019
+ * Per-tool retry (`AfterToolCallEvent.retry`) is isolated — it lives inside
1020
+ * `executeTool`'s own `while(true)` loop, so one tool retrying does not
1021
+ * disturb its siblings.
1022
+ */
1023
+ async *_executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState) {
1024
+ let toolResultMessage;
1025
+ const gens = toolUseBlocks.map((block) => ({
1026
+ block,
1027
+ gen: this.executeTool(block, toolRegistry, invocationState),
1028
+ }));
1029
+ const step = (idx) => gens[idx].gen.next().then((res) => ({ idx, kind: 'next', res }), (error) => ({ idx, kind: 'throw', error }));
1030
+ const pendingNext = new Map(gens.map((_, idx) => [idx, step(idx)]));
1031
+ const resultsByToolUseId = new Map();
1032
+ try {
1033
+ while (pendingNext.size > 0) {
1034
+ const winner = await Promise.race(pendingNext.values());
1035
+ const { idx } = winner;
1036
+ const block = gens[idx].block;
1037
+ if (winner.kind === 'throw') {
1038
+ pendingNext.delete(idx);
1039
+ const err = normalizeError(winner.error);
1040
+ const result = new ToolResultBlock({
1041
+ toolUseId: block.toolUseId,
1042
+ status: 'error',
1043
+ content: [new TextBlock(err.message)],
1044
+ error: err,
1045
+ });
1046
+ resultsByToolUseId.set(block.toolUseId, result);
1047
+ yield new ToolResultEvent({ agent: this, result, invocationState });
1048
+ continue;
1049
+ }
1050
+ if (winner.res.done) {
1051
+ pendingNext.delete(idx);
1052
+ resultsByToolUseId.set(block.toolUseId, winner.res.value);
1053
+ yield new ToolResultEvent({ agent: this, result: winner.res.value, invocationState });
1054
+ }
1055
+ else {
1056
+ yield winner.res.value;
1057
+ pendingNext.set(idx, step(idx));
870
1058
  }
871
1059
  }
872
1060
  }
873
1061
  finally {
874
- toolResultMessage = new Message({
875
- role: 'user',
876
- content: toolResultBlocks,
877
- });
878
- yield new AfterToolsEvent({ agent: this, message: toolResultMessage });
1062
+ // Close any generators still in-flight (e.g. consumer broke out of stream).
1063
+ await Promise.allSettled(Array.from(pendingNext.keys(), (idx) => gens[idx].gen.return(undefined)));
1064
+ // Build the result message from whatever completed, in source order.
1065
+ // Missing entries get a fallback error block so the message always
1066
+ // accounts for every toolUseBlock the model emitted.
1067
+ const toolResultBlocks = [];
1068
+ for (const block of toolUseBlocks) {
1069
+ const result = resultsByToolUseId.get(block.toolUseId);
1070
+ if (result) {
1071
+ toolResultBlocks.push(result);
1072
+ }
1073
+ else {
1074
+ toolResultBlocks.push(new ToolResultBlock({
1075
+ toolUseId: block.toolUseId,
1076
+ status: 'error',
1077
+ content: [new TextBlock('Tool execution interrupted')],
1078
+ }));
1079
+ }
1080
+ }
1081
+ toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
1082
+ yield new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
879
1083
  }
880
1084
  return toolResultMessage;
881
1085
  }
@@ -889,7 +1093,7 @@ export class Agent {
889
1093
  * @param toolRegistry - Registry containing available tools
890
1094
  * @returns Tool result block
891
1095
  */
892
- async *executeTool(toolUseBlock, toolRegistry) {
1096
+ async *executeTool(toolUseBlock, toolRegistry, invocationState) {
893
1097
  const tool = toolRegistry.get(toolUseBlock.name);
894
1098
  // Create toolUse object for hook events and telemetry
895
1099
  const toolUse = {
@@ -899,11 +1103,11 @@ export class Agent {
899
1103
  };
900
1104
  // Retry loop for tool execution
901
1105
  while (true) {
902
- const beforeToolCallEvent = new BeforeToolCallEvent({ agent: this, toolUse, tool });
1106
+ const beforeToolCallEvent = new BeforeToolCallEvent({ agent: this, toolUse, tool, invocationState });
903
1107
  yield beforeToolCallEvent;
904
1108
  // Cancel individual tool if hook requested it
905
1109
  if (beforeToolCallEvent.cancel) {
906
- const cancelMessage = cancelToolMessage(beforeToolCallEvent.cancel);
1110
+ const cancelMessage = typeof beforeToolCallEvent.cancel === 'string' ? beforeToolCallEvent.cancel : 'Tool cancelled by hook';
907
1111
  const toolResult = new ToolResultBlock({
908
1112
  toolUseId: toolUseBlock.toolUseId,
909
1113
  status: 'error',
@@ -914,12 +1118,13 @@ export class Agent {
914
1118
  toolUse,
915
1119
  tool,
916
1120
  result: toolResult,
1121
+ invocationState,
917
1122
  });
918
1123
  yield afterToolCallEvent;
919
1124
  if (afterToolCallEvent.retry) {
920
1125
  continue;
921
1126
  }
922
- return toolResult;
1127
+ return afterToolCallEvent.result;
923
1128
  }
924
1129
  // Start tool span within loop span context
925
1130
  const toolSpan = this._tracer.startToolCallSpan({
@@ -946,6 +1151,7 @@ export class Agent {
946
1151
  input: toolUseBlock.input,
947
1152
  },
948
1153
  agent: this,
1154
+ invocationState,
949
1155
  };
950
1156
  try {
951
1157
  // Manually iterate tool stream to wrap each ToolStreamEvent in ToolStreamUpdateEvent.
@@ -956,7 +1162,7 @@ export class Agent {
956
1162
  const toolGenerator = this._tracer.withSpanContext(toolSpan, () => tool.stream(toolContext));
957
1163
  let toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
958
1164
  while (!toolNext.done) {
959
- yield new ToolStreamUpdateEvent({ agent: this, event: toolNext.value });
1165
+ yield new ToolStreamUpdateEvent({ agent: this, event: toolNext.value, invocationState });
960
1166
  toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
961
1167
  }
962
1168
  const result = toolNext.value;
@@ -998,13 +1204,14 @@ export class Agent {
998
1204
  toolUse,
999
1205
  tool,
1000
1206
  result: toolResult,
1207
+ invocationState,
1001
1208
  ...(error !== undefined && { error }),
1002
1209
  });
1003
1210
  yield afterToolCallEvent;
1004
1211
  if (afterToolCallEvent.retry) {
1005
1212
  continue;
1006
1213
  }
1007
- return toolResult;
1214
+ return afterToolCallEvent.result;
1008
1215
  }
1009
1216
  }
1010
1217
  /**
@@ -1052,25 +1259,58 @@ export class Agent {
1052
1259
  }
1053
1260
  }
1054
1261
  }
1262
+ /**
1263
+ * Estimate the input token count for the next model call.
1264
+ *
1265
+ * Uses the token counting strategy: reads inputTokens + outputTokens
1266
+ * from the last assistant message's metadata as a known baseline, then estimates
1267
+ * only new messages added after it. Falls back to full estimation when no metadata
1268
+ * is available (cold start or first call).
1269
+ *
1270
+ * @param streamOptions - The stream options containing system prompt and tool specs
1271
+ * @returns Estimated input token count
1272
+ */
1273
+ async _estimateInputTokens(streamOptions) {
1274
+ // Find the last assistant message with usage metadata
1275
+ let lastAssistantIdx = -1;
1276
+ for (let i = this.messages.length - 1; i >= 0; i--) {
1277
+ if (this.messages[i].role === 'assistant' && this.messages[i].metadata?.usage) {
1278
+ lastAssistantIdx = i;
1279
+ break;
1280
+ }
1281
+ }
1282
+ let estimate;
1283
+ if (lastAssistantIdx >= 0) {
1284
+ const usage = this.messages[lastAssistantIdx].metadata.usage;
1285
+ const knownBaseline = usage.inputTokens + usage.outputTokens;
1286
+ const newMessages = this.messages.slice(lastAssistantIdx + 1);
1287
+ if (newMessages.length === 0) {
1288
+ estimate = knownBaseline;
1289
+ }
1290
+ else {
1291
+ // System prompt and tool spec tokens are already included in the baseline from the prior model call
1292
+ estimate = knownBaseline + (await this.model.countTokens(newMessages));
1293
+ }
1294
+ }
1295
+ else {
1296
+ estimate = await this.model.countTokens(this.messages, {
1297
+ ...(streamOptions.systemPrompt !== undefined && { systemPrompt: streamOptions.systemPrompt }),
1298
+ ...(streamOptions.toolSpecs !== undefined && { toolSpecs: streamOptions.toolSpecs }),
1299
+ });
1300
+ }
1301
+ return estimate;
1302
+ }
1055
1303
  /**
1056
1304
  * Appends a message to the conversation history and returns the event for yielding.
1057
1305
  *
1058
1306
  * @param message - The message to append
1059
1307
  * @returns MessageAddedEvent to be yielded
1060
1308
  */
1061
- _appendMessage(message) {
1309
+ _appendMessage(message, invocationState) {
1062
1310
  this.messages.push(message);
1063
- return new MessageAddedEvent({ agent: this, message });
1311
+ return new MessageAddedEvent({ agent: this, message, invocationState });
1064
1312
  }
1065
1313
  }
1066
- /**
1067
- * Returns the cancel message for a cancelled tool.
1068
- * @param cancelTool - The cancel value (true or custom message)
1069
- * @returns The cancel message string
1070
- */
1071
- function cancelToolMessage(cancelTool) {
1072
- return typeof cancelTool === 'string' ? cancelTool : 'tool cancelled by hook';
1073
- }
1074
1314
  /**
1075
1315
  * Recursively flattens nested arrays of tools into a single flat array.
1076
1316
  * @param tools - Tools or nested arrays of tools