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