@strands-agents/sdk 1.0.0-rc.5 → 1.1.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 +22 -1
- package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/agent-helpers.js +45 -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 +5 -2
- package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/tool-helpers.js +23 -4
- package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
- package/dist/src/__tests__/interrupt.test.d.ts +2 -0
- package/dist/src/__tests__/interrupt.test.d.ts.map +1 -0
- package/dist/src/__tests__/interrupt.test.js +259 -0
- package/dist/src/__tests__/interrupt.test.js.map +1 -0
- package/dist/src/__tests__/mcp.test.js +448 -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 +724 -12
- package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.interrupt.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.interrupt.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.interrupt.test.js +730 -0
- package/dist/src/agent/__tests__/agent.interrupt.test.js.map +1 -0
- 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.model-retry.test.d.ts +2 -0
- package/dist/src/agent/__tests__/agent.model-retry.test.d.ts.map +1 -0
- package/dist/src/agent/__tests__/agent.model-retry.test.js +161 -0
- package/dist/src/agent/__tests__/agent.model-retry.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 +217 -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 +51 -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 +109 -4
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +790 -224
- 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 +20 -2
- package/dist/src/agent/snapshot.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +230 -9
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +19 -6
- 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 +58 -4
- 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 +76 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/conversation-manager.d.ts +67 -22
- package/dist/src/conversation-manager/conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/conversation-manager.js +65 -13
- package/dist/src/conversation-manager/conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/index.d.ts +1 -1
- package/dist/src/conversation-manager/index.d.ts.map +1 -1
- package/dist/src/conversation-manager/index.js +1 -1
- package/dist/src/conversation-manager/index.js.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts +17 -3
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +10 -4
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts +23 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/summarizing-conversation-manager.js +39 -17
- package/dist/src/conversation-manager/summarizing-conversation-manager.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 +267 -73
- package/dist/src/hooks/__tests__/events.test.js.map +1 -1
- package/dist/src/hooks/__tests__/registry.test.js +182 -18
- package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
- package/dist/src/hooks/events.d.ts +193 -51
- package/dist/src/hooks/events.d.ts.map +1 -1
- package/dist/src/hooks/events.js +182 -26
- package/dist/src/hooks/events.js.map +1 -1
- package/dist/src/hooks/index.d.ts +3 -2
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/registry.d.ts +12 -12
- package/dist/src/hooks/registry.d.ts.map +1 -1
- package/dist/src/hooks/registry.js +55 -15
- package/dist/src/hooks/registry.js.map +1 -1
- package/dist/src/hooks/types.d.ts +23 -0
- package/dist/src/hooks/types.d.ts.map +1 -1
- package/dist/src/hooks/types.js +17 -1
- package/dist/src/hooks/types.js.map +1 -1
- package/dist/src/index.d.ts +12 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/interrupt.d.ts +220 -0
- package/dist/src/interrupt.d.ts.map +1 -0
- package/dist/src/interrupt.js +274 -0
- package/dist/src/interrupt.js.map +1 -0
- 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 +43 -3
- package/dist/src/mcp.d.ts.map +1 -1
- package/dist/src/mcp.js +85 -17
- 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 +147 -3
- package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
- package/dist/src/models/__tests__/bedrock.test.js +228 -2
- package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
- package/dist/src/models/__tests__/defaults.test.d.ts +2 -0
- package/dist/src/models/__tests__/defaults.test.d.ts.map +1 -0
- package/dist/src/models/__tests__/defaults.test.js +36 -0
- package/dist/src/models/__tests__/defaults.test.js.map +1 -0
- package/dist/src/models/__tests__/google.test.js +135 -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 +20 -1
- package/dist/src/models/anthropic.d.ts.map +1 -1
- package/dist/src/models/anthropic.js +42 -8
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +27 -1
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +100 -12
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/defaults.d.ts +47 -0
- package/dist/src/models/defaults.d.ts.map +1 -0
- package/dist/src/models/defaults.js +170 -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 +54 -8
- package/dist/src/models/google/model.js.map +1 -1
- package/dist/src/models/google/types.d.ts +8 -0
- package/dist/src/models/google/types.d.ts.map +1 -1
- package/dist/src/models/model.d.ts +65 -0
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +138 -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} +117 -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__/graph.test.js +69 -0
- package/dist/src/multiagent/__tests__/graph.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/nodes.test.js +18 -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/__tests__/swarm.test.js +77 -0
- package/dist/src/multiagent/__tests__/swarm.test.js.map +1 -1
- 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 +27 -5
- package/dist/src/multiagent/graph.d.ts.map +1 -1
- package/dist/src/multiagent/graph.js +61 -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 +21 -6
- package/dist/src/multiagent/multiagent.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.d.ts +28 -3
- package/dist/src/multiagent/nodes.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.js +42 -7
- package/dist/src/multiagent/nodes.js.map +1 -1
- package/dist/src/multiagent/swarm.d.ts +20 -4
- package/dist/src/multiagent/swarm.d.ts.map +1 -1
- package/dist/src/multiagent/swarm.js +65 -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/registry/__tests__/tool-registry.test.js +11 -0
- package/dist/src/registry/__tests__/tool-registry.test.js.map +1 -1
- package/dist/src/registry/tool-registry.d.ts +4 -0
- package/dist/src/registry/tool-registry.d.ts.map +1 -1
- package/dist/src/registry/tool-registry.js +6 -0
- package/dist/src/registry/tool-registry.js.map +1 -1
- package/dist/src/retry/__tests__/backoff-strategy.test.d.ts +2 -0
- package/dist/src/retry/__tests__/backoff-strategy.test.d.ts.map +1 -0
- package/dist/src/retry/__tests__/backoff-strategy.test.js +116 -0
- package/dist/src/retry/__tests__/backoff-strategy.test.js.map +1 -0
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts +2 -0
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts.map +1 -0
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.js +225 -0
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.js.map +1 -0
- package/dist/src/retry/backoff-strategy.d.ts +108 -0
- package/dist/src/retry/backoff-strategy.d.ts.map +1 -0
- package/dist/src/retry/backoff-strategy.js +86 -0
- package/dist/src/retry/backoff-strategy.js.map +1 -0
- package/dist/src/retry/default-model-retry-strategy.d.ts +76 -0
- package/dist/src/retry/default-model-retry-strategy.d.ts.map +1 -0
- package/dist/src/retry/default-model-retry-strategy.js +104 -0
- package/dist/src/retry/default-model-retry-strategy.js.map +1 -0
- package/dist/src/retry/index.d.ts +8 -0
- package/dist/src/retry/index.d.ts.map +1 -0
- package/dist/src/retry/index.js +7 -0
- package/dist/src/retry/index.js.map +1 -0
- package/dist/src/retry/model-retry-strategy.d.ts +80 -0
- package/dist/src/retry/model-retry-strategy.d.ts.map +1 -0
- package/dist/src/retry/model-retry-strategy.js +85 -0
- package/dist/src/retry/model-retry-strategy.js.map +1 -0
- package/dist/src/retry/retry-strategy.d.ts +34 -0
- package/dist/src/retry/retry-strategy.d.ts.map +1 -0
- package/dist/src/retry/retry-strategy.js +25 -0
- package/dist/src/retry/retry-strategy.js.map +1 -0
- package/dist/src/session/__tests__/session-manager.test.js +52 -11
- package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +6 -0
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +17 -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/__tests__/tool.test.js +24 -1
- package/dist/src/tools/__tests__/tool.test.js.map +1 -1
- package/dist/src/tools/function-tool.d.ts.map +1 -1
- package/dist/src/tools/function-tool.js +6 -1
- package/dist/src/tools/function-tool.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 +21 -2
- package/dist/src/tools/tool.d.ts.map +1 -1
- package/dist/src/tools/tool.js +12 -0
- 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 +77 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +30 -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/types/interrupt.d.ts +103 -0
- package/dist/src/types/interrupt.d.ts.map +1 -0
- package/dist/src/types/interrupt.js +63 -0
- package/dist/src/types/interrupt.js.map +1 -0
- package/dist/src/types/messages.d.ts +2 -1
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js +292 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts +2 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js +148 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts +2 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js +78 -0
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/index.d.ts +23 -0
- package/dist/src/vended-plugins/context-offloader/index.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/index.js +21 -0
- package/dist/src/vended-plugins/context-offloader/index.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/plugin.d.ts +48 -0
- package/dist/src/vended-plugins/context-offloader/plugin.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/plugin.js +244 -0
- package/dist/src/vended-plugins/context-offloader/plugin.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/storage.d.ts +114 -0
- package/dist/src/vended-plugins/context-offloader/storage.d.ts.map +1 -0
- package/dist/src/vended-plugins/context-offloader/storage.js +204 -0
- package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -0
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +21 -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 +4 -0
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
- package/dist/src/vended-tools/bash/bash.d.ts.map +1 -1
- package/dist/src/vended-tools/bash/bash.js +0 -3
- package/dist/src/vended-tools/bash/bash.js.map +1 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +4 -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 +4 -0
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
- package/package.json +17 -9
- 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
|
@@ -54,16 +54,18 @@ import { AgentResult, } from '../types/agent.js';
|
|
|
54
54
|
import { BedrockModel } from '../models/bedrock.js';
|
|
55
55
|
import { contentBlockFromData, Message, TextBlock, ToolResultBlock, ToolUseBlock, } from '../types/messages.js';
|
|
56
56
|
import { McpClient } from '../mcp.js';
|
|
57
|
-
import {} from '../tools/tool.js';
|
|
57
|
+
import { isValidToolName } 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';
|
|
@@ -74,6 +76,10 @@ import { Tracer } from '../telemetry/tracer.js';
|
|
|
74
76
|
import { Meter } from '../telemetry/meter.js';
|
|
75
77
|
import { logger } from '../logging/logger.js';
|
|
76
78
|
import { CancelledError } from '../errors.js';
|
|
79
|
+
import { DefaultModelRetryStrategy } from '../retry/default-model-retry-strategy.js';
|
|
80
|
+
import { warnOnDuplicateRetryStrategyTypes } from '../retry/retry-strategy.js';
|
|
81
|
+
import { InterruptError, InterruptState, interruptFromAgent } from '../interrupt.js';
|
|
82
|
+
import { isInterruptResponseContent } from '../types/interrupt.js';
|
|
77
83
|
/** Default name assigned to agents when none is provided. */
|
|
78
84
|
const DEFAULT_AGENT_NAME = 'Strands Agent';
|
|
79
85
|
/** Default identifier assigned to agents when none is provided. */
|
|
@@ -93,6 +99,12 @@ export class Agent {
|
|
|
93
99
|
* State is not passed to the model during inference.
|
|
94
100
|
*/
|
|
95
101
|
appState;
|
|
102
|
+
/**
|
|
103
|
+
* Runtime state for the model provider. Used by stateful models to persist
|
|
104
|
+
* provider-specific data (e.g., response IDs for conversation chaining)
|
|
105
|
+
* across invocations.
|
|
106
|
+
*/
|
|
107
|
+
modelState;
|
|
96
108
|
_conversationManager;
|
|
97
109
|
/**
|
|
98
110
|
* The model provider used by the agent for inference.
|
|
@@ -132,6 +144,10 @@ export class Agent {
|
|
|
132
144
|
_tracer;
|
|
133
145
|
/** Meter instance for accumulating loop metrics during invocation. */
|
|
134
146
|
_meter;
|
|
147
|
+
/** Interrupt state for human-in-the-loop workflows. */
|
|
148
|
+
_interruptState;
|
|
149
|
+
/** Strategy for executing tool calls from a single assistant turn. */
|
|
150
|
+
_toolExecutor;
|
|
135
151
|
/**
|
|
136
152
|
* Creates an instance of the Agent.
|
|
137
153
|
* @param config - The configuration for the agent.
|
|
@@ -140,7 +156,7 @@ export class Agent {
|
|
|
140
156
|
// Initialize public fields
|
|
141
157
|
this.messages = (config?.messages ?? []).map((msg) => (msg instanceof Message ? msg : Message.fromMessageData(msg)));
|
|
142
158
|
this.appState = new StateStore(config?.appState);
|
|
143
|
-
this.
|
|
159
|
+
this.modelState = new StateStore(config?.modelState);
|
|
144
160
|
this.name = config?.name ?? DEFAULT_AGENT_NAME;
|
|
145
161
|
this.id = config?.id ?? DEFAULT_AGENT_ID;
|
|
146
162
|
if (config?.description !== undefined)
|
|
@@ -152,16 +168,45 @@ export class Agent {
|
|
|
152
168
|
else {
|
|
153
169
|
this.model = config?.model ?? new BedrockModel();
|
|
154
170
|
}
|
|
171
|
+
// Validate and assign conversation manager
|
|
172
|
+
if (this.model.stateful) {
|
|
173
|
+
if (config?.conversationManager) {
|
|
174
|
+
throw new Error('Cannot use a conversationManager with a stateful model. The model manages conversation state server-side.');
|
|
175
|
+
}
|
|
176
|
+
this._conversationManager = new NullConversationManager();
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this._conversationManager =
|
|
180
|
+
config?.conversationManager ?? new SlidingWindowConversationManager({ windowSize: 40 });
|
|
181
|
+
}
|
|
155
182
|
const { tools, mcpClients } = flattenTools(config?.tools ?? []);
|
|
156
183
|
this._toolRegistry = new ToolRegistry(tools);
|
|
157
184
|
this._mcpClients = mcpClients;
|
|
158
185
|
// Initialize hooks registry
|
|
159
186
|
this._hooksRegistry = new HookRegistryImplementation();
|
|
160
|
-
//
|
|
187
|
+
// `undefined` (omitted) → install the default; `null`/`[]` → explicit opt-out.
|
|
188
|
+
const retryStrategies = config?.retryStrategy === null
|
|
189
|
+
? []
|
|
190
|
+
: config?.retryStrategy === undefined
|
|
191
|
+
? [new DefaultModelRetryStrategy()]
|
|
192
|
+
: Array.isArray(config.retryStrategy)
|
|
193
|
+
? config.retryStrategy
|
|
194
|
+
: [config.retryStrategy];
|
|
195
|
+
warnOnDuplicateRetryStrategyTypes(retryStrategies);
|
|
196
|
+
// Initialize plugin registry with all plugins to be initialized during initialize().
|
|
197
|
+
// Ordering notes:
|
|
198
|
+
// - ModelPlugin is registered last so that on AfterInvocationEvent (which uses
|
|
199
|
+
// reverse callback ordering), it runs first — clearing messages before
|
|
200
|
+
// SessionManager saves.
|
|
201
|
+
// - Retry-strategy ordering is not load-bearing for correctness: `DefaultModelRetryStrategy`
|
|
202
|
+
// guards on `event.retry`, so a user hook that already set it short-circuits
|
|
203
|
+
// the strategy regardless of registration order.
|
|
161
204
|
this._pluginRegistry = new PluginRegistry([
|
|
162
205
|
this._conversationManager,
|
|
206
|
+
...retryStrategies,
|
|
163
207
|
...(config?.plugins ?? []),
|
|
164
208
|
...(config?.sessionManager ? [config.sessionManager] : []),
|
|
209
|
+
new ModelPlugin(this.model),
|
|
165
210
|
]);
|
|
166
211
|
if (config?.systemPrompt !== undefined) {
|
|
167
212
|
this.systemPrompt = systemPromptFromData(config.systemPrompt);
|
|
@@ -177,6 +222,9 @@ export class Agent {
|
|
|
177
222
|
this._tracer = new Tracer(config?.traceAttributes);
|
|
178
223
|
// Initialize meter for local metrics accumulation
|
|
179
224
|
this._meter = new Meter();
|
|
225
|
+
// Initialize interrupt state for human-in-the-loop workflows
|
|
226
|
+
this._interruptState = new InterruptState();
|
|
227
|
+
this._toolExecutor = config?.toolExecutor ?? 'concurrent';
|
|
180
228
|
this._initialized = false;
|
|
181
229
|
}
|
|
182
230
|
/**
|
|
@@ -184,6 +232,7 @@ export class Agent {
|
|
|
184
232
|
*
|
|
185
233
|
* @param eventType - The event class constructor to register the callback for
|
|
186
234
|
* @param callback - The callback function to invoke when the event occurs
|
|
235
|
+
* @param options - Optional configuration including execution order
|
|
187
236
|
* @returns Cleanup function that removes the callback when invoked
|
|
188
237
|
*
|
|
189
238
|
* @example
|
|
@@ -198,8 +247,8 @@ export class Agent {
|
|
|
198
247
|
* cleanup()
|
|
199
248
|
* ```
|
|
200
249
|
*/
|
|
201
|
-
addHook(eventType, callback) {
|
|
202
|
-
return this._hooksRegistry.addCallback(eventType, callback);
|
|
250
|
+
addHook(eventType, callback, options) {
|
|
251
|
+
return this._hooksRegistry.addCallback(eventType, callback, options);
|
|
203
252
|
}
|
|
204
253
|
async initialize() {
|
|
205
254
|
if (this._initialized) {
|
|
@@ -358,53 +407,86 @@ export class Agent {
|
|
|
358
407
|
async *stream(args, options) {
|
|
359
408
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
360
409
|
try {
|
|
361
|
-
const _lock = __addDisposableResource(env_1, this.acquireLock()
|
|
362
|
-
// Create AbortController for this invocation and compose with external signal
|
|
363
|
-
, false);
|
|
364
|
-
// Create AbortController for this invocation and compose with external signal
|
|
365
|
-
this._abortController = new AbortController();
|
|
366
|
-
this._abortSignal = options?.cancelSignal
|
|
367
|
-
? AbortSignal.any([this._abortController.signal, options.cancelSignal])
|
|
368
|
-
: this._abortController.signal;
|
|
410
|
+
const _lock = __addDisposableResource(env_1, this.acquireLock(), false);
|
|
369
411
|
await this.initialize();
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (caughtError) {
|
|
394
|
-
yield await this._invokeCallbacks(result.value);
|
|
412
|
+
let currentArgs = args;
|
|
413
|
+
// Outer loop: re-enters _stream when a hook sets AfterInvocationEvent.resume.
|
|
414
|
+
// One invocation lock spans the whole resume chain.
|
|
415
|
+
while (true) {
|
|
416
|
+
// Fresh AbortController per invocation iteration, composed with any external signal.
|
|
417
|
+
this._abortController = new AbortController();
|
|
418
|
+
this._abortSignal = options?.cancelSignal
|
|
419
|
+
? AbortSignal.any([this._abortController.signal, options.cancelSignal])
|
|
420
|
+
: this._abortController.signal;
|
|
421
|
+
const streamGenerator = this._stream(currentArgs, options);
|
|
422
|
+
let caughtError;
|
|
423
|
+
let lastAfterInvocation;
|
|
424
|
+
let iterationResult;
|
|
425
|
+
try {
|
|
426
|
+
iterationResult = await streamGenerator.next();
|
|
427
|
+
while (!iterationResult.done) {
|
|
428
|
+
try {
|
|
429
|
+
const processed = await this._invokeCallbacks(iterationResult.value);
|
|
430
|
+
if (processed instanceof AfterInvocationEvent) {
|
|
431
|
+
lastAfterInvocation = processed;
|
|
432
|
+
}
|
|
433
|
+
yield processed;
|
|
434
|
+
iterationResult = await streamGenerator.next();
|
|
395
435
|
}
|
|
396
|
-
|
|
397
|
-
|
|
436
|
+
catch (error) {
|
|
437
|
+
// Throw interrupt errors back into _stream so executeTools can store the
|
|
438
|
+
// assistant message as pending execution state for resume.
|
|
439
|
+
if (error instanceof InterruptError) {
|
|
440
|
+
iterationResult = await streamGenerator.throw(error);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
398
445
|
}
|
|
399
446
|
}
|
|
400
|
-
|
|
401
|
-
|
|
447
|
+
// Suppress AgentResultEvent for resumed iterations — only the final
|
|
448
|
+
// invocation in a resume chain reports an agent result.
|
|
449
|
+
if (lastAfterInvocation?.resume === undefined) {
|
|
450
|
+
yield await this._invokeCallbacks(new AgentResultEvent({
|
|
451
|
+
agent: this,
|
|
452
|
+
result: iterationResult.value,
|
|
453
|
+
invocationState: iterationResult.value.invocationState,
|
|
454
|
+
}));
|
|
402
455
|
}
|
|
403
|
-
result = await streamGenerator.next();
|
|
404
456
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
457
|
+
catch (error) {
|
|
458
|
+
caughtError = error;
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
// Drain _stream() so cleanup hooks and printer still fire.
|
|
463
|
+
// Yield only on error (consumer may still be iterating); on a consumer
|
|
464
|
+
// break, yielding would suspend the generator and leak the lock.
|
|
465
|
+
let drainResult = await streamGenerator.return(undefined);
|
|
466
|
+
while (!drainResult.done) {
|
|
467
|
+
try {
|
|
468
|
+
if (caughtError) {
|
|
469
|
+
yield await this._invokeCallbacks(drainResult.value);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
await this._invokeCallbacks(drainResult.value);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
logger.warn(`event_type=<${drainResult.value.type}>, error=<${error}> | error invoking callbacks during cleanup`);
|
|
477
|
+
}
|
|
478
|
+
drainResult = await streamGenerator.next();
|
|
479
|
+
}
|
|
480
|
+
// Reset controller and signal for next iteration / invocation
|
|
481
|
+
this._abortController = new AbortController();
|
|
482
|
+
this._abortSignal = this._abortController.signal;
|
|
483
|
+
}
|
|
484
|
+
// Resume only on a clean invocation — errors propagate above.
|
|
485
|
+
if (lastAfterInvocation?.resume !== undefined) {
|
|
486
|
+
currentArgs = lastAfterInvocation.resume;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
return iterationResult.value;
|
|
408
490
|
}
|
|
409
491
|
}
|
|
410
492
|
catch (e_1) {
|
|
@@ -471,8 +553,35 @@ export class Agent {
|
|
|
471
553
|
const structuredOutputSchema = options?.structuredOutputSchema ?? this._structuredOutputSchema;
|
|
472
554
|
const structuredOutputTool = structuredOutputSchema ? new StructuredOutputTool(structuredOutputSchema) : undefined;
|
|
473
555
|
let structuredOutputChoice;
|
|
474
|
-
//
|
|
475
|
-
|
|
556
|
+
// Resolve per-invocation state once. The same object is threaded through
|
|
557
|
+
// every lifecycle hook event, every tool context, and is surfaced on the
|
|
558
|
+
// AgentResult. Mutations by hooks/tools are visible across all recursive
|
|
559
|
+
// agent loop cycles within this invocation.
|
|
560
|
+
const invocationState = options?.invocationState ?? {};
|
|
561
|
+
// Handle interrupt responses if present in input
|
|
562
|
+
const interruptResponses = this._extractInterruptResponses(args);
|
|
563
|
+
if (interruptResponses.length > 0) {
|
|
564
|
+
this._interruptState.resume(interruptResponses);
|
|
565
|
+
}
|
|
566
|
+
// Reject non-interrupt input while in interrupted state
|
|
567
|
+
if (this._interruptState.activated && interruptResponses.length === 0) {
|
|
568
|
+
throw new TypeError('Agent is in an interrupted state. Resume by invoking with interruptResponse content blocks.');
|
|
569
|
+
}
|
|
570
|
+
const beforeInvocationEvent = new BeforeInvocationEvent({ agent: this, invocationState });
|
|
571
|
+
yield beforeInvocationEvent;
|
|
572
|
+
if (beforeInvocationEvent.cancel) {
|
|
573
|
+
const cancelText = typeof beforeInvocationEvent.cancel === 'string' ? beforeInvocationEvent.cancel : 'invocation denied by hook';
|
|
574
|
+
const message = new Message({ role: 'assistant', content: [new TextBlock(cancelText)] });
|
|
575
|
+
yield this._appendMessage(message, invocationState);
|
|
576
|
+
yield new AfterInvocationEvent({ agent: this, invocationState });
|
|
577
|
+
return new AgentResult({
|
|
578
|
+
stopReason: 'endTurn',
|
|
579
|
+
lastMessage: message,
|
|
580
|
+
traces: this._tracer.localTraces,
|
|
581
|
+
metrics: this._meter.metrics,
|
|
582
|
+
invocationState,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
476
585
|
// Normalize input to get the user messages for telemetry
|
|
477
586
|
const inputMessages = this._normalizeInput(args);
|
|
478
587
|
// Start agent trace span
|
|
@@ -510,77 +619,123 @@ export class Agent {
|
|
|
510
619
|
if (currentArgs !== undefined) {
|
|
511
620
|
const messagesToAppend = this._normalizeInput(currentArgs);
|
|
512
621
|
for (const message of messagesToAppend) {
|
|
513
|
-
yield this._appendMessage(message);
|
|
622
|
+
yield this._appendMessage(message, invocationState);
|
|
514
623
|
}
|
|
515
624
|
currentArgs = undefined;
|
|
516
625
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
626
|
+
// Check if we're resuming from a tool interrupt
|
|
627
|
+
const pendingExecution = this._interruptState.getPendingExecution();
|
|
628
|
+
let assistantMessage;
|
|
629
|
+
let completedToolResults;
|
|
630
|
+
if (pendingExecution) {
|
|
631
|
+
// Resume from stored state - skip model call
|
|
632
|
+
assistantMessage = pendingExecution.assistantMessage;
|
|
633
|
+
completedToolResults = pendingExecution.completedToolResults;
|
|
634
|
+
this._interruptState.clearPendingToolExecution();
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const modelResult = yield* this._invokeModel(invocationState, structuredOutputChoice);
|
|
638
|
+
if (modelResult.stopReason !== 'toolUse') {
|
|
639
|
+
// If structured output is required, force it
|
|
640
|
+
if (structuredOutputTool) {
|
|
641
|
+
if (structuredOutputChoice) {
|
|
642
|
+
throw new StructuredOutputError('The model failed to invoke the structured output tool even after it was forced.');
|
|
643
|
+
}
|
|
644
|
+
structuredOutputChoice = { tool: { name: STRUCTURED_OUTPUT_TOOL_NAME } };
|
|
645
|
+
}
|
|
646
|
+
this._meter.endCycle(cycleStartTime);
|
|
647
|
+
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
648
|
+
yield this._appendMessage(modelResult.message, invocationState);
|
|
521
649
|
if (structuredOutputChoice) {
|
|
522
|
-
|
|
650
|
+
continue;
|
|
523
651
|
}
|
|
524
|
-
|
|
652
|
+
result = new AgentResult({
|
|
653
|
+
stopReason: modelResult.stopReason,
|
|
654
|
+
lastMessage: modelResult.message,
|
|
655
|
+
traces: this._tracer.localTraces,
|
|
656
|
+
metrics: this._meter.metrics,
|
|
657
|
+
invocationState,
|
|
658
|
+
});
|
|
659
|
+
return result;
|
|
525
660
|
}
|
|
526
|
-
|
|
527
|
-
this.
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
661
|
+
// Cancel before tool execution: create error results for all pending tools
|
|
662
|
+
if (this.isCancelled) {
|
|
663
|
+
const toolUseBlocks = modelResult.message.content.filter((block) => block.type === 'toolUseBlock');
|
|
664
|
+
const cancelBlocks = toolUseBlocks.map((block) => new ToolResultBlock({
|
|
665
|
+
toolUseId: block.toolUseId,
|
|
666
|
+
status: 'error',
|
|
667
|
+
content: [new TextBlock('Tool execution cancelled')],
|
|
668
|
+
}));
|
|
669
|
+
const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
|
|
670
|
+
yield this._appendMessage(modelResult.message, invocationState);
|
|
671
|
+
yield this._appendMessage(toolResultMessage, invocationState);
|
|
672
|
+
this._meter.endCycle(cycleStartTime);
|
|
673
|
+
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
674
|
+
result = new AgentResult({
|
|
675
|
+
stopReason: 'cancelled',
|
|
676
|
+
lastMessage: modelResult.message,
|
|
677
|
+
traces: this._tracer.localTraces,
|
|
678
|
+
metrics: this._meter.metrics,
|
|
679
|
+
invocationState,
|
|
680
|
+
});
|
|
681
|
+
return result;
|
|
531
682
|
}
|
|
532
|
-
|
|
533
|
-
stopReason: modelResult.stopReason,
|
|
534
|
-
lastMessage: modelResult.message,
|
|
535
|
-
traces: this._tracer.localTraces,
|
|
536
|
-
metrics: this._meter.metrics,
|
|
537
|
-
});
|
|
538
|
-
return result;
|
|
683
|
+
assistantMessage = modelResult.message;
|
|
539
684
|
}
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
status: 'error',
|
|
546
|
-
content: [new TextBlock('Tool execution cancelled')],
|
|
547
|
-
}));
|
|
548
|
-
const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
|
|
549
|
-
yield this._appendMessage(modelResult.message);
|
|
550
|
-
yield this._appendMessage(toolResultMessage);
|
|
685
|
+
// Execute tools
|
|
686
|
+
const toolsResult = yield* this.executeTools(assistantMessage, this._toolRegistry, invocationState, completedToolResults);
|
|
687
|
+
// When the consumer breaks the stream (e.g. agent.cancel() + break),
|
|
688
|
+
// yield* returns undefined because the inner generator was closed.
|
|
689
|
+
if (!toolsResult) {
|
|
551
690
|
this._meter.endCycle(cycleStartTime);
|
|
552
691
|
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
553
|
-
|
|
554
|
-
stopReason: 'cancelled',
|
|
555
|
-
lastMessage: modelResult.message,
|
|
556
|
-
traces: this._tracer.localTraces,
|
|
557
|
-
metrics: this._meter.metrics,
|
|
558
|
-
});
|
|
559
|
-
return result;
|
|
692
|
+
continue;
|
|
560
693
|
}
|
|
561
|
-
|
|
562
|
-
const toolResultMessage = yield* this.executeTools(modelResult.message, this._toolRegistry);
|
|
694
|
+
const toolResultMessage = toolsResult.message;
|
|
563
695
|
/**
|
|
564
696
|
* Deferred append: both messages are added AFTER tool execution completes.
|
|
565
697
|
* This keeps agent.messages in a valid, reinvokable state at all times.
|
|
566
698
|
* If interrupted during tool execution, messages has no dangling toolUse
|
|
567
699
|
* without a matching toolResult, so the agent can be reinvoked cleanly.
|
|
568
700
|
*/
|
|
569
|
-
yield this._appendMessage(
|
|
570
|
-
yield this._appendMessage(toolResultMessage);
|
|
701
|
+
yield this._appendMessage(assistantMessage, invocationState);
|
|
702
|
+
yield this._appendMessage(toolResultMessage, invocationState);
|
|
703
|
+
// Deactivate interrupt state after successful tool execution so the next
|
|
704
|
+
// cycle starts with a clean slate (new interrupts can be raised again).
|
|
705
|
+
if (this._interruptState.activated) {
|
|
706
|
+
this._interruptState.deactivate();
|
|
707
|
+
}
|
|
571
708
|
this._meter.endCycle(cycleStartTime);
|
|
572
709
|
this._tracer.endAgentLoopSpan(cycleSpan);
|
|
710
|
+
// Hook requested halt: exit without calling the model again
|
|
711
|
+
const { afterToolsEvent } = toolsResult;
|
|
712
|
+
if (afterToolsEvent.endTurn) {
|
|
713
|
+
const endTurnText = typeof afterToolsEvent.endTurn === 'string'
|
|
714
|
+
? afterToolsEvent.endTurn
|
|
715
|
+
: 'Turn ended early by hook after tool execution';
|
|
716
|
+
const lastMessage = new Message({ role: 'assistant', content: [new TextBlock(endTurnText)] });
|
|
717
|
+
yield this._appendMessage(lastMessage, invocationState);
|
|
718
|
+
result = new AgentResult({
|
|
719
|
+
stopReason: 'endTurn',
|
|
720
|
+
lastMessage,
|
|
721
|
+
traces: this._tracer.localTraces,
|
|
722
|
+
metrics: this._meter.metrics,
|
|
723
|
+
invocationState,
|
|
724
|
+
});
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
573
727
|
// Structured output captured: exit
|
|
574
728
|
const structuredOutput = structuredOutputTool
|
|
575
|
-
? this._extractStructuredOutput(
|
|
729
|
+
? this._extractStructuredOutput(assistantMessage, toolResultMessage)
|
|
576
730
|
: undefined;
|
|
577
731
|
if (structuredOutput !== undefined) {
|
|
578
732
|
result = new AgentResult({
|
|
579
|
-
stopReason:
|
|
580
|
-
lastMessage:
|
|
733
|
+
stopReason: 'toolUse',
|
|
734
|
+
lastMessage: assistantMessage,
|
|
581
735
|
traces: this._tracer.localTraces,
|
|
582
736
|
structuredOutput,
|
|
583
737
|
metrics: this._meter.metrics,
|
|
738
|
+
invocationState,
|
|
584
739
|
});
|
|
585
740
|
return result;
|
|
586
741
|
}
|
|
@@ -600,15 +755,20 @@ export class Agent {
|
|
|
600
755
|
role: 'assistant',
|
|
601
756
|
content: [new TextBlock('Cancelled by user')],
|
|
602
757
|
});
|
|
603
|
-
yield this._appendMessage(cancelMessage);
|
|
758
|
+
yield this._appendMessage(cancelMessage, invocationState);
|
|
604
759
|
result = new AgentResult({
|
|
605
760
|
stopReason: 'cancelled',
|
|
606
761
|
lastMessage: cancelMessage,
|
|
607
762
|
traces: this._tracer.localTraces,
|
|
608
763
|
metrics: this._meter.metrics,
|
|
764
|
+
invocationState,
|
|
609
765
|
});
|
|
610
766
|
return result;
|
|
611
767
|
}
|
|
768
|
+
if (error instanceof InterruptError) {
|
|
769
|
+
result = this._createInterruptResult(invocationState);
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
612
772
|
caughtError = error;
|
|
613
773
|
throw error;
|
|
614
774
|
}
|
|
@@ -621,7 +781,7 @@ export class Agent {
|
|
|
621
781
|
role: 'assistant',
|
|
622
782
|
content: [new TextBlock('Cancelled by user')],
|
|
623
783
|
});
|
|
624
|
-
yield this._appendMessage(cancelMessage);
|
|
784
|
+
yield this._appendMessage(cancelMessage, invocationState);
|
|
625
785
|
}
|
|
626
786
|
this._tracer.endAgentSpan(agentSpan, {
|
|
627
787
|
...(caughtError && { error: caughtError }),
|
|
@@ -634,7 +794,7 @@ export class Agent {
|
|
|
634
794
|
this._toolRegistry.remove(STRUCTURED_OUTPUT_TOOL_NAME);
|
|
635
795
|
}
|
|
636
796
|
// Always emit final event
|
|
637
|
-
yield new AfterInvocationEvent({ agent: this });
|
|
797
|
+
yield new AfterInvocationEvent({ agent: this, invocationState });
|
|
638
798
|
}
|
|
639
799
|
}
|
|
640
800
|
/**
|
|
@@ -654,6 +814,51 @@ export class Agent {
|
|
|
654
814
|
const firstContent = toolResult.content[0];
|
|
655
815
|
return firstContent?.type === 'jsonBlock' ? firstContent.json : undefined;
|
|
656
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Creates an AgentResult for an interrupt stop.
|
|
819
|
+
*
|
|
820
|
+
* @param invocationState - The current invocation state
|
|
821
|
+
* @returns AgentResult with stopReason 'interrupt'
|
|
822
|
+
*/
|
|
823
|
+
_createInterruptResult(invocationState) {
|
|
824
|
+
this._interruptState.activate();
|
|
825
|
+
return new AgentResult({
|
|
826
|
+
stopReason: 'interrupt',
|
|
827
|
+
lastMessage: this.messages.length > 0
|
|
828
|
+
? this.messages[this.messages.length - 1]
|
|
829
|
+
: new Message({ role: 'assistant', content: [new TextBlock('Interrupted')] }),
|
|
830
|
+
traces: this._tracer.localTraces,
|
|
831
|
+
metrics: this._meter.metrics,
|
|
832
|
+
interrupts: this._interruptState.getUnansweredInterrupts(),
|
|
833
|
+
invocationState,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Extracts interrupt response content blocks from invocation args.
|
|
838
|
+
*
|
|
839
|
+
* @param args - The invocation arguments
|
|
840
|
+
* @returns Array of InterruptResponseContent blocks, empty if none found
|
|
841
|
+
* @throws TypeError if args mix interrupt responses with other content
|
|
842
|
+
*/
|
|
843
|
+
_extractInterruptResponses(args) {
|
|
844
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
845
|
+
return [];
|
|
846
|
+
}
|
|
847
|
+
const responses = [];
|
|
848
|
+
let hasNonInterrupt = false;
|
|
849
|
+
for (const item of args) {
|
|
850
|
+
if (isInterruptResponseContent(item)) {
|
|
851
|
+
responses.push(item);
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
hasNonInterrupt = true;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (responses.length > 0 && hasNonInterrupt) {
|
|
858
|
+
throw new TypeError('Must resume from interrupt with a list of interruptResponse content blocks only');
|
|
859
|
+
}
|
|
860
|
+
return responses;
|
|
861
|
+
}
|
|
657
862
|
/**
|
|
658
863
|
* Normalizes agent invocation input into an array of messages to append.
|
|
659
864
|
*
|
|
@@ -673,6 +878,11 @@ export class Agent {
|
|
|
673
878
|
}
|
|
674
879
|
else if (Array.isArray(args) && args.length > 0) {
|
|
675
880
|
const firstElement = args[0];
|
|
881
|
+
// Check if it's interrupt responses - skip creating messages for these
|
|
882
|
+
if (isInterruptResponseContent(firstElement)) {
|
|
883
|
+
// Pure interrupt responses: no messages to add
|
|
884
|
+
return [];
|
|
885
|
+
}
|
|
676
886
|
// Check if it's Message[] or MessageData[]
|
|
677
887
|
if ('role' in firstElement && typeof firstElement.role === 'string') {
|
|
678
888
|
// Check if it's a Message instance or MessageData
|
|
@@ -716,9 +926,9 @@ export class Agent {
|
|
|
716
926
|
* @param toolChoice - Optional tool choice to force specific tool usage
|
|
717
927
|
* @returns Object containing the assistant message, stop reason, and optional redaction message
|
|
718
928
|
*/
|
|
719
|
-
async *_invokeModel(toolChoice) {
|
|
929
|
+
async *_invokeModel(invocationState, toolChoice) {
|
|
720
930
|
const toolSpecs = this._toolRegistry.list().map((tool) => tool.toolSpec);
|
|
721
|
-
const streamOptions = { toolSpecs };
|
|
931
|
+
const streamOptions = { toolSpecs, modelState: this.modelState };
|
|
722
932
|
if (this.systemPrompt !== undefined) {
|
|
723
933
|
streamOptions.systemPrompt = this.systemPrompt;
|
|
724
934
|
}
|
|
@@ -726,63 +936,117 @@ export class Agent {
|
|
|
726
936
|
if (toolChoice) {
|
|
727
937
|
streamOptions.toolChoice = toolChoice;
|
|
728
938
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
this._tracer.endModelInvokeSpan(modelSpan, {
|
|
745
|
-
output: result.message,
|
|
746
|
-
stopReason: result.stopReason,
|
|
747
|
-
...(usage && { usage }),
|
|
748
|
-
...(metrics && { metrics }),
|
|
939
|
+
let attemptCount = 1;
|
|
940
|
+
while (true) {
|
|
941
|
+
// Estimate input tokens for the upcoming model call (non-fatal if estimation fails)
|
|
942
|
+
let projectedInputTokens;
|
|
943
|
+
try {
|
|
944
|
+
projectedInputTokens = await this._estimateInputTokens(streamOptions);
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
logger.debug(`error=<${e}> | token estimation failed, proceeding without estimate`);
|
|
948
|
+
}
|
|
949
|
+
const beforeModelCallEvent = new BeforeModelCallEvent({
|
|
950
|
+
agent: this,
|
|
951
|
+
model: this.model,
|
|
952
|
+
invocationState,
|
|
953
|
+
...(projectedInputTokens !== undefined && { projectedInputTokens }),
|
|
749
954
|
});
|
|
750
|
-
yield
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
955
|
+
yield beforeModelCallEvent;
|
|
956
|
+
if (beforeModelCallEvent.cancel) {
|
|
957
|
+
const cancelText = typeof beforeModelCallEvent.cancel === 'string' ? beforeModelCallEvent.cancel : 'model call denied by hook';
|
|
958
|
+
const message = new Message({ role: 'assistant', content: [new TextBlock(cancelText)] });
|
|
959
|
+
const stopData = { message, stopReason: 'endTurn' };
|
|
960
|
+
const afterModelCallEvent = new AfterModelCallEvent({
|
|
961
|
+
agent: this,
|
|
962
|
+
model: this.model,
|
|
963
|
+
attemptCount,
|
|
964
|
+
stopData,
|
|
965
|
+
invocationState,
|
|
966
|
+
});
|
|
967
|
+
yield afterModelCallEvent;
|
|
968
|
+
if (afterModelCallEvent.retry) {
|
|
969
|
+
attemptCount += 1;
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
return { message, stopReason: 'endTurn' };
|
|
754
973
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
974
|
+
// Start model span within loop span context
|
|
975
|
+
const modelId = this.model.modelId;
|
|
976
|
+
const modelSpan = this._tracer.startModelInvokeSpan({
|
|
977
|
+
messages: this.messages,
|
|
978
|
+
...(modelId && { modelId }),
|
|
979
|
+
...(this.systemPrompt !== undefined && { systemPrompt: this.systemPrompt }),
|
|
980
|
+
});
|
|
981
|
+
try {
|
|
982
|
+
const result = yield* this._streamFromModel(this.messages, streamOptions, invocationState);
|
|
983
|
+
// Accumulate token usage and model latency metrics
|
|
984
|
+
this._meter.updateCycle(result.metadata);
|
|
985
|
+
// End model span with usage
|
|
986
|
+
const usage = result.metadata?.usage;
|
|
987
|
+
const metrics = result.metadata?.metrics;
|
|
988
|
+
this._tracer.endModelInvokeSpan(modelSpan, {
|
|
989
|
+
output: result.message,
|
|
990
|
+
stopReason: result.stopReason,
|
|
991
|
+
...(usage && { usage }),
|
|
992
|
+
...(metrics && { metrics }),
|
|
993
|
+
});
|
|
994
|
+
yield new ModelMessageEvent({
|
|
995
|
+
agent: this,
|
|
996
|
+
message: result.message,
|
|
997
|
+
stopReason: result.stopReason,
|
|
998
|
+
invocationState,
|
|
999
|
+
});
|
|
1000
|
+
// Handle user content redaction if guardrails blocked input
|
|
1001
|
+
if (result.redaction?.userMessage) {
|
|
1002
|
+
this._redactLastMessage(result.redaction.userMessage);
|
|
1003
|
+
}
|
|
1004
|
+
const stopData = {
|
|
1005
|
+
message: result.message,
|
|
1006
|
+
stopReason: result.stopReason,
|
|
1007
|
+
...(result.redaction && { redaction: result.redaction }),
|
|
1008
|
+
};
|
|
1009
|
+
const afterModelCallEvent = new AfterModelCallEvent({
|
|
1010
|
+
agent: this,
|
|
1011
|
+
model: this.model,
|
|
1012
|
+
attemptCount,
|
|
1013
|
+
stopData,
|
|
1014
|
+
invocationState,
|
|
1015
|
+
});
|
|
1016
|
+
yield afterModelCallEvent;
|
|
1017
|
+
if (afterModelCallEvent.retry) {
|
|
1018
|
+
attemptCount += 1;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
return result;
|
|
764
1022
|
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1023
|
+
catch (error) {
|
|
1024
|
+
const modelError = normalizeError(error);
|
|
1025
|
+
// End model span with error
|
|
1026
|
+
this._tracer.endModelInvokeSpan(modelSpan, { error: modelError });
|
|
1027
|
+
// Create error event
|
|
1028
|
+
const errorEvent = new AfterModelCallEvent({
|
|
1029
|
+
agent: this,
|
|
1030
|
+
model: this.model,
|
|
1031
|
+
attemptCount,
|
|
1032
|
+
error: modelError,
|
|
1033
|
+
invocationState,
|
|
1034
|
+
});
|
|
1035
|
+
// Yield error event - stream will invoke hooks
|
|
1036
|
+
yield errorEvent;
|
|
1037
|
+
// Let CancelledError propagate directly — no retry
|
|
1038
|
+
// (we emit the AfterModelCall because we already emitted Before and we guarentee the pair)
|
|
1039
|
+
if (error instanceof CancelledError) {
|
|
1040
|
+
throw error;
|
|
1041
|
+
}
|
|
1042
|
+
// After yielding, hooks have been invoked and may have set retry
|
|
1043
|
+
if (errorEvent.retry) {
|
|
1044
|
+
attemptCount += 1;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
// Re-throw error
|
|
778
1048
|
throw error;
|
|
779
1049
|
}
|
|
780
|
-
// After yielding, hooks have been invoked and may have set retry
|
|
781
|
-
if (errorEvent.retry) {
|
|
782
|
-
return yield* this._invokeModel(toolChoice);
|
|
783
|
-
}
|
|
784
|
-
// Re-throw error
|
|
785
|
-
throw error;
|
|
786
1050
|
}
|
|
787
1051
|
}
|
|
788
1052
|
/**
|
|
@@ -801,7 +1065,8 @@ export class Agent {
|
|
|
801
1065
|
* @param streamOptions - Options for streaming
|
|
802
1066
|
* @returns StreamAggregatedResult containing message, stop reason, and optional redaction message
|
|
803
1067
|
*/
|
|
804
|
-
async *_streamFromModel(messages, streamOptions) {
|
|
1068
|
+
async *_streamFromModel(messages, streamOptions, invocationState) {
|
|
1069
|
+
messages = normalizeToolUseNames(messages);
|
|
805
1070
|
const streamGenerator = this.model.streamAggregated(messages, streamOptions);
|
|
806
1071
|
let result = await streamGenerator.next();
|
|
807
1072
|
while (!result.done) {
|
|
@@ -809,11 +1074,11 @@ export class Agent {
|
|
|
809
1074
|
const event = result.value;
|
|
810
1075
|
if (isModelStreamEvent(event)) {
|
|
811
1076
|
// ModelStreamEvent: wrap in ModelStreamUpdateEvent
|
|
812
|
-
yield new ModelStreamUpdateEvent({ agent: this, event });
|
|
1077
|
+
yield new ModelStreamUpdateEvent({ agent: this, event, invocationState });
|
|
813
1078
|
}
|
|
814
1079
|
else {
|
|
815
1080
|
// ContentBlock: wrap in ContentBlockEvent
|
|
816
|
-
yield new ContentBlockEvent({ agent: this, contentBlock: event });
|
|
1081
|
+
yield new ContentBlockEvent({ agent: this, contentBlock: event, invocationState });
|
|
817
1082
|
}
|
|
818
1083
|
result = await streamGenerator.next();
|
|
819
1084
|
}
|
|
@@ -821,63 +1086,268 @@ export class Agent {
|
|
|
821
1086
|
return result.value;
|
|
822
1087
|
}
|
|
823
1088
|
/**
|
|
824
|
-
*
|
|
1089
|
+
* Emits `BeforeToolsEvent`, handles the pre-launch cancel paths, then
|
|
1090
|
+
* delegates per-tool execution to the configured {@link ToolExecutorStrategy}.
|
|
1091
|
+
* Always pairs `BeforeToolsEvent` with a terminal `AfterToolsEvent`, even on
|
|
1092
|
+
* the invariant-violation throw path.
|
|
825
1093
|
*
|
|
826
1094
|
* @param assistantMessage - The assistant message containing tool use blocks
|
|
827
1095
|
* @param toolRegistry - Registry containing available tools
|
|
828
|
-
* @returns
|
|
1096
|
+
* @returns Tool-result message and the dispatched AfterToolsEvent
|
|
829
1097
|
*/
|
|
830
|
-
async *executeTools(assistantMessage, toolRegistry) {
|
|
831
|
-
const beforeToolsEvent = new BeforeToolsEvent({ agent: this, message: assistantMessage });
|
|
832
|
-
|
|
1098
|
+
async *executeTools(assistantMessage, toolRegistry, invocationState, completedToolResults) {
|
|
1099
|
+
const beforeToolsEvent = new BeforeToolsEvent({ agent: this, message: assistantMessage, invocationState });
|
|
1100
|
+
try {
|
|
1101
|
+
yield beforeToolsEvent;
|
|
1102
|
+
}
|
|
1103
|
+
catch (error) {
|
|
1104
|
+
// Store pending state before re-throwing so the agent can resume from this point.
|
|
1105
|
+
// The error must still propagate to _stream which handles the interrupt stop.
|
|
1106
|
+
if (error instanceof InterruptError) {
|
|
1107
|
+
this._interruptState.setPendingToolExecution({
|
|
1108
|
+
assistantMessageData: assistantMessage.toJSON(),
|
|
1109
|
+
completedToolResults: {},
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
throw error;
|
|
1113
|
+
}
|
|
1114
|
+
const toolUseBlocks = assistantMessage.content.filter((block) => block.type === 'toolUseBlock');
|
|
1115
|
+
if (toolUseBlocks.length === 0) {
|
|
1116
|
+
// Preserve BeforeToolsEvent/AfterToolsEvent bracket symmetry even on
|
|
1117
|
+
// this invariant-violation branch.
|
|
1118
|
+
yield new AfterToolsEvent({
|
|
1119
|
+
agent: this,
|
|
1120
|
+
message: new Message({ role: 'user', content: [] }),
|
|
1121
|
+
invocationState,
|
|
1122
|
+
});
|
|
1123
|
+
throw new Error('Model indicated toolUse but no tool use blocks found in message');
|
|
1124
|
+
}
|
|
1125
|
+
// Pre-launch cancel paths are strategy-independent.
|
|
1126
|
+
if (beforeToolsEvent.cancel) {
|
|
1127
|
+
const message = typeof beforeToolsEvent.cancel === 'string' ? beforeToolsEvent.cancel : 'Tool cancelled by hook';
|
|
1128
|
+
return yield* this._yieldCancelledToolResults(toolUseBlocks, message, invocationState);
|
|
1129
|
+
}
|
|
1130
|
+
if (this.isCancelled) {
|
|
1131
|
+
return yield* this._yieldCancelledToolResults(toolUseBlocks, 'Tool execution cancelled', invocationState);
|
|
1132
|
+
}
|
|
1133
|
+
switch (this._toolExecutor) {
|
|
1134
|
+
case 'sequential':
|
|
1135
|
+
return yield* this._executeToolsSequential(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage);
|
|
1136
|
+
case 'concurrent':
|
|
1137
|
+
return yield* this._executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage);
|
|
1138
|
+
default: {
|
|
1139
|
+
const _exhaustive = this._toolExecutor;
|
|
1140
|
+
throw new Error(`Unknown toolExecutor: ${_exhaustive}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Emits a `ToolResultEvent` for every block plus an `AfterToolsEvent`, and
|
|
1146
|
+
* returns the resulting tool-result message and dispatched event. Used by the pre-launch cancel
|
|
1147
|
+
* paths shared across executors.
|
|
1148
|
+
*/
|
|
1149
|
+
async *_yieldCancelledToolResults(toolUseBlocks, message, invocationState) {
|
|
1150
|
+
const cancelBlocks = this._cancelAllAsResults(toolUseBlocks, message);
|
|
1151
|
+
for (const result of cancelBlocks) {
|
|
1152
|
+
yield new ToolResultEvent({ agent: this, result, invocationState });
|
|
1153
|
+
}
|
|
1154
|
+
const toolResultMessage = new Message({ role: 'user', content: cancelBlocks });
|
|
1155
|
+
const afterToolsEvent = new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
|
|
1156
|
+
yield afterToolsEvent;
|
|
1157
|
+
return { message: toolResultMessage, afterToolsEvent };
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Executes tools one at a time, honoring `agent.cancelSignal` between
|
|
1161
|
+
* iterations to short-circuit not-yet-started tools.
|
|
1162
|
+
*/
|
|
1163
|
+
async *_executeToolsSequential(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage) {
|
|
833
1164
|
const toolResultBlocks = [];
|
|
834
1165
|
let toolResultMessage;
|
|
1166
|
+
let afterToolsEvent;
|
|
835
1167
|
try {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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 });
|
|
1168
|
+
for (const toolUseBlock of toolUseBlocks) {
|
|
1169
|
+
// Skip tools that were already completed before the interrupt
|
|
1170
|
+
if (completedToolResults?.has(toolUseBlock.toolUseId)) {
|
|
1171
|
+
const completedResult = completedToolResults.get(toolUseBlock.toolUseId);
|
|
1172
|
+
// No events emitted for already-completed tools.
|
|
1173
|
+
// The result is included in the final tool result message.
|
|
1174
|
+
toolResultBlocks.push(completedResult);
|
|
1175
|
+
continue;
|
|
852
1176
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1177
|
+
if (this.isCancelled) {
|
|
1178
|
+
const cancelBlock = new ToolResultBlock({
|
|
1179
|
+
toolUseId: toolUseBlock.toolUseId,
|
|
1180
|
+
status: 'error',
|
|
1181
|
+
content: [new TextBlock('Tool execution cancelled')],
|
|
1182
|
+
});
|
|
1183
|
+
toolResultBlocks.push(cancelBlock);
|
|
1184
|
+
yield new ToolResultEvent({ agent: this, result: cancelBlock, invocationState });
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
try {
|
|
1188
|
+
const toolResultBlock = yield* this.executeTool(toolUseBlock, toolRegistry, invocationState);
|
|
1189
|
+
toolResultBlocks.push(toolResultBlock);
|
|
1190
|
+
yield new ToolResultEvent({ agent: this, result: toolResultBlock, invocationState });
|
|
1191
|
+
}
|
|
1192
|
+
catch (error) {
|
|
1193
|
+
if (error instanceof InterruptError) {
|
|
1194
|
+
// Store pending state with completed results so far
|
|
1195
|
+
const completedSoFar = {};
|
|
1196
|
+
for (const block of toolResultBlocks) {
|
|
1197
|
+
completedSoFar[block.toolUseId] = block.toJSON();
|
|
1198
|
+
}
|
|
1199
|
+
// Also include any previously completed results
|
|
1200
|
+
if (completedToolResults) {
|
|
1201
|
+
for (const [id, block] of completedToolResults) {
|
|
1202
|
+
completedSoFar[id] = block.toJSON();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
this._interruptState.setPendingToolExecution({
|
|
1206
|
+
assistantMessageData: assistantMessage.toJSON(),
|
|
1207
|
+
completedToolResults: completedSoFar,
|
|
862
1208
|
});
|
|
863
|
-
|
|
864
|
-
|
|
1209
|
+
throw error;
|
|
1210
|
+
}
|
|
1211
|
+
throw error;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
finally {
|
|
1216
|
+
toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
|
|
1217
|
+
afterToolsEvent = new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
|
|
1218
|
+
yield afterToolsEvent;
|
|
1219
|
+
}
|
|
1220
|
+
return { message: toolResultMessage, afterToolsEvent };
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Produces one error ToolResultBlock per tool use block, each carrying
|
|
1224
|
+
* `message` as its error text. Shared by pre-launch cancel paths.
|
|
1225
|
+
*/
|
|
1226
|
+
_cancelAllAsResults(toolUseBlocks, message) {
|
|
1227
|
+
return toolUseBlocks.map((block) => new ToolResultBlock({
|
|
1228
|
+
toolUseId: block.toolUseId,
|
|
1229
|
+
status: 'error',
|
|
1230
|
+
content: [new TextBlock(message)],
|
|
1231
|
+
}));
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Executes tools concurrently by merging N per-tool {@link executeTool}
|
|
1235
|
+
* async generators via `Promise.race`. Per-tool event order is preserved
|
|
1236
|
+
* (because each generator is iterated serially); cross-tool events may
|
|
1237
|
+
* interleave at race resolution boundaries.
|
|
1238
|
+
*
|
|
1239
|
+
* Per-tool retry (`AfterToolCallEvent.retry`) is isolated — it lives inside
|
|
1240
|
+
* `executeTool`'s own `while(true)` loop, so one tool retrying does not
|
|
1241
|
+
* disturb its siblings.
|
|
1242
|
+
*/
|
|
1243
|
+
async *_executeToolsConcurrent(toolUseBlocks, toolRegistry, invocationState, completedToolResults, assistantMessage) {
|
|
1244
|
+
let toolResultMessage;
|
|
1245
|
+
let afterToolsEvent;
|
|
1246
|
+
const gens = toolUseBlocks.map((block) => ({
|
|
1247
|
+
block,
|
|
1248
|
+
gen: completedToolResults?.has(block.toolUseId)
|
|
1249
|
+
? undefined // Skip already-completed tools
|
|
1250
|
+
: this.executeTool(block, toolRegistry, invocationState),
|
|
1251
|
+
}));
|
|
1252
|
+
const step = (idx) => gens[idx].gen.next().then((res) => ({ idx, kind: 'next', res }), (error) => ({ idx, kind: 'throw', error }));
|
|
1253
|
+
// Seed completed results from resume state
|
|
1254
|
+
const resultsByToolUseId = new Map();
|
|
1255
|
+
if (completedToolResults) {
|
|
1256
|
+
for (const [id, result] of completedToolResults) {
|
|
1257
|
+
resultsByToolUseId.set(id, result);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// Only race tools that need execution
|
|
1261
|
+
const pendingNext = new Map();
|
|
1262
|
+
for (let idx = 0; idx < gens.length; idx++) {
|
|
1263
|
+
if (gens[idx].gen) {
|
|
1264
|
+
pendingNext.set(idx, step(idx));
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
// Track interrupts — let all other tools finish before propagating
|
|
1268
|
+
let interruptError;
|
|
1269
|
+
try {
|
|
1270
|
+
while (pendingNext.size > 0) {
|
|
1271
|
+
const winner = await Promise.race(pendingNext.values());
|
|
1272
|
+
const { idx } = winner;
|
|
1273
|
+
const block = gens[idx].block;
|
|
1274
|
+
if (winner.kind === 'throw') {
|
|
1275
|
+
pendingNext.delete(idx);
|
|
1276
|
+
// Detect InterruptError — don't convert to error result, track it
|
|
1277
|
+
if (winner.error instanceof InterruptError) {
|
|
1278
|
+
interruptError = winner.error;
|
|
865
1279
|
continue;
|
|
866
1280
|
}
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
1281
|
+
const err = normalizeError(winner.error);
|
|
1282
|
+
const result = new ToolResultBlock({
|
|
1283
|
+
toolUseId: block.toolUseId,
|
|
1284
|
+
status: 'error',
|
|
1285
|
+
content: [new TextBlock(err.message)],
|
|
1286
|
+
error: err,
|
|
1287
|
+
});
|
|
1288
|
+
resultsByToolUseId.set(block.toolUseId, result);
|
|
1289
|
+
yield new ToolResultEvent({ agent: this, result, invocationState });
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (winner.res.done) {
|
|
1293
|
+
pendingNext.delete(idx);
|
|
1294
|
+
resultsByToolUseId.set(block.toolUseId, winner.res.value);
|
|
1295
|
+
yield new ToolResultEvent({ agent: this, result: winner.res.value, invocationState });
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
try {
|
|
1299
|
+
yield winner.res.value;
|
|
1300
|
+
}
|
|
1301
|
+
catch (e) {
|
|
1302
|
+
// InterruptError thrown back into generator from stream() error injection
|
|
1303
|
+
if (e instanceof InterruptError) {
|
|
1304
|
+
interruptError = e;
|
|
1305
|
+
pendingNext.delete(idx);
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
throw e;
|
|
1309
|
+
}
|
|
1310
|
+
pendingNext.set(idx, step(idx));
|
|
870
1311
|
}
|
|
871
1312
|
}
|
|
1313
|
+
// After all tools finish, propagate interrupt if one was raised
|
|
1314
|
+
if (interruptError) {
|
|
1315
|
+
const completedSoFar = {};
|
|
1316
|
+
for (const [id, result] of resultsByToolUseId) {
|
|
1317
|
+
completedSoFar[id] = result.toJSON();
|
|
1318
|
+
}
|
|
1319
|
+
this._interruptState.setPendingToolExecution({
|
|
1320
|
+
assistantMessageData: assistantMessage.toJSON(),
|
|
1321
|
+
completedToolResults: completedSoFar,
|
|
1322
|
+
});
|
|
1323
|
+
throw interruptError;
|
|
1324
|
+
}
|
|
872
1325
|
}
|
|
873
1326
|
finally {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1327
|
+
// Close any generators still in-flight (e.g. consumer broke out of stream).
|
|
1328
|
+
await Promise.allSettled(Array.from(pendingNext.keys(), (idx) => gens[idx].gen.return(undefined)));
|
|
1329
|
+
// Build the result message from whatever completed, in source order.
|
|
1330
|
+
// Missing entries get a fallback error block so the message always
|
|
1331
|
+
// accounts for every toolUseBlock the model emitted.
|
|
1332
|
+
const toolResultBlocks = [];
|
|
1333
|
+
for (const block of toolUseBlocks) {
|
|
1334
|
+
const result = resultsByToolUseId.get(block.toolUseId);
|
|
1335
|
+
if (result) {
|
|
1336
|
+
toolResultBlocks.push(result);
|
|
1337
|
+
}
|
|
1338
|
+
else {
|
|
1339
|
+
toolResultBlocks.push(new ToolResultBlock({
|
|
1340
|
+
toolUseId: block.toolUseId,
|
|
1341
|
+
status: 'error',
|
|
1342
|
+
content: [new TextBlock('Tool execution interrupted')],
|
|
1343
|
+
}));
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
toolResultMessage = new Message({ role: 'user', content: toolResultBlocks });
|
|
1347
|
+
afterToolsEvent = new AfterToolsEvent({ agent: this, message: toolResultMessage, invocationState });
|
|
1348
|
+
yield afterToolsEvent;
|
|
879
1349
|
}
|
|
880
|
-
return toolResultMessage;
|
|
1350
|
+
return { message: toolResultMessage, afterToolsEvent };
|
|
881
1351
|
}
|
|
882
1352
|
/**
|
|
883
1353
|
* Executes a single tool and returns the result.
|
|
@@ -889,9 +1359,10 @@ export class Agent {
|
|
|
889
1359
|
* @param toolRegistry - Registry containing available tools
|
|
890
1360
|
* @returns Tool result block
|
|
891
1361
|
*/
|
|
892
|
-
async *executeTool(toolUseBlock, toolRegistry) {
|
|
893
|
-
const
|
|
894
|
-
// Create toolUse object for hook events and telemetry
|
|
1362
|
+
async *executeTool(toolUseBlock, toolRegistry, invocationState) {
|
|
1363
|
+
const registryTool = toolRegistry.get(toolUseBlock.name);
|
|
1364
|
+
// Create toolUse object for hook events and telemetry. Callbacks may mutate
|
|
1365
|
+
// this object's fields (input/name/toolUseId) inside BeforeToolCallEvent.
|
|
895
1366
|
const toolUse = {
|
|
896
1367
|
name: toolUseBlock.name,
|
|
897
1368
|
toolUseId: toolUseBlock.toolUseId,
|
|
@@ -899,27 +1370,40 @@ export class Agent {
|
|
|
899
1370
|
};
|
|
900
1371
|
// Retry loop for tool execution
|
|
901
1372
|
while (true) {
|
|
902
|
-
const beforeToolCallEvent = new BeforeToolCallEvent({
|
|
1373
|
+
const beforeToolCallEvent = new BeforeToolCallEvent({
|
|
1374
|
+
agent: this,
|
|
1375
|
+
toolUse,
|
|
1376
|
+
tool: registryTool,
|
|
1377
|
+
invocationState,
|
|
1378
|
+
});
|
|
903
1379
|
yield beforeToolCallEvent;
|
|
1380
|
+
// Resolve the tool that would actually execute. selectedTool wins;
|
|
1381
|
+
// otherwise if the hook renamed toolUse.name, re-resolve from the
|
|
1382
|
+
// registry under the new name; otherwise use the original registry
|
|
1383
|
+
// lookup. Resolved before the cancel check so AfterToolCallEvent.tool
|
|
1384
|
+
// is consistent whether the cancel or execution branch runs.
|
|
1385
|
+
const effectiveTool = beforeToolCallEvent.selectedTool ??
|
|
1386
|
+
(toolUse.name !== toolUseBlock.name ? toolRegistry.get(toolUse.name) : registryTool);
|
|
904
1387
|
// Cancel individual tool if hook requested it
|
|
905
1388
|
if (beforeToolCallEvent.cancel) {
|
|
906
|
-
const cancelMessage =
|
|
907
|
-
const
|
|
908
|
-
toolUseId:
|
|
1389
|
+
const cancelMessage = typeof beforeToolCallEvent.cancel === 'string' ? beforeToolCallEvent.cancel : 'Tool cancelled by hook';
|
|
1390
|
+
const cancelResult = new ToolResultBlock({
|
|
1391
|
+
toolUseId: toolUse.toolUseId,
|
|
909
1392
|
status: 'error',
|
|
910
1393
|
content: [new TextBlock(cancelMessage)],
|
|
911
1394
|
});
|
|
912
1395
|
const afterToolCallEvent = new AfterToolCallEvent({
|
|
913
1396
|
agent: this,
|
|
914
1397
|
toolUse,
|
|
915
|
-
tool,
|
|
916
|
-
result:
|
|
1398
|
+
tool: effectiveTool,
|
|
1399
|
+
result: cancelResult,
|
|
1400
|
+
invocationState,
|
|
917
1401
|
});
|
|
918
1402
|
yield afterToolCallEvent;
|
|
919
1403
|
if (afterToolCallEvent.retry) {
|
|
920
1404
|
continue;
|
|
921
1405
|
}
|
|
922
|
-
return
|
|
1406
|
+
return afterToolCallEvent.result;
|
|
923
1407
|
}
|
|
924
1408
|
// Start tool span within loop span context
|
|
925
1409
|
const toolSpan = this._tracer.startToolCallSpan({
|
|
@@ -929,23 +1413,27 @@ export class Agent {
|
|
|
929
1413
|
const toolStartTime = Date.now();
|
|
930
1414
|
let toolResult;
|
|
931
1415
|
let error;
|
|
932
|
-
if (!
|
|
1416
|
+
if (!effectiveTool) {
|
|
933
1417
|
// Tool not found
|
|
934
1418
|
toolResult = new ToolResultBlock({
|
|
935
|
-
toolUseId:
|
|
1419
|
+
toolUseId: toolUse.toolUseId,
|
|
936
1420
|
status: 'error',
|
|
937
|
-
content: [new TextBlock(`Tool '${
|
|
1421
|
+
content: [new TextBlock(`Tool '${toolUse.name}' not found in registry`)],
|
|
938
1422
|
});
|
|
939
1423
|
}
|
|
940
1424
|
else {
|
|
941
1425
|
// Execute tool within the tool span context
|
|
942
1426
|
const toolContext = {
|
|
943
1427
|
toolUse: {
|
|
944
|
-
name:
|
|
945
|
-
toolUseId:
|
|
946
|
-
input:
|
|
1428
|
+
name: toolUse.name,
|
|
1429
|
+
toolUseId: toolUse.toolUseId,
|
|
1430
|
+
input: toolUse.input,
|
|
947
1431
|
},
|
|
948
1432
|
agent: this,
|
|
1433
|
+
invocationState,
|
|
1434
|
+
interrupt: (params) => {
|
|
1435
|
+
return interruptFromAgent(this, `tool:${toolUseBlock.toolUseId}:${params.name}`, params);
|
|
1436
|
+
},
|
|
949
1437
|
};
|
|
950
1438
|
try {
|
|
951
1439
|
// Manually iterate tool stream to wrap each ToolStreamEvent in ToolStreamUpdateEvent.
|
|
@@ -953,19 +1441,19 @@ export class Agent {
|
|
|
953
1441
|
// without knowledge of agents or hooks, and we wrap at the boundary.
|
|
954
1442
|
// Tool execution is ran within the tool span's context so that
|
|
955
1443
|
// downstream calls (e.g., MCP clients) can propagate trace context
|
|
956
|
-
const toolGenerator = this._tracer.withSpanContext(toolSpan, () =>
|
|
1444
|
+
const toolGenerator = this._tracer.withSpanContext(toolSpan, () => effectiveTool.stream(toolContext));
|
|
957
1445
|
let toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
|
|
958
1446
|
while (!toolNext.done) {
|
|
959
|
-
yield new ToolStreamUpdateEvent({ agent: this, event: toolNext.value });
|
|
1447
|
+
yield new ToolStreamUpdateEvent({ agent: this, event: toolNext.value, invocationState });
|
|
960
1448
|
toolNext = await this._tracer.withSpanContext(toolSpan, () => toolGenerator.next());
|
|
961
1449
|
}
|
|
962
1450
|
const result = toolNext.value;
|
|
963
1451
|
if (!result) {
|
|
964
1452
|
// Tool didn't return a result
|
|
965
1453
|
toolResult = new ToolResultBlock({
|
|
966
|
-
toolUseId:
|
|
1454
|
+
toolUseId: toolUse.toolUseId,
|
|
967
1455
|
status: 'error',
|
|
968
|
-
content: [new TextBlock(`Tool '${
|
|
1456
|
+
content: [new TextBlock(`Tool '${toolUse.name}' did not return a result`)],
|
|
969
1457
|
});
|
|
970
1458
|
}
|
|
971
1459
|
else {
|
|
@@ -974,17 +1462,22 @@ export class Agent {
|
|
|
974
1462
|
}
|
|
975
1463
|
}
|
|
976
1464
|
catch (e) {
|
|
1465
|
+
// Re-throw InterruptError to allow interrupt handling
|
|
1466
|
+
if (e instanceof InterruptError) {
|
|
1467
|
+
throw e;
|
|
1468
|
+
}
|
|
977
1469
|
// Tool execution failed with error
|
|
978
1470
|
error = normalizeError(e);
|
|
979
1471
|
toolResult = new ToolResultBlock({
|
|
980
|
-
toolUseId:
|
|
1472
|
+
toolUseId: toolUse.toolUseId,
|
|
981
1473
|
status: 'error',
|
|
982
1474
|
content: [new TextBlock(error.message)],
|
|
983
1475
|
error,
|
|
984
1476
|
});
|
|
985
1477
|
}
|
|
986
1478
|
}
|
|
987
|
-
// End tool span
|
|
1479
|
+
// End tool span with the raw tool result — telemetry reflects what the
|
|
1480
|
+
// tool actually returned, independent of AfterToolCallEvent mutations.
|
|
988
1481
|
this._tracer.endToolCallSpan(toolSpan, { toolResult, ...(error && { error }) });
|
|
989
1482
|
// End tool metrics tracking
|
|
990
1483
|
this._meter.endToolCall({
|
|
@@ -996,15 +1489,18 @@ export class Agent {
|
|
|
996
1489
|
const afterToolCallEvent = new AfterToolCallEvent({
|
|
997
1490
|
agent: this,
|
|
998
1491
|
toolUse,
|
|
999
|
-
tool,
|
|
1492
|
+
tool: effectiveTool,
|
|
1000
1493
|
result: toolResult,
|
|
1494
|
+
invocationState,
|
|
1001
1495
|
...(error !== undefined && { error }),
|
|
1002
1496
|
});
|
|
1003
1497
|
yield afterToolCallEvent;
|
|
1004
1498
|
if (afterToolCallEvent.retry) {
|
|
1005
1499
|
continue;
|
|
1006
1500
|
}
|
|
1007
|
-
|
|
1501
|
+
// Return the (possibly mutated) result so hook transformations propagate
|
|
1502
|
+
// to ToolResultEvent and the conversation message the model will see.
|
|
1503
|
+
return afterToolCallEvent.result;
|
|
1008
1504
|
}
|
|
1009
1505
|
}
|
|
1010
1506
|
/**
|
|
@@ -1052,24 +1548,94 @@ export class Agent {
|
|
|
1052
1548
|
}
|
|
1053
1549
|
}
|
|
1054
1550
|
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Estimate the input token count for the next model call.
|
|
1553
|
+
*
|
|
1554
|
+
* Uses the token counting strategy: reads inputTokens + outputTokens
|
|
1555
|
+
* from the last assistant message's metadata as a known baseline, then estimates
|
|
1556
|
+
* only new messages added after it. Falls back to full estimation when no metadata
|
|
1557
|
+
* is available (cold start or first call).
|
|
1558
|
+
*
|
|
1559
|
+
* @param streamOptions - The stream options containing system prompt and tool specs
|
|
1560
|
+
* @returns Estimated input token count
|
|
1561
|
+
*/
|
|
1562
|
+
async _estimateInputTokens(streamOptions) {
|
|
1563
|
+
// Find the last assistant message with usage metadata
|
|
1564
|
+
let lastAssistantIdx = -1;
|
|
1565
|
+
for (let i = this.messages.length - 1; i >= 0; i--) {
|
|
1566
|
+
if (this.messages[i].role === 'assistant' && this.messages[i].metadata?.usage) {
|
|
1567
|
+
lastAssistantIdx = i;
|
|
1568
|
+
break;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
let estimate;
|
|
1572
|
+
if (lastAssistantIdx >= 0) {
|
|
1573
|
+
const usage = this.messages[lastAssistantIdx].metadata.usage;
|
|
1574
|
+
const knownBaseline = usage.inputTokens + usage.outputTokens;
|
|
1575
|
+
const newMessages = this.messages.slice(lastAssistantIdx + 1);
|
|
1576
|
+
if (newMessages.length === 0) {
|
|
1577
|
+
estimate = knownBaseline;
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
// System prompt and tool spec tokens are already included in the baseline from the prior model call
|
|
1581
|
+
estimate = knownBaseline + (await this.model.countTokens(newMessages));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
estimate = await this.model.countTokens(this.messages, {
|
|
1586
|
+
...(streamOptions.systemPrompt !== undefined && { systemPrompt: streamOptions.systemPrompt }),
|
|
1587
|
+
...(streamOptions.toolSpecs !== undefined && { toolSpecs: streamOptions.toolSpecs }),
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
return estimate;
|
|
1591
|
+
}
|
|
1055
1592
|
/**
|
|
1056
1593
|
* Appends a message to the conversation history and returns the event for yielding.
|
|
1057
1594
|
*
|
|
1058
1595
|
* @param message - The message to append
|
|
1059
1596
|
* @returns MessageAddedEvent to be yielded
|
|
1060
1597
|
*/
|
|
1061
|
-
_appendMessage(message) {
|
|
1598
|
+
_appendMessage(message, invocationState) {
|
|
1062
1599
|
this.messages.push(message);
|
|
1063
|
-
return new MessageAddedEvent({ agent: this, message });
|
|
1600
|
+
return new MessageAddedEvent({ agent: this, message, invocationState });
|
|
1064
1601
|
}
|
|
1065
1602
|
}
|
|
1603
|
+
const INVALID_TOOL_NAME_PLACEHOLDER = 'INVALID_TOOL_NAME';
|
|
1066
1604
|
/**
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1605
|
+
* Replaces invalid tool-use names on assistant messages with `INVALID_TOOL_NAME`
|
|
1606
|
+
* so providers that reject malformed names don't fail the whole request.
|
|
1607
|
+
* Returns the input unchanged (same reference) when nothing needs replacing.
|
|
1070
1608
|
*/
|
|
1071
|
-
function
|
|
1072
|
-
|
|
1609
|
+
function normalizeToolUseNames(messages) {
|
|
1610
|
+
let replaced = false;
|
|
1611
|
+
const next = messages.map((message) => {
|
|
1612
|
+
if (!message || message.role !== 'assistant')
|
|
1613
|
+
return message;
|
|
1614
|
+
let messageReplaced = false;
|
|
1615
|
+
const content = message.content.map((block) => {
|
|
1616
|
+
if (block.type !== 'toolUseBlock')
|
|
1617
|
+
return block;
|
|
1618
|
+
if (isValidToolName(block.name))
|
|
1619
|
+
return block;
|
|
1620
|
+
messageReplaced = true;
|
|
1621
|
+
logger.debug(`tool_name=<${block.name}> | replacing invalid tool name with ${INVALID_TOOL_NAME_PLACEHOLDER}`);
|
|
1622
|
+
return new ToolUseBlock({
|
|
1623
|
+
name: INVALID_TOOL_NAME_PLACEHOLDER,
|
|
1624
|
+
toolUseId: block.toolUseId,
|
|
1625
|
+
input: block.input,
|
|
1626
|
+
...(block.reasoningSignature !== undefined && { reasoningSignature: block.reasoningSignature }),
|
|
1627
|
+
});
|
|
1628
|
+
});
|
|
1629
|
+
if (!messageReplaced)
|
|
1630
|
+
return message;
|
|
1631
|
+
replaced = true;
|
|
1632
|
+
return new Message({
|
|
1633
|
+
role: message.role,
|
|
1634
|
+
content,
|
|
1635
|
+
...(message.metadata !== undefined && { metadata: message.metadata }),
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
return replaced ? next : messages;
|
|
1073
1639
|
}
|
|
1074
1640
|
/**
|
|
1075
1641
|
* Recursively flattens nested arrays of tools into a single flat array.
|