@strands-agents/sdk 1.0.0-rc.5 → 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.
- package/LICENSE +175 -0
- package/README.md +340 -0
- package/dist/src/__fixtures__/agent-helpers.d.ts +6 -0
- package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/agent-helpers.js +3 -1
- package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
- package/dist/src/__fixtures__/mock-plugin.d.ts.map +1 -1
- package/dist/src/__fixtures__/mock-plugin.js +3 -1
- package/dist/src/__fixtures__/mock-plugin.js.map +1 -1
- package/dist/src/__fixtures__/tool-helpers.d.ts +3 -1
- package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/tool-helpers.js +3 -1
- package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
- package/dist/src/__tests__/mcp.test.js +222 -2
- package/dist/src/__tests__/mcp.test.js.map +1 -1
- package/dist/src/a2a/__tests__/events.test.js +2 -0
- package/dist/src/a2a/__tests__/events.test.js.map +1 -1
- package/dist/src/a2a/__tests__/executor.test.js +16 -5
- package/dist/src/a2a/__tests__/executor.test.js.map +1 -1
- package/dist/src/a2a/a2a-agent.d.ts +8 -3
- package/dist/src/a2a/a2a-agent.d.ts.map +1 -1
- package/dist/src/a2a/a2a-agent.js +12 -6
- package/dist/src/a2a/a2a-agent.js.map +1 -1
- package/dist/src/a2a/executor.d.ts +13 -0
- package/dist/src/a2a/executor.d.ts.map +1 -1
- package/dist/src/a2a/executor.js +19 -1
- package/dist/src/a2a/executor.js.map +1 -1
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js +23 -0
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js.map +1 -0
- package/dist/src/agent/__tests__/agent.cancel.test.js +1 -1
- package/dist/src/agent/__tests__/agent.cancel.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.concurrent.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.concurrent.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.concurrent.test.js +488 -0
- package/dist/src/agent/__tests__/agent.concurrent.test.js.map +1 -0
- package/dist/src/agent/__tests__/agent.hook.test.js +174 -12
- package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.invocation-state.test.js +219 -0
- package/dist/src/agent/__tests__/agent.invocation-state.test.js.map +1 -0
- package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.stateful-model.test.js +169 -0
- package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +1 -0
- package/dist/src/agent/__tests__/agent.test.js +99 -2
- package/dist/src/agent/__tests__/agent.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.tracer.test.node.js +39 -0
- package/dist/src/agent/__tests__/agent.tracer.test.node.js.map +1 -1
- package/dist/src/agent/__tests__/snapshot.test.js +5 -4
- package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/agent/agent-as-tool.d.ts.map +1 -1
- package/dist/src/agent/agent-as-tool.js +4 -2
- package/dist/src/agent/agent-as-tool.js.map +1 -1
- package/dist/src/agent/agent.d.ts +75 -1
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +323 -83
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/agent/snapshot.d.ts +2 -2
- package/dist/src/agent/snapshot.d.ts.map +1 -1
- package/dist/src/agent/snapshot.js +8 -2
- package/dist/src/agent/snapshot.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +4 -4
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +2 -2
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +8 -3
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +1 -0
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
- package/dist/src/errors.d.ts +11 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +12 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/hooks/__tests__/events.test.js +177 -70
- package/dist/src/hooks/__tests__/events.test.js.map +1 -1
- package/dist/src/hooks/__tests__/registry.test.js +16 -16
- package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
- package/dist/src/hooks/events.d.ts +95 -25
- package/dist/src/hooks/events.d.ts.map +1 -1
- package/dist/src/hooks/events.js +98 -23
- package/dist/src/hooks/events.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging/__tests__/warn-once.test.d.ts +2 -0
- package/dist/src/logging/__tests__/warn-once.test.d.ts.map +1 -0
- package/dist/src/logging/__tests__/warn-once.test.js +30 -0
- package/dist/src/logging/__tests__/warn-once.test.js.map +1 -0
- package/dist/src/logging/warn-once.d.ts +13 -0
- package/dist/src/logging/warn-once.d.ts.map +1 -0
- package/dist/src/logging/warn-once.js +18 -0
- package/dist/src/logging/warn-once.js.map +1 -0
- package/dist/src/mcp.d.ts +20 -1
- package/dist/src/mcp.d.ts.map +1 -1
- package/dist/src/mcp.js +10 -1
- package/dist/src/mcp.js.map +1 -1
- package/dist/src/mime.d.ts +2 -1
- package/dist/src/mime.d.ts.map +1 -1
- package/dist/src/mime.js +1 -0
- package/dist/src/mime.js.map +1 -1
- package/dist/src/models/__tests__/anthropic.test.js +92 -3
- package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
- package/dist/src/models/__tests__/bedrock.test.js +113 -2
- package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
- package/dist/src/models/__tests__/google.test.js +77 -0
- package/dist/src/models/__tests__/google.test.js.map +1 -1
- package/dist/src/models/__tests__/model.test.js +149 -1
- package/dist/src/models/__tests__/model.test.js.map +1 -1
- package/dist/src/models/anthropic.d.ts +12 -1
- package/dist/src/models/anthropic.d.ts.map +1 -1
- package/dist/src/models/anthropic.js +38 -6
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +12 -1
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +45 -11
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/defaults.d.ts +37 -0
- package/dist/src/models/defaults.d.ts.map +1 -0
- package/dist/src/models/defaults.js +41 -0
- package/dist/src/models/defaults.js.map +1 -0
- package/dist/src/models/google/model.d.ts +14 -1
- package/dist/src/models/google/model.d.ts.map +1 -1
- package/dist/src/models/google/model.js +50 -6
- package/dist/src/models/google/model.js.map +1 -1
- package/dist/src/models/model.d.ts +50 -0
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +120 -0
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/models/openai/__tests__/chat.test.d.ts +2 -0
- package/dist/src/models/openai/__tests__/chat.test.d.ts.map +1 -0
- package/dist/src/models/{__tests__/openai.test.js → openai/__tests__/chat.test.js} +72 -7
- package/dist/src/models/openai/__tests__/chat.test.js.map +1 -0
- package/dist/src/models/openai/__tests__/responses.test.d.ts +2 -0
- package/dist/src/models/openai/__tests__/responses.test.d.ts.map +1 -0
- package/dist/src/models/openai/__tests__/responses.test.js +668 -0
- package/dist/src/models/openai/__tests__/responses.test.js.map +1 -0
- package/dist/src/models/openai/chat-adapter.d.ts +33 -0
- package/dist/src/models/openai/chat-adapter.d.ts.map +1 -0
- package/dist/src/models/openai/chat-adapter.js +383 -0
- package/dist/src/models/openai/chat-adapter.js.map +1 -0
- package/dist/src/models/openai/errors.d.ts +16 -0
- package/dist/src/models/openai/errors.d.ts.map +1 -0
- package/dist/src/models/openai/errors.js +40 -0
- package/dist/src/models/openai/errors.js.map +1 -0
- package/dist/src/models/openai/formatting.d.ts +18 -0
- package/dist/src/models/openai/formatting.d.ts.map +1 -0
- package/dist/src/models/openai/formatting.js +38 -0
- package/dist/src/models/openai/formatting.js.map +1 -0
- package/dist/src/models/openai/index.d.ts +19 -0
- package/dist/src/models/openai/index.d.ts.map +1 -0
- package/dist/src/models/openai/index.js +18 -0
- package/dist/src/models/openai/index.js.map +1 -0
- package/dist/src/models/openai/model.d.ts +77 -0
- package/dist/src/models/openai/model.d.ts.map +1 -0
- package/dist/src/models/openai/model.js +211 -0
- package/dist/src/models/openai/model.js.map +1 -0
- package/dist/src/models/openai/responses-adapter.d.ts +78 -0
- package/dist/src/models/openai/responses-adapter.d.ts.map +1 -0
- package/dist/src/models/openai/responses-adapter.js +467 -0
- package/dist/src/models/openai/responses-adapter.js.map +1 -0
- package/dist/src/models/openai/types.d.ts +131 -0
- package/dist/src/models/openai/types.d.ts.map +1 -0
- package/dist/src/models/openai/types.js +5 -0
- package/dist/src/models/openai/types.js.map +1 -0
- package/dist/src/multiagent/__tests__/events.test.js +122 -28
- package/dist/src/multiagent/__tests__/events.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts +2 -0
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts.map +1 -0
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.js +95 -0
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.js.map +1 -0
- package/dist/src/multiagent/__tests__/nodes.test.js +5 -2
- package/dist/src/multiagent/__tests__/nodes.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts +2 -0
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts.map +1 -0
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js +56 -0
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js.map +1 -0
- package/dist/src/multiagent/events.d.ts +19 -1
- package/dist/src/multiagent/events.d.ts.map +1 -1
- package/dist/src/multiagent/events.js +18 -0
- package/dist/src/multiagent/events.js.map +1 -1
- package/dist/src/multiagent/graph.d.ts +5 -3
- package/dist/src/multiagent/graph.d.ts.map +1 -1
- package/dist/src/multiagent/graph.js +22 -15
- package/dist/src/multiagent/graph.js.map +1 -1
- package/dist/src/multiagent/index.d.ts +1 -1
- package/dist/src/multiagent/index.d.ts.map +1 -1
- package/dist/src/multiagent/multiagent.d.ts +16 -3
- package/dist/src/multiagent/multiagent.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.d.ts +10 -3
- package/dist/src/multiagent/nodes.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.js +28 -6
- package/dist/src/multiagent/nodes.js.map +1 -1
- package/dist/src/multiagent/swarm.d.ts +5 -3
- package/dist/src/multiagent/swarm.d.ts.map +1 -1
- package/dist/src/multiagent/swarm.js +22 -16
- package/dist/src/multiagent/swarm.js.map +1 -1
- package/dist/src/plugins/__tests__/registry.test.js +1 -1
- package/dist/src/plugins/__tests__/registry.test.js.map +1 -1
- package/dist/src/plugins/model-plugin.d.ts +20 -0
- package/dist/src/plugins/model-plugin.d.ts.map +1 -0
- package/dist/src/plugins/model-plugin.js +29 -0
- package/dist/src/plugins/model-plugin.js.map +1 -0
- package/dist/src/session/__tests__/session-manager.test.js +13 -11
- package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +9 -0
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/telemetry/__tests__/meter.test.js +23 -0
- package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
- package/dist/src/telemetry/meter.d.ts +15 -0
- package/dist/src/telemetry/meter.d.ts.map +1 -1
- package/dist/src/telemetry/meter.js +14 -0
- package/dist/src/telemetry/meter.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +24 -3
- package/dist/src/tools/mcp-tool.d.ts.map +1 -1
- package/dist/src/tools/mcp-tool.js +103 -31
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/tool.d.ts +11 -1
- package/dist/src/tools/tool.d.ts.map +1 -1
- package/dist/src/tools/tool.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/__tests__/agent.test.js +48 -0
- package/dist/src/types/__tests__/agent.test.js.map +1 -1
- package/dist/src/types/agent.d.ts +55 -6
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +22 -6
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/types/elicitation.d.ts +15 -0
- package/dist/src/types/elicitation.d.ts.map +1 -0
- package/dist/src/types/elicitation.js +2 -0
- package/dist/src/types/elicitation.js.map +1 -0
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +9 -5
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +1 -1
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +1 -0
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +1 -0
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -1
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +1 -0
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
- package/package.json +9 -5
- package/dist/src/models/__tests__/openai.test.d.ts +0 -2
- package/dist/src/models/__tests__/openai.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/openai.test.js.map +0 -1
- package/dist/src/models/openai.d.ts +0 -312
- package/dist/src/models/openai.d.ts.map +0 -1
- package/dist/src/models/openai.js +0 -789
- package/dist/src/models/openai.js.map +0 -1
package/dist/src/agent/agent.js
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
475
|
-
|
|
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
|
-
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
-
*
|
|
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
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
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
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|