@strands-agents/sdk 1.7.0 → 1.8.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/dist/src/agent/agent.d.ts +1 -1
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +1 -1
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts +3 -3
- package/dist/src/context-manager/modes/agentic/agentic-context.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/index.node.d.ts.map +1 -1
- package/dist/src/index.node.js +3 -0
- package/dist/src/index.node.js.map +1 -1
- package/dist/src/interventions/actions.d.ts.map +1 -1
- package/dist/src/interventions/actions.js.map +1 -1
- package/dist/src/{mcp.d.ts → mcp/client.d.ts} +13 -4
- package/dist/src/mcp/client.d.ts.map +1 -0
- package/dist/src/{mcp.js → mcp/client.js} +15 -3
- package/dist/src/mcp/client.js.map +1 -0
- package/dist/src/mcp/config.d.ts +52 -0
- package/dist/src/mcp/config.d.ts.map +1 -0
- package/dist/src/mcp/config.js +12 -0
- package/dist/src/mcp/config.js.map +1 -0
- package/dist/src/mcp/config.node.d.ts +16 -0
- package/dist/src/mcp/config.node.d.ts.map +1 -0
- package/dist/src/mcp/config.node.js +145 -0
- package/dist/src/mcp/config.node.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +2 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +2 -1
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/multiagent/graph.d.ts +7 -0
- package/dist/src/multiagent/graph.d.ts.map +1 -1
- package/dist/src/multiagent/graph.js +22 -1
- package/dist/src/multiagent/graph.js.map +1 -1
- package/dist/src/multiagent/nodes.d.ts +5 -0
- package/dist/src/multiagent/nodes.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.js +15 -0
- package/dist/src/multiagent/nodes.js.map +1 -1
- package/dist/src/multiagent/state.d.ts.map +1 -1
- package/dist/src/multiagent/state.js.map +1 -1
- package/dist/src/tools/function-tool.d.ts +1 -1
- package/dist/src/tools/function-tool.d.ts.map +1 -1
- package/dist/src/tools/function-tool.js +4 -12
- package/dist/src/tools/function-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +2 -1
- package/dist/src/tools/mcp-tool.d.ts.map +1 -1
- package/dist/src/tools/mcp-tool.js +1 -0
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/tool-factory.d.ts +1 -1
- package/dist/src/tools/tool-factory.d.ts.map +1 -1
- package/dist/src/tools/types.d.ts +4 -0
- package/dist/src/tools/types.d.ts.map +1 -1
- package/dist/src/tools/zod-tool.d.ts +4 -2
- package/dist/src/tools/zod-tool.d.ts.map +1 -1
- package/dist/src/tools/zod-tool.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/media.d.ts.map +1 -1
- package/dist/src/types/media.js.map +1 -1
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/vended-memory-stores/local/index.d.ts +2 -0
- package/dist/src/vended-memory-stores/local/index.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/local/index.js +2 -0
- package/dist/src/vended-memory-stores/local/index.js.map +1 -0
- package/dist/src/vended-memory-stores/local/store.d.ts +122 -0
- package/dist/src/vended-memory-stores/local/store.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/local/store.js +281 -0
- package/dist/src/vended-memory-stores/local/store.js.map +1 -0
- package/dist/src/vended-plugins/context-offloader/search.d.ts.map +1 -1
- package/dist/src/vended-plugins/context-offloader/search.js +4 -2
- package/dist/src/vended-plugins/context-offloader/search.js.map +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.js +1 -1
- package/dist/src/vended-plugins/context-offloader/storage.js.map +1 -1
- package/dist/src/vended-plugins/skills/skill.d.ts.map +1 -1
- package/dist/src/vended-plugins/skills/skill.js.map +1 -1
- package/dist/src/vended-tools/notebook/notebook.d.ts +1 -1
- package/package.json +21 -14
- package/dist/src/__fixtures__/agent-helpers.d.ts +0 -136
- package/dist/src/__fixtures__/agent-helpers.d.ts.map +0 -1
- package/dist/src/__fixtures__/agent-helpers.js +0 -156
- package/dist/src/__fixtures__/agent-helpers.js.map +0 -1
- package/dist/src/__fixtures__/environment.d.ts +0 -12
- package/dist/src/__fixtures__/environment.d.ts.map +0 -1
- package/dist/src/__fixtures__/environment.js +0 -12
- package/dist/src/__fixtures__/environment.js.map +0 -1
- package/dist/src/__fixtures__/metrics-helpers.d.ts +0 -55
- package/dist/src/__fixtures__/metrics-helpers.d.ts.map +0 -1
- package/dist/src/__fixtures__/metrics-helpers.js +0 -57
- package/dist/src/__fixtures__/metrics-helpers.js.map +0 -1
- package/dist/src/__fixtures__/mock-message-model.d.ts +0 -103
- package/dist/src/__fixtures__/mock-message-model.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-message-model.js +0 -245
- package/dist/src/__fixtures__/mock-message-model.js.map +0 -1
- package/dist/src/__fixtures__/mock-meter.d.ts +0 -32
- package/dist/src/__fixtures__/mock-meter.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-meter.js +0 -47
- package/dist/src/__fixtures__/mock-meter.js.map +0 -1
- package/dist/src/__fixtures__/mock-plugin.d.ts +0 -13
- package/dist/src/__fixtures__/mock-plugin.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-plugin.js +0 -35
- package/dist/src/__fixtures__/mock-plugin.js.map +0 -1
- package/dist/src/__fixtures__/mock-span.d.ts +0 -78
- package/dist/src/__fixtures__/mock-span.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-span.js +0 -93
- package/dist/src/__fixtures__/mock-span.js.map +0 -1
- package/dist/src/__fixtures__/mock-storage-provider.d.ts +0 -42
- package/dist/src/__fixtures__/mock-storage-provider.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-storage-provider.js +0 -122
- package/dist/src/__fixtures__/mock-storage-provider.js.map +0 -1
- package/dist/src/__fixtures__/model-test-helpers.d.ts +0 -56
- package/dist/src/__fixtures__/model-test-helpers.d.ts.map +0 -1
- package/dist/src/__fixtures__/model-test-helpers.js +0 -85
- package/dist/src/__fixtures__/model-test-helpers.js.map +0 -1
- package/dist/src/__fixtures__/register-node-defaults.d.ts +0 -2
- package/dist/src/__fixtures__/register-node-defaults.d.ts.map +0 -1
- package/dist/src/__fixtures__/register-node-defaults.js +0 -6
- package/dist/src/__fixtures__/register-node-defaults.js.map +0 -1
- package/dist/src/__fixtures__/slim-types.d.ts +0 -51
- package/dist/src/__fixtures__/slim-types.d.ts.map +0 -1
- package/dist/src/__fixtures__/slim-types.js +0 -6
- package/dist/src/__fixtures__/slim-types.js.map +0 -1
- package/dist/src/__fixtures__/test-sandbox.node.d.ts +0 -15
- package/dist/src/__fixtures__/test-sandbox.node.d.ts.map +0 -1
- package/dist/src/__fixtures__/test-sandbox.node.js +0 -23
- package/dist/src/__fixtures__/test-sandbox.node.js.map +0 -1
- package/dist/src/__fixtures__/tool-helpers.d.ts +0 -45
- package/dist/src/__fixtures__/tool-helpers.d.ts.map +0 -1
- package/dist/src/__fixtures__/tool-helpers.js +0 -101
- package/dist/src/__fixtures__/tool-helpers.js.map +0 -1
- package/dist/src/__tests__/default-slot.test.d.ts +0 -2
- package/dist/src/__tests__/default-slot.test.d.ts.map +0 -1
- package/dist/src/__tests__/default-slot.test.js +0 -33
- package/dist/src/__tests__/default-slot.test.js.map +0 -1
- package/dist/src/__tests__/errors.test.d.ts +0 -2
- package/dist/src/__tests__/errors.test.d.ts.map +0 -1
- package/dist/src/__tests__/errors.test.js +0 -170
- package/dist/src/__tests__/errors.test.js.map +0 -1
- package/dist/src/__tests__/index.test.d.ts +0 -2
- package/dist/src/__tests__/index.test.d.ts.map +0 -1
- package/dist/src/__tests__/index.test.js +0 -48
- package/dist/src/__tests__/index.test.js.map +0 -1
- package/dist/src/__tests__/interrupt.test.d.ts +0 -2
- package/dist/src/__tests__/interrupt.test.d.ts.map +0 -1
- package/dist/src/__tests__/interrupt.test.js +0 -264
- package/dist/src/__tests__/interrupt.test.js.map +0 -1
- package/dist/src/__tests__/mcp.test.d.ts +0 -2
- package/dist/src/__tests__/mcp.test.d.ts.map +0 -1
- package/dist/src/__tests__/mcp.test.js +0 -1003
- package/dist/src/__tests__/mcp.test.js.map +0 -1
- package/dist/src/__tests__/mime.test.d.ts +0 -2
- package/dist/src/__tests__/mime.test.d.ts.map +0 -1
- package/dist/src/__tests__/mime.test.js +0 -83
- package/dist/src/__tests__/mime.test.js.map +0 -1
- package/dist/src/__tests__/state-store.test.d.ts +0 -2
- package/dist/src/__tests__/state-store.test.d.ts.map +0 -1
- package/dist/src/__tests__/state-store.test.js +0 -285
- package/dist/src/__tests__/state-store.test.js.map +0 -1
- package/dist/src/a2a/__tests__/a2a-agent.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/a2a-agent.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/a2a-agent.test.js +0 -383
- package/dist/src/a2a/__tests__/a2a-agent.test.js.map +0 -1
- package/dist/src/a2a/__tests__/adapters.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/adapters.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/adapters.test.js +0 -151
- package/dist/src/a2a/__tests__/adapters.test.js.map +0 -1
- package/dist/src/a2a/__tests__/async-lock.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/async-lock.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/async-lock.test.js +0 -137
- package/dist/src/a2a/__tests__/async-lock.test.js.map +0 -1
- package/dist/src/a2a/__tests__/events.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/events.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/events.test.js +0 -68
- package/dist/src/a2a/__tests__/events.test.js.map +0 -1
- package/dist/src/a2a/__tests__/executor.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/executor.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/executor.test.js +0 -345
- package/dist/src/a2a/__tests__/executor.test.js.map +0 -1
- package/dist/src/a2a/__tests__/server.test.d.ts +0 -2
- package/dist/src/a2a/__tests__/server.test.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/server.test.js +0 -71
- package/dist/src/a2a/__tests__/server.test.js.map +0 -1
- package/dist/src/a2a/__tests__/server.test.node.d.ts +0 -2
- package/dist/src/a2a/__tests__/server.test.node.d.ts.map +0 -1
- package/dist/src/a2a/__tests__/server.test.node.js +0 -110
- package/dist/src/a2a/__tests__/server.test.node.js.map +0 -1
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js +0 -23
- package/dist/src/agent/__tests__/agent-as-tool.invocation-state.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent-as-tool.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent-as-tool.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent-as-tool.test.js +0 -347
- package/dist/src/agent/__tests__/agent-as-tool.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.cancel.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.cancel.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.cancel.test.js +0 -337
- package/dist/src/agent/__tests__/agent.cancel.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.concurrent.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.concurrent.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.concurrent.test.js +0 -488
- package/dist/src/agent/__tests__/agent.concurrent.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.context-manager.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.context-manager.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.context-manager.test.js +0 -106
- package/dist/src/agent/__tests__/agent.context-manager.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.hook.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.hook.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.hook.test.js +0 -1332
- package/dist/src/agent/__tests__/agent.hook.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.interrupt.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.interrupt.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.interrupt.test.js +0 -779
- package/dist/src/agent/__tests__/agent.interrupt.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.invocation-state.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.invocation-state.test.js +0 -219
- package/dist/src/agent/__tests__/agent.invocation-state.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.model-retry.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.model-retry.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.model-retry.test.js +0 -161
- package/dist/src/agent/__tests__/agent.model-retry.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.stateful-model.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.stateful-model.test.js +0 -169
- package/dist/src/agent/__tests__/agent.stateful-model.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.test.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.test.js +0 -1624
- package/dist/src/agent/__tests__/agent.test.js.map +0 -1
- package/dist/src/agent/__tests__/agent.tracer.test.node.d.ts +0 -2
- package/dist/src/agent/__tests__/agent.tracer.test.node.d.ts.map +0 -1
- package/dist/src/agent/__tests__/agent.tracer.test.node.js +0 -535
- package/dist/src/agent/__tests__/agent.tracer.test.node.js.map +0 -1
- package/dist/src/agent/__tests__/printer.test.d.ts +0 -2
- package/dist/src/agent/__tests__/printer.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/printer.test.js +0 -188
- package/dist/src/agent/__tests__/printer.test.js.map +0 -1
- package/dist/src/agent/__tests__/snapshot.test.d.ts +0 -2
- package/dist/src/agent/__tests__/snapshot.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/snapshot.test.js +0 -442
- package/dist/src/agent/__tests__/snapshot.test.js.map +0 -1
- package/dist/src/agent/__tests__/tool-caller.test.d.ts +0 -2
- package/dist/src/agent/__tests__/tool-caller.test.d.ts.map +0 -1
- package/dist/src/agent/__tests__/tool-caller.test.js +0 -459
- package/dist/src/agent/__tests__/tool-caller.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/agentic-context.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js +0 -332
- package/dist/src/conversation-manager/__tests__/agentic-context.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/context-compression.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/context-compression.test.js +0 -176
- package/dist/src/conversation-manager/__tests__/context-compression.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +0 -321
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +0 -64
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/pin.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/pin.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/pin.test.js +0 -119
- package/dist/src/conversation-manager/__tests__/pin.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +0 -1080
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js +0 -400
- package/dist/src/conversation-manager/__tests__/summarizing-conversation-manager.test.js.map +0 -1
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts +0 -2
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.d.ts.map +0 -1
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js +0 -138
- package/dist/src/conversation-manager/__tests__/token-usage-middleware.test.js.map +0 -1
- package/dist/src/hooks/__tests__/events.test.d.ts +0 -2
- package/dist/src/hooks/__tests__/events.test.d.ts.map +0 -1
- package/dist/src/hooks/__tests__/events.test.js +0 -1060
- package/dist/src/hooks/__tests__/events.test.js.map +0 -1
- package/dist/src/hooks/__tests__/registry.test.d.ts +0 -2
- package/dist/src/hooks/__tests__/registry.test.d.ts.map +0 -1
- package/dist/src/hooks/__tests__/registry.test.js +0 -325
- package/dist/src/hooks/__tests__/registry.test.js.map +0 -1
- package/dist/src/injection/__tests__/message-injection.test.d.ts +0 -2
- package/dist/src/injection/__tests__/message-injection.test.d.ts.map +0 -1
- package/dist/src/injection/__tests__/message-injection.test.js +0 -200
- package/dist/src/injection/__tests__/message-injection.test.js.map +0 -1
- package/dist/src/interventions/__tests__/handler.test.d.ts +0 -2
- package/dist/src/interventions/__tests__/handler.test.d.ts.map +0 -1
- package/dist/src/interventions/__tests__/handler.test.js +0 -35
- package/dist/src/interventions/__tests__/handler.test.js.map +0 -1
- package/dist/src/interventions/__tests__/registry.test.d.ts +0 -2
- package/dist/src/interventions/__tests__/registry.test.d.ts.map +0 -1
- package/dist/src/interventions/__tests__/registry.test.js +0 -692
- package/dist/src/interventions/__tests__/registry.test.js.map +0 -1
- package/dist/src/logging/__tests__/logger.test.d.ts +0 -2
- package/dist/src/logging/__tests__/logger.test.d.ts.map +0 -1
- package/dist/src/logging/__tests__/logger.test.js +0 -70
- package/dist/src/logging/__tests__/logger.test.js.map +0 -1
- package/dist/src/logging/__tests__/warn-once.test.d.ts +0 -2
- package/dist/src/logging/__tests__/warn-once.test.d.ts.map +0 -1
- package/dist/src/logging/__tests__/warn-once.test.js +0 -30
- package/dist/src/logging/__tests__/warn-once.test.js.map +0 -1
- package/dist/src/mcp.d.ts.map +0 -1
- package/dist/src/mcp.js.map +0 -1
- package/dist/src/memory/__tests__/memory-manager.test.d.ts +0 -2
- package/dist/src/memory/__tests__/memory-manager.test.d.ts.map +0 -1
- package/dist/src/memory/__tests__/memory-manager.test.js +0 -679
- package/dist/src/memory/__tests__/memory-manager.test.js.map +0 -1
- package/dist/src/memory/__tests__/memory-tracing.test.d.ts +0 -2
- package/dist/src/memory/__tests__/memory-tracing.test.d.ts.map +0 -1
- package/dist/src/memory/__tests__/memory-tracing.test.js +0 -250
- package/dist/src/memory/__tests__/memory-tracing.test.js.map +0 -1
- package/dist/src/memory/extraction/__tests__/extraction.test.d.ts +0 -2
- package/dist/src/memory/extraction/__tests__/extraction.test.d.ts.map +0 -1
- package/dist/src/memory/extraction/__tests__/extraction.test.js +0 -639
- package/dist/src/memory/extraction/__tests__/extraction.test.js.map +0 -1
- package/dist/src/memory/extraction/__tests__/model-extractor.test.d.ts +0 -2
- package/dist/src/memory/extraction/__tests__/model-extractor.test.d.ts.map +0 -1
- package/dist/src/memory/extraction/__tests__/model-extractor.test.js +0 -104
- package/dist/src/memory/extraction/__tests__/model-extractor.test.js.map +0 -1
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.d.ts +0 -2
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.d.ts.map +0 -1
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.js +0 -81
- package/dist/src/memory/extraction/__tests__/resolve-extraction-config.test.js.map +0 -1
- package/dist/src/middleware/__tests__/agent-middleware.test.d.ts +0 -2
- package/dist/src/middleware/__tests__/agent-middleware.test.d.ts.map +0 -1
- package/dist/src/middleware/__tests__/agent-middleware.test.js +0 -1206
- package/dist/src/middleware/__tests__/agent-middleware.test.js.map +0 -1
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts +0 -2
- package/dist/src/middleware/__tests__/copy-on-input.test.d.ts.map +0 -1
- package/dist/src/middleware/__tests__/copy-on-input.test.js +0 -379
- package/dist/src/middleware/__tests__/copy-on-input.test.js.map +0 -1
- package/dist/src/middleware/__tests__/custom-stages.test.d.ts +0 -2
- package/dist/src/middleware/__tests__/custom-stages.test.d.ts.map +0 -1
- package/dist/src/middleware/__tests__/custom-stages.test.js +0 -97
- package/dist/src/middleware/__tests__/custom-stages.test.js.map +0 -1
- package/dist/src/middleware/__tests__/middleware-interrupts.test.d.ts +0 -2
- package/dist/src/middleware/__tests__/middleware-interrupts.test.d.ts.map +0 -1
- package/dist/src/middleware/__tests__/middleware-interrupts.test.js +0 -267
- package/dist/src/middleware/__tests__/middleware-interrupts.test.js.map +0 -1
- package/dist/src/middleware/__tests__/registry.test.d.ts +0 -2
- package/dist/src/middleware/__tests__/registry.test.d.ts.map +0 -1
- package/dist/src/middleware/__tests__/registry.test.js +0 -525
- package/dist/src/middleware/__tests__/registry.test.js.map +0 -1
- package/dist/src/models/__tests__/anthropic.test.d.ts +0 -2
- package/dist/src/models/__tests__/anthropic.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/anthropic.test.js +0 -793
- package/dist/src/models/__tests__/anthropic.test.js.map +0 -1
- package/dist/src/models/__tests__/bedrock.test.d.ts +0 -2
- package/dist/src/models/__tests__/bedrock.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/bedrock.test.js +0 -3964
- package/dist/src/models/__tests__/bedrock.test.js.map +0 -1
- package/dist/src/models/__tests__/defaults.test.d.ts +0 -2
- package/dist/src/models/__tests__/defaults.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/defaults.test.js +0 -36
- package/dist/src/models/__tests__/defaults.test.js.map +0 -1
- package/dist/src/models/__tests__/google.test.d.ts +0 -2
- package/dist/src/models/__tests__/google.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/google.test.js +0 -1145
- package/dist/src/models/__tests__/google.test.js.map +0 -1
- package/dist/src/models/__tests__/model.test.d.ts +0 -2
- package/dist/src/models/__tests__/model.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/model.test.js +0 -921
- package/dist/src/models/__tests__/model.test.js.map +0 -1
- package/dist/src/models/__tests__/streaming.test.d.ts +0 -2
- package/dist/src/models/__tests__/streaming.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/streaming.test.js +0 -50
- package/dist/src/models/__tests__/streaming.test.js.map +0 -1
- package/dist/src/models/__tests__/test-utils.d.ts +0 -10
- package/dist/src/models/__tests__/test-utils.d.ts.map +0 -1
- package/dist/src/models/__tests__/test-utils.js +0 -17
- package/dist/src/models/__tests__/test-utils.js.map +0 -1
- package/dist/src/models/__tests__/vercel.test.d.ts +0 -2
- package/dist/src/models/__tests__/vercel.test.d.ts.map +0 -1
- package/dist/src/models/__tests__/vercel.test.js +0 -740
- package/dist/src/models/__tests__/vercel.test.js.map +0 -1
- package/dist/src/models/openai/__tests__/chat.test.d.ts +0 -2
- package/dist/src/models/openai/__tests__/chat.test.d.ts.map +0 -1
- package/dist/src/models/openai/__tests__/chat.test.js +0 -1586
- package/dist/src/models/openai/__tests__/chat.test.js.map +0 -1
- package/dist/src/models/openai/__tests__/errors.test.d.ts +0 -2
- package/dist/src/models/openai/__tests__/errors.test.d.ts.map +0 -1
- package/dist/src/models/openai/__tests__/errors.test.js +0 -39
- package/dist/src/models/openai/__tests__/errors.test.js.map +0 -1
- package/dist/src/models/openai/__tests__/mantle.test.d.ts +0 -2
- package/dist/src/models/openai/__tests__/mantle.test.d.ts.map +0 -1
- package/dist/src/models/openai/__tests__/mantle.test.js +0 -189
- package/dist/src/models/openai/__tests__/mantle.test.js.map +0 -1
- package/dist/src/models/openai/__tests__/responses.test.d.ts +0 -2
- package/dist/src/models/openai/__tests__/responses.test.d.ts.map +0 -1
- package/dist/src/models/openai/__tests__/responses.test.js +0 -709
- package/dist/src/models/openai/__tests__/responses.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/events.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/events.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/events.test.js +0 -493
- package/dist/src/multiagent/__tests__/events.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.js +0 -95
- package/dist/src/multiagent/__tests__/graph.invocation-state.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/graph.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/graph.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/graph.test.js +0 -698
- package/dist/src/multiagent/__tests__/graph.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/graph.tracer.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/graph.tracer.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/graph.tracer.test.js +0 -218
- package/dist/src/multiagent/__tests__/graph.tracer.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/interrupts.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/interrupts.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/interrupts.test.js +0 -390
- package/dist/src/multiagent/__tests__/interrupts.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/nodes.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/nodes.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/nodes.test.js +0 -321
- package/dist/src/multiagent/__tests__/nodes.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/queue.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/queue.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/queue.test.js +0 -118
- package/dist/src/multiagent/__tests__/queue.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/snapshot.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/snapshot.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/snapshot.test.js +0 -161
- package/dist/src/multiagent/__tests__/snapshot.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/state.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/state.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/state.test.js +0 -526
- package/dist/src/multiagent/__tests__/state.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js +0 -56
- package/dist/src/multiagent/__tests__/swarm.invocation-state.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/swarm.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.test.js +0 -477
- package/dist/src/multiagent/__tests__/swarm.test.js.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.tracer.test.d.ts +0 -2
- package/dist/src/multiagent/__tests__/swarm.tracer.test.d.ts.map +0 -1
- package/dist/src/multiagent/__tests__/swarm.tracer.test.js +0 -235
- package/dist/src/multiagent/__tests__/swarm.tracer.test.js.map +0 -1
- package/dist/src/plugins/__tests__/plugin.test.d.ts +0 -2
- package/dist/src/plugins/__tests__/plugin.test.d.ts.map +0 -1
- package/dist/src/plugins/__tests__/plugin.test.js +0 -114
- package/dist/src/plugins/__tests__/plugin.test.js.map +0 -1
- package/dist/src/plugins/__tests__/registry.test.d.ts +0 -2
- package/dist/src/plugins/__tests__/registry.test.d.ts.map +0 -1
- package/dist/src/plugins/__tests__/registry.test.js +0 -147
- package/dist/src/plugins/__tests__/registry.test.js.map +0 -1
- package/dist/src/registry/__tests__/tool-registry.test.d.ts +0 -2
- package/dist/src/registry/__tests__/tool-registry.test.d.ts.map +0 -1
- package/dist/src/registry/__tests__/tool-registry.test.js +0 -210
- package/dist/src/registry/__tests__/tool-registry.test.js.map +0 -1
- package/dist/src/retry/__tests__/backoff-strategy.test.d.ts +0 -2
- package/dist/src/retry/__tests__/backoff-strategy.test.d.ts.map +0 -1
- package/dist/src/retry/__tests__/backoff-strategy.test.js +0 -116
- package/dist/src/retry/__tests__/backoff-strategy.test.js.map +0 -1
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts +0 -2
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.d.ts.map +0 -1
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.js +0 -225
- package/dist/src/retry/__tests__/default-model-retry-strategy.test.js.map +0 -1
- package/dist/src/sandbox/__tests__/default.test.browser.d.ts +0 -2
- package/dist/src/sandbox/__tests__/default.test.browser.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/default.test.browser.js +0 -11
- package/dist/src/sandbox/__tests__/default.test.browser.js.map +0 -1
- package/dist/src/sandbox/__tests__/default.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/default.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/default.test.node.js +0 -23
- package/dist/src/sandbox/__tests__/default.test.node.js.map +0 -1
- package/dist/src/sandbox/__tests__/docker.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/docker.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/docker.test.node.js +0 -89
- package/dist/src/sandbox/__tests__/docker.test.node.js.map +0 -1
- package/dist/src/sandbox/__tests__/errors.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/errors.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/errors.test.node.js +0 -33
- package/dist/src/sandbox/__tests__/errors.test.node.js.map +0 -1
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.js +0 -124
- package/dist/src/sandbox/__tests__/not-a-sandbox-local-environment.test.node.js.map +0 -1
- package/dist/src/sandbox/__tests__/posix-shell.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/posix-shell.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/posix-shell.test.node.js +0 -298
- package/dist/src/sandbox/__tests__/posix-shell.test.node.js.map +0 -1
- package/dist/src/sandbox/__tests__/ssh.test.node.d.ts +0 -2
- package/dist/src/sandbox/__tests__/ssh.test.node.d.ts.map +0 -1
- package/dist/src/sandbox/__tests__/ssh.test.node.js +0 -262
- package/dist/src/sandbox/__tests__/ssh.test.node.js.map +0 -1
- package/dist/src/session/__tests__/file-storage.test.node.d.ts +0 -2
- package/dist/src/session/__tests__/file-storage.test.node.d.ts.map +0 -1
- package/dist/src/session/__tests__/file-storage.test.node.js +0 -278
- package/dist/src/session/__tests__/file-storage.test.node.js.map +0 -1
- package/dist/src/session/__tests__/s3-storage.test.d.ts +0 -2
- package/dist/src/session/__tests__/s3-storage.test.d.ts.map +0 -1
- package/dist/src/session/__tests__/s3-storage.test.js +0 -461
- package/dist/src/session/__tests__/s3-storage.test.js.map +0 -1
- package/dist/src/session/__tests__/session-manager.test.d.ts +0 -2
- package/dist/src/session/__tests__/session-manager.test.d.ts.map +0 -1
- package/dist/src/session/__tests__/session-manager.test.js +0 -832
- package/dist/src/session/__tests__/session-manager.test.js.map +0 -1
- package/dist/src/session/__tests__/validation.test.d.ts +0 -2
- package/dist/src/session/__tests__/validation.test.d.ts.map +0 -1
- package/dist/src/session/__tests__/validation.test.js +0 -47
- package/dist/src/session/__tests__/validation.test.js.map +0 -1
- package/dist/src/telemetry/__tests__/config.test.d.ts +0 -2
- package/dist/src/telemetry/__tests__/config.test.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/config.test.js +0 -64
- package/dist/src/telemetry/__tests__/config.test.js.map +0 -1
- package/dist/src/telemetry/__tests__/config.test.node.d.ts +0 -2
- package/dist/src/telemetry/__tests__/config.test.node.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/config.test.node.js +0 -164
- package/dist/src/telemetry/__tests__/config.test.node.js.map +0 -1
- package/dist/src/telemetry/__tests__/json.test.d.ts +0 -2
- package/dist/src/telemetry/__tests__/json.test.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/json.test.js +0 -89
- package/dist/src/telemetry/__tests__/json.test.js.map +0 -1
- package/dist/src/telemetry/__tests__/local-trace.test.d.ts +0 -2
- package/dist/src/telemetry/__tests__/local-trace.test.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/local-trace.test.js +0 -158
- package/dist/src/telemetry/__tests__/local-trace.test.js.map +0 -1
- package/dist/src/telemetry/__tests__/meter.test.d.ts +0 -2
- package/dist/src/telemetry/__tests__/meter.test.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/meter.test.js +0 -695
- package/dist/src/telemetry/__tests__/meter.test.js.map +0 -1
- package/dist/src/telemetry/__tests__/tracer.test.node.d.ts +0 -2
- package/dist/src/telemetry/__tests__/tracer.test.node.d.ts.map +0 -1
- package/dist/src/telemetry/__tests__/tracer.test.node.js +0 -958
- package/dist/src/telemetry/__tests__/tracer.test.node.js.map +0 -1
- package/dist/src/tools/__tests__/structured-output-tool.test.d.ts +0 -2
- package/dist/src/tools/__tests__/structured-output-tool.test.d.ts.map +0 -1
- package/dist/src/tools/__tests__/structured-output-tool.test.js +0 -84
- package/dist/src/tools/__tests__/structured-output-tool.test.js.map +0 -1
- package/dist/src/tools/__tests__/tool-factory.test.d.ts +0 -2
- package/dist/src/tools/__tests__/tool-factory.test.d.ts.map +0 -1
- package/dist/src/tools/__tests__/tool-factory.test.js +0 -98
- package/dist/src/tools/__tests__/tool-factory.test.js.map +0 -1
- package/dist/src/tools/__tests__/tool.test.d.ts +0 -2
- package/dist/src/tools/__tests__/tool.test.d.ts.map +0 -1
- package/dist/src/tools/__tests__/tool.test.js +0 -1043
- package/dist/src/tools/__tests__/tool.test.js.map +0 -1
- package/dist/src/tools/__tests__/zod-tool.test-d.d.ts +0 -2
- package/dist/src/tools/__tests__/zod-tool.test-d.d.ts.map +0 -1
- package/dist/src/tools/__tests__/zod-tool.test-d.js +0 -227
- package/dist/src/tools/__tests__/zod-tool.test-d.js.map +0 -1
- package/dist/src/tools/__tests__/zod-tool.test.d.ts +0 -2
- package/dist/src/tools/__tests__/zod-tool.test.d.ts.map +0 -1
- package/dist/src/tools/__tests__/zod-tool.test.js +0 -563
- package/dist/src/tools/__tests__/zod-tool.test.js.map +0 -1
- package/dist/src/types/__tests__/agent.test.d.ts +0 -2
- package/dist/src/types/__tests__/agent.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/agent.test.js +0 -476
- package/dist/src/types/__tests__/agent.test.js.map +0 -1
- package/dist/src/types/__tests__/citations.test.d.ts +0 -2
- package/dist/src/types/__tests__/citations.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/citations.test.js +0 -104
- package/dist/src/types/__tests__/citations.test.js.map +0 -1
- package/dist/src/types/__tests__/json.test.d.ts +0 -2
- package/dist/src/types/__tests__/json.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/json.test.js +0 -298
- package/dist/src/types/__tests__/json.test.js.map +0 -1
- package/dist/src/types/__tests__/media.test.d.ts +0 -2
- package/dist/src/types/__tests__/media.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/media.test.js +0 -478
- package/dist/src/types/__tests__/media.test.js.map +0 -1
- package/dist/src/types/__tests__/messages.test.d.ts +0 -2
- package/dist/src/types/__tests__/messages.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/messages.test.js +0 -681
- package/dist/src/types/__tests__/messages.test.js.map +0 -1
- package/dist/src/types/__tests__/validation.test.d.ts +0 -2
- package/dist/src/types/__tests__/validation.test.d.ts.map +0 -1
- package/dist/src/types/__tests__/validation.test.js +0 -30
- package/dist/src/types/__tests__/validation.test.js.map +0 -1
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts +0 -2
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.d.ts.map +0 -1
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js +0 -756
- package/dist/src/vended-interventions/cedar/__tests__/cedar.test.node.js.map +0 -1
- package/dist/src/vended-interventions/hitl/__tests__/hitl.test.d.ts +0 -2
- package/dist/src/vended-interventions/hitl/__tests__/hitl.test.d.ts.map +0 -1
- package/dist/src/vended-interventions/hitl/__tests__/hitl.test.js +0 -358
- package/dist/src/vended-interventions/hitl/__tests__/hitl.test.js.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/handler.test.d.ts +0 -2
- package/dist/src/vended-interventions/steering/__tests__/handler.test.d.ts.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/handler.test.js +0 -163
- package/dist/src/vended-interventions/steering/__tests__/handler.test.js.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/llm.test.d.ts +0 -2
- package/dist/src/vended-interventions/steering/__tests__/llm.test.d.ts.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/llm.test.js +0 -60
- package/dist/src/vended-interventions/steering/__tests__/llm.test.js.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/tool-ledger.test.d.ts +0 -2
- package/dist/src/vended-interventions/steering/__tests__/tool-ledger.test.d.ts.map +0 -1
- package/dist/src/vended-interventions/steering/__tests__/tool-ledger.test.js +0 -94
- package/dist/src/vended-interventions/steering/__tests__/tool-ledger.test.js.map +0 -1
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.node.d.ts +0 -2
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.node.d.ts.map +0 -1
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.node.js +0 -758
- package/dist/src/vended-memory-stores/bedrock-knowledge-base/__tests__/bedrock-knowledge-base-store.test.node.js.map +0 -1
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts +0 -2
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js +0 -96
- package/dist/src/vended-plugins/context-injector/__tests__/plugin.test.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.js +0 -68
- package/dist/src/vended-plugins/context-offloader/__tests__/file-storage-sandbox.test.node.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js +0 -539
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.js +0 -97
- package/dist/src/vended-plugins/context-offloader/__tests__/plugin.test.node.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/search.test.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/search.test.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/search.test.js +0 -149
- package/dist/src/vended-plugins/context-offloader/__tests__/search.test.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js +0 -216
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.js.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts +0 -2
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.d.ts.map +0 -1
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js +0 -78
- package/dist/src/vended-plugins/context-offloader/__tests__/storage.test.node.js.map +0 -1
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.d.ts +0 -2
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.d.ts.map +0 -1
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.js +0 -736
- package/dist/src/vended-plugins/goal/__tests__/plugin.test.js.map +0 -1
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.d.ts +0 -2
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.d.ts.map +0 -1
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js +0 -482
- package/dist/src/vended-plugins/skills/__tests__/agent-skills.test.node.js.map +0 -1
- package/dist/src/vended-plugins/skills/__tests__/skill.test.node.d.ts +0 -2
- package/dist/src/vended-plugins/skills/__tests__/skill.test.node.d.ts.map +0 -1
- package/dist/src/vended-plugins/skills/__tests__/skill.test.node.js +0 -476
- package/dist/src/vended-plugins/skills/__tests__/skill.test.node.js.map +0 -1
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.d.ts +0 -2
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.d.ts.map +0 -1
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +0 -390
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +0 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.d.ts +0 -2
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.d.ts.map +0 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js +0 -452
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +0 -1
- package/dist/src/vended-tools/http-request/__tests__/http-request.test.d.ts +0 -2
- package/dist/src/vended-tools/http-request/__tests__/http-request.test.d.ts.map +0 -1
- package/dist/src/vended-tools/http-request/__tests__/http-request.test.js +0 -189
- package/dist/src/vended-tools/http-request/__tests__/http-request.test.js.map +0 -1
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.d.ts +0 -2
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.d.ts.map +0 -1
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +0 -388
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +0 -1
|
@@ -1,1624 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { Agent } from '../agent.js';
|
|
4
|
-
import { McpClient } from '../../mcp.js';
|
|
5
|
-
import { McpTool } from '../../tools/mcp-tool.js';
|
|
6
|
-
import { MockMessageModel } from '../../__fixtures__/mock-message-model.js';
|
|
7
|
-
import { collectGenerator } from '../../__fixtures__/model-test-helpers.js';
|
|
8
|
-
import { createMockTool, createRandomTool } from '../../__fixtures__/tool-helpers.js';
|
|
9
|
-
import { ConcurrentInvocationError } from '../../errors.js';
|
|
10
|
-
import { MaxTokensError, TextBlock, CachePointBlock, Message, ToolUseBlock, ToolResultBlock, ReasoningBlock, GuardContentBlock, ImageBlock, VideoBlock, DocumentBlock, } from '../../index.js';
|
|
11
|
-
import { AgentPrinter } from '../printer.js';
|
|
12
|
-
import { AfterInvocationEvent, AfterToolCallEvent, AfterToolsEvent, BeforeInvocationEvent, BeforeModelCallEvent, BeforeToolsEvent, } from '../../hooks/events.js';
|
|
13
|
-
import { BedrockModel } from '../../models/bedrock.js';
|
|
14
|
-
import { StructuredOutputError } from '../../errors.js';
|
|
15
|
-
import { expectLoopMetrics } from '../../__fixtures__/metrics-helpers.js';
|
|
16
|
-
import { expectAgentResult } from '../../__fixtures__/agent-helpers.js';
|
|
17
|
-
describe('Agent', () => {
|
|
18
|
-
describe('stream', () => {
|
|
19
|
-
describe('basic streaming', () => {
|
|
20
|
-
it('returns AsyncGenerator', () => {
|
|
21
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
22
|
-
const agent = new Agent({ model });
|
|
23
|
-
const result = agent.stream('Test prompt');
|
|
24
|
-
expect(result).toBeDefined();
|
|
25
|
-
expect(typeof result[Symbol.asyncIterator]).toBe('function');
|
|
26
|
-
});
|
|
27
|
-
it('returns AsyncGenerator that can be iterated without type errors', async () => {
|
|
28
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
29
|
-
const agent = new Agent({ model });
|
|
30
|
-
// Ensures that the signature of agent.stream is correct
|
|
31
|
-
for await (const _ of agent.stream('Test prompt')) {
|
|
32
|
-
/* intentionally empty */
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
it('yields AgentStreamEvent objects', async () => {
|
|
36
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
37
|
-
const agent = new Agent({ model });
|
|
38
|
-
const { items } = await collectGenerator(agent.stream('Test prompt'));
|
|
39
|
-
expect(items.length).toBeGreaterThan(0);
|
|
40
|
-
const firstItem = items[0];
|
|
41
|
-
expect(firstItem).toEqual(new BeforeInvocationEvent({ agent: agent, invocationState: {} }));
|
|
42
|
-
});
|
|
43
|
-
it('returns AgentResult as generator return value', async () => {
|
|
44
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
45
|
-
const agent = new Agent({ model });
|
|
46
|
-
const { result } = await collectGenerator(agent.stream('Test prompt'));
|
|
47
|
-
expect(result).toEqual(expectAgentResult({
|
|
48
|
-
stopReason: 'endTurn',
|
|
49
|
-
messageText: 'Hello',
|
|
50
|
-
cycleCount: 1,
|
|
51
|
-
traceCount: 1,
|
|
52
|
-
}));
|
|
53
|
-
// Verify trace structure
|
|
54
|
-
expect(result.traces?.[0]?.children).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'stream_messages' })]));
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('with tool use', () => {
|
|
58
|
-
it('handles tool execution flow', async () => {
|
|
59
|
-
const model = new MockMessageModel()
|
|
60
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
61
|
-
.addTurn({ type: 'textBlock', text: 'Tool result processed' });
|
|
62
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
63
|
-
toolUseId: 'tool-1',
|
|
64
|
-
status: 'success',
|
|
65
|
-
content: [new TextBlock('Tool executed')],
|
|
66
|
-
}));
|
|
67
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
68
|
-
const { items, result } = await collectGenerator(agent.stream('Use the tool'));
|
|
69
|
-
// Check that tool-related events are yielded
|
|
70
|
-
const toolEvents = items.filter((event) => event.type === 'beforeToolsEvent' || event.type === 'afterToolsEvent');
|
|
71
|
-
expect(toolEvents.length).toBeGreaterThan(0);
|
|
72
|
-
// Check final result
|
|
73
|
-
expect(result.stopReason).toBe('endTurn');
|
|
74
|
-
});
|
|
75
|
-
it('yields tool-related events', async () => {
|
|
76
|
-
const model = new MockMessageModel()
|
|
77
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
78
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
79
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
80
|
-
toolUseId: 'tool-1',
|
|
81
|
-
status: 'success',
|
|
82
|
-
content: [new TextBlock('Success')],
|
|
83
|
-
}));
|
|
84
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
85
|
-
const { items } = await collectGenerator(agent.stream('Test'));
|
|
86
|
-
const beforeTools = items.find((e) => e.type === 'beforeToolsEvent');
|
|
87
|
-
const afterTools = items.find((e) => e.type === 'afterToolsEvent');
|
|
88
|
-
expect(beforeTools).toEqual(new BeforeToolsEvent({
|
|
89
|
-
agent: agent,
|
|
90
|
-
message: new Message({
|
|
91
|
-
role: 'assistant',
|
|
92
|
-
content: [new ToolUseBlock({ name: 'testTool', toolUseId: 'tool-1', input: {} })],
|
|
93
|
-
}),
|
|
94
|
-
invocationState: {},
|
|
95
|
-
}));
|
|
96
|
-
expect(afterTools).toBeDefined();
|
|
97
|
-
expect(afterTools?.type).toBe('afterToolsEvent');
|
|
98
|
-
expect(afterTools?.message).toEqual({
|
|
99
|
-
type: 'message',
|
|
100
|
-
role: 'user',
|
|
101
|
-
content: [
|
|
102
|
-
{
|
|
103
|
-
type: 'toolResultBlock',
|
|
104
|
-
toolUseId: 'tool-1',
|
|
105
|
-
status: 'success',
|
|
106
|
-
content: [{ type: 'textBlock', text: 'Success' }],
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
});
|
|
110
|
-
expect(afterTools).toHaveProperty('agent', agent);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
describe('error handling', () => {
|
|
114
|
-
it('throws MaxTokensError when model hits token limit', async () => {
|
|
115
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Partial...' }, { stopReason: 'maxTokens' });
|
|
116
|
-
const agent = new Agent({ model });
|
|
117
|
-
await expect(async () => {
|
|
118
|
-
await collectGenerator(agent.stream('Test'));
|
|
119
|
-
}).rejects.toThrow(MaxTokensError);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
describe('hook error cleanup', () => {
|
|
123
|
-
it('fires AfterInvocationEvent when consumer breaks from stream and allows reinvocation', async () => {
|
|
124
|
-
const model = new MockMessageModel()
|
|
125
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
126
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
127
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
128
|
-
toolUseId: 'tool-1',
|
|
129
|
-
status: 'success',
|
|
130
|
-
content: [new TextBlock('ok')],
|
|
131
|
-
}));
|
|
132
|
-
const agent = new Agent({ model, tools: [tool], printer: false });
|
|
133
|
-
const afterInvocationCallback = vi.fn();
|
|
134
|
-
agent.addHook(AfterInvocationEvent, afterInvocationCallback);
|
|
135
|
-
for await (const event of agent.stream('Test')) {
|
|
136
|
-
if (event.type === 'beforeToolsEvent') {
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
expect(afterInvocationCallback).toHaveBeenCalledOnce();
|
|
141
|
-
const result = await agent.invoke('Test again');
|
|
142
|
-
expect(result.stopReason).toBe('endTurn');
|
|
143
|
-
expect(result.lastMessage.content[0]).toEqual(new TextBlock('Done'));
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
describe('invoke', () => {
|
|
148
|
-
describe('basic invocation', () => {
|
|
149
|
-
it('returns Promise<AgentResult>', async () => {
|
|
150
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
151
|
-
const agent = new Agent({ model });
|
|
152
|
-
const result = agent.invoke('Test prompt');
|
|
153
|
-
expect(result).toBeInstanceOf(Promise);
|
|
154
|
-
const awaited = await result;
|
|
155
|
-
expect(awaited).toHaveProperty('stopReason');
|
|
156
|
-
expect(awaited).toHaveProperty('lastMessage');
|
|
157
|
-
});
|
|
158
|
-
it('returns correct stopReason and lastMessage', async () => {
|
|
159
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response text' });
|
|
160
|
-
const agent = new Agent({ model });
|
|
161
|
-
const result = await agent.invoke('Test prompt');
|
|
162
|
-
expect(result).toEqual(expectAgentResult({
|
|
163
|
-
stopReason: 'endTurn',
|
|
164
|
-
messageText: 'Response text',
|
|
165
|
-
cycleCount: 1,
|
|
166
|
-
traceCount: 1,
|
|
167
|
-
}));
|
|
168
|
-
});
|
|
169
|
-
it('consumes stream events internally', async () => {
|
|
170
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
171
|
-
const agent = new Agent({ model });
|
|
172
|
-
const result = await agent.invoke('Test');
|
|
173
|
-
expect(result).toEqual(expect.objectContaining({
|
|
174
|
-
stopReason: 'endTurn',
|
|
175
|
-
lastMessage: expect.objectContaining({
|
|
176
|
-
type: 'message',
|
|
177
|
-
role: 'assistant',
|
|
178
|
-
content: expect.arrayContaining([expect.objectContaining({ type: 'textBlock', text: 'Hello' })]),
|
|
179
|
-
}),
|
|
180
|
-
metrics: expectLoopMetrics({ cycleCount: 1 }),
|
|
181
|
-
}));
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
describe('with tool use', () => {
|
|
185
|
-
it('executes tools and returns final result', async () => {
|
|
186
|
-
const model = new MockMessageModel()
|
|
187
|
-
.addTurn({ type: 'toolUseBlock', name: 'calc', toolUseId: 'tool-1', input: { a: 1, b: 2 } }, {
|
|
188
|
-
usage: { inputTokens: 100, outputTokens: 50, totalTokens: 150 },
|
|
189
|
-
})
|
|
190
|
-
.addTurn({ type: 'textBlock', text: 'The answer is 3' }, {
|
|
191
|
-
usage: { inputTokens: 200, outputTokens: 30, totalTokens: 230 },
|
|
192
|
-
});
|
|
193
|
-
const tool = createMockTool('calc', () => new ToolResultBlock({
|
|
194
|
-
toolUseId: 'tool-1',
|
|
195
|
-
status: 'success',
|
|
196
|
-
content: [new TextBlock('3')],
|
|
197
|
-
}));
|
|
198
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
199
|
-
const result = await agent.invoke('What is 1 + 2?');
|
|
200
|
-
expect(result).toEqual(expectAgentResult({
|
|
201
|
-
stopReason: 'endTurn',
|
|
202
|
-
messageText: 'The answer is 3',
|
|
203
|
-
cycleCount: 2,
|
|
204
|
-
toolNames: ['calc'],
|
|
205
|
-
traceCount: 2,
|
|
206
|
-
usage: { inputTokens: 300, outputTokens: 80, totalTokens: 380 },
|
|
207
|
-
}));
|
|
208
|
-
// Verify detailed trace children structure
|
|
209
|
-
expect(result.traces?.[0]?.children).toEqual(expect.arrayContaining([
|
|
210
|
-
expect.objectContaining({ name: 'stream_messages' }),
|
|
211
|
-
expect.objectContaining({ name: 'Tool: calc' }),
|
|
212
|
-
]));
|
|
213
|
-
expect(result.traces?.[1]?.children).toEqual(expect.arrayContaining([expect.objectContaining({ name: 'stream_messages' })]));
|
|
214
|
-
});
|
|
215
|
-
it('stores cycleId in trace metadata', async () => {
|
|
216
|
-
const model = new MockMessageModel()
|
|
217
|
-
.addTurn({ type: 'toolUseBlock', name: 'calc', toolUseId: 'tool-1', input: {} })
|
|
218
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
219
|
-
const tool = createMockTool('calc', () => new ToolResultBlock({
|
|
220
|
-
toolUseId: 'tool-1',
|
|
221
|
-
status: 'success',
|
|
222
|
-
content: [new TextBlock('result')],
|
|
223
|
-
}));
|
|
224
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
225
|
-
const result = await agent.invoke('Test');
|
|
226
|
-
expect(result.traces).toEqual([
|
|
227
|
-
expect.objectContaining({
|
|
228
|
-
name: 'Cycle 1',
|
|
229
|
-
metadata: expect.objectContaining({ cycleId: 'cycle-1' }),
|
|
230
|
-
}),
|
|
231
|
-
expect.objectContaining({
|
|
232
|
-
name: 'Cycle 2',
|
|
233
|
-
metadata: expect.objectContaining({ cycleId: 'cycle-2' }),
|
|
234
|
-
}),
|
|
235
|
-
]);
|
|
236
|
-
});
|
|
237
|
-
it('stores tool metadata in trace children', async () => {
|
|
238
|
-
const model = new MockMessageModel()
|
|
239
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-abc123', input: {} })
|
|
240
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
241
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
242
|
-
toolUseId: 'tool-abc123',
|
|
243
|
-
status: 'success',
|
|
244
|
-
content: [new TextBlock('result')],
|
|
245
|
-
}));
|
|
246
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
247
|
-
const result = await agent.invoke('Test');
|
|
248
|
-
expect(result.traces).toEqual(expect.arrayContaining([
|
|
249
|
-
expect.objectContaining({
|
|
250
|
-
name: 'Cycle 1',
|
|
251
|
-
children: expect.arrayContaining([
|
|
252
|
-
expect.objectContaining({
|
|
253
|
-
name: 'Tool: testTool',
|
|
254
|
-
metadata: expect.objectContaining({
|
|
255
|
-
toolUseId: 'tool-abc123',
|
|
256
|
-
toolName: 'testTool',
|
|
257
|
-
}),
|
|
258
|
-
}),
|
|
259
|
-
]),
|
|
260
|
-
}),
|
|
261
|
-
]));
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
describe('error handling', () => {
|
|
265
|
-
it('propagates maxTokens error', async () => {
|
|
266
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Partial' }, { stopReason: 'maxTokens' });
|
|
267
|
-
const agent = new Agent({ model });
|
|
268
|
-
await expect(agent.invoke('Test')).rejects.toThrow(MaxTokensError);
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
describe('metrics on errors', () => {
|
|
272
|
-
it('tracks cycle count when maxTokens error occurs', async () => {
|
|
273
|
-
const model = new MockMessageModel()
|
|
274
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} }, {
|
|
275
|
-
usage: { inputTokens: 100, outputTokens: 50, totalTokens: 150 },
|
|
276
|
-
})
|
|
277
|
-
.addTurn({ type: 'textBlock', text: 'Partial' }, {
|
|
278
|
-
stopReason: 'maxTokens',
|
|
279
|
-
usage: { inputTokens: 80, outputTokens: 20, totalTokens: 100 },
|
|
280
|
-
});
|
|
281
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
282
|
-
toolUseId: 'tool-1',
|
|
283
|
-
status: 'success',
|
|
284
|
-
content: [new TextBlock('Done')],
|
|
285
|
-
}));
|
|
286
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
287
|
-
const meter = agent._meter;
|
|
288
|
-
await expect(agent.invoke('Test')).rejects.toThrow(MaxTokensError);
|
|
289
|
-
expect(meter.metrics.cycleCount).toBe(2);
|
|
290
|
-
// Only the first turn's usage is accumulated; the second turn throws
|
|
291
|
-
// MaxTokensError inside streamAggregated before metadata reaches updateCycle
|
|
292
|
-
expect(meter.metrics.accumulatedUsage).toStrictEqual({
|
|
293
|
-
inputTokens: 100,
|
|
294
|
-
outputTokens: 50,
|
|
295
|
-
totalTokens: 150,
|
|
296
|
-
});
|
|
297
|
-
expect(meter.metrics.accumulatedMetrics).toStrictEqual({
|
|
298
|
-
latencyMs: expect.any(Number),
|
|
299
|
-
});
|
|
300
|
-
expect(meter.metrics.toolMetrics).toStrictEqual({
|
|
301
|
-
testTool: {
|
|
302
|
-
callCount: 1,
|
|
303
|
-
successCount: 1,
|
|
304
|
-
errorCount: 0,
|
|
305
|
-
totalTime: expect.any(Number),
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
it('collects local traces for completed cycles when error occurs mid-run', async () => {
|
|
310
|
-
const model = new MockMessageModel()
|
|
311
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} }, {
|
|
312
|
-
usage: { inputTokens: 100, outputTokens: 50, totalTokens: 150 },
|
|
313
|
-
})
|
|
314
|
-
.addTurn({ type: 'textBlock', text: 'Partial' }, {
|
|
315
|
-
stopReason: 'maxTokens',
|
|
316
|
-
usage: { inputTokens: 80, outputTokens: 20, totalTokens: 100 },
|
|
317
|
-
});
|
|
318
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
319
|
-
toolUseId: 'tool-1',
|
|
320
|
-
status: 'success',
|
|
321
|
-
content: [new TextBlock('Done')],
|
|
322
|
-
}));
|
|
323
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
324
|
-
const tracer = agent._tracer;
|
|
325
|
-
await expect(agent.invoke('Test')).rejects.toThrow(MaxTokensError);
|
|
326
|
-
// Cycle 1 completed (tool use), cycle 2 errored (maxTokens)
|
|
327
|
-
expect(tracer.localTraces).toEqual([
|
|
328
|
-
expect.objectContaining({
|
|
329
|
-
name: 'Cycle 1',
|
|
330
|
-
children: [
|
|
331
|
-
expect.objectContaining({ name: 'stream_messages' }),
|
|
332
|
-
expect.objectContaining({ name: 'Tool: testTool' }),
|
|
333
|
-
],
|
|
334
|
-
}),
|
|
335
|
-
expect.objectContaining({
|
|
336
|
-
name: 'Cycle 2',
|
|
337
|
-
children: [expect.objectContaining({ name: 'stream_messages' })],
|
|
338
|
-
}),
|
|
339
|
-
]);
|
|
340
|
-
});
|
|
341
|
-
it('tracks metrics when a hook throws an error', async () => {
|
|
342
|
-
const model = new MockMessageModel()
|
|
343
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} }, {
|
|
344
|
-
usage: { inputTokens: 60, outputTokens: 25, totalTokens: 85 },
|
|
345
|
-
})
|
|
346
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
347
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
348
|
-
toolUseId: 'tool-1',
|
|
349
|
-
status: 'success',
|
|
350
|
-
content: [new TextBlock('Result')],
|
|
351
|
-
}));
|
|
352
|
-
const agent = new Agent({ model, tools: [tool] });
|
|
353
|
-
agent.addHook(BeforeToolsEvent, () => {
|
|
354
|
-
throw new Error('Hook failure');
|
|
355
|
-
});
|
|
356
|
-
const meter = agent._meter;
|
|
357
|
-
await expect(agent.invoke('Test')).rejects.toThrow('Hook failure');
|
|
358
|
-
// The hook throws after the model returns but before tools execute,
|
|
359
|
-
// so the first cycle's model usage is recorded but no tool metrics exist
|
|
360
|
-
expect(meter.metrics.cycleCount).toBe(1);
|
|
361
|
-
expect(meter.metrics.accumulatedUsage).toStrictEqual({
|
|
362
|
-
inputTokens: 60,
|
|
363
|
-
outputTokens: 25,
|
|
364
|
-
totalTokens: 85,
|
|
365
|
-
});
|
|
366
|
-
expect(meter.metrics.accumulatedMetrics).toStrictEqual({
|
|
367
|
-
latencyMs: expect.any(Number),
|
|
368
|
-
});
|
|
369
|
-
expect(meter.metrics.toolMetrics).toStrictEqual({});
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
describe('hook error cleanup', () => {
|
|
373
|
-
it('fires AfterInvocationEvent when a mid-stream hook throws', async () => {
|
|
374
|
-
const model = new MockMessageModel()
|
|
375
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
376
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
377
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
378
|
-
toolUseId: 'tool-1',
|
|
379
|
-
status: 'success',
|
|
380
|
-
content: [new TextBlock('ok')],
|
|
381
|
-
}));
|
|
382
|
-
const agent = new Agent({ model, tools: [tool], printer: false });
|
|
383
|
-
agent.addHook(AfterToolCallEvent, () => {
|
|
384
|
-
throw new Error('hook error');
|
|
385
|
-
});
|
|
386
|
-
const afterInvocationCallback = vi.fn();
|
|
387
|
-
agent.addHook(AfterInvocationEvent, afterInvocationCallback);
|
|
388
|
-
await expect(agent.invoke('Test')).rejects.toThrow('hook error');
|
|
389
|
-
expect(afterInvocationCallback).toHaveBeenCalledOnce();
|
|
390
|
-
});
|
|
391
|
-
it('fires AfterToolsEvent when a mid-stream hook throws', async () => {
|
|
392
|
-
const model = new MockMessageModel()
|
|
393
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
394
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
395
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
396
|
-
toolUseId: 'tool-1',
|
|
397
|
-
status: 'success',
|
|
398
|
-
content: [new TextBlock('ok')],
|
|
399
|
-
}));
|
|
400
|
-
const agent = new Agent({ model, tools: [tool], printer: false });
|
|
401
|
-
agent.addHook(AfterToolCallEvent, () => {
|
|
402
|
-
throw new Error('hook error');
|
|
403
|
-
});
|
|
404
|
-
const afterToolsCallback = vi.fn();
|
|
405
|
-
agent.addHook(AfterToolsEvent, afterToolsCallback);
|
|
406
|
-
await expect(agent.invoke('Test')).rejects.toThrow('hook error');
|
|
407
|
-
expect(afterToolsCallback).toHaveBeenCalledOnce();
|
|
408
|
-
});
|
|
409
|
-
it('does not fire AfterInvocationEvent when BeforeInvocationEvent hook throws', async () => {
|
|
410
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
411
|
-
const agent = new Agent({ model, printer: false });
|
|
412
|
-
agent.addHook(BeforeInvocationEvent, () => {
|
|
413
|
-
throw new Error('before hook error');
|
|
414
|
-
});
|
|
415
|
-
const afterInvocationCallback = vi.fn();
|
|
416
|
-
agent.addHook(AfterInvocationEvent, afterInvocationCallback);
|
|
417
|
-
await expect(agent.invoke('Test')).rejects.toThrow('before hook error');
|
|
418
|
-
expect(afterInvocationCallback).not.toHaveBeenCalled();
|
|
419
|
-
});
|
|
420
|
-
it('does not fire AfterToolsEvent when BeforeToolsEvent hook throws', async () => {
|
|
421
|
-
const model = new MockMessageModel()
|
|
422
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'tool-1', input: {} })
|
|
423
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
424
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
425
|
-
toolUseId: 'tool-1',
|
|
426
|
-
status: 'success',
|
|
427
|
-
content: [new TextBlock('ok')],
|
|
428
|
-
}));
|
|
429
|
-
const agent = new Agent({ model, tools: [tool], printer: false });
|
|
430
|
-
agent.addHook(BeforeToolsEvent, () => {
|
|
431
|
-
throw new Error('before tools hook error');
|
|
432
|
-
});
|
|
433
|
-
const afterToolsCallback = vi.fn();
|
|
434
|
-
agent.addHook(AfterToolsEvent, afterToolsCallback);
|
|
435
|
-
await expect(agent.invoke('Test')).rejects.toThrow('before tools hook error');
|
|
436
|
-
expect(afterToolsCallback).not.toHaveBeenCalled();
|
|
437
|
-
});
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
describe('API consistency', () => {
|
|
441
|
-
it('invoke() and stream() produce same final result', async () => {
|
|
442
|
-
const model1 = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Consistent response' });
|
|
443
|
-
const model2 = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Consistent response' });
|
|
444
|
-
const agent1 = new Agent({ model: model1 });
|
|
445
|
-
const agent2 = new Agent({ model: model2 });
|
|
446
|
-
const invokeResult = await agent1.invoke('Test');
|
|
447
|
-
const { result: streamResult } = await collectGenerator(agent2.stream('Test'));
|
|
448
|
-
expect(invokeResult.stopReason).toBe(streamResult.stopReason);
|
|
449
|
-
expect(invokeResult.lastMessage.content).toEqual(streamResult.lastMessage.content);
|
|
450
|
-
});
|
|
451
|
-
it('both methods produce same result with tool use', async () => {
|
|
452
|
-
const createToolAndModels = () => {
|
|
453
|
-
const model = new MockMessageModel()
|
|
454
|
-
.addTurn({ type: 'toolUseBlock', name: 'testTool', toolUseId: 'id', input: {} })
|
|
455
|
-
.addTurn({ type: 'textBlock', text: 'Final' });
|
|
456
|
-
const tool = createMockTool('testTool', () => new ToolResultBlock({
|
|
457
|
-
toolUseId: 'id',
|
|
458
|
-
status: 'success',
|
|
459
|
-
content: [new TextBlock('Tool ran')],
|
|
460
|
-
}));
|
|
461
|
-
return { model, tool };
|
|
462
|
-
};
|
|
463
|
-
const { model: model1, tool: tool1 } = createToolAndModels();
|
|
464
|
-
const { model: model2, tool: tool2 } = createToolAndModels();
|
|
465
|
-
const agent1 = new Agent({ model: model1, tools: [tool1] });
|
|
466
|
-
const agent2 = new Agent({ model: model2, tools: [tool2] });
|
|
467
|
-
const invokeResult = await agent1.invoke('Use tool');
|
|
468
|
-
const { result: streamResult } = await collectGenerator(agent2.stream('Use tool'));
|
|
469
|
-
expect(invokeResult).toEqual(expect.objectContaining({
|
|
470
|
-
stopReason: streamResult.stopReason,
|
|
471
|
-
lastMessage: streamResult.lastMessage,
|
|
472
|
-
traces: streamResult.traces?.map((t) => expect.objectContaining({
|
|
473
|
-
name: t.name,
|
|
474
|
-
children: expect.arrayContaining(Array(t.children.length).fill(expect.objectContaining({ name: expect.any(String) }))),
|
|
475
|
-
})),
|
|
476
|
-
}));
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
describe('messages', () => {
|
|
480
|
-
it('returns array of messages', () => {
|
|
481
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
482
|
-
const agent = new Agent({ model });
|
|
483
|
-
expect(agent.messages).toEqual([]);
|
|
484
|
-
});
|
|
485
|
-
it('reflects conversation history after invoke', async () => {
|
|
486
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
487
|
-
const agent = new Agent({ model });
|
|
488
|
-
await agent.invoke('Hello');
|
|
489
|
-
expect(agent.messages).toEqual([
|
|
490
|
-
expect.objectContaining({
|
|
491
|
-
role: 'user',
|
|
492
|
-
content: [{ type: 'textBlock', text: 'Hello' }],
|
|
493
|
-
}),
|
|
494
|
-
expect.objectContaining({
|
|
495
|
-
role: 'assistant',
|
|
496
|
-
content: [{ type: 'textBlock', text: 'Response' }],
|
|
497
|
-
}),
|
|
498
|
-
]);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
501
|
-
describe('printer configuration', () => {
|
|
502
|
-
it('validates output when printer is enabled', async () => {
|
|
503
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello world' });
|
|
504
|
-
// Capture output
|
|
505
|
-
const outputs = [];
|
|
506
|
-
const mockAppender = (text) => outputs.push(text);
|
|
507
|
-
// Create agent with custom printer for testing
|
|
508
|
-
const agent = new Agent({ model, printer: false });
|
|
509
|
-
agent._printer = new AgentPrinter(mockAppender);
|
|
510
|
-
await collectGenerator(agent.stream('Test'));
|
|
511
|
-
// Validate that text was output
|
|
512
|
-
const allOutput = outputs.join('');
|
|
513
|
-
expect(allOutput).toContain('Hello world');
|
|
514
|
-
});
|
|
515
|
-
it('does not create printer when printer is false', () => {
|
|
516
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
517
|
-
const agent = new Agent({ model, printer: false });
|
|
518
|
-
expect(agent).toBeDefined();
|
|
519
|
-
expect(agent._printer).toBeUndefined();
|
|
520
|
-
});
|
|
521
|
-
it('defaults to printer=true when not specified', () => {
|
|
522
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
523
|
-
const agent = new Agent({ model });
|
|
524
|
-
expect(agent).toBeDefined();
|
|
525
|
-
expect(agent._printer).toBeDefined();
|
|
526
|
-
});
|
|
527
|
-
it('agent works correctly with printer disabled', async () => {
|
|
528
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
529
|
-
const agent = new Agent({ model, printer: false });
|
|
530
|
-
const { result } = await collectGenerator(agent.stream('Test'));
|
|
531
|
-
expect(result).toBeDefined();
|
|
532
|
-
expect(result.lastMessage.content).toEqual([{ type: 'textBlock', text: 'Hello' }]);
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
describe('concurrency guards', () => {
|
|
536
|
-
it('prevents parallel invocations', async () => {
|
|
537
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
538
|
-
const agent = new Agent({ model });
|
|
539
|
-
// Test parallel invoke() calls
|
|
540
|
-
const invokePromise1 = agent.invoke('First');
|
|
541
|
-
const invokePromise2 = agent.invoke('Second');
|
|
542
|
-
await expect(invokePromise2).rejects.toThrow(ConcurrentInvocationError);
|
|
543
|
-
await expect(invokePromise1).resolves.toBeDefined();
|
|
544
|
-
});
|
|
545
|
-
it('allows sequential invocations after lock is released', async () => {
|
|
546
|
-
const model = new MockMessageModel()
|
|
547
|
-
.addTurn({ type: 'textBlock', text: 'First response' })
|
|
548
|
-
.addTurn({ type: 'textBlock', text: 'Second response' });
|
|
549
|
-
const agent = new Agent({ model });
|
|
550
|
-
const result1 = await agent.invoke('First');
|
|
551
|
-
expect(result1.lastMessage.content).toEqual([{ type: 'textBlock', text: 'First response' }]);
|
|
552
|
-
const result2 = await agent.invoke('Second');
|
|
553
|
-
expect(result2.lastMessage.content).toEqual([{ type: 'textBlock', text: 'Second response' }]);
|
|
554
|
-
});
|
|
555
|
-
it('releases lock after errors and abandoned streams', async () => {
|
|
556
|
-
// Test error case
|
|
557
|
-
const model = new MockMessageModel()
|
|
558
|
-
.addTurn({ type: 'textBlock', text: 'Partial' }, { stopReason: 'maxTokens' })
|
|
559
|
-
.addTurn({ type: 'textBlock', text: 'Success' });
|
|
560
|
-
const agent = new Agent({ model });
|
|
561
|
-
await expect(agent.invoke('First')).rejects.toThrow(MaxTokensError);
|
|
562
|
-
const result = await agent.invoke('Second');
|
|
563
|
-
expect(result.lastMessage.content).toEqual([{ type: 'textBlock', text: 'Success' }]);
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
describe('nested tool arrays', () => {
|
|
567
|
-
describe('flattens nested arrays at any depth', () => {
|
|
568
|
-
const tool1 = createRandomTool();
|
|
569
|
-
const tool2 = createRandomTool();
|
|
570
|
-
const tool3 = createRandomTool();
|
|
571
|
-
it.for([
|
|
572
|
-
['flat array', [tool1, tool2, tool3], [tool1, tool2, tool3]],
|
|
573
|
-
['single tool', [tool1], [tool1]],
|
|
574
|
-
['empty array', [], []],
|
|
575
|
-
['single level nesting', [[tool1, tool2], tool3], [tool1, tool2, tool3]],
|
|
576
|
-
['empty nested arrays', [[], tool1, []], [tool1]],
|
|
577
|
-
['deeply nested', [[[tool1]], [tool2], tool3], [tool1, tool2, tool3]],
|
|
578
|
-
['mixed nesting', [[tool1, [tool2]], tool3], [tool1, tool2, tool3]],
|
|
579
|
-
['very deep nesting', [[[[tool1]]]], [tool1]],
|
|
580
|
-
])('%i', ([, input, expected]) => {
|
|
581
|
-
const agent = new Agent({ tools: input });
|
|
582
|
-
expect(agent.tools).toEqual(expected);
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
it('accepts undefined tools', () => {
|
|
586
|
-
const agent = new Agent({});
|
|
587
|
-
expect(agent.tools).toEqual([]);
|
|
588
|
-
});
|
|
589
|
-
it('catches duplicate tool names across nested arrays', () => {
|
|
590
|
-
const tool1 = createRandomTool('duplicate');
|
|
591
|
-
const tool2 = createRandomTool('duplicate');
|
|
592
|
-
expect(() => new Agent({ tools: [[tool1], [tool2]] })).toThrow("Tool with name 'duplicate' already registered");
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
describe('systemPrompt configuration', () => {
|
|
596
|
-
describe('when provided as string SystemPromptData', () => {
|
|
597
|
-
it('accepts and stores string system prompt', () => {
|
|
598
|
-
const agent = new Agent({ systemPrompt: 'You are a helpful assistant' });
|
|
599
|
-
expect(agent).toBeDefined();
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
describe('when provided as array SystemPromptData', () => {
|
|
603
|
-
it('converts TextBlockData to TextBlock', () => {
|
|
604
|
-
const agent = new Agent({ systemPrompt: [{ text: 'System prompt text' }] });
|
|
605
|
-
expect(agent).toBeDefined();
|
|
606
|
-
});
|
|
607
|
-
it('converts mixed block data types', () => {
|
|
608
|
-
const agent = new Agent({
|
|
609
|
-
systemPrompt: [{ text: 'First block' }, { cachePoint: { cacheType: 'default' } }, { text: 'Second block' }],
|
|
610
|
-
});
|
|
611
|
-
expect(agent).toBeDefined();
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
describe('when provided as SystemPrompt (class instances)', () => {
|
|
615
|
-
it('accepts array of class instances', () => {
|
|
616
|
-
const systemPrompt = [new TextBlock('System prompt'), new CachePointBlock({ cacheType: 'default' })];
|
|
617
|
-
const agent = new Agent({ systemPrompt });
|
|
618
|
-
expect(agent).toBeDefined();
|
|
619
|
-
});
|
|
620
|
-
});
|
|
621
|
-
describe('when modifying systemPrompt', () => {
|
|
622
|
-
it('allows systemPrompt to be set after initialization', () => {
|
|
623
|
-
const agent = new Agent({ systemPrompt: 'Initial prompt' });
|
|
624
|
-
agent.systemPrompt = 'Updated prompt';
|
|
625
|
-
expect(agent.systemPrompt).toEqual('Updated prompt');
|
|
626
|
-
});
|
|
627
|
-
it('allows systemPrompt to be changed between turns', async () => {
|
|
628
|
-
const firstModel = new MockMessageModel().addTurn({ type: 'textBlock', text: 'First response' });
|
|
629
|
-
const streamSpy = vi.spyOn(firstModel, 'stream');
|
|
630
|
-
const agent = new Agent({ model: firstModel, systemPrompt: [new TextBlock('You are a helpful assistant')] });
|
|
631
|
-
// First invocation with initial system prompt
|
|
632
|
-
await agent.invoke('First prompt');
|
|
633
|
-
expect(agent.systemPrompt).toEqual([new TextBlock('You are a helpful assistant')]);
|
|
634
|
-
// Should have been called with the given promp
|
|
635
|
-
expect(streamSpy).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({
|
|
636
|
-
systemPrompt: [new TextBlock('You are a helpful assistant')],
|
|
637
|
-
toolSpecs: [],
|
|
638
|
-
}));
|
|
639
|
-
// Change system prompt and model
|
|
640
|
-
agent.systemPrompt = 'You are a coding expert';
|
|
641
|
-
// Second invocation should use new system prompt
|
|
642
|
-
streamSpy.mockReset();
|
|
643
|
-
await agent.invoke('Second prompt');
|
|
644
|
-
expect(agent.systemPrompt).toEqual('You are a coding expert');
|
|
645
|
-
expect(streamSpy).toHaveBeenCalledWith(expect.any(Array), expect.objectContaining({
|
|
646
|
-
systemPrompt: 'You are a coding expert',
|
|
647
|
-
toolSpecs: [],
|
|
648
|
-
}));
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
describe('model property', () => {
|
|
653
|
-
describe('when accessing the model field', () => {
|
|
654
|
-
it('returns the configured model instance', () => {
|
|
655
|
-
const model = new MockMessageModel();
|
|
656
|
-
const agent = new Agent({ model });
|
|
657
|
-
expect(agent.model).toBe(model);
|
|
658
|
-
});
|
|
659
|
-
it('returns default BedrockModel when no model provided', () => {
|
|
660
|
-
const agent = new Agent();
|
|
661
|
-
expect(agent.model).toBeDefined();
|
|
662
|
-
expect(agent.model.constructor.name).toBe('BedrockModel');
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
describe('when modifying the model field', () => {
|
|
666
|
-
it('updates the model instance', () => {
|
|
667
|
-
const initialModel = new MockMessageModel();
|
|
668
|
-
const newModel = new MockMessageModel();
|
|
669
|
-
const agent = new Agent({ model: initialModel });
|
|
670
|
-
agent.model = newModel;
|
|
671
|
-
expect(agent.model).toBe(newModel);
|
|
672
|
-
expect(agent.model).not.toBe(initialModel);
|
|
673
|
-
});
|
|
674
|
-
it('allows model change to persist across invocations', async () => {
|
|
675
|
-
const firstModel = new MockMessageModel().addTurn({ type: 'textBlock', text: 'First response' });
|
|
676
|
-
const secondModel = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Second response' });
|
|
677
|
-
const agent = new Agent({ model: firstModel });
|
|
678
|
-
// First invocation with initial model
|
|
679
|
-
const firstResult = await agent.invoke('First prompt');
|
|
680
|
-
expect(firstResult.lastMessage?.content[0]).toEqual(new TextBlock('First response'));
|
|
681
|
-
// Change model
|
|
682
|
-
agent.model = secondModel;
|
|
683
|
-
// Second invocation should use new model
|
|
684
|
-
const secondResult = await agent.invoke('Second prompt');
|
|
685
|
-
expect(secondResult.lastMessage?.content[0]).toEqual(new TextBlock('Second response'));
|
|
686
|
-
});
|
|
687
|
-
it('successfully switches between different model providers', async () => {
|
|
688
|
-
const bedrockModel = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Bedrock response' });
|
|
689
|
-
const openaiModel = new MockMessageModel().addTurn({ type: 'textBlock', text: 'OpenAI response' });
|
|
690
|
-
const agent = new Agent({ model: bedrockModel });
|
|
691
|
-
// First invocation
|
|
692
|
-
const firstResult = await agent.invoke('First prompt');
|
|
693
|
-
expect(firstResult.lastMessage?.content[0]).toEqual(new TextBlock('Bedrock response'));
|
|
694
|
-
// Switch to different provider
|
|
695
|
-
agent.model = openaiModel;
|
|
696
|
-
// Second invocation with new provider
|
|
697
|
-
const secondResult = await agent.invoke('Second prompt');
|
|
698
|
-
expect(secondResult.lastMessage?.content[0]).toEqual(new TextBlock('OpenAI response'));
|
|
699
|
-
});
|
|
700
|
-
});
|
|
701
|
-
});
|
|
702
|
-
describe('multimodal input', () => {
|
|
703
|
-
describe('with string input', () => {
|
|
704
|
-
it('creates user message with single TextBlock', async () => {
|
|
705
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
706
|
-
const agent = new Agent({ model });
|
|
707
|
-
await agent.invoke('Hello');
|
|
708
|
-
expect(agent.messages).toHaveLength(2);
|
|
709
|
-
expect(agent.messages[0]).toEqual(new Message({
|
|
710
|
-
role: 'user',
|
|
711
|
-
content: [new TextBlock('Hello')],
|
|
712
|
-
}));
|
|
713
|
-
});
|
|
714
|
-
});
|
|
715
|
-
describe('with ContentBlock[] input', () => {
|
|
716
|
-
it('creates single user message with single TextBlock', async () => {
|
|
717
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
718
|
-
const agent = new Agent({ model });
|
|
719
|
-
await agent.invoke([new TextBlock('Hello')]);
|
|
720
|
-
expect(agent.messages).toHaveLength(2);
|
|
721
|
-
expect(agent.messages[0]).toEqual(new Message({
|
|
722
|
-
role: 'user',
|
|
723
|
-
content: [new TextBlock('Hello')],
|
|
724
|
-
}));
|
|
725
|
-
});
|
|
726
|
-
it('creates single user message with multiple blocks', async () => {
|
|
727
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
728
|
-
const agent = new Agent({ model });
|
|
729
|
-
const contentBlocks = [new TextBlock('Analyze this'), new TextBlock('and this')];
|
|
730
|
-
await agent.invoke(contentBlocks);
|
|
731
|
-
expect(agent.messages).toHaveLength(2);
|
|
732
|
-
expect(agent.messages[0]).toEqual(new Message({
|
|
733
|
-
role: 'user',
|
|
734
|
-
content: contentBlocks,
|
|
735
|
-
}));
|
|
736
|
-
});
|
|
737
|
-
it('supports all ContentBlock types', async () => {
|
|
738
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
739
|
-
const agent = new Agent({ model });
|
|
740
|
-
const contentBlocks = [
|
|
741
|
-
new TextBlock('Text content'),
|
|
742
|
-
new ToolUseBlock({ name: 'tool1', toolUseId: 'id-1', input: { key: 'value' } }),
|
|
743
|
-
new ToolResultBlock({
|
|
744
|
-
toolUseId: 'id-1',
|
|
745
|
-
status: 'success',
|
|
746
|
-
content: [new TextBlock('Result')],
|
|
747
|
-
}),
|
|
748
|
-
new ReasoningBlock({ text: 'My reasoning' }),
|
|
749
|
-
new CachePointBlock({ cacheType: 'default' }),
|
|
750
|
-
new GuardContentBlock({ text: { text: 'Guard content', qualifiers: ['grounding_source'] } }),
|
|
751
|
-
new ImageBlock({
|
|
752
|
-
format: 'png',
|
|
753
|
-
source: { url: 'https://example.com/image.png' },
|
|
754
|
-
}),
|
|
755
|
-
new VideoBlock({
|
|
756
|
-
format: 'mp4',
|
|
757
|
-
source: { location: { type: 's3', uri: 's3://bucket/video.mp4' } },
|
|
758
|
-
}),
|
|
759
|
-
new DocumentBlock({
|
|
760
|
-
format: 'pdf',
|
|
761
|
-
name: 'doc.pdf',
|
|
762
|
-
source: { bytes: new Uint8Array([1, 2, 3]) },
|
|
763
|
-
}),
|
|
764
|
-
];
|
|
765
|
-
await agent.invoke(contentBlocks);
|
|
766
|
-
expect(agent.messages).toHaveLength(2);
|
|
767
|
-
expect(agent.messages[0]).toEqual(new Message({
|
|
768
|
-
role: 'user',
|
|
769
|
-
content: contentBlocks,
|
|
770
|
-
}));
|
|
771
|
-
});
|
|
772
|
-
it('handles empty ContentBlock array', async () => {
|
|
773
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
774
|
-
const agent = new Agent({ model });
|
|
775
|
-
await agent.invoke([]);
|
|
776
|
-
expect(agent.messages).toHaveLength(1); // Only response message added
|
|
777
|
-
});
|
|
778
|
-
it('accepts ContentBlockData[] and converts to ContentBlock[]', async () => {
|
|
779
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
780
|
-
const agent = new Agent({ model });
|
|
781
|
-
await agent.invoke([
|
|
782
|
-
{ text: 'Hello from data format' },
|
|
783
|
-
{
|
|
784
|
-
toolUse: {
|
|
785
|
-
name: 'testTool',
|
|
786
|
-
toolUseId: 'id-1',
|
|
787
|
-
input: { key: 'value' },
|
|
788
|
-
},
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
toolResult: {
|
|
792
|
-
toolUseId: 'id-1',
|
|
793
|
-
status: 'success',
|
|
794
|
-
content: [{ text: 'Tool result' }, { json: { result: 42 } }],
|
|
795
|
-
},
|
|
796
|
-
},
|
|
797
|
-
{ reasoning: { text: 'My reasoning' } },
|
|
798
|
-
{ cachePoint: { cacheType: 'default' } },
|
|
799
|
-
{ guardContent: { text: { text: 'Guard text', qualifiers: ['query'] } } },
|
|
800
|
-
{
|
|
801
|
-
image: {
|
|
802
|
-
format: 'png',
|
|
803
|
-
source: { url: 'https://example.com/image.png' },
|
|
804
|
-
},
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
video: {
|
|
808
|
-
format: 'mp4',
|
|
809
|
-
source: { location: { type: 's3', uri: 's3://bucket/video.mp4' } },
|
|
810
|
-
},
|
|
811
|
-
},
|
|
812
|
-
{
|
|
813
|
-
document: {
|
|
814
|
-
format: 'pdf',
|
|
815
|
-
name: 'doc.pdf',
|
|
816
|
-
source: { bytes: new Uint8Array([1, 2, 3]) },
|
|
817
|
-
},
|
|
818
|
-
},
|
|
819
|
-
]);
|
|
820
|
-
expect(agent.messages).toHaveLength(2);
|
|
821
|
-
const userMessage = agent.messages[0];
|
|
822
|
-
expect(userMessage.role).toBe('user');
|
|
823
|
-
expect(userMessage.content).toHaveLength(9);
|
|
824
|
-
expect(userMessage.content[0]).toEqual(new TextBlock('Hello from data format'));
|
|
825
|
-
expect(userMessage.content[1]).toEqual(new ToolUseBlock({ name: 'testTool', toolUseId: 'id-1', input: { key: 'value' } }));
|
|
826
|
-
});
|
|
827
|
-
});
|
|
828
|
-
describe('with Message[] input', () => {
|
|
829
|
-
it('appends single message to conversation', async () => {
|
|
830
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
831
|
-
const agent = new Agent({ model });
|
|
832
|
-
const userMessage = new Message({
|
|
833
|
-
role: 'user',
|
|
834
|
-
content: [new TextBlock('Hello')],
|
|
835
|
-
});
|
|
836
|
-
await agent.invoke([userMessage]);
|
|
837
|
-
expect(agent.messages).toHaveLength(2);
|
|
838
|
-
expect(agent.messages[0]).toEqual(userMessage);
|
|
839
|
-
});
|
|
840
|
-
it('appends multiple messages in order', async () => {
|
|
841
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
842
|
-
const agent = new Agent({ model });
|
|
843
|
-
const messages = [
|
|
844
|
-
new Message({
|
|
845
|
-
role: 'user',
|
|
846
|
-
content: [new TextBlock('First message')],
|
|
847
|
-
}),
|
|
848
|
-
new Message({
|
|
849
|
-
role: 'assistant',
|
|
850
|
-
content: [new TextBlock('Second message')],
|
|
851
|
-
}),
|
|
852
|
-
new Message({
|
|
853
|
-
role: 'user',
|
|
854
|
-
content: [new TextBlock('Third message')],
|
|
855
|
-
}),
|
|
856
|
-
];
|
|
857
|
-
await agent.invoke(messages);
|
|
858
|
-
expect(agent.messages).toHaveLength(4); // 3 input + 1 response
|
|
859
|
-
expect(agent.messages[0]).toEqual(messages[0]);
|
|
860
|
-
expect(agent.messages[1]).toEqual(messages[1]);
|
|
861
|
-
expect(agent.messages[2]).toEqual(messages[2]);
|
|
862
|
-
});
|
|
863
|
-
it('handles empty Message array', async () => {
|
|
864
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
865
|
-
const agent = new Agent({ model });
|
|
866
|
-
await agent.invoke([]);
|
|
867
|
-
expect(agent.messages).toHaveLength(1); // Only response message added
|
|
868
|
-
});
|
|
869
|
-
it('accepts MessageData[] and converts to Message[]', async () => {
|
|
870
|
-
const model = new MockMessageModel().addTurn(new TextBlock('Response'));
|
|
871
|
-
const agent = new Agent({ model });
|
|
872
|
-
const messageDataArray = [
|
|
873
|
-
{
|
|
874
|
-
role: 'user',
|
|
875
|
-
content: [{ text: 'First message' }],
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
role: 'assistant',
|
|
879
|
-
content: [{ text: 'Second message' }],
|
|
880
|
-
},
|
|
881
|
-
];
|
|
882
|
-
await agent.invoke(messageDataArray);
|
|
883
|
-
expect(agent.messages).toHaveLength(3); // 2 input + 1 response
|
|
884
|
-
expect(agent.messages[0]).toEqual(new Message({
|
|
885
|
-
role: 'user',
|
|
886
|
-
content: [new TextBlock('First message')],
|
|
887
|
-
}));
|
|
888
|
-
expect(agent.messages[1]).toEqual(new Message({
|
|
889
|
-
role: 'assistant',
|
|
890
|
-
content: [new TextBlock('Second message')],
|
|
891
|
-
}));
|
|
892
|
-
});
|
|
893
|
-
});
|
|
894
|
-
});
|
|
895
|
-
describe('model initialization', () => {
|
|
896
|
-
describe('when model is a string', () => {
|
|
897
|
-
it('creates BedrockModel with specified modelId', () => {
|
|
898
|
-
const agent = new Agent({ model: 'anthropic.claude-3-5-sonnet-20240620-v1:0' });
|
|
899
|
-
expect(agent.model).toBeDefined();
|
|
900
|
-
expect(agent.model.constructor.name).toBe('BedrockModel');
|
|
901
|
-
expect(agent.model.getConfig().modelId).toBe('anthropic.claude-3-5-sonnet-20240620-v1:0');
|
|
902
|
-
});
|
|
903
|
-
it('creates BedrockModel with custom model ID', () => {
|
|
904
|
-
const customModelId = 'custom.model.id';
|
|
905
|
-
const agent = new Agent({ model: customModelId });
|
|
906
|
-
expect(agent.model.getConfig().modelId).toBe(customModelId);
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
describe('when model is explicit BedrockModel', () => {
|
|
910
|
-
it('uses provided BedrockModel instance', () => {
|
|
911
|
-
const explicitModel = new BedrockModel({ modelId: 'explicit-model-id' });
|
|
912
|
-
const agent = new Agent({ model: explicitModel });
|
|
913
|
-
expect(agent.model).toBe(explicitModel);
|
|
914
|
-
expect(agent.model.getConfig().modelId).toBe('explicit-model-id');
|
|
915
|
-
});
|
|
916
|
-
});
|
|
917
|
-
describe('when no model is provided', () => {
|
|
918
|
-
it('creates default BedrockModel', () => {
|
|
919
|
-
const agent = new Agent();
|
|
920
|
-
expect(agent.model).toBeDefined();
|
|
921
|
-
expect(agent.model.constructor.name).toBe('BedrockModel');
|
|
922
|
-
});
|
|
923
|
-
});
|
|
924
|
-
describe('behavior parity', () => {
|
|
925
|
-
it('string model behaves identically to explicit BedrockModel with same modelId', () => {
|
|
926
|
-
const modelId = 'anthropic.claude-3-5-sonnet-20240620-v1:0';
|
|
927
|
-
// Create agent with string model ID
|
|
928
|
-
const agentWithString = new Agent({ model: modelId });
|
|
929
|
-
// Create agent with explicit BedrockModel
|
|
930
|
-
const explicitModel = new BedrockModel({ modelId });
|
|
931
|
-
const agentWithExplicit = new Agent({ model: explicitModel });
|
|
932
|
-
// Both should have same modelId
|
|
933
|
-
expect(agentWithString.model.getConfig().modelId).toBe(agentWithExplicit.model.getConfig().modelId);
|
|
934
|
-
expect(agentWithString.model.getConfig().modelId).toBe(modelId);
|
|
935
|
-
});
|
|
936
|
-
});
|
|
937
|
-
});
|
|
938
|
-
describe('structured output', () => {
|
|
939
|
-
it('returns structured output when schema provided and tool used', async () => {
|
|
940
|
-
const schema = z.object({ name: z.string(), age: z.number() });
|
|
941
|
-
const model = new MockMessageModel()
|
|
942
|
-
.addTurn({
|
|
943
|
-
type: 'toolUseBlock',
|
|
944
|
-
name: 'strands_structured_output',
|
|
945
|
-
toolUseId: 'tool-1',
|
|
946
|
-
input: { name: 'John', age: 30 },
|
|
947
|
-
})
|
|
948
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
949
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
950
|
-
const result = await agent.invoke('Test');
|
|
951
|
-
expect(result.structuredOutput).toEqual({ name: 'John', age: 30 });
|
|
952
|
-
expect(model.callCount).toBe(1);
|
|
953
|
-
});
|
|
954
|
-
it('forces structured output tool when model does not use it', async () => {
|
|
955
|
-
const schema = z.object({ value: z.number() });
|
|
956
|
-
const model = new MockMessageModel()
|
|
957
|
-
.addTurn({ type: 'textBlock', text: 'First response' })
|
|
958
|
-
.addTurn({ type: 'toolUseBlock', name: 'strands_structured_output', toolUseId: 'tool-1', input: { value: 42 } })
|
|
959
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
960
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
961
|
-
const result = await agent.invoke('Test');
|
|
962
|
-
expect(result.structuredOutput).toEqual({ value: 42 });
|
|
963
|
-
});
|
|
964
|
-
it('does not send assistant-ended conversation when forcing structured output retry', async () => {
|
|
965
|
-
// Regression for https://github.com/strands-agents/sdk-typescript/issues/1039
|
|
966
|
-
// When the model responds with plain text instead of calling the structured output tool,
|
|
967
|
-
// the forced-retry model call must not see a conversation ending with an assistant message.
|
|
968
|
-
// Bedrock/Anthropic-family models reject assistant message prefill.
|
|
969
|
-
const schema = z.object({ value: z.number() });
|
|
970
|
-
const model = new MockMessageModel()
|
|
971
|
-
.addTurn({ type: 'textBlock', text: 'Plain text, no tool call' })
|
|
972
|
-
.addTurn({ type: 'toolUseBlock', name: 'strands_structured_output', toolUseId: 'tool-1', input: { value: 42 } })
|
|
973
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
974
|
-
// Snapshot the role sequence at each model call, since `messages` is passed by reference
|
|
975
|
-
// and mutates during the agent loop.
|
|
976
|
-
const roleSnapshots = [];
|
|
977
|
-
const originalStream = model.stream.bind(model);
|
|
978
|
-
vi.spyOn(model, 'stream').mockImplementation((messages, options) => {
|
|
979
|
-
roleSnapshots.push(messages.map((m) => m.role));
|
|
980
|
-
return originalStream(messages, options);
|
|
981
|
-
});
|
|
982
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
983
|
-
await agent.invoke('Test');
|
|
984
|
-
expect(roleSnapshots.length).toBeGreaterThanOrEqual(2);
|
|
985
|
-
// The forced-retry (second) call must not see a conversation ending with an assistant turn.
|
|
986
|
-
const secondCallRoles = roleSnapshots[1];
|
|
987
|
-
expect(secondCallRoles[secondCallRoles.length - 1]).toBe('user');
|
|
988
|
-
});
|
|
989
|
-
it('throws StructuredOutputError when model refuses to use tool after forcing', async () => {
|
|
990
|
-
const schema = z.object({ value: z.number() });
|
|
991
|
-
// Model returns text twice - once normally, once when forced
|
|
992
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
993
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
994
|
-
await expect(agent.invoke('Test')).rejects.toThrow(StructuredOutputError);
|
|
995
|
-
});
|
|
996
|
-
it('throws MaxTokensError when maxTokens reached before structured output', async () => {
|
|
997
|
-
const schema = z.object({ value: z.number() });
|
|
998
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Partial...' }, { stopReason: 'maxTokens' });
|
|
999
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1000
|
-
await expect(agent.invoke('Test')).rejects.toThrow(MaxTokensError);
|
|
1001
|
-
});
|
|
1002
|
-
it('retries with validation feedback when structured output tool returns error', async () => {
|
|
1003
|
-
const schema = z.object({ name: z.string(), age: z.number() });
|
|
1004
|
-
const model = new MockMessageModel()
|
|
1005
|
-
.addTurn({
|
|
1006
|
-
type: 'toolUseBlock',
|
|
1007
|
-
name: 'strands_structured_output',
|
|
1008
|
-
toolUseId: 'tool-1',
|
|
1009
|
-
input: { name: 'John', age: 'invalid' },
|
|
1010
|
-
})
|
|
1011
|
-
.addTurn({
|
|
1012
|
-
type: 'toolUseBlock',
|
|
1013
|
-
name: 'strands_structured_output',
|
|
1014
|
-
toolUseId: 'tool-2',
|
|
1015
|
-
input: { name: 'John', age: 30 },
|
|
1016
|
-
})
|
|
1017
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1018
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1019
|
-
const result = await agent.invoke('Test');
|
|
1020
|
-
expect(result.structuredOutput).toEqual({ name: 'John', age: 30 });
|
|
1021
|
-
});
|
|
1022
|
-
it('works without structured output schema', async () => {
|
|
1023
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Hello' });
|
|
1024
|
-
const agent = new Agent({ model });
|
|
1025
|
-
const result = await agent.invoke('Test');
|
|
1026
|
-
expect(result.structuredOutput).toBeUndefined();
|
|
1027
|
-
});
|
|
1028
|
-
it('cleans up structured output tool after invocation', async () => {
|
|
1029
|
-
const schema = z.object({ value: z.number() });
|
|
1030
|
-
const model = new MockMessageModel()
|
|
1031
|
-
.addTurn({ type: 'toolUseBlock', name: 'strands_structured_output', toolUseId: 'tool-1', input: { value: 42 } })
|
|
1032
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1033
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1034
|
-
await agent.invoke('Test');
|
|
1035
|
-
const toolNames = agent.tools.map((t) => t.name);
|
|
1036
|
-
expect(toolNames).not.toContain('strands_structured_output');
|
|
1037
|
-
});
|
|
1038
|
-
it('cleans up structured output tool even when error occurs', async () => {
|
|
1039
|
-
const schema = z.object({ value: z.number() });
|
|
1040
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Partial...' }, { stopReason: 'maxTokens' });
|
|
1041
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1042
|
-
await expect(agent.invoke('Test')).rejects.toThrow();
|
|
1043
|
-
const toolNames = agent.tools.map((t) => t.name);
|
|
1044
|
-
expect(toolNames).not.toContain('strands_structured_output');
|
|
1045
|
-
});
|
|
1046
|
-
it('validates nested objects in structured output', async () => {
|
|
1047
|
-
const schema = z.object({
|
|
1048
|
-
user: z.object({
|
|
1049
|
-
name: z.string(),
|
|
1050
|
-
age: z.number(),
|
|
1051
|
-
}),
|
|
1052
|
-
});
|
|
1053
|
-
const model = new MockMessageModel()
|
|
1054
|
-
.addTurn({
|
|
1055
|
-
type: 'toolUseBlock',
|
|
1056
|
-
name: 'strands_structured_output',
|
|
1057
|
-
toolUseId: 'tool-1',
|
|
1058
|
-
input: { user: { name: 'Alice', age: 25 } },
|
|
1059
|
-
})
|
|
1060
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1061
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1062
|
-
const result = await agent.invoke('Test');
|
|
1063
|
-
expect(result.structuredOutput).toEqual({ user: { name: 'Alice', age: 25 } });
|
|
1064
|
-
});
|
|
1065
|
-
it('validates arrays in structured output', async () => {
|
|
1066
|
-
const schema = z.object({
|
|
1067
|
-
items: z.array(z.string()),
|
|
1068
|
-
});
|
|
1069
|
-
const model = new MockMessageModel()
|
|
1070
|
-
.addTurn({
|
|
1071
|
-
type: 'toolUseBlock',
|
|
1072
|
-
name: 'strands_structured_output',
|
|
1073
|
-
toolUseId: 'tool-1',
|
|
1074
|
-
input: { items: ['a', 'b', 'c'] },
|
|
1075
|
-
})
|
|
1076
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1077
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1078
|
-
const result = await agent.invoke('Test');
|
|
1079
|
-
expect(result.structuredOutput).toEqual({ items: ['a', 'b', 'c'] });
|
|
1080
|
-
});
|
|
1081
|
-
it('uses per-invocation override schema and restores constructor schema on next call', async () => {
|
|
1082
|
-
const constructorSchema = z.object({ name: z.string() });
|
|
1083
|
-
const overrideSchema = z.object({ value: z.number() });
|
|
1084
|
-
const model = new MockMessageModel()
|
|
1085
|
-
.addTurn({
|
|
1086
|
-
type: 'toolUseBlock',
|
|
1087
|
-
name: 'strands_structured_output',
|
|
1088
|
-
toolUseId: 'tool-1',
|
|
1089
|
-
input: { value: 99 },
|
|
1090
|
-
})
|
|
1091
|
-
.addTurn({ type: 'textBlock', text: 'Done' })
|
|
1092
|
-
.addTurn({
|
|
1093
|
-
type: 'toolUseBlock',
|
|
1094
|
-
name: 'strands_structured_output',
|
|
1095
|
-
toolUseId: 'tool-2',
|
|
1096
|
-
input: { name: 'Bob' },
|
|
1097
|
-
})
|
|
1098
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1099
|
-
const agent = new Agent({ model, structuredOutputSchema: constructorSchema });
|
|
1100
|
-
const first = await agent.invoke('First', { structuredOutputSchema: overrideSchema });
|
|
1101
|
-
expect(first.structuredOutput).toEqual({ value: 99 });
|
|
1102
|
-
const second = await agent.invoke('Second');
|
|
1103
|
-
expect(second.structuredOutput).toEqual({ name: 'Bob' });
|
|
1104
|
-
});
|
|
1105
|
-
it('skips structured output extraction when AfterToolsEvent.endTurn halts the loop', async () => {
|
|
1106
|
-
const schema = z.object({ name: z.string() });
|
|
1107
|
-
const model = new MockMessageModel()
|
|
1108
|
-
.addTurn({
|
|
1109
|
-
type: 'toolUseBlock',
|
|
1110
|
-
name: 'strands_structured_output',
|
|
1111
|
-
toolUseId: 'tool-1',
|
|
1112
|
-
input: { name: 'John' },
|
|
1113
|
-
})
|
|
1114
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1115
|
-
const agent = new Agent({ model, structuredOutputSchema: schema });
|
|
1116
|
-
agent.addHook(AfterToolsEvent, (event) => {
|
|
1117
|
-
event.endTurn = true;
|
|
1118
|
-
});
|
|
1119
|
-
const result = await agent.invoke('Test');
|
|
1120
|
-
expect(result.stopReason).toBe('endTurn');
|
|
1121
|
-
expect(result.structuredOutput).toBeUndefined();
|
|
1122
|
-
expect(model.callCount).toBe(1);
|
|
1123
|
-
});
|
|
1124
|
-
});
|
|
1125
|
-
});
|
|
1126
|
-
describe('Agent._redactLastMessage', () => {
|
|
1127
|
-
const redactMessage = '[REDACTED]';
|
|
1128
|
-
it('redacts last user message with only text blocks', () => {
|
|
1129
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
1130
|
-
const agent = new Agent({ model });
|
|
1131
|
-
// Add a user message
|
|
1132
|
-
agent['messages'].push(new Message({
|
|
1133
|
-
role: 'user',
|
|
1134
|
-
content: [new TextBlock('sensitive content')],
|
|
1135
|
-
}));
|
|
1136
|
-
agent['_redactLastMessage'](redactMessage);
|
|
1137
|
-
const lastMessage = agent['messages'][agent['messages'].length - 1];
|
|
1138
|
-
expect(lastMessage.role).toBe('user');
|
|
1139
|
-
expect(lastMessage.content).toHaveLength(1);
|
|
1140
|
-
expect(lastMessage.content[0].type).toBe('textBlock');
|
|
1141
|
-
expect(lastMessage.content[0].text).toBe(redactMessage);
|
|
1142
|
-
});
|
|
1143
|
-
it('preserves tool result blocks with redacted content', () => {
|
|
1144
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
1145
|
-
const agent = new Agent({ model });
|
|
1146
|
-
// Add a user message with tool result and text blocks
|
|
1147
|
-
agent['messages'].push(new Message({
|
|
1148
|
-
role: 'user',
|
|
1149
|
-
content: [
|
|
1150
|
-
new TextBlock('some text'),
|
|
1151
|
-
new ToolResultBlock({
|
|
1152
|
-
toolUseId: 'tool-1',
|
|
1153
|
-
status: 'success',
|
|
1154
|
-
content: [new TextBlock('tool result content')],
|
|
1155
|
-
}),
|
|
1156
|
-
new TextBlock('more text'),
|
|
1157
|
-
new ToolResultBlock({
|
|
1158
|
-
toolUseId: 'tool-2',
|
|
1159
|
-
status: 'error',
|
|
1160
|
-
content: [new TextBlock('error content')],
|
|
1161
|
-
}),
|
|
1162
|
-
],
|
|
1163
|
-
}));
|
|
1164
|
-
agent['_redactLastMessage'](redactMessage);
|
|
1165
|
-
const lastMessage = agent['messages'][agent['messages'].length - 1];
|
|
1166
|
-
expect(lastMessage.role).toBe('user');
|
|
1167
|
-
expect(lastMessage.content).toHaveLength(2);
|
|
1168
|
-
// Only tool result blocks should remain
|
|
1169
|
-
expect(lastMessage.content[0].type).toBe('toolResultBlock');
|
|
1170
|
-
expect(lastMessage.content[1].type).toBe('toolResultBlock');
|
|
1171
|
-
// Tool result blocks should have redacted content but preserve structure
|
|
1172
|
-
const toolResult1 = lastMessage.content[0];
|
|
1173
|
-
expect(toolResult1.toolUseId).toBe('tool-1');
|
|
1174
|
-
expect(toolResult1.status).toBe('success');
|
|
1175
|
-
expect(toolResult1.content).toHaveLength(1);
|
|
1176
|
-
expect(toolResult1.content[0].text).toBe(redactMessage);
|
|
1177
|
-
const toolResult2 = lastMessage.content[1];
|
|
1178
|
-
expect(toolResult2.toolUseId).toBe('tool-2');
|
|
1179
|
-
expect(toolResult2.status).toBe('error');
|
|
1180
|
-
expect(toolResult2.content).toHaveLength(1);
|
|
1181
|
-
expect(toolResult2.content[0].text).toBe(redactMessage);
|
|
1182
|
-
});
|
|
1183
|
-
it('does not redact when last message is not from user', () => {
|
|
1184
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
1185
|
-
const agent = new Agent({ model });
|
|
1186
|
-
// Add an assistant message
|
|
1187
|
-
const assistantMessage = new Message({
|
|
1188
|
-
role: 'assistant',
|
|
1189
|
-
content: [new TextBlock('assistant response')],
|
|
1190
|
-
});
|
|
1191
|
-
agent['messages'].push(assistantMessage);
|
|
1192
|
-
const originalContent = assistantMessage.content;
|
|
1193
|
-
agent['_redactLastMessage'](redactMessage);
|
|
1194
|
-
const lastMessage = agent['messages'][agent['messages'].length - 1];
|
|
1195
|
-
expect(lastMessage.role).toBe('assistant');
|
|
1196
|
-
expect(lastMessage.content).toBe(originalContent);
|
|
1197
|
-
});
|
|
1198
|
-
it('handles empty messages array gracefully', () => {
|
|
1199
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'Response' });
|
|
1200
|
-
const agent = new Agent({ model });
|
|
1201
|
-
expect(() => agent['_redactLastMessage'](redactMessage)).not.toThrow();
|
|
1202
|
-
expect(agent['messages']).toHaveLength(0);
|
|
1203
|
-
});
|
|
1204
|
-
});
|
|
1205
|
-
describe('_estimateInputTokens', () => {
|
|
1206
|
-
function captureProjectedTokens(agent) {
|
|
1207
|
-
return new Promise((resolve) => {
|
|
1208
|
-
agent.addHook(BeforeModelCallEvent, (event) => {
|
|
1209
|
-
resolve(event.projectedInputTokens);
|
|
1210
|
-
});
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
it('uses full estimation on cold start (no prior usage metadata)', async () => {
|
|
1214
|
-
const model = new MockMessageModel();
|
|
1215
|
-
model.addTurn({ type: 'textBlock', text: 'Hello' });
|
|
1216
|
-
const countTokensSpy = vi.spyOn(model, 'countTokens');
|
|
1217
|
-
countTokensSpy.mockResolvedValue(42);
|
|
1218
|
-
const agent = new Agent({ model, printer: false });
|
|
1219
|
-
const tokenPromise = captureProjectedTokens(agent);
|
|
1220
|
-
await agent.invoke('Hi');
|
|
1221
|
-
expect(await tokenPromise).toBe(42);
|
|
1222
|
-
expect(countTokensSpy).toHaveBeenCalledWith(expect.any(Array), expect.any(Object));
|
|
1223
|
-
});
|
|
1224
|
-
it('uses known baseline when no new messages after last assistant', async () => {
|
|
1225
|
-
const model = new MockMessageModel();
|
|
1226
|
-
model.addTurn({ type: 'textBlock', text: 'Hello' });
|
|
1227
|
-
const agent = new Agent({
|
|
1228
|
-
model,
|
|
1229
|
-
printer: false,
|
|
1230
|
-
messages: [
|
|
1231
|
-
new Message({ role: 'user', content: [new TextBlock('Hi')] }),
|
|
1232
|
-
new Message({
|
|
1233
|
-
role: 'assistant',
|
|
1234
|
-
content: [new TextBlock('Hello')],
|
|
1235
|
-
metadata: { usage: { inputTokens: 100, outputTokens: 20, totalTokens: 120 } },
|
|
1236
|
-
}),
|
|
1237
|
-
],
|
|
1238
|
-
});
|
|
1239
|
-
// Invoke with no args — no new user message appended, so the last assistant
|
|
1240
|
-
// message is still the final message and newMessages.length === 0
|
|
1241
|
-
const tokenPromise = captureProjectedTokens(agent);
|
|
1242
|
-
await agent.invoke([]);
|
|
1243
|
-
// baseline = inputTokens(100) + outputTokens(20) = 120
|
|
1244
|
-
expect(await tokenPromise).toBe(120);
|
|
1245
|
-
});
|
|
1246
|
-
it('returns undefined projectedInputTokens when estimation fails', async () => {
|
|
1247
|
-
const model = new MockMessageModel();
|
|
1248
|
-
model.addTurn({ type: 'textBlock', text: 'Hello' });
|
|
1249
|
-
vi.spyOn(model, 'countTokens').mockRejectedValue(new Error('API unavailable'));
|
|
1250
|
-
const agent = new Agent({ model, printer: false });
|
|
1251
|
-
const tokenPromise = captureProjectedTokens(agent);
|
|
1252
|
-
await agent.invoke('Hi');
|
|
1253
|
-
expect(await tokenPromise).toBeUndefined();
|
|
1254
|
-
});
|
|
1255
|
-
it('estimates delta for new messages after last assistant', async () => {
|
|
1256
|
-
const model = new MockMessageModel();
|
|
1257
|
-
model
|
|
1258
|
-
.addTurn([{ type: 'toolUseBlock', name: 'test', toolUseId: 'id-1', input: {} }], {
|
|
1259
|
-
usage: { inputTokens: 100, outputTokens: 30, totalTokens: 130 },
|
|
1260
|
-
})
|
|
1261
|
-
.addTurn({ type: 'textBlock', text: 'Done' });
|
|
1262
|
-
const countTokensSpy = vi.spyOn(model, 'countTokens');
|
|
1263
|
-
countTokensSpy.mockResolvedValue(50);
|
|
1264
|
-
const tool = createMockTool('test', () => new ToolResultBlock({
|
|
1265
|
-
toolUseId: 'id-1',
|
|
1266
|
-
status: 'success',
|
|
1267
|
-
content: [new TextBlock('result')],
|
|
1268
|
-
}));
|
|
1269
|
-
const agent = new Agent({ model, tools: [tool], printer: false });
|
|
1270
|
-
// Capture the second BeforeModelCallEvent (after tool execution)
|
|
1271
|
-
let callCount = 0;
|
|
1272
|
-
const tokenPromise = new Promise((resolve) => {
|
|
1273
|
-
agent.addHook(BeforeModelCallEvent, (event) => {
|
|
1274
|
-
callCount++;
|
|
1275
|
-
if (callCount === 2)
|
|
1276
|
-
resolve(event.projectedInputTokens);
|
|
1277
|
-
});
|
|
1278
|
-
});
|
|
1279
|
-
await agent.invoke('Use the tool');
|
|
1280
|
-
// baseline (100+30) + estimated delta (50) = 180
|
|
1281
|
-
expect(await tokenPromise).toBe(180);
|
|
1282
|
-
expect(countTokensSpy).toHaveBeenCalled();
|
|
1283
|
-
});
|
|
1284
|
-
it('uses baseline from prior invocation on second invoke', async () => {
|
|
1285
|
-
const model = new MockMessageModel();
|
|
1286
|
-
model
|
|
1287
|
-
.addTurn({ type: 'textBlock', text: 'First response' }, { usage: { inputTokens: 200, outputTokens: 50, totalTokens: 250 } })
|
|
1288
|
-
.addTurn({ type: 'textBlock', text: 'Second response' });
|
|
1289
|
-
const countTokensSpy = vi.spyOn(model, 'countTokens');
|
|
1290
|
-
countTokensSpy.mockResolvedValue(15);
|
|
1291
|
-
const agent = new Agent({ model, printer: false });
|
|
1292
|
-
await agent.invoke('First question');
|
|
1293
|
-
// Second invocation — the user message "Second question" is appended after
|
|
1294
|
-
// the assistant message with usage metadata, so it hits the baseline + delta path
|
|
1295
|
-
const tokenPromise = captureProjectedTokens(agent);
|
|
1296
|
-
await agent.invoke('Second question');
|
|
1297
|
-
// baseline (200+50) + estimated delta for new user message (15) = 265
|
|
1298
|
-
expect(await tokenPromise).toBe(265);
|
|
1299
|
-
});
|
|
1300
|
-
});
|
|
1301
|
-
describe('normalizeToolUseNames', () => {
|
|
1302
|
-
it('replaces invalid tool-use names with INVALID_TOOL_NAME before calling model', async () => {
|
|
1303
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'ok' });
|
|
1304
|
-
const streamSpy = vi.spyOn(model, 'stream');
|
|
1305
|
-
const agent = new Agent({
|
|
1306
|
-
model,
|
|
1307
|
-
printer: false,
|
|
1308
|
-
messages: [
|
|
1309
|
-
new Message({ role: 'user', content: [new TextBlock('do thing')] }),
|
|
1310
|
-
new Message({
|
|
1311
|
-
role: 'assistant',
|
|
1312
|
-
content: [new ToolUseBlock({ name: 'bad name!', toolUseId: 'tu-1', input: {} })],
|
|
1313
|
-
}),
|
|
1314
|
-
new Message({
|
|
1315
|
-
role: 'user',
|
|
1316
|
-
content: [
|
|
1317
|
-
new ToolResultBlock({
|
|
1318
|
-
toolUseId: 'tu-1',
|
|
1319
|
-
status: 'success',
|
|
1320
|
-
content: [new TextBlock('result')],
|
|
1321
|
-
}),
|
|
1322
|
-
],
|
|
1323
|
-
}),
|
|
1324
|
-
],
|
|
1325
|
-
});
|
|
1326
|
-
await agent.invoke('continue');
|
|
1327
|
-
const sentMessages = streamSpy.mock.calls[0]?.[0];
|
|
1328
|
-
const sentToolUse = sentMessages
|
|
1329
|
-
.find((m) => m.role === 'assistant')
|
|
1330
|
-
.content.find((b) => b.type === 'toolUseBlock');
|
|
1331
|
-
expect(sentToolUse).toStrictEqual(new ToolUseBlock({ name: 'INVALID_TOOL_NAME', toolUseId: 'tu-1', input: {} }));
|
|
1332
|
-
// Agent's stored history is not mutated.
|
|
1333
|
-
const storedToolUse = agent.messages
|
|
1334
|
-
.find((m) => m.role === 'assistant')
|
|
1335
|
-
.content.find((b) => b.type === 'toolUseBlock');
|
|
1336
|
-
expect(storedToolUse).toStrictEqual(new ToolUseBlock({ name: 'bad name!', toolUseId: 'tu-1', input: {} }));
|
|
1337
|
-
});
|
|
1338
|
-
it('preserves reasoningSignature on replaced tool-use blocks', async () => {
|
|
1339
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'ok' });
|
|
1340
|
-
const streamSpy = vi.spyOn(model, 'stream');
|
|
1341
|
-
const agent = new Agent({
|
|
1342
|
-
model,
|
|
1343
|
-
printer: false,
|
|
1344
|
-
messages: [
|
|
1345
|
-
new Message({ role: 'user', content: [new TextBlock('do thing')] }),
|
|
1346
|
-
new Message({
|
|
1347
|
-
role: 'assistant',
|
|
1348
|
-
content: [new ToolUseBlock({ name: 'bad!', toolUseId: 'tu-1', input: {}, reasoningSignature: 'sig-abc' })],
|
|
1349
|
-
}),
|
|
1350
|
-
new Message({
|
|
1351
|
-
role: 'user',
|
|
1352
|
-
content: [new ToolResultBlock({ toolUseId: 'tu-1', status: 'success', content: [new TextBlock('ok')] })],
|
|
1353
|
-
}),
|
|
1354
|
-
],
|
|
1355
|
-
});
|
|
1356
|
-
await agent.invoke('continue');
|
|
1357
|
-
const sentMessages = streamSpy.mock.calls[0]?.[0];
|
|
1358
|
-
const sentToolUse = sentMessages
|
|
1359
|
-
.find((m) => m.role === 'assistant')
|
|
1360
|
-
.content.find((b) => b.type === 'toolUseBlock');
|
|
1361
|
-
expect(sentToolUse).toStrictEqual(new ToolUseBlock({
|
|
1362
|
-
name: 'INVALID_TOOL_NAME',
|
|
1363
|
-
toolUseId: 'tu-1',
|
|
1364
|
-
input: {},
|
|
1365
|
-
reasoningSignature: 'sig-abc',
|
|
1366
|
-
}));
|
|
1367
|
-
});
|
|
1368
|
-
it('leaves valid names untouched', async () => {
|
|
1369
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'ok' });
|
|
1370
|
-
const streamSpy = vi.spyOn(model, 'stream');
|
|
1371
|
-
const agent = new Agent({
|
|
1372
|
-
model,
|
|
1373
|
-
printer: false,
|
|
1374
|
-
messages: [
|
|
1375
|
-
new Message({ role: 'user', content: [new TextBlock('do thing')] }),
|
|
1376
|
-
new Message({
|
|
1377
|
-
role: 'assistant',
|
|
1378
|
-
content: [new ToolUseBlock({ name: 'good_tool-1', toolUseId: 'tu-1', input: {} })],
|
|
1379
|
-
}),
|
|
1380
|
-
new Message({
|
|
1381
|
-
role: 'user',
|
|
1382
|
-
content: [
|
|
1383
|
-
new ToolResultBlock({
|
|
1384
|
-
toolUseId: 'tu-1',
|
|
1385
|
-
status: 'success',
|
|
1386
|
-
content: [new TextBlock('result')],
|
|
1387
|
-
}),
|
|
1388
|
-
],
|
|
1389
|
-
}),
|
|
1390
|
-
],
|
|
1391
|
-
});
|
|
1392
|
-
await agent.invoke('continue');
|
|
1393
|
-
const sentMessages = streamSpy.mock.calls[0]?.[0];
|
|
1394
|
-
const sentToolUse = sentMessages
|
|
1395
|
-
.find((m) => m.role === 'assistant')
|
|
1396
|
-
.content.find((b) => b.type === 'toolUseBlock');
|
|
1397
|
-
expect(sentToolUse).toStrictEqual(new ToolUseBlock({ name: 'good_tool-1', toolUseId: 'tu-1', input: {} }));
|
|
1398
|
-
});
|
|
1399
|
-
describe('MCP toolsChanged integration', () => {
|
|
1400
|
-
it('removes old tools and adds new tools when onToolsChanged fires', async () => {
|
|
1401
|
-
const mcpClient = new McpClient({
|
|
1402
|
-
transport: { start: vi.fn(), send: vi.fn(), close: vi.fn() },
|
|
1403
|
-
});
|
|
1404
|
-
const initialTools = [
|
|
1405
|
-
new McpTool({ name: 'tool_a', description: 'A', inputSchema: {}, client: mcpClient }),
|
|
1406
|
-
new McpTool({ name: 'tool_b', description: 'B', inputSchema: {}, client: mcpClient }),
|
|
1407
|
-
];
|
|
1408
|
-
vi.spyOn(mcpClient, 'listTools').mockResolvedValue(initialTools);
|
|
1409
|
-
let capturedCallback;
|
|
1410
|
-
const setterSpy = vi.spyOn(McpClient.prototype, 'onToolsChanged', 'set').mockImplementation((cb) => {
|
|
1411
|
-
capturedCallback = cb;
|
|
1412
|
-
});
|
|
1413
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'done' });
|
|
1414
|
-
const agent = new Agent({ model, tools: [mcpClient] });
|
|
1415
|
-
await agent.initialize();
|
|
1416
|
-
expect(agent.tools.map((t) => t.name)).toEqual(['tool_a', 'tool_b']);
|
|
1417
|
-
expect(capturedCallback).toBeDefined();
|
|
1418
|
-
const newTools = [
|
|
1419
|
-
new McpTool({ name: 'tool_b', description: 'B-updated', inputSchema: {}, client: mcpClient }),
|
|
1420
|
-
new McpTool({ name: 'tool_c', description: 'C', inputSchema: {}, client: mcpClient }),
|
|
1421
|
-
];
|
|
1422
|
-
capturedCallback(['tool_a', 'tool_b'], newTools);
|
|
1423
|
-
expect(agent.tools.map((t) => t.name)).toEqual(['tool_b', 'tool_c']);
|
|
1424
|
-
expect(agent.tools.find((t) => t.name === 'tool_b').description).toBe('B-updated');
|
|
1425
|
-
setterSpy.mockRestore();
|
|
1426
|
-
});
|
|
1427
|
-
});
|
|
1428
|
-
describe('limits', () => {
|
|
1429
|
-
const toolUseTurn = (toolUseId, usage) => [
|
|
1430
|
-
{ type: 'toolUseBlock', name: 'loop', toolUseId, input: {} },
|
|
1431
|
-
{ usage },
|
|
1432
|
-
];
|
|
1433
|
-
const passthroughTool = () => createMockTool('loop', (context) => new ToolResultBlock({
|
|
1434
|
-
toolUseId: context.toolUse.toolUseId,
|
|
1435
|
-
status: 'success',
|
|
1436
|
-
content: [new TextBlock('ok')],
|
|
1437
|
-
}));
|
|
1438
|
-
describe('when limits.turns is reached', () => {
|
|
1439
|
-
it('runs the cycle to completion and bails at top of next iteration', async () => {
|
|
1440
|
-
const model = new MockMessageModel()
|
|
1441
|
-
.addTurn(...toolUseTurn('tool-1', { inputTokens: 10, outputTokens: 5, totalTokens: 15 }))
|
|
1442
|
-
.addTurn(...toolUseTurn('tool-2', { inputTokens: 20, outputTokens: 5, totalTokens: 25 }));
|
|
1443
|
-
const agent = new Agent({ model, tools: [passthroughTool()] });
|
|
1444
|
-
const result = await agent.invoke('go', { limits: { turns: 1 } });
|
|
1445
|
-
// Bail after tools — lastMessage is the user toolResult, so we don't
|
|
1446
|
-
// use expectAgentResult (which assumes role 'assistant').
|
|
1447
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1448
|
-
type: 'agentResult',
|
|
1449
|
-
stopReason: 'limitTurns',
|
|
1450
|
-
lastMessage: expect.objectContaining({
|
|
1451
|
-
role: 'user',
|
|
1452
|
-
content: expect.arrayContaining([expect.any(ToolResultBlock)]),
|
|
1453
|
-
}),
|
|
1454
|
-
metrics: expectLoopMetrics({
|
|
1455
|
-
cycleCount: 1,
|
|
1456
|
-
toolNames: ['loop'],
|
|
1457
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1458
|
-
}),
|
|
1459
|
-
}));
|
|
1460
|
-
});
|
|
1461
|
-
});
|
|
1462
|
-
describe('when limits is generous', () => {
|
|
1463
|
-
it('does not trip and the model ends naturally', async () => {
|
|
1464
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'done' }, { usage: { inputTokens: 5, outputTokens: 5, totalTokens: 10 } });
|
|
1465
|
-
const agent = new Agent({ model });
|
|
1466
|
-
const result = await agent.invoke('go', { limits: { turns: 5, outputTokens: 1000, totalTokens: 1000 } });
|
|
1467
|
-
expect(result).toEqual(expectAgentResult({
|
|
1468
|
-
stopReason: 'endTurn',
|
|
1469
|
-
messageText: 'done',
|
|
1470
|
-
cycleCount: 1,
|
|
1471
|
-
usage: { inputTokens: 5, outputTokens: 5, totalTokens: 10 },
|
|
1472
|
-
}));
|
|
1473
|
-
});
|
|
1474
|
-
});
|
|
1475
|
-
describe('when limits.outputTokens is reached', () => {
|
|
1476
|
-
it('returns limitOutputTokens once cumulative outputTokens hits the cap', async () => {
|
|
1477
|
-
const model = new MockMessageModel()
|
|
1478
|
-
.addTurn(...toolUseTurn('tool-1', { inputTokens: 10, outputTokens: 60, totalTokens: 70 }))
|
|
1479
|
-
.addTurn(...toolUseTurn('tool-2', { inputTokens: 10, outputTokens: 60, totalTokens: 70 }));
|
|
1480
|
-
const agent = new Agent({ model, tools: [passthroughTool()] });
|
|
1481
|
-
const result = await agent.invoke('go', { limits: { outputTokens: 100 } });
|
|
1482
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1483
|
-
type: 'agentResult',
|
|
1484
|
-
stopReason: 'limitOutputTokens',
|
|
1485
|
-
lastMessage: expect.objectContaining({
|
|
1486
|
-
role: 'user',
|
|
1487
|
-
content: expect.arrayContaining([expect.any(ToolResultBlock)]),
|
|
1488
|
-
}),
|
|
1489
|
-
metrics: expectLoopMetrics({
|
|
1490
|
-
cycleCount: 2,
|
|
1491
|
-
toolNames: ['loop'],
|
|
1492
|
-
usage: { inputTokens: 20, outputTokens: 120, totalTokens: 140 },
|
|
1493
|
-
}),
|
|
1494
|
-
}));
|
|
1495
|
-
});
|
|
1496
|
-
it('uses at-most (>=) semantics: stops when count exactly equals the cap', async () => {
|
|
1497
|
-
const model = new MockMessageModel()
|
|
1498
|
-
.addTurn(...toolUseTurn('tool-1', { inputTokens: 10, outputTokens: 100, totalTokens: 110 }))
|
|
1499
|
-
.addTurn(...toolUseTurn('tool-2', { inputTokens: 10, outputTokens: 100, totalTokens: 110 }));
|
|
1500
|
-
const agent = new Agent({ model, tools: [passthroughTool()] });
|
|
1501
|
-
const result = await agent.invoke('go', { limits: { outputTokens: 100 } });
|
|
1502
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1503
|
-
type: 'agentResult',
|
|
1504
|
-
stopReason: 'limitOutputTokens',
|
|
1505
|
-
metrics: expectLoopMetrics({
|
|
1506
|
-
cycleCount: 1,
|
|
1507
|
-
toolNames: ['loop'],
|
|
1508
|
-
usage: { inputTokens: 10, outputTokens: 100, totalTokens: 110 },
|
|
1509
|
-
}),
|
|
1510
|
-
}));
|
|
1511
|
-
});
|
|
1512
|
-
});
|
|
1513
|
-
describe('when limits.totalTokens is reached', () => {
|
|
1514
|
-
it('returns limitTotalTokens once cumulative totalTokens hits the cap', async () => {
|
|
1515
|
-
const model = new MockMessageModel()
|
|
1516
|
-
.addTurn(...toolUseTurn('tool-1', { inputTokens: 200, outputTokens: 100, totalTokens: 300 }))
|
|
1517
|
-
.addTurn(...toolUseTurn('tool-2', { inputTokens: 200, outputTokens: 100, totalTokens: 300 }));
|
|
1518
|
-
const agent = new Agent({ model, tools: [passthroughTool()] });
|
|
1519
|
-
const result = await agent.invoke('go', { limits: { totalTokens: 500 } });
|
|
1520
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1521
|
-
type: 'agentResult',
|
|
1522
|
-
stopReason: 'limitTotalTokens',
|
|
1523
|
-
lastMessage: expect.objectContaining({
|
|
1524
|
-
role: 'user',
|
|
1525
|
-
content: expect.arrayContaining([expect.any(ToolResultBlock)]),
|
|
1526
|
-
}),
|
|
1527
|
-
metrics: expectLoopMetrics({
|
|
1528
|
-
cycleCount: 2,
|
|
1529
|
-
toolNames: ['loop'],
|
|
1530
|
-
usage: { inputTokens: 400, outputTokens: 200, totalTokens: 600 },
|
|
1531
|
-
}),
|
|
1532
|
-
}));
|
|
1533
|
-
});
|
|
1534
|
-
});
|
|
1535
|
-
describe('when the model ends naturally on the same turn the limit would trip', () => {
|
|
1536
|
-
it('returns endTurn — the model answer wins', async () => {
|
|
1537
|
-
const model = new MockMessageModel().addTurn({ type: 'textBlock', text: 'final answer' }, { usage: { inputTokens: 300, outputTokens: 300, totalTokens: 600 } });
|
|
1538
|
-
const agent = new Agent({ model });
|
|
1539
|
-
const result = await agent.invoke('go', { limits: { totalTokens: 500 } });
|
|
1540
|
-
expect(result).toEqual(expectAgentResult({
|
|
1541
|
-
stopReason: 'endTurn',
|
|
1542
|
-
messageText: 'final answer',
|
|
1543
|
-
cycleCount: 1,
|
|
1544
|
-
usage: { inputTokens: 300, outputTokens: 300, totalTokens: 600 },
|
|
1545
|
-
}));
|
|
1546
|
-
});
|
|
1547
|
-
});
|
|
1548
|
-
describe('when multiple limits trip simultaneously', () => {
|
|
1549
|
-
const heavyUsage = { inputTokens: 100, outputTokens: 100, totalTokens: 200 };
|
|
1550
|
-
const buildAgent = () => {
|
|
1551
|
-
const model = new MockMessageModel()
|
|
1552
|
-
.addTurn(...toolUseTurn('tool-1', heavyUsage))
|
|
1553
|
-
.addTurn(...toolUseTurn('tool-2', heavyUsage));
|
|
1554
|
-
return new Agent({ model, tools: [passthroughTool()] });
|
|
1555
|
-
};
|
|
1556
|
-
it('prefers turns when all three trip', async () => {
|
|
1557
|
-
const result = await buildAgent().invoke('go', {
|
|
1558
|
-
limits: { turns: 1, totalTokens: 1, outputTokens: 1 },
|
|
1559
|
-
});
|
|
1560
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1561
|
-
type: 'agentResult',
|
|
1562
|
-
stopReason: 'limitTurns',
|
|
1563
|
-
metrics: expectLoopMetrics({ cycleCount: 1, toolNames: ['loop'] }),
|
|
1564
|
-
}));
|
|
1565
|
-
});
|
|
1566
|
-
it('prefers totalTokens over outputTokens', async () => {
|
|
1567
|
-
const result = await buildAgent().invoke('go', { limits: { totalTokens: 1, outputTokens: 1 } });
|
|
1568
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1569
|
-
type: 'agentResult',
|
|
1570
|
-
stopReason: 'limitTotalTokens',
|
|
1571
|
-
metrics: expectLoopMetrics({ cycleCount: 1, toolNames: ['loop'] }),
|
|
1572
|
-
}));
|
|
1573
|
-
});
|
|
1574
|
-
it('falls back to outputTokens when no higher-priority cap is set', async () => {
|
|
1575
|
-
const result = await buildAgent().invoke('go', { limits: { outputTokens: 1 } });
|
|
1576
|
-
expect(result).toEqual(expect.objectContaining({
|
|
1577
|
-
type: 'agentResult',
|
|
1578
|
-
stopReason: 'limitOutputTokens',
|
|
1579
|
-
metrics: expectLoopMetrics({ cycleCount: 1, toolNames: ['loop'] }),
|
|
1580
|
-
}));
|
|
1581
|
-
});
|
|
1582
|
-
});
|
|
1583
|
-
describe('when the same agent is reused across invocations', () => {
|
|
1584
|
-
it('scopes the limit to the current invocation, not lifetime', async () => {
|
|
1585
|
-
// Each turn uses 50 output tokens. With limits.outputTokens: 75, a single
|
|
1586
|
-
// invocation tolerates one turn but trips on the second. If the cap
|
|
1587
|
-
// were lifetime-scoped, the second `invoke()` would trip on its first
|
|
1588
|
-
// turn (75 cumulative across both calls).
|
|
1589
|
-
const model = new MockMessageModel()
|
|
1590
|
-
.addTurn({ type: 'textBlock', text: 'first' }, { usage: { inputTokens: 10, outputTokens: 50, totalTokens: 60 } })
|
|
1591
|
-
.addTurn({ type: 'textBlock', text: 'second' }, { usage: { inputTokens: 10, outputTokens: 50, totalTokens: 60 } });
|
|
1592
|
-
const agent = new Agent({ model });
|
|
1593
|
-
const r1 = await agent.invoke('go', { limits: { outputTokens: 75 } });
|
|
1594
|
-
expect(r1.stopReason).toBe('endTurn');
|
|
1595
|
-
expect(r1.metrics?.latestAgentInvocation?.cycles.length).toBe(1);
|
|
1596
|
-
const r2 = await agent.invoke('go again', { limits: { outputTokens: 75 } });
|
|
1597
|
-
expect(r2.stopReason).toBe('endTurn');
|
|
1598
|
-
expect(r2.metrics?.latestAgentInvocation?.cycles.length).toBe(1);
|
|
1599
|
-
});
|
|
1600
|
-
});
|
|
1601
|
-
describe('when a limit is invalid', () => {
|
|
1602
|
-
it.each([
|
|
1603
|
-
['negative', { limits: { turns: -1 } }],
|
|
1604
|
-
['zero', { limits: { turns: 0 } }],
|
|
1605
|
-
['NaN', { limits: { outputTokens: NaN } }],
|
|
1606
|
-
['Infinity', { limits: { totalTokens: Infinity } }],
|
|
1607
|
-
])('rejects %s with TypeError', async (_label, options) => {
|
|
1608
|
-
const agent = new Agent({ model: new MockMessageModel().addTurn({ type: 'textBlock', text: 'never reached' }) });
|
|
1609
|
-
await expect(agent.invoke('go', options)).rejects.toThrow(TypeError);
|
|
1610
|
-
});
|
|
1611
|
-
});
|
|
1612
|
-
describe('when invoked via stream()', () => {
|
|
1613
|
-
it('returns limitTurns as the generator return value', async () => {
|
|
1614
|
-
const model = new MockMessageModel()
|
|
1615
|
-
.addTurn(...toolUseTurn('tool-1', { inputTokens: 10, outputTokens: 5, totalTokens: 15 }))
|
|
1616
|
-
.addTurn(...toolUseTurn('tool-2', { inputTokens: 10, outputTokens: 5, totalTokens: 15 }));
|
|
1617
|
-
const agent = new Agent({ model, tools: [passthroughTool()] });
|
|
1618
|
-
const { result } = await collectGenerator(agent.stream('go', { limits: { turns: 1 } }));
|
|
1619
|
-
expect(result).toEqual(expect.objectContaining({ type: 'agentResult', stopReason: 'limitTurns' }));
|
|
1620
|
-
});
|
|
1621
|
-
});
|
|
1622
|
-
});
|
|
1623
|
-
});
|
|
1624
|
-
//# sourceMappingURL=agent.test.js.map
|