@strands-agents/sdk 1.7.0 → 1.9.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 +32 -2
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +92 -4
- 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/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +2 -0
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/errors.d.ts +7 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +10 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/experimental/checkpoint.d.ts +118 -0
- package/dist/src/experimental/checkpoint.d.ts.map +1 -0
- package/dist/src/experimental/checkpoint.js +108 -0
- package/dist/src/experimental/checkpoint.js.map +1 -0
- package/dist/src/experimental/index.d.ts +10 -0
- package/dist/src/experimental/index.d.ts.map +1 -0
- package/dist/src/experimental/index.js +9 -0
- package/dist/src/experimental/index.js.map +1 -0
- 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/injection/message-injection.d.ts.map +1 -1
- package/dist/src/injection/message-injection.js +2 -0
- package/dist/src/injection/message-injection.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/multiagent.d.ts +2 -1
- package/dist/src/multiagent/multiagent.d.ts.map +1 -1
- package/dist/src/multiagent/multiagent.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/agent.d.ts +18 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js +12 -0
- package/dist/src/types/agent.js.map +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 +22 -1
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js +22 -0
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/vended-memory-stores/test-memory-store/index.d.ts +2 -0
- package/dist/src/vended-memory-stores/test-memory-store/index.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/test-memory-store/index.js +2 -0
- package/dist/src/vended-memory-stores/test-memory-store/index.js.map +1 -0
- package/dist/src/vended-memory-stores/test-memory-store/store.d.ts +122 -0
- package/dist/src/vended-memory-stores/test-memory-store/store.d.ts.map +1 -0
- package/dist/src/vended-memory-stores/test-memory-store/store.js +281 -0
- package/dist/src/vended-memory-stores/test-memory-store/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 +41 -30
- 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,3964 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { BedrockRuntimeClient, ConverseStreamCommand, CountTokensCommand, ValidationException, } from '@aws-sdk/client-bedrock-runtime';
|
|
3
|
-
import { isNode } from '../../__fixtures__/environment.js';
|
|
4
|
-
import { BedrockModel } from '../bedrock.js';
|
|
5
|
-
import { ContextWindowOverflowError, ModelThrottledError } from '../../errors.js';
|
|
6
|
-
import { Message, ReasoningBlock, ToolUseBlock, ToolResultBlock, JsonBlock } from '../../types/messages.js';
|
|
7
|
-
import { TextBlock, GuardContentBlock, CachePointBlock } from '../../types/messages.js';
|
|
8
|
-
import { ImageBlock, VideoBlock, DocumentBlock } from '../../types/media.js';
|
|
9
|
-
import { CitationsBlock } from '../../types/citations.js';
|
|
10
|
-
import { collectIterator } from '../../__fixtures__/model-test-helpers.js';
|
|
11
|
-
import { NOOP_TOOL_SPEC } from '../../tools/noop-tool.js';
|
|
12
|
-
import { warnOnce } from '../../logging/warn-once.js';
|
|
13
|
-
/**
|
|
14
|
-
* Helper function to mock BedrockRuntimeClient implementation with customizable config.
|
|
15
|
-
* @param options - Optional configuration for mock region, useFipsEndpoint, and send functions
|
|
16
|
-
*/
|
|
17
|
-
function mockBedrockClientImplementation(options) {
|
|
18
|
-
const mockSend = vi.fn(options?.send ??
|
|
19
|
-
(async () => {
|
|
20
|
-
throw new Error('send() not mocked - specify send option if needed');
|
|
21
|
-
}));
|
|
22
|
-
vi.mocked(BedrockRuntimeClient).mockImplementation(function (...args) {
|
|
23
|
-
// Extract region from constructor args if provided
|
|
24
|
-
const clientConfig = args[0] ?? {};
|
|
25
|
-
const configuredRegion = clientConfig.region;
|
|
26
|
-
const mockRegion = vi.fn(options?.region ??
|
|
27
|
-
(async () => {
|
|
28
|
-
// If region was explicitly configured in constructor, return it; otherwise return default
|
|
29
|
-
if (configuredRegion)
|
|
30
|
-
return configuredRegion;
|
|
31
|
-
return 'us-east-1';
|
|
32
|
-
}));
|
|
33
|
-
const mockUseFipsEndpoint = vi.fn(options?.useFipsEndpoint ?? (async () => false));
|
|
34
|
-
return {
|
|
35
|
-
send: mockSend,
|
|
36
|
-
middlewareStack: { add: vi.fn() },
|
|
37
|
-
config: {
|
|
38
|
-
region: mockRegion,
|
|
39
|
-
useFipsEndpoint: mockUseFipsEndpoint,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Helper function to setup mock send with custom stream generator.
|
|
46
|
-
*/
|
|
47
|
-
function setupMockSend(streamGenerator) {
|
|
48
|
-
vi.clearAllMocks();
|
|
49
|
-
const mockSend = vi.fn(async () => ({
|
|
50
|
-
stream: streamGenerator(),
|
|
51
|
-
}));
|
|
52
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
53
|
-
}
|
|
54
|
-
// Mock the AWS SDK
|
|
55
|
-
vi.mock('@aws-sdk/client-bedrock-runtime', async (importOriginal) => {
|
|
56
|
-
const originalModule = await importOriginal();
|
|
57
|
-
// Mock command classes that the code under test will instantiate
|
|
58
|
-
const ConverseStreamCommand = vi.fn();
|
|
59
|
-
const ConverseCommand = vi.fn();
|
|
60
|
-
const mockSend = vi.fn(async (command) => {
|
|
61
|
-
// Check which constructor was used to create the command object
|
|
62
|
-
if (command instanceof ConverseStreamCommand) {
|
|
63
|
-
// Return a streaming response
|
|
64
|
-
return {
|
|
65
|
-
stream: (async function* () {
|
|
66
|
-
yield { messageStart: { role: 'assistant' } };
|
|
67
|
-
yield { contentBlockStart: {} };
|
|
68
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
69
|
-
yield { contentBlockStop: {} };
|
|
70
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
71
|
-
yield {
|
|
72
|
-
metadata: {
|
|
73
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
74
|
-
metrics: { latencyMs: 100 },
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
})(),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (command instanceof ConverseCommand) {
|
|
81
|
-
// Return a non-streaming (full) response for the non-streaming API
|
|
82
|
-
return {
|
|
83
|
-
output: {
|
|
84
|
-
message: {
|
|
85
|
-
role: 'assistant',
|
|
86
|
-
content: [{ text: 'Hello' }],
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
stopReason: 'end_turn',
|
|
90
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
91
|
-
metrics: { latencyMs: 100 },
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
throw new Error('Unhandled command type in mock');
|
|
95
|
-
});
|
|
96
|
-
// Create a mock CountTokensCommand class
|
|
97
|
-
const CountTokensCommand = vi.fn();
|
|
98
|
-
// Create a mock ValidationException class
|
|
99
|
-
class MockValidationException extends Error {
|
|
100
|
-
constructor(opts) {
|
|
101
|
-
super(opts.message);
|
|
102
|
-
this.name = 'ValidationException';
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
...originalModule,
|
|
107
|
-
BedrockRuntimeClient: vi.fn(function () {
|
|
108
|
-
return {
|
|
109
|
-
send: mockSend,
|
|
110
|
-
middlewareStack: { add: vi.fn() },
|
|
111
|
-
config: {
|
|
112
|
-
region: vi.fn(async () => 'us-east-1'),
|
|
113
|
-
useFipsEndpoint: vi.fn(async () => false),
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}),
|
|
117
|
-
ConverseStreamCommand,
|
|
118
|
-
ConverseCommand,
|
|
119
|
-
CountTokensCommand,
|
|
120
|
-
ValidationException: MockValidationException,
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
vi.mock('../../logging/warn-once.js', () => ({
|
|
124
|
-
warnOnce: vi.fn(),
|
|
125
|
-
}));
|
|
126
|
-
describe('BedrockModel', () => {
|
|
127
|
-
const BEDROCK_NOOP_TOOL_CONFIG = {
|
|
128
|
-
tools: [{ toolSpec: { ...NOOP_TOOL_SPEC, inputSchema: { json: NOOP_TOOL_SPEC.inputSchema } } }],
|
|
129
|
-
};
|
|
130
|
-
beforeEach(() => {
|
|
131
|
-
vi.clearAllMocks();
|
|
132
|
-
// Reset mock to a working implementation to ensure test isolation
|
|
133
|
-
setupMockSend(async function* () {
|
|
134
|
-
yield { messageStart: { role: 'assistant' } };
|
|
135
|
-
yield { contentBlockStart: {} };
|
|
136
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
137
|
-
yield { contentBlockStop: {} };
|
|
138
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
139
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
140
|
-
});
|
|
141
|
-
// Clean up AWS_REGION env var in Node.js only
|
|
142
|
-
if (isNode && process.env) {
|
|
143
|
-
delete process.env.AWS_REGION;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
afterEach(() => {
|
|
147
|
-
vi.clearAllMocks();
|
|
148
|
-
});
|
|
149
|
-
describe('constructor', () => {
|
|
150
|
-
it('creates an instance with default configuration', () => {
|
|
151
|
-
const provider = new BedrockModel();
|
|
152
|
-
const config = provider.getConfig();
|
|
153
|
-
expect(config.modelId).toBeDefined();
|
|
154
|
-
});
|
|
155
|
-
it('warns when modelId is not explicitly set', () => {
|
|
156
|
-
new BedrockModel();
|
|
157
|
-
expect(warnOnce).toHaveBeenCalledWith(expect.objectContaining({ warn: expect.any(Function) }), expect.stringContaining('using default modelId'));
|
|
158
|
-
});
|
|
159
|
-
it('does not warn when modelId is explicitly set', () => {
|
|
160
|
-
new BedrockModel({ modelId: 'us.anthropic.claude-3-5-sonnet-20241022-v2:0' });
|
|
161
|
-
expect(warnOnce).not.toHaveBeenCalledWith(expect.objectContaining({ warn: expect.any(Function) }), expect.stringContaining('using default modelId'));
|
|
162
|
-
});
|
|
163
|
-
it('uses provided model ID ', () => {
|
|
164
|
-
const customModelId = 'us.anthropic.claude-3-5-sonnet-20241022-v2:0';
|
|
165
|
-
const provider = new BedrockModel({ modelId: customModelId });
|
|
166
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
167
|
-
modelId: customModelId,
|
|
168
|
-
contextWindowLimit: 200_000,
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
it('uses provided region', () => {
|
|
172
|
-
const customRegion = 'eu-west-1';
|
|
173
|
-
new BedrockModel({ region: customRegion });
|
|
174
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith({
|
|
175
|
-
region: customRegion,
|
|
176
|
-
customUserAgent: 'strands-agents-ts-sdk',
|
|
177
|
-
requestHandler: { requestTimeout: 120_000 },
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
it('extends custom user agent if provided', () => {
|
|
181
|
-
const customAgent = 'my-app/1.0';
|
|
182
|
-
new BedrockModel({ region: 'us-west-2', clientConfig: { customUserAgent: customAgent } });
|
|
183
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith({
|
|
184
|
-
region: 'us-west-2',
|
|
185
|
-
customUserAgent: 'my-app/1.0 strands-agents-ts-sdk',
|
|
186
|
-
requestHandler: { requestTimeout: 120_000 },
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
it('passes custom endpoint to client', () => {
|
|
190
|
-
const endpoint = 'https://vpce-abc.bedrock-runtime.us-west-2.vpce.amazonaws.com';
|
|
191
|
-
const region = 'us-west-2';
|
|
192
|
-
new BedrockModel({ region, clientConfig: { endpoint } });
|
|
193
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith({
|
|
194
|
-
region,
|
|
195
|
-
endpoint,
|
|
196
|
-
customUserAgent: 'strands-agents-ts-sdk',
|
|
197
|
-
requestHandler: { requestTimeout: 120_000 },
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
it('passes custom credentials to client', () => {
|
|
201
|
-
const credentials = {
|
|
202
|
-
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
|
|
203
|
-
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
|
|
204
|
-
};
|
|
205
|
-
const region = 'us-west-2';
|
|
206
|
-
new BedrockModel({ region, clientConfig: { credentials } });
|
|
207
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith({
|
|
208
|
-
region,
|
|
209
|
-
credentials,
|
|
210
|
-
customUserAgent: 'strands-agents-ts-sdk',
|
|
211
|
-
requestHandler: { requestTimeout: 120_000 },
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
it('applies a default 120s request timeout', () => {
|
|
215
|
-
new BedrockModel({ region: 'us-west-2' });
|
|
216
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith(expect.objectContaining({ requestHandler: { requestTimeout: 120_000 } }));
|
|
217
|
-
});
|
|
218
|
-
it('lets the caller override requestTimeout', () => {
|
|
219
|
-
new BedrockModel({ region: 'us-west-2', clientConfig: { requestHandler: { requestTimeout: 5_000 } } });
|
|
220
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith(expect.objectContaining({ requestHandler: { requestTimeout: 5_000 } }));
|
|
221
|
-
});
|
|
222
|
-
it('merges the default timeout with other requestHandler options', () => {
|
|
223
|
-
new BedrockModel({ region: 'us-west-2', clientConfig: { requestHandler: { connectionTimeout: 1_000 } } });
|
|
224
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith(expect.objectContaining({ requestHandler: { requestTimeout: 120_000, connectionTimeout: 1_000 } }));
|
|
225
|
-
});
|
|
226
|
-
it('passes a user-provided handler instance through untouched', () => {
|
|
227
|
-
const handler = { handle: vi.fn(), updateHttpClientConfig: vi.fn(), httpHandlerConfigs: vi.fn() };
|
|
228
|
-
new BedrockModel({ region: 'us-west-2', clientConfig: { requestHandler: handler } });
|
|
229
|
-
expect(BedrockRuntimeClient).toHaveBeenCalledWith(expect.objectContaining({ requestHandler: handler }));
|
|
230
|
-
});
|
|
231
|
-
it('adds api key middleware when apiKey is provided', () => {
|
|
232
|
-
const provider = new BedrockModel({ region: 'us-east-1', apiKey: 'br-test-key' });
|
|
233
|
-
const mockAdd = provider['_client'].middlewareStack.add;
|
|
234
|
-
expect(mockAdd).toHaveBeenCalledWith(expect.any(Function), {
|
|
235
|
-
step: 'finalizeRequest',
|
|
236
|
-
priority: 'low',
|
|
237
|
-
name: 'bedrockApiKeyMiddleware',
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
it('does not add api key middleware when apiKey is not provided', () => {
|
|
241
|
-
const provider = new BedrockModel({ region: 'us-east-1' });
|
|
242
|
-
const mockAdd = provider['_client'].middlewareStack.add;
|
|
243
|
-
expect(mockAdd).not.toHaveBeenCalled();
|
|
244
|
-
});
|
|
245
|
-
it('api key middleware sets authorization header', async () => {
|
|
246
|
-
const provider = new BedrockModel({ region: 'us-east-1', apiKey: 'br-test-key' });
|
|
247
|
-
const mockAdd = provider['_client'].middlewareStack.add;
|
|
248
|
-
const middlewareFn = mockAdd.mock.calls[0][0];
|
|
249
|
-
const mockNext = vi.fn(async (args) => args);
|
|
250
|
-
const handler = middlewareFn(mockNext);
|
|
251
|
-
const args = { request: { headers: { authorization: 'AWS4-HMAC-SHA256 ...' } } };
|
|
252
|
-
await handler(args);
|
|
253
|
-
expect(args.request.headers['authorization']).toBe('Bearer br-test-key');
|
|
254
|
-
expect(mockNext).toHaveBeenCalledWith(args);
|
|
255
|
-
});
|
|
256
|
-
it('does not include apiKey in model config', () => {
|
|
257
|
-
const provider = new BedrockModel({ region: 'us-east-1', apiKey: 'br-test-key', temperature: 0.5 });
|
|
258
|
-
const config = provider.getConfig();
|
|
259
|
-
expect(config).toStrictEqual({
|
|
260
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
261
|
-
temperature: 0.5,
|
|
262
|
-
contextWindowLimit: 1_000_000,
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
it('includes contextWindowLimit in config when provided', () => {
|
|
266
|
-
const provider = new BedrockModel({
|
|
267
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
268
|
-
contextWindowLimit: 200_000,
|
|
269
|
-
});
|
|
270
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
271
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
272
|
-
contextWindowLimit: 200_000,
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
it('auto-populates contextWindowLimit from model ID lookup', () => {
|
|
276
|
-
const provider = new BedrockModel({ modelId: 'anthropic.claude-sonnet-4-20250514-v1:0' });
|
|
277
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
278
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
279
|
-
contextWindowLimit: 1_000_000,
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
it('auto-populates contextWindowLimit for cross-region model IDs', () => {
|
|
283
|
-
const provider = new BedrockModel({ modelId: 'us.anthropic.claude-sonnet-4-6' });
|
|
284
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
285
|
-
modelId: 'us.anthropic.claude-sonnet-4-6',
|
|
286
|
-
contextWindowLimit: 1_000_000,
|
|
287
|
-
});
|
|
288
|
-
});
|
|
289
|
-
it('auto-populates contextWindowLimit for default model ID', () => {
|
|
290
|
-
const provider = new BedrockModel();
|
|
291
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
292
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
293
|
-
contextWindowLimit: 1_000_000,
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
it('does not override explicit contextWindowLimit', () => {
|
|
297
|
-
const provider = new BedrockModel({
|
|
298
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
299
|
-
contextWindowLimit: 100_000,
|
|
300
|
-
});
|
|
301
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
302
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
303
|
-
contextWindowLimit: 100_000,
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
it('leaves contextWindowLimit undefined for unknown model IDs', () => {
|
|
307
|
-
const provider = new BedrockModel({ modelId: 'unknown.model-v1:0' });
|
|
308
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
309
|
-
modelId: 'unknown.model-v1:0',
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
describe('updateConfig', () => {
|
|
314
|
-
it('merges new config with existing config', () => {
|
|
315
|
-
const provider = new BedrockModel({ region: 'us-west-2', temperature: 0.5 });
|
|
316
|
-
provider.updateConfig({ temperature: 0.8, maxTokens: 2048 });
|
|
317
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
318
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
319
|
-
temperature: 0.8,
|
|
320
|
-
maxTokens: 2048,
|
|
321
|
-
contextWindowLimit: 1_000_000,
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
it('preserves fields not included in the update', () => {
|
|
325
|
-
const provider = new BedrockModel({
|
|
326
|
-
region: 'us-west-2',
|
|
327
|
-
modelId: 'custom-model',
|
|
328
|
-
temperature: 0.5,
|
|
329
|
-
maxTokens: 1024,
|
|
330
|
-
});
|
|
331
|
-
provider.updateConfig({ temperature: 0.8 });
|
|
332
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
333
|
-
modelId: 'custom-model',
|
|
334
|
-
temperature: 0.8,
|
|
335
|
-
maxTokens: 1024,
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
it('re-resolves contextWindowLimit when modelId changes and it was auto-resolved', () => {
|
|
339
|
-
const provider = new BedrockModel({ region: 'us-west-2' });
|
|
340
|
-
expect(provider.getConfig().contextWindowLimit).toBe(1_000_000);
|
|
341
|
-
provider.updateConfig({ modelId: 'anthropic.claude-haiku-4-5-20251001-v1:0' });
|
|
342
|
-
expect(provider.getConfig().contextWindowLimit).toBe(200_000);
|
|
343
|
-
});
|
|
344
|
-
it('clears contextWindowLimit when modelId changes to unknown model', () => {
|
|
345
|
-
const provider = new BedrockModel({ region: 'us-west-2' });
|
|
346
|
-
expect(provider.getConfig().contextWindowLimit).toBe(1_000_000);
|
|
347
|
-
provider.updateConfig({ modelId: 'my-custom-finetuned-model' });
|
|
348
|
-
expect(provider.getConfig().contextWindowLimit).toBeUndefined();
|
|
349
|
-
});
|
|
350
|
-
it('preserves explicit contextWindowLimit when modelId changes', () => {
|
|
351
|
-
const provider = new BedrockModel({ region: 'us-west-2', contextWindowLimit: 50_000 });
|
|
352
|
-
expect(provider.getConfig().contextWindowLimit).toBe(50_000);
|
|
353
|
-
provider.updateConfig({ modelId: 'anthropic.claude-haiku-4-5-20251001-v1:0' });
|
|
354
|
-
expect(provider.getConfig().contextWindowLimit).toBe(50_000);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
describe('getConfig', () => {
|
|
358
|
-
it('returns the current configuration', () => {
|
|
359
|
-
const provider = new BedrockModel({
|
|
360
|
-
region: 'us-west-2',
|
|
361
|
-
modelId: 'test-model',
|
|
362
|
-
maxTokens: 1024,
|
|
363
|
-
temperature: 0.7,
|
|
364
|
-
});
|
|
365
|
-
expect(provider.getConfig()).toStrictEqual({
|
|
366
|
-
modelId: 'test-model',
|
|
367
|
-
maxTokens: 1024,
|
|
368
|
-
temperature: 0.7,
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
describe('format_message', async () => {
|
|
373
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
374
|
-
it('formats the request to bedrock properly', async () => {
|
|
375
|
-
const provider = new BedrockModel({
|
|
376
|
-
region: 'us-west-2',
|
|
377
|
-
modelId: 'anthropic.claude-test-model',
|
|
378
|
-
maxTokens: 1024,
|
|
379
|
-
temperature: 0.7,
|
|
380
|
-
topP: 0.9,
|
|
381
|
-
stopSequences: ['STOP'],
|
|
382
|
-
cacheConfig: { strategy: 'auto' },
|
|
383
|
-
additionalResponseFieldPaths: ['Hello!'],
|
|
384
|
-
additionalRequestFields: ['World!'],
|
|
385
|
-
additionalArgs: {
|
|
386
|
-
MyExtraArg: 'ExtraArg',
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
390
|
-
const options = {
|
|
391
|
-
systemPrompt: 'You are a helpful assistant',
|
|
392
|
-
toolSpecs: [
|
|
393
|
-
{
|
|
394
|
-
name: 'calculator',
|
|
395
|
-
description: 'Perform calculations',
|
|
396
|
-
inputSchema: { type: 'object', properties: { expression: { type: 'string' } } },
|
|
397
|
-
},
|
|
398
|
-
],
|
|
399
|
-
toolChoice: { auto: {} },
|
|
400
|
-
};
|
|
401
|
-
// Trigger the stream to make the request, but ignore the events for now
|
|
402
|
-
collectIterator(provider.stream(messages, options));
|
|
403
|
-
// Verify ConverseStreamCommand was called with properly formatted request
|
|
404
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
405
|
-
MyExtraArg: 'ExtraArg',
|
|
406
|
-
additionalModelRequestFields: ['World!'],
|
|
407
|
-
additionalModelResponseFieldPaths: ['Hello!'],
|
|
408
|
-
modelId: 'anthropic.claude-test-model',
|
|
409
|
-
messages: [
|
|
410
|
-
{
|
|
411
|
-
role: 'user',
|
|
412
|
-
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
413
|
-
},
|
|
414
|
-
],
|
|
415
|
-
system: [{ text: 'You are a helpful assistant' }],
|
|
416
|
-
toolConfig: {
|
|
417
|
-
toolChoice: { auto: {} },
|
|
418
|
-
tools: [
|
|
419
|
-
{
|
|
420
|
-
toolSpec: {
|
|
421
|
-
name: 'calculator',
|
|
422
|
-
description: 'Perform calculations',
|
|
423
|
-
inputSchema: { json: { type: 'object', properties: { expression: { type: 'string' } } } },
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
{ cachePoint: { type: 'default' } },
|
|
427
|
-
],
|
|
428
|
-
},
|
|
429
|
-
inferenceConfig: {
|
|
430
|
-
maxTokens: 1024,
|
|
431
|
-
temperature: 0.7,
|
|
432
|
-
topP: 0.9,
|
|
433
|
-
stopSequences: ['STOP'],
|
|
434
|
-
},
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
it('formats tool use messages', async () => {
|
|
438
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
439
|
-
const provider = new BedrockModel();
|
|
440
|
-
const messages = [
|
|
441
|
-
new Message({
|
|
442
|
-
role: 'assistant',
|
|
443
|
-
content: [
|
|
444
|
-
new ToolUseBlock({
|
|
445
|
-
name: 'calculator',
|
|
446
|
-
toolUseId: 'tool-123',
|
|
447
|
-
input: { a: 5, b: 3 },
|
|
448
|
-
}),
|
|
449
|
-
],
|
|
450
|
-
}),
|
|
451
|
-
];
|
|
452
|
-
// Run the stream but ignore the output
|
|
453
|
-
collectIterator(provider.stream(messages));
|
|
454
|
-
// Verify ConverseStreamCommand was called with properly formatted request
|
|
455
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
456
|
-
messages: expect.arrayContaining([
|
|
457
|
-
expect.objectContaining({
|
|
458
|
-
role: 'assistant',
|
|
459
|
-
content: expect.arrayContaining([
|
|
460
|
-
expect.objectContaining({
|
|
461
|
-
toolUse: expect.objectContaining({
|
|
462
|
-
name: 'calculator',
|
|
463
|
-
toolUseId: 'tool-123',
|
|
464
|
-
input: { a: 5, b: 3 },
|
|
465
|
-
}),
|
|
466
|
-
}),
|
|
467
|
-
]),
|
|
468
|
-
}),
|
|
469
|
-
]),
|
|
470
|
-
}));
|
|
471
|
-
});
|
|
472
|
-
it('formats tool result messages', async () => {
|
|
473
|
-
const provider = new BedrockModel();
|
|
474
|
-
const messages = [
|
|
475
|
-
new Message({
|
|
476
|
-
role: 'user',
|
|
477
|
-
content: [
|
|
478
|
-
new ToolResultBlock({
|
|
479
|
-
toolUseId: 'tool-123',
|
|
480
|
-
status: 'success',
|
|
481
|
-
content: [new TextBlock('Result: 8'), new JsonBlock({ json: { hello: 'world' } })],
|
|
482
|
-
}),
|
|
483
|
-
],
|
|
484
|
-
}),
|
|
485
|
-
];
|
|
486
|
-
// Start the stream
|
|
487
|
-
collectIterator(provider.stream(messages));
|
|
488
|
-
// Verify ConverseStreamCommand was called with properly formatted request
|
|
489
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
490
|
-
messages: [
|
|
491
|
-
{
|
|
492
|
-
content: [
|
|
493
|
-
{
|
|
494
|
-
toolResult: {
|
|
495
|
-
content: [
|
|
496
|
-
{
|
|
497
|
-
text: 'Result: 8',
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
json: {
|
|
501
|
-
hello: 'world',
|
|
502
|
-
},
|
|
503
|
-
},
|
|
504
|
-
],
|
|
505
|
-
status: 'success',
|
|
506
|
-
toolUseId: 'tool-123',
|
|
507
|
-
},
|
|
508
|
-
},
|
|
509
|
-
],
|
|
510
|
-
role: 'user',
|
|
511
|
-
},
|
|
512
|
-
],
|
|
513
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
514
|
-
modelId: expect.any(String),
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
it('injects noop tool config when messages have tool blocks but no toolSpecs', async () => {
|
|
518
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
519
|
-
const provider = new BedrockModel();
|
|
520
|
-
const messages = [
|
|
521
|
-
new Message({
|
|
522
|
-
role: 'assistant',
|
|
523
|
-
content: [new ToolUseBlock({ name: 'calc', toolUseId: 'id-1', input: { a: 1 } })],
|
|
524
|
-
}),
|
|
525
|
-
new Message({
|
|
526
|
-
role: 'user',
|
|
527
|
-
content: [new ToolResultBlock({ toolUseId: 'id-1', status: 'success', content: [new TextBlock('42')] })],
|
|
528
|
-
}),
|
|
529
|
-
new Message({ role: 'user', content: [new TextBlock('Summarize')] }),
|
|
530
|
-
];
|
|
531
|
-
collectIterator(provider.stream(messages));
|
|
532
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
533
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
534
|
-
}));
|
|
535
|
-
});
|
|
536
|
-
it('does not inject noop tool config when messages have no tool blocks', async () => {
|
|
537
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
538
|
-
const provider = new BedrockModel();
|
|
539
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
540
|
-
collectIterator(provider.stream(messages));
|
|
541
|
-
const call = mockConverseStreamCommand.mock.calls[0][0];
|
|
542
|
-
expect(call.toolConfig).toBeUndefined();
|
|
543
|
-
});
|
|
544
|
-
it('does not inject noop tool config when toolSpecs are provided', async () => {
|
|
545
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
546
|
-
const provider = new BedrockModel();
|
|
547
|
-
const messages = [
|
|
548
|
-
new Message({
|
|
549
|
-
role: 'assistant',
|
|
550
|
-
content: [new ToolUseBlock({ name: 'calc', toolUseId: 'id-1', input: {} })],
|
|
551
|
-
}),
|
|
552
|
-
new Message({
|
|
553
|
-
role: 'user',
|
|
554
|
-
content: [new ToolResultBlock({ toolUseId: 'id-1', status: 'success', content: [new TextBlock('ok')] })],
|
|
555
|
-
}),
|
|
556
|
-
];
|
|
557
|
-
const options = {
|
|
558
|
-
toolSpecs: [{ name: 'calc', description: 'Calculator', inputSchema: { type: 'object', properties: {} } }],
|
|
559
|
-
};
|
|
560
|
-
collectIterator(provider.stream(messages, options));
|
|
561
|
-
const call = mockConverseStreamCommand.mock.calls[0][0];
|
|
562
|
-
const toolConfig = call.toolConfig;
|
|
563
|
-
expect(toolConfig.tools[0].toolSpec.name).toBe('calc');
|
|
564
|
-
expect(toolConfig.tools.length).toBe(1);
|
|
565
|
-
});
|
|
566
|
-
it('formats reasoning messages properly', async () => {
|
|
567
|
-
const provider = new BedrockModel();
|
|
568
|
-
const messages = [
|
|
569
|
-
new Message({
|
|
570
|
-
role: 'user',
|
|
571
|
-
content: [
|
|
572
|
-
new ReasoningBlock({
|
|
573
|
-
text: 'Hello',
|
|
574
|
-
signature: 'World',
|
|
575
|
-
}),
|
|
576
|
-
new ReasoningBlock({
|
|
577
|
-
redactedContent: new Uint8Array(1),
|
|
578
|
-
}),
|
|
579
|
-
],
|
|
580
|
-
}),
|
|
581
|
-
];
|
|
582
|
-
// Start the stream but don't await it
|
|
583
|
-
collectIterator(provider.stream(messages));
|
|
584
|
-
// Verify ConverseStreamCommand was called with properly formatted request
|
|
585
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
586
|
-
messages: [
|
|
587
|
-
{
|
|
588
|
-
role: 'user',
|
|
589
|
-
content: [
|
|
590
|
-
{
|
|
591
|
-
reasoningContent: {
|
|
592
|
-
reasoningText: {
|
|
593
|
-
signature: 'World',
|
|
594
|
-
text: 'Hello',
|
|
595
|
-
},
|
|
596
|
-
},
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
reasoningContent: {
|
|
600
|
-
redactedContent: new Uint8Array(1),
|
|
601
|
-
},
|
|
602
|
-
},
|
|
603
|
-
],
|
|
604
|
-
},
|
|
605
|
-
],
|
|
606
|
-
modelId: expect.any(String),
|
|
607
|
-
});
|
|
608
|
-
});
|
|
609
|
-
it('formats a signature-only reasoning block', async () => {
|
|
610
|
-
const provider = new BedrockModel();
|
|
611
|
-
const messages = [
|
|
612
|
-
new Message({
|
|
613
|
-
role: 'assistant',
|
|
614
|
-
content: [new ReasoningBlock({ signature: 'sig-abc' }), new TextBlock('the answer')],
|
|
615
|
-
}),
|
|
616
|
-
];
|
|
617
|
-
collectIterator(provider.stream(messages));
|
|
618
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
619
|
-
messages: [
|
|
620
|
-
{
|
|
621
|
-
role: 'assistant',
|
|
622
|
-
content: [
|
|
623
|
-
{
|
|
624
|
-
reasoningContent: {
|
|
625
|
-
reasoningText: {
|
|
626
|
-
text: '',
|
|
627
|
-
signature: 'sig-abc',
|
|
628
|
-
},
|
|
629
|
-
},
|
|
630
|
-
},
|
|
631
|
-
{ text: 'the answer' },
|
|
632
|
-
],
|
|
633
|
-
},
|
|
634
|
-
],
|
|
635
|
-
modelId: expect.any(String),
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
it('formats cache point blocks in messages', async () => {
|
|
639
|
-
const provider = new BedrockModel();
|
|
640
|
-
const messages = [
|
|
641
|
-
new Message({
|
|
642
|
-
role: 'user',
|
|
643
|
-
content: [new TextBlock('Message with cache point'), new CachePointBlock({ cacheType: 'default' })],
|
|
644
|
-
}),
|
|
645
|
-
];
|
|
646
|
-
collectIterator(provider.stream(messages));
|
|
647
|
-
// Verify ConverseStreamCommand was called with properly formatted request
|
|
648
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
649
|
-
messages: [
|
|
650
|
-
{
|
|
651
|
-
role: 'user',
|
|
652
|
-
content: [{ text: 'Message with cache point' }, { cachePoint: { type: 'default' } }],
|
|
653
|
-
},
|
|
654
|
-
],
|
|
655
|
-
modelId: expect.any(String),
|
|
656
|
-
});
|
|
657
|
-
});
|
|
658
|
-
it('preserves ttl on user-supplied cache point blocks in messages', async () => {
|
|
659
|
-
const provider = new BedrockModel();
|
|
660
|
-
const messages = [
|
|
661
|
-
new Message({
|
|
662
|
-
role: 'user',
|
|
663
|
-
content: [
|
|
664
|
-
new TextBlock('Message with 1h cache point'),
|
|
665
|
-
new CachePointBlock({ cacheType: 'default', ttl: '1h' }),
|
|
666
|
-
],
|
|
667
|
-
}),
|
|
668
|
-
];
|
|
669
|
-
collectIterator(provider.stream(messages));
|
|
670
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
671
|
-
messages: [
|
|
672
|
-
{
|
|
673
|
-
role: 'user',
|
|
674
|
-
content: [{ text: 'Message with 1h cache point' }, { cachePoint: { type: 'default', ttl: '1h' } }],
|
|
675
|
-
},
|
|
676
|
-
],
|
|
677
|
-
modelId: expect.any(String),
|
|
678
|
-
});
|
|
679
|
-
});
|
|
680
|
-
it('preserves ttl on cache point blocks in system prompt', async () => {
|
|
681
|
-
const provider = new BedrockModel();
|
|
682
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
683
|
-
const options = {
|
|
684
|
-
systemPrompt: [
|
|
685
|
-
new TextBlock('You are a helpful assistant'),
|
|
686
|
-
new CachePointBlock({ cacheType: 'default', ttl: '5m' }),
|
|
687
|
-
],
|
|
688
|
-
};
|
|
689
|
-
collectIterator(provider.stream(messages, options));
|
|
690
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
691
|
-
expect(call?.system).toStrictEqual([
|
|
692
|
-
{ text: 'You are a helpful assistant' },
|
|
693
|
-
{ cachePoint: { type: 'default', ttl: '5m' } },
|
|
694
|
-
]);
|
|
695
|
-
});
|
|
696
|
-
it('forwards arbitrary ttl strings without client-side validation (Bedrock validates server-side)', async () => {
|
|
697
|
-
const provider = new BedrockModel();
|
|
698
|
-
const messages = [
|
|
699
|
-
new Message({
|
|
700
|
-
role: 'user',
|
|
701
|
-
content: [new TextBlock('Hello'), new CachePointBlock({ cacheType: 'default', ttl: '2h' })],
|
|
702
|
-
}),
|
|
703
|
-
];
|
|
704
|
-
collectIterator(provider.stream(messages));
|
|
705
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
706
|
-
const userMsg = call?.messages?.[0];
|
|
707
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
708
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default', ttl: '2h' } });
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
describe.each([
|
|
712
|
-
{ mode: 'streaming', stream: true },
|
|
713
|
-
{ mode: 'non-streaming', stream: false },
|
|
714
|
-
])('BedrockModel in $mode mode', ({ stream }) => {
|
|
715
|
-
it('yields and validates text events correctly', async () => {
|
|
716
|
-
const mockSend = vi.fn(async () => {
|
|
717
|
-
if (stream) {
|
|
718
|
-
return {
|
|
719
|
-
stream: (async function* () {
|
|
720
|
-
yield { messageStart: { role: 'assistant' } };
|
|
721
|
-
yield { contentBlockStart: {} };
|
|
722
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
723
|
-
yield { contentBlockStop: {} };
|
|
724
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
725
|
-
yield {
|
|
726
|
-
metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }, metrics: { latencyMs: 100 } },
|
|
727
|
-
};
|
|
728
|
-
})(),
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
else {
|
|
732
|
-
return {
|
|
733
|
-
output: { message: { role: 'assistant', content: [{ text: 'Hello' }] } },
|
|
734
|
-
stopReason: 'end_turn',
|
|
735
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
736
|
-
metrics: { latencyMs: 100 },
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
741
|
-
const provider = new BedrockModel({ stream });
|
|
742
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
743
|
-
const events = await collectIterator(provider.stream(messages));
|
|
744
|
-
expect(events).toContainEqual({ role: 'assistant', type: 'modelMessageStartEvent' });
|
|
745
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStartEvent' });
|
|
746
|
-
expect(events).toContainEqual({
|
|
747
|
-
type: 'modelContentBlockDeltaEvent',
|
|
748
|
-
delta: { type: 'textDelta', text: 'Hello' },
|
|
749
|
-
});
|
|
750
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStopEvent' });
|
|
751
|
-
expect(events).toContainEqual({ type: 'modelMessageStopEvent', stopReason: 'endTurn' });
|
|
752
|
-
expect(events).toContainEqual({
|
|
753
|
-
type: 'modelMetadataEvent',
|
|
754
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
755
|
-
metrics: { latencyMs: 100 },
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
|
-
it('yields and validates toolUse events correctly', async () => {
|
|
759
|
-
const mockSend = vi.fn(async () => {
|
|
760
|
-
if (stream) {
|
|
761
|
-
return {
|
|
762
|
-
stream: (async function* () {
|
|
763
|
-
yield { messageStart: { role: 'assistant' } };
|
|
764
|
-
yield {
|
|
765
|
-
contentBlockStart: {
|
|
766
|
-
start: { toolUse: { toolUseId: 'tool-use-123', name: 'get_weather' } },
|
|
767
|
-
},
|
|
768
|
-
};
|
|
769
|
-
yield {
|
|
770
|
-
contentBlockDelta: {
|
|
771
|
-
delta: { toolUse: { input: '{"location":"San Francisco"}' } },
|
|
772
|
-
},
|
|
773
|
-
};
|
|
774
|
-
yield { contentBlockStop: {} };
|
|
775
|
-
yield { messageStop: { stopReason: 'tool_use' } };
|
|
776
|
-
yield {
|
|
777
|
-
metadata: {
|
|
778
|
-
usage: { inputTokens: 10, outputTokens: 25, totalTokens: 35 },
|
|
779
|
-
metrics: { latencyMs: 120 },
|
|
780
|
-
},
|
|
781
|
-
};
|
|
782
|
-
})(),
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
786
|
-
return {
|
|
787
|
-
output: {
|
|
788
|
-
message: {
|
|
789
|
-
role: 'assistant',
|
|
790
|
-
content: [
|
|
791
|
-
{ toolUse: { toolUseId: 'tool-use-123', name: 'get_weather', input: { location: 'San Francisco' } } },
|
|
792
|
-
],
|
|
793
|
-
},
|
|
794
|
-
},
|
|
795
|
-
stopReason: 'tool_use',
|
|
796
|
-
usage: { inputTokens: 10, outputTokens: 25, totalTokens: 35 },
|
|
797
|
-
metrics: { latencyMs: 120 },
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
802
|
-
const provider = new BedrockModel({ stream });
|
|
803
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Weather?')] })];
|
|
804
|
-
const events = await collectIterator(provider.stream(messages));
|
|
805
|
-
const startEvent = events.find((e) => e.type === 'modelContentBlockStartEvent');
|
|
806
|
-
const inputDeltaEvent = events.find((e) => e.type === 'modelContentBlockDeltaEvent' && e.delta.type === 'toolUseInputDelta');
|
|
807
|
-
expect(events).toContainEqual({ role: 'assistant', type: 'modelMessageStartEvent' });
|
|
808
|
-
expect(startEvent).toStrictEqual({
|
|
809
|
-
type: 'modelContentBlockStartEvent',
|
|
810
|
-
start: { type: 'toolUseStart', name: 'get_weather', toolUseId: 'tool-use-123' },
|
|
811
|
-
});
|
|
812
|
-
expect(inputDeltaEvent).toStrictEqual({
|
|
813
|
-
type: 'modelContentBlockDeltaEvent',
|
|
814
|
-
delta: { type: 'toolUseInputDelta', input: '{"location":"San Francisco"}' },
|
|
815
|
-
});
|
|
816
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStopEvent' });
|
|
817
|
-
expect(events).toContainEqual({ stopReason: 'toolUse', type: 'modelMessageStopEvent' });
|
|
818
|
-
expect(events).toContainEqual({
|
|
819
|
-
type: 'modelMetadataEvent',
|
|
820
|
-
usage: { inputTokens: 10, outputTokens: 25, totalTokens: 35 },
|
|
821
|
-
metrics: { latencyMs: 120 },
|
|
822
|
-
});
|
|
823
|
-
});
|
|
824
|
-
it('yields and validates reasoningText events correctly', async () => {
|
|
825
|
-
const mockSend = vi.fn(async () => {
|
|
826
|
-
if (stream) {
|
|
827
|
-
return {
|
|
828
|
-
stream: (async function* () {
|
|
829
|
-
yield { messageStart: { role: 'assistant' } };
|
|
830
|
-
yield { contentBlockStart: {} };
|
|
831
|
-
yield {
|
|
832
|
-
contentBlockDelta: { delta: { reasoningContent: { text: 'Thinking...' } } },
|
|
833
|
-
};
|
|
834
|
-
yield { contentBlockStop: {} };
|
|
835
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
836
|
-
yield {
|
|
837
|
-
metadata: {
|
|
838
|
-
usage: { inputTokens: 15, outputTokens: 30, totalTokens: 45 },
|
|
839
|
-
metrics: { latencyMs: 150 },
|
|
840
|
-
},
|
|
841
|
-
};
|
|
842
|
-
})(),
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
else {
|
|
846
|
-
return {
|
|
847
|
-
output: {
|
|
848
|
-
message: {
|
|
849
|
-
role: 'assistant',
|
|
850
|
-
content: [{ reasoningContent: { reasoningText: { text: 'Thinking...' } } }],
|
|
851
|
-
},
|
|
852
|
-
},
|
|
853
|
-
stopReason: 'end_turn',
|
|
854
|
-
usage: { inputTokens: 15, outputTokens: 30, totalTokens: 45 },
|
|
855
|
-
metrics: { latencyMs: 150 },
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
860
|
-
const provider = new BedrockModel({ stream });
|
|
861
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('A question.')] })];
|
|
862
|
-
const events = await collectIterator(provider.stream(messages));
|
|
863
|
-
expect(events).toContainEqual({ role: 'assistant', type: 'modelMessageStartEvent' });
|
|
864
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStartEvent' });
|
|
865
|
-
expect(events).toContainEqual({
|
|
866
|
-
type: 'modelContentBlockDeltaEvent',
|
|
867
|
-
delta: { type: 'reasoningContentDelta', text: 'Thinking...' },
|
|
868
|
-
});
|
|
869
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStopEvent' });
|
|
870
|
-
expect(events).toContainEqual({ stopReason: 'endTurn', type: 'modelMessageStopEvent' });
|
|
871
|
-
expect(events).toContainEqual({
|
|
872
|
-
type: 'modelMetadataEvent',
|
|
873
|
-
usage: { inputTokens: 15, outputTokens: 30, totalTokens: 45 },
|
|
874
|
-
metrics: { latencyMs: 150 },
|
|
875
|
-
});
|
|
876
|
-
});
|
|
877
|
-
it('yields and validates redactedContent events correctly', async () => {
|
|
878
|
-
const redactedBytes = new Uint8Array([1, 2, 3]);
|
|
879
|
-
const mockSend = vi.fn(async () => {
|
|
880
|
-
if (stream) {
|
|
881
|
-
return {
|
|
882
|
-
stream: (async function* () {
|
|
883
|
-
yield { messageStart: { role: 'assistant' } };
|
|
884
|
-
yield { contentBlockStart: {} };
|
|
885
|
-
yield {
|
|
886
|
-
contentBlockDelta: {
|
|
887
|
-
delta: { reasoningContent: { redactedContent: redactedBytes } },
|
|
888
|
-
},
|
|
889
|
-
};
|
|
890
|
-
yield { contentBlockStop: {} };
|
|
891
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
892
|
-
yield {
|
|
893
|
-
metadata: { usage: { inputTokens: 15, outputTokens: 5, totalTokens: 20 }, metrics: { latencyMs: 110 } },
|
|
894
|
-
};
|
|
895
|
-
})(),
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
return {
|
|
900
|
-
output: {
|
|
901
|
-
message: {
|
|
902
|
-
role: 'assistant',
|
|
903
|
-
content: [{ reasoningContent: { redactedContent: redactedBytes } }],
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
stopReason: 'end_turn',
|
|
907
|
-
usage: { inputTokens: 15, outputTokens: 5, totalTokens: 20 },
|
|
908
|
-
metrics: { latencyMs: 110 },
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
913
|
-
const provider = new BedrockModel({ stream });
|
|
914
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('A sensitive question.')] })];
|
|
915
|
-
const events = await collectIterator(provider.stream(messages));
|
|
916
|
-
expect(events).toContainEqual({ role: 'assistant', type: 'modelMessageStartEvent' });
|
|
917
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStartEvent' });
|
|
918
|
-
expect(events).toContainEqual({
|
|
919
|
-
type: 'modelContentBlockDeltaEvent',
|
|
920
|
-
delta: { type: 'reasoningContentDelta', redactedContent: redactedBytes },
|
|
921
|
-
});
|
|
922
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStopEvent' });
|
|
923
|
-
expect(events).toContainEqual({ stopReason: 'endTurn', type: 'modelMessageStopEvent' });
|
|
924
|
-
expect(events).toContainEqual({
|
|
925
|
-
type: 'modelMetadataEvent',
|
|
926
|
-
usage: { inputTokens: 15, outputTokens: 5, totalTokens: 20 },
|
|
927
|
-
metrics: { latencyMs: 110 },
|
|
928
|
-
});
|
|
929
|
-
});
|
|
930
|
-
it('yields and validates citation events correctly', async () => {
|
|
931
|
-
// Bedrock streaming sends individual citation deltas with key 'citation'
|
|
932
|
-
const bedrockCitationDelta = {
|
|
933
|
-
location: { documentChar: { documentIndex: 0, start: 10, end: 50 } },
|
|
934
|
-
sourceContent: [{ text: 'source text' }],
|
|
935
|
-
source: 'doc-0',
|
|
936
|
-
title: 'Test Doc',
|
|
937
|
-
};
|
|
938
|
-
// Bedrock non-streaming wire format uses object-key discrimination
|
|
939
|
-
const bedrockCitationsData = {
|
|
940
|
-
citations: [bedrockCitationDelta],
|
|
941
|
-
content: [{ text: 'generated text' }],
|
|
942
|
-
};
|
|
943
|
-
const mockSend = vi.fn(async () => {
|
|
944
|
-
if (stream) {
|
|
945
|
-
return {
|
|
946
|
-
stream: (async function* () {
|
|
947
|
-
yield { messageStart: { role: 'assistant' } };
|
|
948
|
-
yield { contentBlockStart: {} };
|
|
949
|
-
yield {
|
|
950
|
-
contentBlockDelta: {
|
|
951
|
-
delta: { citation: bedrockCitationDelta },
|
|
952
|
-
},
|
|
953
|
-
};
|
|
954
|
-
yield { contentBlockStop: {} };
|
|
955
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
956
|
-
yield {
|
|
957
|
-
metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 }, metrics: { latencyMs: 100 } },
|
|
958
|
-
};
|
|
959
|
-
})(),
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
else {
|
|
963
|
-
return {
|
|
964
|
-
output: {
|
|
965
|
-
message: {
|
|
966
|
-
role: 'assistant',
|
|
967
|
-
content: [{ citationsContent: bedrockCitationsData }],
|
|
968
|
-
},
|
|
969
|
-
},
|
|
970
|
-
stopReason: 'end_turn',
|
|
971
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
972
|
-
metrics: { latencyMs: 100 },
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
977
|
-
const provider = new BedrockModel({ stream });
|
|
978
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Cite this.')] })];
|
|
979
|
-
const events = await collectIterator(provider.stream(messages));
|
|
980
|
-
// SDK events should use type-field discrimination
|
|
981
|
-
expect(events).toContainEqual({ role: 'assistant', type: 'modelMessageStartEvent' });
|
|
982
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStartEvent' });
|
|
983
|
-
expect(events).toContainEqual({
|
|
984
|
-
type: 'modelContentBlockDeltaEvent',
|
|
985
|
-
delta: {
|
|
986
|
-
type: 'citationsDelta',
|
|
987
|
-
citations: [
|
|
988
|
-
{
|
|
989
|
-
location: { type: 'documentChar', documentIndex: 0, start: 10, end: 50 },
|
|
990
|
-
sourceContent: [{ text: 'source text' }],
|
|
991
|
-
source: 'doc-0',
|
|
992
|
-
title: 'Test Doc',
|
|
993
|
-
},
|
|
994
|
-
],
|
|
995
|
-
content: stream ? [] : [{ text: 'generated text' }],
|
|
996
|
-
},
|
|
997
|
-
});
|
|
998
|
-
expect(events).toContainEqual({ type: 'modelContentBlockStopEvent' });
|
|
999
|
-
expect(events).toContainEqual({ stopReason: 'endTurn', type: 'modelMessageStopEvent' });
|
|
1000
|
-
});
|
|
1001
|
-
describe('error handling', async () => {
|
|
1002
|
-
it.each([
|
|
1003
|
-
{
|
|
1004
|
-
name: 'ContextWindowOverflowError for context overflow',
|
|
1005
|
-
error: new Error('Input is too long for requested model'),
|
|
1006
|
-
expected: ContextWindowOverflowError,
|
|
1007
|
-
},
|
|
1008
|
-
{
|
|
1009
|
-
name: 'ValidationException for invalid input',
|
|
1010
|
-
error: new ValidationException({ message: 'ValidationException', $metadata: {} }),
|
|
1011
|
-
expected: ValidationException,
|
|
1012
|
-
},
|
|
1013
|
-
])('throws $name', async ({ error, expected }) => {
|
|
1014
|
-
vi.clearAllMocks();
|
|
1015
|
-
const mockSendError = vi.fn().mockRejectedValue(error);
|
|
1016
|
-
mockBedrockClientImplementation({ send: mockSendError });
|
|
1017
|
-
const provider = new BedrockModel();
|
|
1018
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1019
|
-
await expect(collectIterator(provider.stream(messages))).rejects.toThrow(expected);
|
|
1020
|
-
});
|
|
1021
|
-
it('throws for a reasoning block with no text, signature, or redacted content', async () => {
|
|
1022
|
-
const provider = new BedrockModel();
|
|
1023
|
-
const messages = [
|
|
1024
|
-
new Message({
|
|
1025
|
-
role: 'assistant',
|
|
1026
|
-
content: [new ReasoningBlock({})],
|
|
1027
|
-
}),
|
|
1028
|
-
];
|
|
1029
|
-
await expect(collectIterator(provider.stream(messages))).rejects.toThrow("reasoning content requires one of 'text', 'signature', or 'redactedContent' to be set");
|
|
1030
|
-
});
|
|
1031
|
-
});
|
|
1032
|
-
});
|
|
1033
|
-
describe('stream', () => {
|
|
1034
|
-
it('handles tool use input delta', async () => {
|
|
1035
|
-
setupMockSend(async function* () {
|
|
1036
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1037
|
-
yield {
|
|
1038
|
-
contentBlockStart: { start: { toolUse: { name: 'calc', toolUseId: 'id' } } },
|
|
1039
|
-
};
|
|
1040
|
-
yield { contentBlockDelta: { delta: { toolUse: { input: '{"a": 1}' } } } };
|
|
1041
|
-
yield { contentBlockStop: {} };
|
|
1042
|
-
yield { messageStop: { stopReason: 'tool_use' } };
|
|
1043
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1044
|
-
});
|
|
1045
|
-
const provider = new BedrockModel();
|
|
1046
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1047
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1048
|
-
expect(events).toContainEqual({
|
|
1049
|
-
type: 'modelContentBlockDeltaEvent',
|
|
1050
|
-
delta: {
|
|
1051
|
-
type: 'toolUseInputDelta',
|
|
1052
|
-
input: '{"a": 1}',
|
|
1053
|
-
},
|
|
1054
|
-
});
|
|
1055
|
-
});
|
|
1056
|
-
it('handles reasoning content delta with both text and signature, as well as redactedContent', async () => {
|
|
1057
|
-
setupMockSend(async function* () {
|
|
1058
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1059
|
-
yield { contentBlockStart: {} };
|
|
1060
|
-
yield {
|
|
1061
|
-
contentBlockDelta: {
|
|
1062
|
-
delta: { reasoningContent: { text: 'thinking...', signature: 'sig123' } },
|
|
1063
|
-
},
|
|
1064
|
-
};
|
|
1065
|
-
yield {
|
|
1066
|
-
contentBlockDelta: {
|
|
1067
|
-
delta: { reasoningContent: { redactedContent: new Uint8Array(1) } },
|
|
1068
|
-
},
|
|
1069
|
-
};
|
|
1070
|
-
yield { contentBlockStop: {} };
|
|
1071
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
1072
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1073
|
-
});
|
|
1074
|
-
const provider = new BedrockModel();
|
|
1075
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1076
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1077
|
-
expect(events).toContainEqual({
|
|
1078
|
-
type: 'modelContentBlockDeltaEvent',
|
|
1079
|
-
delta: {
|
|
1080
|
-
type: 'reasoningContentDelta',
|
|
1081
|
-
text: 'thinking...',
|
|
1082
|
-
signature: 'sig123',
|
|
1083
|
-
},
|
|
1084
|
-
});
|
|
1085
|
-
expect(events).toContainEqual({
|
|
1086
|
-
type: 'modelContentBlockDeltaEvent',
|
|
1087
|
-
delta: {
|
|
1088
|
-
type: 'reasoningContentDelta',
|
|
1089
|
-
redactedContent: new Uint8Array(1),
|
|
1090
|
-
},
|
|
1091
|
-
});
|
|
1092
|
-
});
|
|
1093
|
-
it('handles reasoning content delta with only text, skips unsupported types', async () => {
|
|
1094
|
-
setupMockSend(async function* () {
|
|
1095
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1096
|
-
yield { contentBlockStart: {} };
|
|
1097
|
-
yield {
|
|
1098
|
-
contentBlockDelta: {
|
|
1099
|
-
delta: { reasoningContent: { text: 'thinking...' } },
|
|
1100
|
-
},
|
|
1101
|
-
};
|
|
1102
|
-
yield {
|
|
1103
|
-
contentBlockDelta: {
|
|
1104
|
-
delta: { unknown: 'type' },
|
|
1105
|
-
},
|
|
1106
|
-
};
|
|
1107
|
-
yield { contentBlockStop: {} };
|
|
1108
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
1109
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1110
|
-
yield { unknown: 'type' };
|
|
1111
|
-
});
|
|
1112
|
-
const provider = new BedrockModel();
|
|
1113
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1114
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1115
|
-
const reasoningDelta = events.find((e) => e.type === 'modelContentBlockDeltaEvent' && e.delta.type === 'reasoningContentDelta');
|
|
1116
|
-
expect(reasoningDelta).toBeDefined();
|
|
1117
|
-
if (reasoningDelta?.type === 'modelContentBlockDeltaEvent' &&
|
|
1118
|
-
reasoningDelta.delta.type === 'reasoningContentDelta') {
|
|
1119
|
-
expect(reasoningDelta.delta.text).toBe('thinking...');
|
|
1120
|
-
expect(reasoningDelta.delta.signature).toBeUndefined();
|
|
1121
|
-
}
|
|
1122
|
-
});
|
|
1123
|
-
it('handles reasoning content delta with only signature', async () => {
|
|
1124
|
-
setupMockSend(async function* () {
|
|
1125
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1126
|
-
yield { contentBlockStart: {} };
|
|
1127
|
-
yield {
|
|
1128
|
-
contentBlockDelta: {
|
|
1129
|
-
delta: { reasoningContent: { signature: 'sig123' } },
|
|
1130
|
-
},
|
|
1131
|
-
};
|
|
1132
|
-
yield { contentBlockStop: {} };
|
|
1133
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
1134
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1135
|
-
});
|
|
1136
|
-
const provider = new BedrockModel();
|
|
1137
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1138
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1139
|
-
const reasoningDelta = events.find((e) => e.type === 'modelContentBlockDeltaEvent' && e.delta.type === 'reasoningContentDelta');
|
|
1140
|
-
expect(reasoningDelta).toBeDefined();
|
|
1141
|
-
if (reasoningDelta?.type === 'modelContentBlockDeltaEvent' &&
|
|
1142
|
-
reasoningDelta.delta.type === 'reasoningContentDelta') {
|
|
1143
|
-
expect(reasoningDelta.delta.text).toBeUndefined();
|
|
1144
|
-
expect(reasoningDelta.delta.signature).toBe('sig123');
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
it('handles cache usage metrics', async () => {
|
|
1148
|
-
setupMockSend(async function* () {
|
|
1149
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1150
|
-
yield { contentBlockStart: {} };
|
|
1151
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
1152
|
-
yield { contentBlockStop: {} };
|
|
1153
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
1154
|
-
yield {
|
|
1155
|
-
metadata: {
|
|
1156
|
-
usage: {
|
|
1157
|
-
inputTokens: 100,
|
|
1158
|
-
outputTokens: 50,
|
|
1159
|
-
totalTokens: 150,
|
|
1160
|
-
cacheReadInputTokens: 80,
|
|
1161
|
-
cacheWriteInputTokens: 20,
|
|
1162
|
-
},
|
|
1163
|
-
},
|
|
1164
|
-
};
|
|
1165
|
-
});
|
|
1166
|
-
const provider = new BedrockModel();
|
|
1167
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1168
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1169
|
-
const metadataEvent = events.find((e) => e.type === 'modelMetadataEvent');
|
|
1170
|
-
expect(metadataEvent).toBeDefined();
|
|
1171
|
-
if (metadataEvent?.type === 'modelMetadataEvent') {
|
|
1172
|
-
expect(metadataEvent.usage?.cacheReadInputTokens).toBe(80);
|
|
1173
|
-
expect(metadataEvent.usage?.cacheWriteInputTokens).toBe(20);
|
|
1174
|
-
}
|
|
1175
|
-
});
|
|
1176
|
-
it('handles trace in metadata', async () => {
|
|
1177
|
-
setupMockSend(async function* () {
|
|
1178
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1179
|
-
yield { contentBlockStart: {} };
|
|
1180
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
1181
|
-
yield { contentBlockStop: {} };
|
|
1182
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
1183
|
-
yield {
|
|
1184
|
-
metadata: {
|
|
1185
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1186
|
-
trace: { guardrail: { action: 'INTERVENED' } },
|
|
1187
|
-
},
|
|
1188
|
-
};
|
|
1189
|
-
});
|
|
1190
|
-
const provider = new BedrockModel();
|
|
1191
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1192
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1193
|
-
const metadataEvent = events.find((e) => e.type === 'modelMetadataEvent');
|
|
1194
|
-
expect(metadataEvent).toBeDefined();
|
|
1195
|
-
if (metadataEvent?.type === 'modelMetadataEvent') {
|
|
1196
|
-
expect(metadataEvent.trace).toBeDefined();
|
|
1197
|
-
}
|
|
1198
|
-
});
|
|
1199
|
-
it('handles additionalModelResponseFields', async () => {
|
|
1200
|
-
setupMockSend(async function* () {
|
|
1201
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1202
|
-
yield { contentBlockStart: {} };
|
|
1203
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
1204
|
-
yield { contentBlockStop: {} };
|
|
1205
|
-
yield { messageStop: { stopReason: 'end_turn', additionalModelResponseFields: { customField: 'value' } } };
|
|
1206
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1207
|
-
});
|
|
1208
|
-
const provider = new BedrockModel();
|
|
1209
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1210
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1211
|
-
const stopEvent = events.find((e) => e.type === 'modelMessageStopEvent');
|
|
1212
|
-
expect(stopEvent).toBeDefined();
|
|
1213
|
-
if (stopEvent?.type === 'modelMessageStopEvent') {
|
|
1214
|
-
expect(stopEvent.additionalModelResponseFields).toStrictEqual({ customField: 'value' });
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
describe('handles all stop reason types', () => {
|
|
1218
|
-
const stopReasons = [
|
|
1219
|
-
['end_turn', 'endTurn'],
|
|
1220
|
-
['tool_use', 'toolUse'],
|
|
1221
|
-
['max_tokens', 'maxTokens'],
|
|
1222
|
-
['stop_sequence', 'stopSequence'],
|
|
1223
|
-
['content_filtered', 'contentFiltered'],
|
|
1224
|
-
['guardrail_intervened', 'guardrailIntervened'],
|
|
1225
|
-
['model_context_window_exceeded', 'modelContextWindowExceeded'],
|
|
1226
|
-
['new_stop_reason', 'newStopReason'],
|
|
1227
|
-
];
|
|
1228
|
-
for (const [bedrockReason, expectedReason] of stopReasons) {
|
|
1229
|
-
it(`handles ${bedrockReason} stop reason types`, async () => {
|
|
1230
|
-
setupMockSend(async function* () {
|
|
1231
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1232
|
-
yield { messageStop: { stopReason: bedrockReason } };
|
|
1233
|
-
yield { metadata: { usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } } };
|
|
1234
|
-
});
|
|
1235
|
-
const provider = new BedrockModel();
|
|
1236
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1237
|
-
const events = [];
|
|
1238
|
-
for await (const event of provider.stream(messages)) {
|
|
1239
|
-
events.push(event);
|
|
1240
|
-
}
|
|
1241
|
-
const stopEvent = events.find((e) => e.type === 'modelMessageStopEvent');
|
|
1242
|
-
expect(stopEvent).toBeDefined();
|
|
1243
|
-
if (stopEvent?.type === 'modelMessageStopEvent') {
|
|
1244
|
-
expect(stopEvent.stopReason).toBe(expectedReason);
|
|
1245
|
-
}
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
});
|
|
1249
|
-
describe('throttling', () => {
|
|
1250
|
-
it('throws ModelThrottledError when throttlingException is received', async () => {
|
|
1251
|
-
setupMockSend(async function* () {
|
|
1252
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1253
|
-
yield { throttlingException: { message: 'Rate exceeded' } };
|
|
1254
|
-
});
|
|
1255
|
-
const provider = new BedrockModel();
|
|
1256
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1257
|
-
await expect(async () => {
|
|
1258
|
-
for await (const _ of provider.stream(messages)) {
|
|
1259
|
-
// consume stream
|
|
1260
|
-
}
|
|
1261
|
-
}).rejects.toThrow(ModelThrottledError);
|
|
1262
|
-
});
|
|
1263
|
-
it('includes throttling message in ModelThrottledError', async () => {
|
|
1264
|
-
setupMockSend(async function* () {
|
|
1265
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1266
|
-
yield { throttlingException: { message: 'Too many requests' } };
|
|
1267
|
-
});
|
|
1268
|
-
const provider = new BedrockModel();
|
|
1269
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1270
|
-
await expect(async () => {
|
|
1271
|
-
for await (const _ of provider.stream(messages)) {
|
|
1272
|
-
// consume stream
|
|
1273
|
-
}
|
|
1274
|
-
}).rejects.toThrow('Too many requests');
|
|
1275
|
-
});
|
|
1276
|
-
it('uses default message when throttlingException has no message', async () => {
|
|
1277
|
-
setupMockSend(async function* () {
|
|
1278
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1279
|
-
yield { throttlingException: {} };
|
|
1280
|
-
});
|
|
1281
|
-
const provider = new BedrockModel();
|
|
1282
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1283
|
-
await expect(async () => {
|
|
1284
|
-
for await (const _ of provider.stream(messages)) {
|
|
1285
|
-
// consume stream
|
|
1286
|
-
}
|
|
1287
|
-
}).rejects.toThrow('Request was throttled by the model provider');
|
|
1288
|
-
});
|
|
1289
|
-
});
|
|
1290
|
-
});
|
|
1291
|
-
describe('system prompt formatting', async () => {
|
|
1292
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1293
|
-
beforeEach(() => {
|
|
1294
|
-
vi.clearAllMocks();
|
|
1295
|
-
});
|
|
1296
|
-
it('does not add cache points to string system prompt with cacheConfig', async () => {
|
|
1297
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1298
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1299
|
-
const options = {
|
|
1300
|
-
systemPrompt: 'You are a helpful assistant',
|
|
1301
|
-
};
|
|
1302
|
-
collectIterator(provider.stream(messages, options));
|
|
1303
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1304
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1305
|
-
messages: [
|
|
1306
|
-
{
|
|
1307
|
-
role: 'user',
|
|
1308
|
-
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
1309
|
-
},
|
|
1310
|
-
],
|
|
1311
|
-
system: [{ text: 'You are a helpful assistant' }],
|
|
1312
|
-
});
|
|
1313
|
-
});
|
|
1314
|
-
it('formats array system prompt with text blocks only', async () => {
|
|
1315
|
-
const provider = new BedrockModel();
|
|
1316
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1317
|
-
const options = {
|
|
1318
|
-
systemPrompt: [
|
|
1319
|
-
{ type: 'textBlock', text: 'You are a helpful assistant' },
|
|
1320
|
-
{ type: 'textBlock', text: 'Additional context here' },
|
|
1321
|
-
],
|
|
1322
|
-
};
|
|
1323
|
-
collectIterator(provider.stream(messages, options));
|
|
1324
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1325
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1326
|
-
messages: [
|
|
1327
|
-
{
|
|
1328
|
-
role: 'user',
|
|
1329
|
-
content: [{ text: 'Hello' }],
|
|
1330
|
-
},
|
|
1331
|
-
],
|
|
1332
|
-
system: [{ text: 'You are a helpful assistant' }, { text: 'Additional context here' }],
|
|
1333
|
-
});
|
|
1334
|
-
});
|
|
1335
|
-
it('formats array system prompt with cache points', async () => {
|
|
1336
|
-
const provider = new BedrockModel();
|
|
1337
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1338
|
-
const options = {
|
|
1339
|
-
systemPrompt: [
|
|
1340
|
-
{ type: 'textBlock', text: 'You are a helpful assistant' },
|
|
1341
|
-
{ type: 'textBlock', text: 'Large context document' },
|
|
1342
|
-
{ type: 'cachePointBlock', cacheType: 'default' },
|
|
1343
|
-
],
|
|
1344
|
-
};
|
|
1345
|
-
collectIterator(provider.stream(messages, options));
|
|
1346
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1347
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1348
|
-
messages: [
|
|
1349
|
-
{
|
|
1350
|
-
role: 'user',
|
|
1351
|
-
content: [{ text: 'Hello' }],
|
|
1352
|
-
},
|
|
1353
|
-
],
|
|
1354
|
-
system: [
|
|
1355
|
-
{ text: 'You are a helpful assistant' },
|
|
1356
|
-
{ text: 'Large context document' },
|
|
1357
|
-
{ cachePoint: { type: 'default' } },
|
|
1358
|
-
],
|
|
1359
|
-
});
|
|
1360
|
-
});
|
|
1361
|
-
it('does not warn when array system prompt is provided without cacheConfig', async () => {
|
|
1362
|
-
const provider = new BedrockModel();
|
|
1363
|
-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
1364
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1365
|
-
const options = {
|
|
1366
|
-
systemPrompt: [
|
|
1367
|
-
{ type: 'textBlock', text: 'You are a helpful assistant' },
|
|
1368
|
-
{ type: 'cachePointBlock', cacheType: 'default' },
|
|
1369
|
-
],
|
|
1370
|
-
};
|
|
1371
|
-
collectIterator(provider.stream(messages, options));
|
|
1372
|
-
// Verify no warning was logged
|
|
1373
|
-
expect(warnSpy).not.toHaveBeenCalled();
|
|
1374
|
-
// Verify array is used as-is
|
|
1375
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1376
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1377
|
-
messages: [
|
|
1378
|
-
{
|
|
1379
|
-
role: 'user',
|
|
1380
|
-
content: [{ text: 'Hello' }],
|
|
1381
|
-
},
|
|
1382
|
-
],
|
|
1383
|
-
system: [{ text: 'You are a helpful assistant' }, { cachePoint: { type: 'default' } }],
|
|
1384
|
-
});
|
|
1385
|
-
warnSpy.mockRestore();
|
|
1386
|
-
});
|
|
1387
|
-
it('adds cache point after tools when cacheConfig enabled', async () => {
|
|
1388
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1389
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1390
|
-
const options = {
|
|
1391
|
-
toolSpecs: [
|
|
1392
|
-
{
|
|
1393
|
-
name: 'calculator',
|
|
1394
|
-
description: 'Calculate',
|
|
1395
|
-
inputSchema: { type: 'object' },
|
|
1396
|
-
},
|
|
1397
|
-
],
|
|
1398
|
-
};
|
|
1399
|
-
collectIterator(provider.stream(messages, options));
|
|
1400
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1401
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1402
|
-
messages: [
|
|
1403
|
-
{
|
|
1404
|
-
role: 'user',
|
|
1405
|
-
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
1406
|
-
},
|
|
1407
|
-
],
|
|
1408
|
-
toolConfig: {
|
|
1409
|
-
tools: [
|
|
1410
|
-
{
|
|
1411
|
-
toolSpec: {
|
|
1412
|
-
name: 'calculator',
|
|
1413
|
-
description: 'Calculate',
|
|
1414
|
-
inputSchema: { json: { type: 'object' } },
|
|
1415
|
-
},
|
|
1416
|
-
},
|
|
1417
|
-
{ cachePoint: { type: 'default' } },
|
|
1418
|
-
],
|
|
1419
|
-
},
|
|
1420
|
-
});
|
|
1421
|
-
});
|
|
1422
|
-
it('adds cache points to tools and messages when cacheConfig enabled', async () => {
|
|
1423
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1424
|
-
const messages = [
|
|
1425
|
-
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1426
|
-
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1427
|
-
];
|
|
1428
|
-
const options = {
|
|
1429
|
-
systemPrompt: 'You are a helpful assistant',
|
|
1430
|
-
toolSpecs: [
|
|
1431
|
-
{
|
|
1432
|
-
name: 'calculator',
|
|
1433
|
-
description: 'Calculate',
|
|
1434
|
-
inputSchema: { type: 'object' },
|
|
1435
|
-
},
|
|
1436
|
-
],
|
|
1437
|
-
};
|
|
1438
|
-
collectIterator(provider.stream(messages, options));
|
|
1439
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1440
|
-
expect(call?.system).toStrictEqual([{ text: 'You are a helpful assistant' }]);
|
|
1441
|
-
expect(call?.toolConfig?.tools).toStrictEqual([
|
|
1442
|
-
{
|
|
1443
|
-
toolSpec: {
|
|
1444
|
-
name: 'calculator',
|
|
1445
|
-
description: 'Calculate',
|
|
1446
|
-
inputSchema: { json: { type: 'object' } },
|
|
1447
|
-
},
|
|
1448
|
-
},
|
|
1449
|
-
{ cachePoint: { type: 'default' } },
|
|
1450
|
-
]);
|
|
1451
|
-
const userMsg = call?.messages?.[0];
|
|
1452
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1453
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1454
|
-
const assistantMsg = call?.messages?.[1];
|
|
1455
|
-
const assistantLastBlock = assistantMsg?.content?.[assistantMsg.content.length - 1];
|
|
1456
|
-
expect(assistantLastBlock).not.toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1457
|
-
});
|
|
1458
|
-
it('propagates cacheConfig ttls independently to tools and last user message', async () => {
|
|
1459
|
-
const provider = new BedrockModel({
|
|
1460
|
-
cacheConfig: { strategy: 'auto', toolsTTL: '1h', messagesTTL: '5m' },
|
|
1461
|
-
});
|
|
1462
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1463
|
-
const options = {
|
|
1464
|
-
toolSpecs: [
|
|
1465
|
-
{
|
|
1466
|
-
name: 'calculator',
|
|
1467
|
-
description: 'Calculate',
|
|
1468
|
-
inputSchema: { type: 'object' },
|
|
1469
|
-
},
|
|
1470
|
-
],
|
|
1471
|
-
};
|
|
1472
|
-
collectIterator(provider.stream(messages, options));
|
|
1473
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1474
|
-
expect(call?.toolConfig?.tools).toStrictEqual([
|
|
1475
|
-
{
|
|
1476
|
-
toolSpec: {
|
|
1477
|
-
name: 'calculator',
|
|
1478
|
-
description: 'Calculate',
|
|
1479
|
-
inputSchema: { json: { type: 'object' } },
|
|
1480
|
-
},
|
|
1481
|
-
},
|
|
1482
|
-
{ cachePoint: { type: 'default', ttl: '1h' } },
|
|
1483
|
-
]);
|
|
1484
|
-
const userMsg = call?.messages?.[0];
|
|
1485
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1486
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default', ttl: '5m' } });
|
|
1487
|
-
});
|
|
1488
|
-
it('propagates only toolsTTL when messagesTTL is not set', async () => {
|
|
1489
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto', toolsTTL: '1h' } });
|
|
1490
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1491
|
-
const options = {
|
|
1492
|
-
toolSpecs: [
|
|
1493
|
-
{
|
|
1494
|
-
name: 'calculator',
|
|
1495
|
-
description: 'Calculate',
|
|
1496
|
-
inputSchema: { type: 'object' },
|
|
1497
|
-
},
|
|
1498
|
-
],
|
|
1499
|
-
};
|
|
1500
|
-
collectIterator(provider.stream(messages, options));
|
|
1501
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1502
|
-
const toolsLast = call?.toolConfig?.tools?.[call.toolConfig.tools.length - 1];
|
|
1503
|
-
expect(toolsLast).toStrictEqual({ cachePoint: { type: 'default', ttl: '1h' } });
|
|
1504
|
-
const userMsg = call?.messages?.[0];
|
|
1505
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1506
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1507
|
-
});
|
|
1508
|
-
it('propagates only messagesTTL when toolsTTL is not set', async () => {
|
|
1509
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto', messagesTTL: '1h' } });
|
|
1510
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1511
|
-
const options = {
|
|
1512
|
-
toolSpecs: [
|
|
1513
|
-
{
|
|
1514
|
-
name: 'calculator',
|
|
1515
|
-
description: 'Calculate',
|
|
1516
|
-
inputSchema: { type: 'object' },
|
|
1517
|
-
},
|
|
1518
|
-
],
|
|
1519
|
-
};
|
|
1520
|
-
collectIterator(provider.stream(messages, options));
|
|
1521
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1522
|
-
const toolsLast = call?.toolConfig?.tools?.[call.toolConfig.tools.length - 1];
|
|
1523
|
-
expect(toolsLast).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1524
|
-
const userMsg = call?.messages?.[0];
|
|
1525
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1526
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default', ttl: '1h' } });
|
|
1527
|
-
});
|
|
1528
|
-
it('omits ttl on auto-injected cache points when no ttl is set', async () => {
|
|
1529
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1530
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1531
|
-
const options = {
|
|
1532
|
-
toolSpecs: [
|
|
1533
|
-
{
|
|
1534
|
-
name: 'calculator',
|
|
1535
|
-
description: 'Calculate',
|
|
1536
|
-
inputSchema: { type: 'object' },
|
|
1537
|
-
},
|
|
1538
|
-
],
|
|
1539
|
-
};
|
|
1540
|
-
collectIterator(provider.stream(messages, options));
|
|
1541
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1542
|
-
const toolsLast = call?.toolConfig?.tools?.[call.toolConfig.tools.length - 1];
|
|
1543
|
-
expect(toolsLast).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1544
|
-
const userMsg = call?.messages?.[0];
|
|
1545
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1546
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1547
|
-
});
|
|
1548
|
-
it('does not mutate the original messages array', async () => {
|
|
1549
|
-
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1550
|
-
const originalMessages = [
|
|
1551
|
-
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1552
|
-
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1553
|
-
];
|
|
1554
|
-
// Create a deep copy to compare against
|
|
1555
|
-
const messagesCopy = JSON.parse(JSON.stringify(originalMessages));
|
|
1556
|
-
collectIterator(provider.stream(originalMessages));
|
|
1557
|
-
// Verify original messages are unchanged
|
|
1558
|
-
expect(JSON.stringify(originalMessages)).toBe(JSON.stringify(messagesCopy));
|
|
1559
|
-
});
|
|
1560
|
-
it('logs warning and disables caching for non-caching models', async () => {
|
|
1561
|
-
const warnSpy = vi.spyOn(console, 'warn');
|
|
1562
|
-
const provider = new BedrockModel({
|
|
1563
|
-
modelId: 'amazon.titan-text-express-v1',
|
|
1564
|
-
cacheConfig: { strategy: 'auto' },
|
|
1565
|
-
});
|
|
1566
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1567
|
-
const options = {
|
|
1568
|
-
systemPrompt: 'You are a helpful assistant',
|
|
1569
|
-
};
|
|
1570
|
-
collectIterator(provider.stream(messages, options));
|
|
1571
|
-
// Verify warning was logged
|
|
1572
|
-
expect(warnSpy).toHaveBeenCalled();
|
|
1573
|
-
// Verify no cache points were added
|
|
1574
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1575
|
-
modelId: 'amazon.titan-text-express-v1',
|
|
1576
|
-
messages: [
|
|
1577
|
-
{
|
|
1578
|
-
role: 'user',
|
|
1579
|
-
content: [{ text: 'Hello' }],
|
|
1580
|
-
},
|
|
1581
|
-
],
|
|
1582
|
-
system: [{ text: 'You are a helpful assistant' }],
|
|
1583
|
-
});
|
|
1584
|
-
warnSpy.mockRestore();
|
|
1585
|
-
});
|
|
1586
|
-
it('enables caching with anthropic strategy for application inference profiles', async () => {
|
|
1587
|
-
const provider = new BedrockModel({
|
|
1588
|
-
modelId: 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123',
|
|
1589
|
-
cacheConfig: { strategy: 'anthropic' },
|
|
1590
|
-
});
|
|
1591
|
-
const messages = [
|
|
1592
|
-
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1593
|
-
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1594
|
-
];
|
|
1595
|
-
collectIterator(provider.stream(messages));
|
|
1596
|
-
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1597
|
-
// Cache point should be on the user message (index 0)
|
|
1598
|
-
const userMsg = call?.messages?.[0];
|
|
1599
|
-
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1600
|
-
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1601
|
-
});
|
|
1602
|
-
it('handles empty array system prompt', async () => {
|
|
1603
|
-
const provider = new BedrockModel();
|
|
1604
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1605
|
-
const options = {
|
|
1606
|
-
systemPrompt: [],
|
|
1607
|
-
};
|
|
1608
|
-
collectIterator(provider.stream(messages, options));
|
|
1609
|
-
// Empty array should not set system field
|
|
1610
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1611
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1612
|
-
messages: [
|
|
1613
|
-
{
|
|
1614
|
-
role: 'user',
|
|
1615
|
-
content: [{ text: 'Hello' }],
|
|
1616
|
-
},
|
|
1617
|
-
],
|
|
1618
|
-
});
|
|
1619
|
-
});
|
|
1620
|
-
it('formats array system prompt with guard content', async () => {
|
|
1621
|
-
const provider = new BedrockModel();
|
|
1622
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1623
|
-
const options = {
|
|
1624
|
-
systemPrompt: [
|
|
1625
|
-
new TextBlock('You are a helpful assistant'),
|
|
1626
|
-
new GuardContentBlock({
|
|
1627
|
-
text: {
|
|
1628
|
-
qualifiers: ['grounding_source'],
|
|
1629
|
-
text: 'This content should be evaluated for grounding.',
|
|
1630
|
-
},
|
|
1631
|
-
}),
|
|
1632
|
-
],
|
|
1633
|
-
};
|
|
1634
|
-
collectIterator(provider.stream(messages, options));
|
|
1635
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1636
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1637
|
-
messages: [
|
|
1638
|
-
{
|
|
1639
|
-
role: 'user',
|
|
1640
|
-
content: [{ text: 'Hello' }],
|
|
1641
|
-
},
|
|
1642
|
-
],
|
|
1643
|
-
system: [
|
|
1644
|
-
{ text: 'You are a helpful assistant' },
|
|
1645
|
-
{
|
|
1646
|
-
guardContent: {
|
|
1647
|
-
text: {
|
|
1648
|
-
text: 'This content should be evaluated for grounding.',
|
|
1649
|
-
qualifiers: ['grounding_source'],
|
|
1650
|
-
},
|
|
1651
|
-
},
|
|
1652
|
-
},
|
|
1653
|
-
],
|
|
1654
|
-
});
|
|
1655
|
-
});
|
|
1656
|
-
it('formats mixed system prompt with text, guard content, and cache points', async () => {
|
|
1657
|
-
const provider = new BedrockModel();
|
|
1658
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1659
|
-
const options = {
|
|
1660
|
-
systemPrompt: [
|
|
1661
|
-
new TextBlock('You are a helpful assistant'),
|
|
1662
|
-
new GuardContentBlock({
|
|
1663
|
-
text: {
|
|
1664
|
-
qualifiers: ['grounding_source', 'query'],
|
|
1665
|
-
text: 'Guard content',
|
|
1666
|
-
},
|
|
1667
|
-
}),
|
|
1668
|
-
new TextBlock('Additional context'),
|
|
1669
|
-
new CachePointBlock({ cacheType: 'default' }),
|
|
1670
|
-
],
|
|
1671
|
-
};
|
|
1672
|
-
collectIterator(provider.stream(messages, options));
|
|
1673
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1674
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1675
|
-
messages: [
|
|
1676
|
-
{
|
|
1677
|
-
role: 'user',
|
|
1678
|
-
content: [{ text: 'Hello' }],
|
|
1679
|
-
},
|
|
1680
|
-
],
|
|
1681
|
-
system: [
|
|
1682
|
-
{ text: 'You are a helpful assistant' },
|
|
1683
|
-
{
|
|
1684
|
-
guardContent: {
|
|
1685
|
-
text: {
|
|
1686
|
-
text: 'Guard content',
|
|
1687
|
-
qualifiers: ['grounding_source', 'query'],
|
|
1688
|
-
},
|
|
1689
|
-
},
|
|
1690
|
-
},
|
|
1691
|
-
{ text: 'Additional context' },
|
|
1692
|
-
{ cachePoint: { type: 'default' } },
|
|
1693
|
-
],
|
|
1694
|
-
});
|
|
1695
|
-
});
|
|
1696
|
-
it('formats guard content with all qualifier types', async () => {
|
|
1697
|
-
const provider = new BedrockModel();
|
|
1698
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1699
|
-
const options = {
|
|
1700
|
-
systemPrompt: [
|
|
1701
|
-
new GuardContentBlock({
|
|
1702
|
-
text: {
|
|
1703
|
-
qualifiers: ['grounding_source', 'query', 'guard_content'],
|
|
1704
|
-
text: 'Multi-qualifier guard content',
|
|
1705
|
-
},
|
|
1706
|
-
}),
|
|
1707
|
-
],
|
|
1708
|
-
};
|
|
1709
|
-
collectIterator(provider.stream(messages, options));
|
|
1710
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1711
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1712
|
-
messages: [
|
|
1713
|
-
{
|
|
1714
|
-
role: 'user',
|
|
1715
|
-
content: [{ text: 'Hello' }],
|
|
1716
|
-
},
|
|
1717
|
-
],
|
|
1718
|
-
system: [
|
|
1719
|
-
{
|
|
1720
|
-
guardContent: {
|
|
1721
|
-
text: {
|
|
1722
|
-
text: 'Multi-qualifier guard content',
|
|
1723
|
-
qualifiers: ['grounding_source', 'query', 'guard_content'],
|
|
1724
|
-
},
|
|
1725
|
-
},
|
|
1726
|
-
},
|
|
1727
|
-
],
|
|
1728
|
-
});
|
|
1729
|
-
});
|
|
1730
|
-
it('formats guard content with image in system prompt', async () => {
|
|
1731
|
-
const provider = new BedrockModel();
|
|
1732
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1733
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
1734
|
-
const options = {
|
|
1735
|
-
systemPrompt: [
|
|
1736
|
-
new GuardContentBlock({
|
|
1737
|
-
image: {
|
|
1738
|
-
format: 'jpeg',
|
|
1739
|
-
source: { bytes: imageBytes },
|
|
1740
|
-
},
|
|
1741
|
-
}),
|
|
1742
|
-
],
|
|
1743
|
-
};
|
|
1744
|
-
collectIterator(provider.stream(messages, options));
|
|
1745
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1746
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1747
|
-
messages: [
|
|
1748
|
-
{
|
|
1749
|
-
role: 'user',
|
|
1750
|
-
content: [{ text: 'Hello' }],
|
|
1751
|
-
},
|
|
1752
|
-
],
|
|
1753
|
-
system: [
|
|
1754
|
-
{
|
|
1755
|
-
guardContent: {
|
|
1756
|
-
image: {
|
|
1757
|
-
format: 'jpeg',
|
|
1758
|
-
source: { bytes: imageBytes },
|
|
1759
|
-
},
|
|
1760
|
-
},
|
|
1761
|
-
},
|
|
1762
|
-
],
|
|
1763
|
-
});
|
|
1764
|
-
});
|
|
1765
|
-
});
|
|
1766
|
-
describe('guard content in messages', async () => {
|
|
1767
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1768
|
-
beforeEach(() => {
|
|
1769
|
-
vi.clearAllMocks();
|
|
1770
|
-
});
|
|
1771
|
-
it('formats guard content with text in message', async () => {
|
|
1772
|
-
const provider = new BedrockModel();
|
|
1773
|
-
const messages = [
|
|
1774
|
-
new Message({
|
|
1775
|
-
role: 'user',
|
|
1776
|
-
content: [
|
|
1777
|
-
new TextBlock('Verify this information:'),
|
|
1778
|
-
new GuardContentBlock({
|
|
1779
|
-
text: {
|
|
1780
|
-
qualifiers: ['grounding_source'],
|
|
1781
|
-
text: 'The capital of France is Paris.',
|
|
1782
|
-
},
|
|
1783
|
-
}),
|
|
1784
|
-
],
|
|
1785
|
-
}),
|
|
1786
|
-
];
|
|
1787
|
-
collectIterator(provider.stream(messages));
|
|
1788
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1789
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1790
|
-
messages: [
|
|
1791
|
-
{
|
|
1792
|
-
role: 'user',
|
|
1793
|
-
content: [
|
|
1794
|
-
{ text: 'Verify this information:' },
|
|
1795
|
-
{
|
|
1796
|
-
guardContent: {
|
|
1797
|
-
text: {
|
|
1798
|
-
text: 'The capital of France is Paris.',
|
|
1799
|
-
qualifiers: ['grounding_source'],
|
|
1800
|
-
},
|
|
1801
|
-
},
|
|
1802
|
-
},
|
|
1803
|
-
],
|
|
1804
|
-
},
|
|
1805
|
-
],
|
|
1806
|
-
});
|
|
1807
|
-
});
|
|
1808
|
-
it('formats guard content with image in message', async () => {
|
|
1809
|
-
const provider = new BedrockModel();
|
|
1810
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
1811
|
-
const messages = [
|
|
1812
|
-
new Message({
|
|
1813
|
-
role: 'user',
|
|
1814
|
-
content: [
|
|
1815
|
-
new TextBlock('Is this image safe?'),
|
|
1816
|
-
new GuardContentBlock({
|
|
1817
|
-
image: {
|
|
1818
|
-
format: 'jpeg',
|
|
1819
|
-
source: { bytes: imageBytes },
|
|
1820
|
-
},
|
|
1821
|
-
}),
|
|
1822
|
-
],
|
|
1823
|
-
}),
|
|
1824
|
-
];
|
|
1825
|
-
collectIterator(provider.stream(messages));
|
|
1826
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1827
|
-
modelId: 'global.anthropic.claude-sonnet-4-6',
|
|
1828
|
-
messages: [
|
|
1829
|
-
{
|
|
1830
|
-
role: 'user',
|
|
1831
|
-
content: [
|
|
1832
|
-
{ text: 'Is this image safe?' },
|
|
1833
|
-
{
|
|
1834
|
-
guardContent: {
|
|
1835
|
-
image: {
|
|
1836
|
-
format: 'jpeg',
|
|
1837
|
-
source: { bytes: imageBytes },
|
|
1838
|
-
},
|
|
1839
|
-
},
|
|
1840
|
-
},
|
|
1841
|
-
],
|
|
1842
|
-
},
|
|
1843
|
-
],
|
|
1844
|
-
});
|
|
1845
|
-
});
|
|
1846
|
-
});
|
|
1847
|
-
describe('media blocks in tool results', () => {
|
|
1848
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1849
|
-
it('formats image block in tool result', async () => {
|
|
1850
|
-
const provider = new BedrockModel();
|
|
1851
|
-
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1852
|
-
const messages = [
|
|
1853
|
-
new Message({
|
|
1854
|
-
role: 'user',
|
|
1855
|
-
content: [
|
|
1856
|
-
new ToolResultBlock({
|
|
1857
|
-
toolUseId: 'tool-1',
|
|
1858
|
-
status: 'success',
|
|
1859
|
-
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1860
|
-
}),
|
|
1861
|
-
],
|
|
1862
|
-
}),
|
|
1863
|
-
];
|
|
1864
|
-
collectIterator(provider.stream(messages));
|
|
1865
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1866
|
-
messages: [
|
|
1867
|
-
{
|
|
1868
|
-
role: 'user',
|
|
1869
|
-
content: [
|
|
1870
|
-
{
|
|
1871
|
-
toolResult: {
|
|
1872
|
-
toolUseId: 'tool-1',
|
|
1873
|
-
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
1874
|
-
status: 'success',
|
|
1875
|
-
},
|
|
1876
|
-
},
|
|
1877
|
-
],
|
|
1878
|
-
},
|
|
1879
|
-
],
|
|
1880
|
-
}));
|
|
1881
|
-
});
|
|
1882
|
-
it('formats video block in tool result with 3gp format mapping', async () => {
|
|
1883
|
-
const provider = new BedrockModel();
|
|
1884
|
-
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
1885
|
-
const messages = [
|
|
1886
|
-
new Message({
|
|
1887
|
-
role: 'user',
|
|
1888
|
-
content: [
|
|
1889
|
-
new ToolResultBlock({
|
|
1890
|
-
toolUseId: 'tool-1',
|
|
1891
|
-
status: 'success',
|
|
1892
|
-
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
1893
|
-
}),
|
|
1894
|
-
],
|
|
1895
|
-
}),
|
|
1896
|
-
];
|
|
1897
|
-
collectIterator(provider.stream(messages));
|
|
1898
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1899
|
-
messages: [
|
|
1900
|
-
{
|
|
1901
|
-
role: 'user',
|
|
1902
|
-
content: [
|
|
1903
|
-
{
|
|
1904
|
-
toolResult: {
|
|
1905
|
-
toolUseId: 'tool-1',
|
|
1906
|
-
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
1907
|
-
status: 'success',
|
|
1908
|
-
},
|
|
1909
|
-
},
|
|
1910
|
-
],
|
|
1911
|
-
},
|
|
1912
|
-
],
|
|
1913
|
-
}));
|
|
1914
|
-
});
|
|
1915
|
-
it('formats document block in tool result', async () => {
|
|
1916
|
-
const provider = new BedrockModel();
|
|
1917
|
-
const docBytes = new Uint8Array([7, 8, 9]);
|
|
1918
|
-
const messages = [
|
|
1919
|
-
new Message({
|
|
1920
|
-
role: 'user',
|
|
1921
|
-
content: [
|
|
1922
|
-
new ToolResultBlock({
|
|
1923
|
-
toolUseId: 'tool-1',
|
|
1924
|
-
status: 'success',
|
|
1925
|
-
content: [new DocumentBlock({ name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } })],
|
|
1926
|
-
}),
|
|
1927
|
-
],
|
|
1928
|
-
}),
|
|
1929
|
-
];
|
|
1930
|
-
collectIterator(provider.stream(messages));
|
|
1931
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1932
|
-
messages: [
|
|
1933
|
-
{
|
|
1934
|
-
role: 'user',
|
|
1935
|
-
content: [
|
|
1936
|
-
{
|
|
1937
|
-
toolResult: {
|
|
1938
|
-
toolUseId: 'tool-1',
|
|
1939
|
-
content: [{ document: { name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } } }],
|
|
1940
|
-
status: 'success',
|
|
1941
|
-
},
|
|
1942
|
-
},
|
|
1943
|
-
],
|
|
1944
|
-
},
|
|
1945
|
-
],
|
|
1946
|
-
}));
|
|
1947
|
-
});
|
|
1948
|
-
it('formats mixed text and media content in tool result', async () => {
|
|
1949
|
-
const provider = new BedrockModel();
|
|
1950
|
-
const imageBytes = new Uint8Array([1, 2]);
|
|
1951
|
-
const messages = [
|
|
1952
|
-
new Message({
|
|
1953
|
-
role: 'user',
|
|
1954
|
-
content: [
|
|
1955
|
-
new ToolResultBlock({
|
|
1956
|
-
toolUseId: 'tool-1',
|
|
1957
|
-
status: 'success',
|
|
1958
|
-
content: [
|
|
1959
|
-
new TextBlock('Here is the image:'),
|
|
1960
|
-
new ImageBlock({ format: 'jpeg', source: { bytes: imageBytes } }),
|
|
1961
|
-
],
|
|
1962
|
-
}),
|
|
1963
|
-
],
|
|
1964
|
-
}),
|
|
1965
|
-
];
|
|
1966
|
-
collectIterator(provider.stream(messages));
|
|
1967
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1968
|
-
messages: [
|
|
1969
|
-
{
|
|
1970
|
-
role: 'user',
|
|
1971
|
-
content: [
|
|
1972
|
-
{
|
|
1973
|
-
toolResult: {
|
|
1974
|
-
toolUseId: 'tool-1',
|
|
1975
|
-
content: [
|
|
1976
|
-
{ text: 'Here is the image:' },
|
|
1977
|
-
{ image: { format: 'jpeg', source: { bytes: imageBytes } } },
|
|
1978
|
-
],
|
|
1979
|
-
status: 'success',
|
|
1980
|
-
},
|
|
1981
|
-
},
|
|
1982
|
-
],
|
|
1983
|
-
},
|
|
1984
|
-
],
|
|
1985
|
-
}));
|
|
1986
|
-
});
|
|
1987
|
-
});
|
|
1988
|
-
describe('media blocks in messages', () => {
|
|
1989
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1990
|
-
it('formats top-level image block', async () => {
|
|
1991
|
-
const provider = new BedrockModel();
|
|
1992
|
-
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1993
|
-
const messages = [
|
|
1994
|
-
new Message({
|
|
1995
|
-
role: 'user',
|
|
1996
|
-
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1997
|
-
}),
|
|
1998
|
-
];
|
|
1999
|
-
collectIterator(provider.stream(messages));
|
|
2000
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2001
|
-
messages: [
|
|
2002
|
-
{
|
|
2003
|
-
role: 'user',
|
|
2004
|
-
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
2005
|
-
},
|
|
2006
|
-
],
|
|
2007
|
-
}));
|
|
2008
|
-
});
|
|
2009
|
-
it('formats top-level image block with S3 source', async () => {
|
|
2010
|
-
const provider = new BedrockModel();
|
|
2011
|
-
const messages = [
|
|
2012
|
-
new Message({
|
|
2013
|
-
role: 'user',
|
|
2014
|
-
content: [
|
|
2015
|
-
new ImageBlock({ format: 'png', source: { location: { type: 's3', uri: 's3://bucket/image.png' } } }),
|
|
2016
|
-
],
|
|
2017
|
-
}),
|
|
2018
|
-
];
|
|
2019
|
-
collectIterator(provider.stream(messages));
|
|
2020
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2021
|
-
messages: [
|
|
2022
|
-
{
|
|
2023
|
-
role: 'user',
|
|
2024
|
-
content: [{ image: { format: 'png', source: { s3Location: { uri: 's3://bucket/image.png' } } } }],
|
|
2025
|
-
},
|
|
2026
|
-
],
|
|
2027
|
-
}));
|
|
2028
|
-
});
|
|
2029
|
-
it('formats top-level video block with 3gp format mapping', async () => {
|
|
2030
|
-
const provider = new BedrockModel();
|
|
2031
|
-
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
2032
|
-
const messages = [
|
|
2033
|
-
new Message({
|
|
2034
|
-
role: 'user',
|
|
2035
|
-
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
2036
|
-
}),
|
|
2037
|
-
];
|
|
2038
|
-
collectIterator(provider.stream(messages));
|
|
2039
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2040
|
-
messages: [
|
|
2041
|
-
{
|
|
2042
|
-
role: 'user',
|
|
2043
|
-
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
2044
|
-
},
|
|
2045
|
-
],
|
|
2046
|
-
}));
|
|
2047
|
-
});
|
|
2048
|
-
it('formats top-level document block with text source converted to bytes', async () => {
|
|
2049
|
-
const provider = new BedrockModel();
|
|
2050
|
-
const messages = [
|
|
2051
|
-
new Message({
|
|
2052
|
-
role: 'user',
|
|
2053
|
-
content: [new DocumentBlock({ name: 'notes.txt', format: 'txt', source: { text: 'Hello world' } })],
|
|
2054
|
-
}),
|
|
2055
|
-
];
|
|
2056
|
-
collectIterator(provider.stream(messages));
|
|
2057
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2058
|
-
messages: [
|
|
2059
|
-
{
|
|
2060
|
-
role: 'user',
|
|
2061
|
-
content: [
|
|
2062
|
-
{
|
|
2063
|
-
document: {
|
|
2064
|
-
name: 'notes.txt',
|
|
2065
|
-
format: 'txt',
|
|
2066
|
-
source: { bytes: new TextEncoder().encode('Hello world') },
|
|
2067
|
-
},
|
|
2068
|
-
},
|
|
2069
|
-
],
|
|
2070
|
-
},
|
|
2071
|
-
],
|
|
2072
|
-
}));
|
|
2073
|
-
});
|
|
2074
|
-
});
|
|
2075
|
-
describe('citations content block formatting', () => {
|
|
2076
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2077
|
-
it('maps SDK CitationLocation types to Bedrock object-key format through formatting pipeline', async () => {
|
|
2078
|
-
const provider = new BedrockModel();
|
|
2079
|
-
const sdkCitations = [
|
|
2080
|
-
{
|
|
2081
|
-
location: { type: 'documentChar', documentIndex: 0, start: 150, end: 300 },
|
|
2082
|
-
source: 'doc-0',
|
|
2083
|
-
sourceContent: [{ text: 'char source' }],
|
|
2084
|
-
title: 'Text Document',
|
|
2085
|
-
},
|
|
2086
|
-
{
|
|
2087
|
-
location: { type: 'documentPage', documentIndex: 0, start: 2, end: 3 },
|
|
2088
|
-
source: 'doc-0',
|
|
2089
|
-
sourceContent: [{ text: 'page source' }],
|
|
2090
|
-
title: 'PDF Document',
|
|
2091
|
-
},
|
|
2092
|
-
{
|
|
2093
|
-
location: { type: 'documentChunk', documentIndex: 1, start: 5, end: 8 },
|
|
2094
|
-
source: 'doc-1',
|
|
2095
|
-
sourceContent: [{ text: 'chunk source' }],
|
|
2096
|
-
title: 'Chunked Document',
|
|
2097
|
-
},
|
|
2098
|
-
{
|
|
2099
|
-
location: { type: 'searchResult', searchResultIndex: 0, start: 25, end: 150 },
|
|
2100
|
-
source: 'search-0',
|
|
2101
|
-
sourceContent: [{ text: 'search source' }],
|
|
2102
|
-
title: 'Search Result',
|
|
2103
|
-
},
|
|
2104
|
-
{
|
|
2105
|
-
location: { type: 'web', url: 'https://example.com/doc', domain: 'example.com' },
|
|
2106
|
-
source: 'web-0',
|
|
2107
|
-
sourceContent: [{ text: 'web source' }],
|
|
2108
|
-
title: 'Web Page',
|
|
2109
|
-
},
|
|
2110
|
-
];
|
|
2111
|
-
const messages = [
|
|
2112
|
-
new Message({
|
|
2113
|
-
role: 'assistant',
|
|
2114
|
-
content: [
|
|
2115
|
-
new CitationsBlock({
|
|
2116
|
-
citations: sdkCitations,
|
|
2117
|
-
content: [{ text: 'generated text with all citation types' }],
|
|
2118
|
-
}),
|
|
2119
|
-
],
|
|
2120
|
-
}),
|
|
2121
|
-
new Message({
|
|
2122
|
-
role: 'user',
|
|
2123
|
-
content: [new TextBlock('Follow up')],
|
|
2124
|
-
}),
|
|
2125
|
-
];
|
|
2126
|
-
collectIterator(provider.stream(messages));
|
|
2127
|
-
// Bedrock wire format uses object-key discrimination
|
|
2128
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2129
|
-
messages: [
|
|
2130
|
-
{
|
|
2131
|
-
role: 'assistant',
|
|
2132
|
-
content: [
|
|
2133
|
-
{
|
|
2134
|
-
citationsContent: {
|
|
2135
|
-
citations: [
|
|
2136
|
-
{
|
|
2137
|
-
location: { documentChar: { documentIndex: 0, start: 150, end: 300 } },
|
|
2138
|
-
source: 'doc-0',
|
|
2139
|
-
sourceContent: [{ text: 'char source' }],
|
|
2140
|
-
title: 'Text Document',
|
|
2141
|
-
},
|
|
2142
|
-
{
|
|
2143
|
-
location: { documentPage: { documentIndex: 0, start: 2, end: 3 } },
|
|
2144
|
-
source: 'doc-0',
|
|
2145
|
-
sourceContent: [{ text: 'page source' }],
|
|
2146
|
-
title: 'PDF Document',
|
|
2147
|
-
},
|
|
2148
|
-
{
|
|
2149
|
-
location: { documentChunk: { documentIndex: 1, start: 5, end: 8 } },
|
|
2150
|
-
source: 'doc-1',
|
|
2151
|
-
sourceContent: [{ text: 'chunk source' }],
|
|
2152
|
-
title: 'Chunked Document',
|
|
2153
|
-
},
|
|
2154
|
-
{
|
|
2155
|
-
location: {
|
|
2156
|
-
searchResultLocation: { searchResultIndex: 0, start: 25, end: 150 },
|
|
2157
|
-
},
|
|
2158
|
-
source: 'search-0',
|
|
2159
|
-
sourceContent: [{ text: 'search source' }],
|
|
2160
|
-
title: 'Search Result',
|
|
2161
|
-
},
|
|
2162
|
-
{
|
|
2163
|
-
location: { web: { url: 'https://example.com/doc', domain: 'example.com' } },
|
|
2164
|
-
source: 'web-0',
|
|
2165
|
-
sourceContent: [{ text: 'web source' }],
|
|
2166
|
-
title: 'Web Page',
|
|
2167
|
-
},
|
|
2168
|
-
],
|
|
2169
|
-
content: [{ text: 'generated text with all citation types' }],
|
|
2170
|
-
},
|
|
2171
|
-
},
|
|
2172
|
-
],
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
role: 'user',
|
|
2176
|
-
content: [{ text: 'Follow up' }],
|
|
2177
|
-
},
|
|
2178
|
-
],
|
|
2179
|
-
}));
|
|
2180
|
-
});
|
|
2181
|
-
});
|
|
2182
|
-
describe('media blocks in tool results', () => {
|
|
2183
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2184
|
-
it('formats image block in tool result', async () => {
|
|
2185
|
-
const provider = new BedrockModel();
|
|
2186
|
-
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
2187
|
-
const messages = [
|
|
2188
|
-
new Message({
|
|
2189
|
-
role: 'user',
|
|
2190
|
-
content: [
|
|
2191
|
-
new ToolResultBlock({
|
|
2192
|
-
toolUseId: 'tool-1',
|
|
2193
|
-
status: 'success',
|
|
2194
|
-
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
2195
|
-
}),
|
|
2196
|
-
],
|
|
2197
|
-
}),
|
|
2198
|
-
];
|
|
2199
|
-
collectIterator(provider.stream(messages));
|
|
2200
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2201
|
-
messages: [
|
|
2202
|
-
{
|
|
2203
|
-
role: 'user',
|
|
2204
|
-
content: [
|
|
2205
|
-
{
|
|
2206
|
-
toolResult: {
|
|
2207
|
-
toolUseId: 'tool-1',
|
|
2208
|
-
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
2209
|
-
status: 'success',
|
|
2210
|
-
},
|
|
2211
|
-
},
|
|
2212
|
-
],
|
|
2213
|
-
},
|
|
2214
|
-
],
|
|
2215
|
-
}));
|
|
2216
|
-
});
|
|
2217
|
-
it('formats video block in tool result with 3gp format mapping', async () => {
|
|
2218
|
-
const provider = new BedrockModel();
|
|
2219
|
-
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
2220
|
-
const messages = [
|
|
2221
|
-
new Message({
|
|
2222
|
-
role: 'user',
|
|
2223
|
-
content: [
|
|
2224
|
-
new ToolResultBlock({
|
|
2225
|
-
toolUseId: 'tool-1',
|
|
2226
|
-
status: 'success',
|
|
2227
|
-
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
2228
|
-
}),
|
|
2229
|
-
],
|
|
2230
|
-
}),
|
|
2231
|
-
];
|
|
2232
|
-
collectIterator(provider.stream(messages));
|
|
2233
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2234
|
-
messages: [
|
|
2235
|
-
{
|
|
2236
|
-
role: 'user',
|
|
2237
|
-
content: [
|
|
2238
|
-
{
|
|
2239
|
-
toolResult: {
|
|
2240
|
-
toolUseId: 'tool-1',
|
|
2241
|
-
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
2242
|
-
status: 'success',
|
|
2243
|
-
},
|
|
2244
|
-
},
|
|
2245
|
-
],
|
|
2246
|
-
},
|
|
2247
|
-
],
|
|
2248
|
-
}));
|
|
2249
|
-
});
|
|
2250
|
-
it('formats document block in tool result', async () => {
|
|
2251
|
-
const provider = new BedrockModel();
|
|
2252
|
-
const docBytes = new Uint8Array([7, 8, 9]);
|
|
2253
|
-
const messages = [
|
|
2254
|
-
new Message({
|
|
2255
|
-
role: 'user',
|
|
2256
|
-
content: [
|
|
2257
|
-
new ToolResultBlock({
|
|
2258
|
-
toolUseId: 'tool-1',
|
|
2259
|
-
status: 'success',
|
|
2260
|
-
content: [new DocumentBlock({ name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } })],
|
|
2261
|
-
}),
|
|
2262
|
-
],
|
|
2263
|
-
}),
|
|
2264
|
-
];
|
|
2265
|
-
collectIterator(provider.stream(messages));
|
|
2266
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2267
|
-
messages: [
|
|
2268
|
-
{
|
|
2269
|
-
role: 'user',
|
|
2270
|
-
content: [
|
|
2271
|
-
{
|
|
2272
|
-
toolResult: {
|
|
2273
|
-
toolUseId: 'tool-1',
|
|
2274
|
-
content: [{ document: { name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } } }],
|
|
2275
|
-
status: 'success',
|
|
2276
|
-
},
|
|
2277
|
-
},
|
|
2278
|
-
],
|
|
2279
|
-
},
|
|
2280
|
-
],
|
|
2281
|
-
}));
|
|
2282
|
-
});
|
|
2283
|
-
it('formats mixed text and media content in tool result', async () => {
|
|
2284
|
-
const provider = new BedrockModel();
|
|
2285
|
-
const imageBytes = new Uint8Array([1, 2]);
|
|
2286
|
-
const messages = [
|
|
2287
|
-
new Message({
|
|
2288
|
-
role: 'user',
|
|
2289
|
-
content: [
|
|
2290
|
-
new ToolResultBlock({
|
|
2291
|
-
toolUseId: 'tool-1',
|
|
2292
|
-
status: 'success',
|
|
2293
|
-
content: [
|
|
2294
|
-
new TextBlock('Here is the image:'),
|
|
2295
|
-
new ImageBlock({ format: 'jpeg', source: { bytes: imageBytes } }),
|
|
2296
|
-
],
|
|
2297
|
-
}),
|
|
2298
|
-
],
|
|
2299
|
-
}),
|
|
2300
|
-
];
|
|
2301
|
-
collectIterator(provider.stream(messages));
|
|
2302
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2303
|
-
messages: [
|
|
2304
|
-
{
|
|
2305
|
-
role: 'user',
|
|
2306
|
-
content: [
|
|
2307
|
-
{
|
|
2308
|
-
toolResult: {
|
|
2309
|
-
toolUseId: 'tool-1',
|
|
2310
|
-
content: [
|
|
2311
|
-
{ text: 'Here is the image:' },
|
|
2312
|
-
{ image: { format: 'jpeg', source: { bytes: imageBytes } } },
|
|
2313
|
-
],
|
|
2314
|
-
status: 'success',
|
|
2315
|
-
},
|
|
2316
|
-
},
|
|
2317
|
-
],
|
|
2318
|
-
},
|
|
2319
|
-
],
|
|
2320
|
-
}));
|
|
2321
|
-
});
|
|
2322
|
-
});
|
|
2323
|
-
describe('media blocks in messages', () => {
|
|
2324
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2325
|
-
it('formats top-level image block', async () => {
|
|
2326
|
-
const provider = new BedrockModel();
|
|
2327
|
-
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
2328
|
-
const messages = [
|
|
2329
|
-
new Message({
|
|
2330
|
-
role: 'user',
|
|
2331
|
-
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
2332
|
-
}),
|
|
2333
|
-
];
|
|
2334
|
-
collectIterator(provider.stream(messages));
|
|
2335
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2336
|
-
messages: [
|
|
2337
|
-
{
|
|
2338
|
-
role: 'user',
|
|
2339
|
-
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
2340
|
-
},
|
|
2341
|
-
],
|
|
2342
|
-
}));
|
|
2343
|
-
});
|
|
2344
|
-
it('formats top-level image block with S3 source', async () => {
|
|
2345
|
-
const provider = new BedrockModel();
|
|
2346
|
-
const messages = [
|
|
2347
|
-
new Message({
|
|
2348
|
-
role: 'user',
|
|
2349
|
-
content: [
|
|
2350
|
-
new ImageBlock({ format: 'png', source: { location: { type: 's3', uri: 's3://bucket/image.png' } } }),
|
|
2351
|
-
],
|
|
2352
|
-
}),
|
|
2353
|
-
];
|
|
2354
|
-
collectIterator(provider.stream(messages));
|
|
2355
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2356
|
-
messages: [
|
|
2357
|
-
{
|
|
2358
|
-
role: 'user',
|
|
2359
|
-
content: [{ image: { format: 'png', source: { s3Location: { uri: 's3://bucket/image.png' } } } }],
|
|
2360
|
-
},
|
|
2361
|
-
],
|
|
2362
|
-
}));
|
|
2363
|
-
});
|
|
2364
|
-
it('formats top-level video block with 3gp format mapping', async () => {
|
|
2365
|
-
const provider = new BedrockModel();
|
|
2366
|
-
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
2367
|
-
const messages = [
|
|
2368
|
-
new Message({
|
|
2369
|
-
role: 'user',
|
|
2370
|
-
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
2371
|
-
}),
|
|
2372
|
-
];
|
|
2373
|
-
collectIterator(provider.stream(messages));
|
|
2374
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2375
|
-
messages: [
|
|
2376
|
-
{
|
|
2377
|
-
role: 'user',
|
|
2378
|
-
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
2379
|
-
},
|
|
2380
|
-
],
|
|
2381
|
-
}));
|
|
2382
|
-
});
|
|
2383
|
-
it('formats top-level document block with text source converted to bytes', async () => {
|
|
2384
|
-
const provider = new BedrockModel();
|
|
2385
|
-
const messages = [
|
|
2386
|
-
new Message({
|
|
2387
|
-
role: 'user',
|
|
2388
|
-
content: [new DocumentBlock({ name: 'notes.txt', format: 'txt', source: { text: 'Hello world' } })],
|
|
2389
|
-
}),
|
|
2390
|
-
];
|
|
2391
|
-
collectIterator(provider.stream(messages));
|
|
2392
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2393
|
-
messages: [
|
|
2394
|
-
{
|
|
2395
|
-
role: 'user',
|
|
2396
|
-
content: [
|
|
2397
|
-
{
|
|
2398
|
-
document: {
|
|
2399
|
-
name: 'notes.txt',
|
|
2400
|
-
format: 'txt',
|
|
2401
|
-
source: { bytes: new TextEncoder().encode('Hello world') },
|
|
2402
|
-
},
|
|
2403
|
-
},
|
|
2404
|
-
],
|
|
2405
|
-
},
|
|
2406
|
-
],
|
|
2407
|
-
}));
|
|
2408
|
-
});
|
|
2409
|
-
});
|
|
2410
|
-
describe('includeToolResultStatus configuration', async () => {
|
|
2411
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2412
|
-
describe('when includeToolResultStatus is true', () => {
|
|
2413
|
-
it('always includes status field in tool results', async () => {
|
|
2414
|
-
const provider = new BedrockModel({ includeToolResultStatus: true });
|
|
2415
|
-
const messages = [
|
|
2416
|
-
new Message({
|
|
2417
|
-
role: 'user',
|
|
2418
|
-
content: [
|
|
2419
|
-
new ToolResultBlock({
|
|
2420
|
-
toolUseId: 'tool-123',
|
|
2421
|
-
status: 'success',
|
|
2422
|
-
content: [new TextBlock('Result')],
|
|
2423
|
-
}),
|
|
2424
|
-
],
|
|
2425
|
-
}),
|
|
2426
|
-
];
|
|
2427
|
-
collectIterator(provider.stream(messages));
|
|
2428
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2429
|
-
messages: [
|
|
2430
|
-
{
|
|
2431
|
-
content: [
|
|
2432
|
-
{
|
|
2433
|
-
toolResult: {
|
|
2434
|
-
content: [{ text: 'Result' }],
|
|
2435
|
-
status: 'success',
|
|
2436
|
-
toolUseId: 'tool-123',
|
|
2437
|
-
},
|
|
2438
|
-
},
|
|
2439
|
-
],
|
|
2440
|
-
role: 'user',
|
|
2441
|
-
},
|
|
2442
|
-
],
|
|
2443
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
2444
|
-
modelId: expect.any(String),
|
|
2445
|
-
});
|
|
2446
|
-
});
|
|
2447
|
-
});
|
|
2448
|
-
describe('when includeToolResultStatus is false', () => {
|
|
2449
|
-
it('never includes status field in tool results', async () => {
|
|
2450
|
-
const provider = new BedrockModel({ includeToolResultStatus: false });
|
|
2451
|
-
const messages = [
|
|
2452
|
-
new Message({
|
|
2453
|
-
role: 'user',
|
|
2454
|
-
content: [
|
|
2455
|
-
new ToolResultBlock({
|
|
2456
|
-
toolUseId: 'tool-123',
|
|
2457
|
-
status: 'success',
|
|
2458
|
-
content: [new TextBlock('Result')],
|
|
2459
|
-
}),
|
|
2460
|
-
],
|
|
2461
|
-
}),
|
|
2462
|
-
];
|
|
2463
|
-
collectIterator(provider.stream(messages));
|
|
2464
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2465
|
-
messages: [
|
|
2466
|
-
{
|
|
2467
|
-
content: [
|
|
2468
|
-
{
|
|
2469
|
-
toolResult: {
|
|
2470
|
-
content: [{ text: 'Result' }],
|
|
2471
|
-
toolUseId: 'tool-123',
|
|
2472
|
-
},
|
|
2473
|
-
},
|
|
2474
|
-
],
|
|
2475
|
-
role: 'user',
|
|
2476
|
-
},
|
|
2477
|
-
],
|
|
2478
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
2479
|
-
modelId: expect.any(String),
|
|
2480
|
-
});
|
|
2481
|
-
});
|
|
2482
|
-
});
|
|
2483
|
-
describe('when includeToolResultStatus is auto', () => {
|
|
2484
|
-
it('includes status field for Claude models', async () => {
|
|
2485
|
-
const provider = new BedrockModel({
|
|
2486
|
-
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
2487
|
-
includeToolResultStatus: 'auto',
|
|
2488
|
-
});
|
|
2489
|
-
const messages = [
|
|
2490
|
-
new Message({
|
|
2491
|
-
role: 'user',
|
|
2492
|
-
content: [
|
|
2493
|
-
new ToolResultBlock({
|
|
2494
|
-
toolUseId: 'tool-123',
|
|
2495
|
-
status: 'success',
|
|
2496
|
-
content: [new TextBlock('Result')],
|
|
2497
|
-
}),
|
|
2498
|
-
],
|
|
2499
|
-
}),
|
|
2500
|
-
];
|
|
2501
|
-
collectIterator(provider.stream(messages));
|
|
2502
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2503
|
-
messages: [
|
|
2504
|
-
{
|
|
2505
|
-
content: [
|
|
2506
|
-
{
|
|
2507
|
-
toolResult: {
|
|
2508
|
-
content: [{ text: 'Result' }],
|
|
2509
|
-
status: 'success',
|
|
2510
|
-
toolUseId: 'tool-123',
|
|
2511
|
-
},
|
|
2512
|
-
},
|
|
2513
|
-
],
|
|
2514
|
-
role: 'user',
|
|
2515
|
-
},
|
|
2516
|
-
],
|
|
2517
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
2518
|
-
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
2519
|
-
});
|
|
2520
|
-
});
|
|
2521
|
-
});
|
|
2522
|
-
describe('when includeToolResultStatus is undefined (default)', () => {
|
|
2523
|
-
it('follows auto logic for non-Claude models', async () => {
|
|
2524
|
-
const provider = new BedrockModel({
|
|
2525
|
-
modelId: 'amazon.nova-lite-v1:0',
|
|
2526
|
-
});
|
|
2527
|
-
const messages = [
|
|
2528
|
-
new Message({
|
|
2529
|
-
role: 'user',
|
|
2530
|
-
content: [
|
|
2531
|
-
new ToolResultBlock({
|
|
2532
|
-
toolUseId: 'tool-123',
|
|
2533
|
-
status: 'success',
|
|
2534
|
-
content: [new TextBlock('Result')],
|
|
2535
|
-
}),
|
|
2536
|
-
],
|
|
2537
|
-
}),
|
|
2538
|
-
];
|
|
2539
|
-
collectIterator(provider.stream(messages));
|
|
2540
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2541
|
-
messages: [
|
|
2542
|
-
{
|
|
2543
|
-
content: [
|
|
2544
|
-
{
|
|
2545
|
-
toolResult: {
|
|
2546
|
-
content: [{ text: 'Result' }],
|
|
2547
|
-
toolUseId: 'tool-123',
|
|
2548
|
-
},
|
|
2549
|
-
},
|
|
2550
|
-
],
|
|
2551
|
-
role: 'user',
|
|
2552
|
-
},
|
|
2553
|
-
],
|
|
2554
|
-
toolConfig: BEDROCK_NOOP_TOOL_CONFIG,
|
|
2555
|
-
modelId: 'amazon.nova-lite-v1:0',
|
|
2556
|
-
});
|
|
2557
|
-
});
|
|
2558
|
-
});
|
|
2559
|
-
});
|
|
2560
|
-
describe('region configuration', () => {
|
|
2561
|
-
beforeEach(() => {
|
|
2562
|
-
vi.clearAllMocks();
|
|
2563
|
-
});
|
|
2564
|
-
it('uses explicit region when provided', async () => {
|
|
2565
|
-
mockBedrockClientImplementation();
|
|
2566
|
-
const provider = new BedrockModel({ region: 'eu-west-1' });
|
|
2567
|
-
// After applyDefaultRegion wraps the config functions, verify they still return the correct value
|
|
2568
|
-
const regionResult = await provider['_client'].config.region();
|
|
2569
|
-
expect(regionResult).toBe('eu-west-1');
|
|
2570
|
-
});
|
|
2571
|
-
it('defaults to us-west-2 when region is missing', async () => {
|
|
2572
|
-
mockBedrockClientImplementation({
|
|
2573
|
-
region: async () => {
|
|
2574
|
-
throw new Error('Region is missing');
|
|
2575
|
-
},
|
|
2576
|
-
useFipsEndpoint: async () => {
|
|
2577
|
-
throw new Error('Region is missing');
|
|
2578
|
-
},
|
|
2579
|
-
});
|
|
2580
|
-
const provider = new BedrockModel();
|
|
2581
|
-
// After applyDefaultRegion wraps the config functions
|
|
2582
|
-
const regionResult = await provider['_client'].config.region();
|
|
2583
|
-
expect(regionResult).toBe('us-west-2');
|
|
2584
|
-
const fipsResult = await provider['_client'].config.useFipsEndpoint();
|
|
2585
|
-
expect(fipsResult).toBe(false);
|
|
2586
|
-
});
|
|
2587
|
-
it('rethrows other region errors', async () => {
|
|
2588
|
-
mockBedrockClientImplementation({
|
|
2589
|
-
region: async () => {
|
|
2590
|
-
throw new Error('Network error');
|
|
2591
|
-
},
|
|
2592
|
-
});
|
|
2593
|
-
const provider = new BedrockModel();
|
|
2594
|
-
// Should rethrow the error
|
|
2595
|
-
await expect(provider['_client'].config.region()).rejects.toThrow('Network error');
|
|
2596
|
-
});
|
|
2597
|
-
});
|
|
2598
|
-
describe('guardrail configuration', () => {
|
|
2599
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2600
|
-
beforeEach(() => {
|
|
2601
|
-
vi.clearAllMocks();
|
|
2602
|
-
});
|
|
2603
|
-
describe('constructor', () => {
|
|
2604
|
-
it('accepts guardrailConfig in options', () => {
|
|
2605
|
-
const provider = new BedrockModel({
|
|
2606
|
-
guardrailConfig: {
|
|
2607
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2608
|
-
guardrailVersion: '1',
|
|
2609
|
-
},
|
|
2610
|
-
});
|
|
2611
|
-
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
2612
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2613
|
-
guardrailVersion: '1',
|
|
2614
|
-
});
|
|
2615
|
-
});
|
|
2616
|
-
it('accepts guardrailConfig with all options', () => {
|
|
2617
|
-
const provider = new BedrockModel({
|
|
2618
|
-
guardrailConfig: {
|
|
2619
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2620
|
-
guardrailVersion: '1',
|
|
2621
|
-
trace: 'enabled_full',
|
|
2622
|
-
streamProcessingMode: 'sync',
|
|
2623
|
-
redaction: {
|
|
2624
|
-
input: true,
|
|
2625
|
-
inputMessage: '[Custom input redacted.]',
|
|
2626
|
-
output: true,
|
|
2627
|
-
outputMessage: '[Custom output redacted.]',
|
|
2628
|
-
},
|
|
2629
|
-
},
|
|
2630
|
-
});
|
|
2631
|
-
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
2632
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2633
|
-
guardrailVersion: '1',
|
|
2634
|
-
trace: 'enabled_full',
|
|
2635
|
-
streamProcessingMode: 'sync',
|
|
2636
|
-
redaction: {
|
|
2637
|
-
input: true,
|
|
2638
|
-
inputMessage: '[Custom input redacted.]',
|
|
2639
|
-
output: true,
|
|
2640
|
-
outputMessage: '[Custom output redacted.]',
|
|
2641
|
-
},
|
|
2642
|
-
});
|
|
2643
|
-
});
|
|
2644
|
-
});
|
|
2645
|
-
describe('request formatting', () => {
|
|
2646
|
-
it('includes guardrailConfig in request with default trace', async () => {
|
|
2647
|
-
const provider = new BedrockModel({
|
|
2648
|
-
guardrailConfig: {
|
|
2649
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2650
|
-
guardrailVersion: '1',
|
|
2651
|
-
},
|
|
2652
|
-
});
|
|
2653
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2654
|
-
collectIterator(provider.stream(messages));
|
|
2655
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2656
|
-
guardrailConfig: {
|
|
2657
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2658
|
-
guardrailVersion: '1',
|
|
2659
|
-
trace: 'enabled',
|
|
2660
|
-
},
|
|
2661
|
-
}));
|
|
2662
|
-
});
|
|
2663
|
-
it('includes guardrailConfig in request with custom trace', async () => {
|
|
2664
|
-
const provider = new BedrockModel({
|
|
2665
|
-
guardrailConfig: {
|
|
2666
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2667
|
-
guardrailVersion: '1',
|
|
2668
|
-
trace: 'disabled',
|
|
2669
|
-
},
|
|
2670
|
-
});
|
|
2671
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2672
|
-
collectIterator(provider.stream(messages));
|
|
2673
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2674
|
-
guardrailConfig: {
|
|
2675
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2676
|
-
guardrailVersion: '1',
|
|
2677
|
-
trace: 'disabled',
|
|
2678
|
-
},
|
|
2679
|
-
}));
|
|
2680
|
-
});
|
|
2681
|
-
it('includes streamProcessingMode when specified', async () => {
|
|
2682
|
-
const provider = new BedrockModel({
|
|
2683
|
-
guardrailConfig: {
|
|
2684
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2685
|
-
guardrailVersion: '1',
|
|
2686
|
-
streamProcessingMode: 'sync',
|
|
2687
|
-
},
|
|
2688
|
-
});
|
|
2689
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2690
|
-
collectIterator(provider.stream(messages));
|
|
2691
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2692
|
-
guardrailConfig: {
|
|
2693
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2694
|
-
guardrailVersion: '1',
|
|
2695
|
-
trace: 'enabled',
|
|
2696
|
-
streamProcessingMode: 'sync',
|
|
2697
|
-
},
|
|
2698
|
-
}));
|
|
2699
|
-
});
|
|
2700
|
-
it('does not include guardrailConfig when not configured', async () => {
|
|
2701
|
-
const provider = new BedrockModel();
|
|
2702
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2703
|
-
collectIterator(provider.stream(messages));
|
|
2704
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.not.objectContaining({
|
|
2705
|
-
guardrailConfig: expect.anything(),
|
|
2706
|
-
}));
|
|
2707
|
-
});
|
|
2708
|
-
});
|
|
2709
|
-
describe('blocked guardrail detection', () => {
|
|
2710
|
-
it('detects blocked guardrail in inputAssessment', async () => {
|
|
2711
|
-
setupMockSend(async function* () {
|
|
2712
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2713
|
-
yield { contentBlockStart: {} };
|
|
2714
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2715
|
-
yield { contentBlockStop: {} };
|
|
2716
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2717
|
-
yield {
|
|
2718
|
-
metadata: {
|
|
2719
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2720
|
-
trace: {
|
|
2721
|
-
guardrail: {
|
|
2722
|
-
inputAssessment: {
|
|
2723
|
-
'1234': {
|
|
2724
|
-
topicPolicy: {
|
|
2725
|
-
topics: [{ name: 'Harmful', action: 'BLOCKED', detected: true }],
|
|
2726
|
-
},
|
|
2727
|
-
},
|
|
2728
|
-
},
|
|
2729
|
-
},
|
|
2730
|
-
},
|
|
2731
|
-
},
|
|
2732
|
-
};
|
|
2733
|
-
});
|
|
2734
|
-
const provider = new BedrockModel({
|
|
2735
|
-
guardrailConfig: {
|
|
2736
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2737
|
-
guardrailVersion: '1',
|
|
2738
|
-
},
|
|
2739
|
-
});
|
|
2740
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2741
|
-
const events = await collectIterator(provider.stream(messages));
|
|
2742
|
-
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2743
|
-
expect(redactEvent).toBeDefined();
|
|
2744
|
-
expect(redactEvent).toStrictEqual({
|
|
2745
|
-
type: 'modelRedactionEvent',
|
|
2746
|
-
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
2747
|
-
});
|
|
2748
|
-
});
|
|
2749
|
-
it('detects blocked guardrail in outputAssessments', async () => {
|
|
2750
|
-
setupMockSend(async function* () {
|
|
2751
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2752
|
-
yield { contentBlockStart: {} };
|
|
2753
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2754
|
-
yield { contentBlockStop: {} };
|
|
2755
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2756
|
-
yield {
|
|
2757
|
-
metadata: {
|
|
2758
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2759
|
-
trace: {
|
|
2760
|
-
guardrail: {
|
|
2761
|
-
outputAssessments: {
|
|
2762
|
-
'1234': {
|
|
2763
|
-
contentPolicy: {
|
|
2764
|
-
filters: [{ type: 'VIOLENCE', action: 'BLOCKED', detected: true }],
|
|
2765
|
-
},
|
|
2766
|
-
},
|
|
2767
|
-
},
|
|
2768
|
-
},
|
|
2769
|
-
},
|
|
2770
|
-
},
|
|
2771
|
-
};
|
|
2772
|
-
});
|
|
2773
|
-
const provider = new BedrockModel({
|
|
2774
|
-
guardrailConfig: {
|
|
2775
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2776
|
-
guardrailVersion: '1',
|
|
2777
|
-
},
|
|
2778
|
-
});
|
|
2779
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2780
|
-
const events = await collectIterator(provider.stream(messages));
|
|
2781
|
-
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2782
|
-
expect(redactEvent).toBeDefined();
|
|
2783
|
-
});
|
|
2784
|
-
it('does not emit redaction events when guardrail not blocked', async () => {
|
|
2785
|
-
setupMockSend(async function* () {
|
|
2786
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2787
|
-
yield { contentBlockStart: {} };
|
|
2788
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2789
|
-
yield { contentBlockStop: {} };
|
|
2790
|
-
yield { messageStop: { stopReason: 'end_turn' } };
|
|
2791
|
-
yield {
|
|
2792
|
-
metadata: {
|
|
2793
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2794
|
-
trace: {
|
|
2795
|
-
guardrail: {
|
|
2796
|
-
inputAssessment: {
|
|
2797
|
-
'1234': {
|
|
2798
|
-
topicPolicy: {
|
|
2799
|
-
topics: [{ name: 'Safe', action: 'NONE', detected: false }],
|
|
2800
|
-
},
|
|
2801
|
-
},
|
|
2802
|
-
},
|
|
2803
|
-
},
|
|
2804
|
-
},
|
|
2805
|
-
},
|
|
2806
|
-
};
|
|
2807
|
-
});
|
|
2808
|
-
const provider = new BedrockModel({
|
|
2809
|
-
guardrailConfig: {
|
|
2810
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
2811
|
-
guardrailVersion: '1',
|
|
2812
|
-
},
|
|
2813
|
-
});
|
|
2814
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2815
|
-
const events = await collectIterator(provider.stream(messages));
|
|
2816
|
-
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2817
|
-
expect(redactEvent).toBeUndefined();
|
|
2818
|
-
});
|
|
2819
|
-
it('does not emit redaction events without guardrailConfig', async () => {
|
|
2820
|
-
setupMockSend(async function* () {
|
|
2821
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2822
|
-
yield { contentBlockStart: {} };
|
|
2823
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2824
|
-
yield { contentBlockStop: {} };
|
|
2825
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2826
|
-
yield {
|
|
2827
|
-
metadata: {
|
|
2828
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2829
|
-
trace: {
|
|
2830
|
-
guardrail: {
|
|
2831
|
-
inputAssessment: {
|
|
2832
|
-
'1234': {
|
|
2833
|
-
topicPolicy: {
|
|
2834
|
-
topics: [{ name: 'Harmful', action: 'BLOCKED', detected: true }],
|
|
2835
|
-
},
|
|
2836
|
-
},
|
|
2837
|
-
},
|
|
2838
|
-
},
|
|
2839
|
-
},
|
|
2840
|
-
},
|
|
2841
|
-
};
|
|
2842
|
-
});
|
|
2843
|
-
const provider = new BedrockModel();
|
|
2844
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2845
|
-
const events = await collectIterator(provider.stream(messages));
|
|
2846
|
-
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2847
|
-
expect(redactEvent).toBeUndefined();
|
|
2848
|
-
});
|
|
2849
|
-
});
|
|
2850
|
-
describe('redaction event generation', () => {
|
|
2851
|
-
it('emits input redaction with default message', async () => {
|
|
2852
|
-
setupMockSend(async function* () {
|
|
2853
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2854
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2855
|
-
yield {
|
|
2856
|
-
metadata: {
|
|
2857
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2858
|
-
trace: {
|
|
2859
|
-
guardrail: {
|
|
2860
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2861
|
-
},
|
|
2862
|
-
},
|
|
2863
|
-
},
|
|
2864
|
-
};
|
|
2865
|
-
});
|
|
2866
|
-
const provider = new BedrockModel({
|
|
2867
|
-
guardrailConfig: {
|
|
2868
|
-
guardrailIdentifier: 'id',
|
|
2869
|
-
guardrailVersion: '1',
|
|
2870
|
-
},
|
|
2871
|
-
});
|
|
2872
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2873
|
-
expect(events).toContainEqual({
|
|
2874
|
-
type: 'modelRedactionEvent',
|
|
2875
|
-
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
2876
|
-
});
|
|
2877
|
-
});
|
|
2878
|
-
it('emits input redaction with custom message', async () => {
|
|
2879
|
-
setupMockSend(async function* () {
|
|
2880
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2881
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2882
|
-
yield {
|
|
2883
|
-
metadata: {
|
|
2884
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2885
|
-
trace: {
|
|
2886
|
-
guardrail: {
|
|
2887
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2888
|
-
},
|
|
2889
|
-
},
|
|
2890
|
-
},
|
|
2891
|
-
};
|
|
2892
|
-
});
|
|
2893
|
-
const provider = new BedrockModel({
|
|
2894
|
-
guardrailConfig: {
|
|
2895
|
-
guardrailIdentifier: 'id',
|
|
2896
|
-
guardrailVersion: '1',
|
|
2897
|
-
redaction: {
|
|
2898
|
-
inputMessage: '[Custom input message]',
|
|
2899
|
-
},
|
|
2900
|
-
},
|
|
2901
|
-
});
|
|
2902
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2903
|
-
expect(events).toContainEqual({
|
|
2904
|
-
type: 'modelRedactionEvent',
|
|
2905
|
-
inputRedaction: { replaceContent: '[Custom input message]' },
|
|
2906
|
-
});
|
|
2907
|
-
});
|
|
2908
|
-
it('does not emit input redaction when redactInput is false', async () => {
|
|
2909
|
-
setupMockSend(async function* () {
|
|
2910
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2911
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2912
|
-
yield {
|
|
2913
|
-
metadata: {
|
|
2914
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2915
|
-
trace: {
|
|
2916
|
-
guardrail: {
|
|
2917
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2918
|
-
},
|
|
2919
|
-
},
|
|
2920
|
-
},
|
|
2921
|
-
};
|
|
2922
|
-
});
|
|
2923
|
-
const provider = new BedrockModel({
|
|
2924
|
-
guardrailConfig: {
|
|
2925
|
-
guardrailIdentifier: 'id',
|
|
2926
|
-
guardrailVersion: '1',
|
|
2927
|
-
redaction: {
|
|
2928
|
-
input: false,
|
|
2929
|
-
},
|
|
2930
|
-
},
|
|
2931
|
-
});
|
|
2932
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2933
|
-
const inputRedactEvent = events.find((e) => e.type === 'modelRedactionEvent' && 'inputRedaction' in e);
|
|
2934
|
-
expect(inputRedactEvent).toBeUndefined();
|
|
2935
|
-
});
|
|
2936
|
-
it('emits output redaction when redactOutput is true', async () => {
|
|
2937
|
-
setupMockSend(async function* () {
|
|
2938
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2939
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2940
|
-
yield {
|
|
2941
|
-
metadata: {
|
|
2942
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2943
|
-
trace: {
|
|
2944
|
-
guardrail: {
|
|
2945
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2946
|
-
},
|
|
2947
|
-
},
|
|
2948
|
-
},
|
|
2949
|
-
};
|
|
2950
|
-
});
|
|
2951
|
-
const provider = new BedrockModel({
|
|
2952
|
-
guardrailConfig: {
|
|
2953
|
-
guardrailIdentifier: 'id',
|
|
2954
|
-
guardrailVersion: '1',
|
|
2955
|
-
redaction: {
|
|
2956
|
-
output: true,
|
|
2957
|
-
},
|
|
2958
|
-
},
|
|
2959
|
-
});
|
|
2960
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2961
|
-
expect(events).toContainEqual({
|
|
2962
|
-
type: 'modelRedactionEvent',
|
|
2963
|
-
outputRedaction: { replaceContent: '[Assistant output redacted.]' },
|
|
2964
|
-
});
|
|
2965
|
-
});
|
|
2966
|
-
it('emits output redaction with custom message', async () => {
|
|
2967
|
-
setupMockSend(async function* () {
|
|
2968
|
-
yield { messageStart: { role: 'assistant' } };
|
|
2969
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2970
|
-
yield {
|
|
2971
|
-
metadata: {
|
|
2972
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2973
|
-
trace: {
|
|
2974
|
-
guardrail: {
|
|
2975
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2976
|
-
},
|
|
2977
|
-
},
|
|
2978
|
-
},
|
|
2979
|
-
};
|
|
2980
|
-
});
|
|
2981
|
-
const provider = new BedrockModel({
|
|
2982
|
-
guardrailConfig: {
|
|
2983
|
-
guardrailIdentifier: 'id',
|
|
2984
|
-
guardrailVersion: '1',
|
|
2985
|
-
redaction: {
|
|
2986
|
-
output: true,
|
|
2987
|
-
outputMessage: '[Custom output message]',
|
|
2988
|
-
},
|
|
2989
|
-
},
|
|
2990
|
-
});
|
|
2991
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2992
|
-
expect(events).toContainEqual({
|
|
2993
|
-
type: 'modelRedactionEvent',
|
|
2994
|
-
outputRedaction: { replaceContent: '[Custom output message]' },
|
|
2995
|
-
});
|
|
2996
|
-
});
|
|
2997
|
-
it('emits both input and output redaction when both are enabled', async () => {
|
|
2998
|
-
setupMockSend(async function* () {
|
|
2999
|
-
yield { messageStart: { role: 'assistant' } };
|
|
3000
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
3001
|
-
yield {
|
|
3002
|
-
metadata: {
|
|
3003
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
3004
|
-
trace: {
|
|
3005
|
-
guardrail: {
|
|
3006
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
3007
|
-
},
|
|
3008
|
-
},
|
|
3009
|
-
},
|
|
3010
|
-
};
|
|
3011
|
-
});
|
|
3012
|
-
const provider = new BedrockModel({
|
|
3013
|
-
guardrailConfig: {
|
|
3014
|
-
guardrailIdentifier: 'id',
|
|
3015
|
-
guardrailVersion: '1',
|
|
3016
|
-
redaction: {
|
|
3017
|
-
input: true,
|
|
3018
|
-
output: true,
|
|
3019
|
-
},
|
|
3020
|
-
},
|
|
3021
|
-
});
|
|
3022
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
3023
|
-
expect(events).toContainEqual({
|
|
3024
|
-
type: 'modelRedactionEvent',
|
|
3025
|
-
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
3026
|
-
});
|
|
3027
|
-
expect(events).toContainEqual({
|
|
3028
|
-
type: 'modelRedactionEvent',
|
|
3029
|
-
outputRedaction: { replaceContent: '[Assistant output redacted.]' },
|
|
3030
|
-
});
|
|
3031
|
-
});
|
|
3032
|
-
it('includes redactedContent from modelOutput when available', async () => {
|
|
3033
|
-
setupMockSend(async function* () {
|
|
3034
|
-
yield { messageStart: { role: 'assistant' } };
|
|
3035
|
-
yield { contentBlockStart: {} };
|
|
3036
|
-
yield { contentBlockDelta: { delta: { text: 'This content was blocked' } } };
|
|
3037
|
-
yield { contentBlockStop: {} };
|
|
3038
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
3039
|
-
yield {
|
|
3040
|
-
metadata: {
|
|
3041
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
3042
|
-
trace: {
|
|
3043
|
-
guardrail: {
|
|
3044
|
-
modelOutput: ['This content ', 'was blocked'],
|
|
3045
|
-
outputAssessments: {
|
|
3046
|
-
'0': [{ topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } }],
|
|
3047
|
-
},
|
|
3048
|
-
},
|
|
3049
|
-
},
|
|
3050
|
-
},
|
|
3051
|
-
};
|
|
3052
|
-
});
|
|
3053
|
-
const provider = new BedrockModel({
|
|
3054
|
-
guardrailConfig: {
|
|
3055
|
-
guardrailIdentifier: 'id',
|
|
3056
|
-
guardrailVersion: '1',
|
|
3057
|
-
redaction: {
|
|
3058
|
-
output: true,
|
|
3059
|
-
outputMessage: '[Blocked]',
|
|
3060
|
-
},
|
|
3061
|
-
},
|
|
3062
|
-
});
|
|
3063
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
3064
|
-
expect(events).toContainEqual({
|
|
3065
|
-
type: 'modelRedactionEvent',
|
|
3066
|
-
outputRedaction: {
|
|
3067
|
-
replaceContent: '[Blocked]',
|
|
3068
|
-
redactedContent: 'This content was blocked',
|
|
3069
|
-
},
|
|
3070
|
-
});
|
|
3071
|
-
});
|
|
3072
|
-
});
|
|
3073
|
-
describe('non-streaming mode', () => {
|
|
3074
|
-
it('emits redaction events in non-streaming mode when guardrail blocks', async () => {
|
|
3075
|
-
const mockSend = vi.fn(async () => ({
|
|
3076
|
-
output: {
|
|
3077
|
-
message: {
|
|
3078
|
-
role: 'assistant',
|
|
3079
|
-
content: [{ text: 'Hello' }],
|
|
3080
|
-
},
|
|
3081
|
-
},
|
|
3082
|
-
stopReason: 'guardrail_intervened',
|
|
3083
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
3084
|
-
trace: {
|
|
3085
|
-
guardrail: {
|
|
3086
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
3087
|
-
},
|
|
3088
|
-
},
|
|
3089
|
-
}));
|
|
3090
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3091
|
-
const provider = new BedrockModel({
|
|
3092
|
-
stream: false,
|
|
3093
|
-
guardrailConfig: {
|
|
3094
|
-
guardrailIdentifier: 'id',
|
|
3095
|
-
guardrailVersion: '1',
|
|
3096
|
-
},
|
|
3097
|
-
});
|
|
3098
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
3099
|
-
expect(events).toContainEqual({
|
|
3100
|
-
type: 'modelRedactionEvent',
|
|
3101
|
-
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
3102
|
-
});
|
|
3103
|
-
});
|
|
3104
|
-
});
|
|
3105
|
-
describe('guardLatestUserMessage', () => {
|
|
3106
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
3107
|
-
beforeEach(() => {
|
|
3108
|
-
vi.clearAllMocks();
|
|
3109
|
-
});
|
|
3110
|
-
it('accepts guardLatestUserMessage in guardrailConfig', () => {
|
|
3111
|
-
const provider = new BedrockModel({
|
|
3112
|
-
guardrailConfig: {
|
|
3113
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3114
|
-
guardrailVersion: '1',
|
|
3115
|
-
guardLatestUserMessage: true,
|
|
3116
|
-
},
|
|
3117
|
-
});
|
|
3118
|
-
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
3119
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3120
|
-
guardrailVersion: '1',
|
|
3121
|
-
guardLatestUserMessage: true,
|
|
3122
|
-
});
|
|
3123
|
-
});
|
|
3124
|
-
it('wraps latest user message text content in guardContent when enabled', async () => {
|
|
3125
|
-
const provider = new BedrockModel({
|
|
3126
|
-
guardrailConfig: {
|
|
3127
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3128
|
-
guardrailVersion: '1',
|
|
3129
|
-
guardLatestUserMessage: true,
|
|
3130
|
-
},
|
|
3131
|
-
});
|
|
3132
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
3133
|
-
collectIterator(provider.stream(messages));
|
|
3134
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3135
|
-
messages: [
|
|
3136
|
-
{
|
|
3137
|
-
role: 'user',
|
|
3138
|
-
content: [
|
|
3139
|
-
{
|
|
3140
|
-
guardContent: {
|
|
3141
|
-
text: {
|
|
3142
|
-
text: 'Hello world',
|
|
3143
|
-
},
|
|
3144
|
-
},
|
|
3145
|
-
},
|
|
3146
|
-
],
|
|
3147
|
-
},
|
|
3148
|
-
],
|
|
3149
|
-
}));
|
|
3150
|
-
});
|
|
3151
|
-
it('wraps latest user message image content in guardContent when enabled', async () => {
|
|
3152
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
3153
|
-
const provider = new BedrockModel({
|
|
3154
|
-
guardrailConfig: {
|
|
3155
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3156
|
-
guardrailVersion: '1',
|
|
3157
|
-
guardLatestUserMessage: true,
|
|
3158
|
-
},
|
|
3159
|
-
});
|
|
3160
|
-
const messages = [
|
|
3161
|
-
new Message({
|
|
3162
|
-
role: 'user',
|
|
3163
|
-
content: [
|
|
3164
|
-
new ImageBlock({
|
|
3165
|
-
format: 'jpeg',
|
|
3166
|
-
source: { bytes: imageBytes },
|
|
3167
|
-
}),
|
|
3168
|
-
],
|
|
3169
|
-
}),
|
|
3170
|
-
];
|
|
3171
|
-
collectIterator(provider.stream(messages));
|
|
3172
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3173
|
-
messages: [
|
|
3174
|
-
{
|
|
3175
|
-
role: 'user',
|
|
3176
|
-
content: [
|
|
3177
|
-
{
|
|
3178
|
-
guardContent: {
|
|
3179
|
-
image: {
|
|
3180
|
-
format: 'jpeg',
|
|
3181
|
-
source: { bytes: imageBytes },
|
|
3182
|
-
},
|
|
3183
|
-
},
|
|
3184
|
-
},
|
|
3185
|
-
],
|
|
3186
|
-
},
|
|
3187
|
-
],
|
|
3188
|
-
}));
|
|
3189
|
-
});
|
|
3190
|
-
it('does not wrap toolResult messages even though role is user', async () => {
|
|
3191
|
-
const provider = new BedrockModel({
|
|
3192
|
-
guardrailConfig: {
|
|
3193
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3194
|
-
guardrailVersion: '1',
|
|
3195
|
-
guardLatestUserMessage: true,
|
|
3196
|
-
},
|
|
3197
|
-
});
|
|
3198
|
-
const messages = [
|
|
3199
|
-
new Message({ role: 'user', content: [new TextBlock('What is 2+2?')] }),
|
|
3200
|
-
new Message({
|
|
3201
|
-
role: 'assistant',
|
|
3202
|
-
content: [
|
|
3203
|
-
new ToolUseBlock({
|
|
3204
|
-
name: 'calculator',
|
|
3205
|
-
toolUseId: 'tool-123',
|
|
3206
|
-
input: { expression: '2+2' },
|
|
3207
|
-
}),
|
|
3208
|
-
],
|
|
3209
|
-
}),
|
|
3210
|
-
new Message({
|
|
3211
|
-
role: 'user',
|
|
3212
|
-
content: [
|
|
3213
|
-
new ToolResultBlock({
|
|
3214
|
-
toolUseId: 'tool-123',
|
|
3215
|
-
status: 'success',
|
|
3216
|
-
content: [new TextBlock('4')],
|
|
3217
|
-
}),
|
|
3218
|
-
],
|
|
3219
|
-
}),
|
|
3220
|
-
];
|
|
3221
|
-
collectIterator(provider.stream(messages));
|
|
3222
|
-
// The latest message is a toolResult, but guardContent should wrap the FIRST user message
|
|
3223
|
-
// which contains text, not the toolResult
|
|
3224
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3225
|
-
messages: [
|
|
3226
|
-
{
|
|
3227
|
-
role: 'user',
|
|
3228
|
-
content: [
|
|
3229
|
-
{
|
|
3230
|
-
guardContent: {
|
|
3231
|
-
text: {
|
|
3232
|
-
text: 'What is 2+2?',
|
|
3233
|
-
},
|
|
3234
|
-
},
|
|
3235
|
-
},
|
|
3236
|
-
],
|
|
3237
|
-
},
|
|
3238
|
-
{
|
|
3239
|
-
role: 'assistant',
|
|
3240
|
-
content: [
|
|
3241
|
-
{
|
|
3242
|
-
toolUse: {
|
|
3243
|
-
name: 'calculator',
|
|
3244
|
-
toolUseId: 'tool-123',
|
|
3245
|
-
input: { expression: '2+2' },
|
|
3246
|
-
},
|
|
3247
|
-
},
|
|
3248
|
-
],
|
|
3249
|
-
},
|
|
3250
|
-
{
|
|
3251
|
-
role: 'user',
|
|
3252
|
-
content: [
|
|
3253
|
-
{
|
|
3254
|
-
toolResult: expect.objectContaining({
|
|
3255
|
-
toolUseId: 'tool-123',
|
|
3256
|
-
}),
|
|
3257
|
-
},
|
|
3258
|
-
],
|
|
3259
|
-
},
|
|
3260
|
-
],
|
|
3261
|
-
}));
|
|
3262
|
-
});
|
|
3263
|
-
it('does not wrap messages when guardLatestUserMessage is false', async () => {
|
|
3264
|
-
const provider = new BedrockModel({
|
|
3265
|
-
guardrailConfig: {
|
|
3266
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3267
|
-
guardrailVersion: '1',
|
|
3268
|
-
guardLatestUserMessage: false,
|
|
3269
|
-
},
|
|
3270
|
-
});
|
|
3271
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
3272
|
-
collectIterator(provider.stream(messages));
|
|
3273
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3274
|
-
messages: [
|
|
3275
|
-
{
|
|
3276
|
-
role: 'user',
|
|
3277
|
-
content: [{ text: 'Hello world' }],
|
|
3278
|
-
},
|
|
3279
|
-
],
|
|
3280
|
-
}));
|
|
3281
|
-
});
|
|
3282
|
-
it('does not wrap messages when guardLatestUserMessage is undefined', async () => {
|
|
3283
|
-
const provider = new BedrockModel({
|
|
3284
|
-
guardrailConfig: {
|
|
3285
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3286
|
-
guardrailVersion: '1',
|
|
3287
|
-
},
|
|
3288
|
-
});
|
|
3289
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
3290
|
-
collectIterator(provider.stream(messages));
|
|
3291
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3292
|
-
messages: [
|
|
3293
|
-
{
|
|
3294
|
-
role: 'user',
|
|
3295
|
-
content: [{ text: 'Hello world' }],
|
|
3296
|
-
},
|
|
3297
|
-
],
|
|
3298
|
-
}));
|
|
3299
|
-
});
|
|
3300
|
-
it('does not wrap assistant messages', async () => {
|
|
3301
|
-
const provider = new BedrockModel({
|
|
3302
|
-
guardrailConfig: {
|
|
3303
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3304
|
-
guardrailVersion: '1',
|
|
3305
|
-
guardLatestUserMessage: true,
|
|
3306
|
-
},
|
|
3307
|
-
});
|
|
3308
|
-
const messages = [
|
|
3309
|
-
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
3310
|
-
new Message({ role: 'assistant', content: [new TextBlock('Hi there!')] }),
|
|
3311
|
-
];
|
|
3312
|
-
collectIterator(provider.stream(messages));
|
|
3313
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3314
|
-
messages: [
|
|
3315
|
-
{
|
|
3316
|
-
role: 'user',
|
|
3317
|
-
content: [
|
|
3318
|
-
{
|
|
3319
|
-
guardContent: {
|
|
3320
|
-
text: {
|
|
3321
|
-
text: 'Hello',
|
|
3322
|
-
},
|
|
3323
|
-
},
|
|
3324
|
-
},
|
|
3325
|
-
],
|
|
3326
|
-
},
|
|
3327
|
-
{
|
|
3328
|
-
role: 'assistant',
|
|
3329
|
-
content: [{ text: 'Hi there!' }],
|
|
3330
|
-
},
|
|
3331
|
-
],
|
|
3332
|
-
}));
|
|
3333
|
-
});
|
|
3334
|
-
it('wraps only the last user text/image message in multi-turn conversation', async () => {
|
|
3335
|
-
const provider = new BedrockModel({
|
|
3336
|
-
guardrailConfig: {
|
|
3337
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3338
|
-
guardrailVersion: '1',
|
|
3339
|
-
guardLatestUserMessage: true,
|
|
3340
|
-
},
|
|
3341
|
-
});
|
|
3342
|
-
const messages = [
|
|
3343
|
-
new Message({ role: 'user', content: [new TextBlock('First message')] }),
|
|
3344
|
-
new Message({ role: 'assistant', content: [new TextBlock('First response')] }),
|
|
3345
|
-
new Message({ role: 'user', content: [new TextBlock('Second message')] }),
|
|
3346
|
-
];
|
|
3347
|
-
collectIterator(provider.stream(messages));
|
|
3348
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3349
|
-
messages: [
|
|
3350
|
-
{
|
|
3351
|
-
role: 'user',
|
|
3352
|
-
content: [{ text: 'First message' }],
|
|
3353
|
-
},
|
|
3354
|
-
{
|
|
3355
|
-
role: 'assistant',
|
|
3356
|
-
content: [{ text: 'First response' }],
|
|
3357
|
-
},
|
|
3358
|
-
{
|
|
3359
|
-
role: 'user',
|
|
3360
|
-
content: [
|
|
3361
|
-
{
|
|
3362
|
-
guardContent: {
|
|
3363
|
-
text: {
|
|
3364
|
-
text: 'Second message',
|
|
3365
|
-
},
|
|
3366
|
-
},
|
|
3367
|
-
},
|
|
3368
|
-
],
|
|
3369
|
-
},
|
|
3370
|
-
],
|
|
3371
|
-
}));
|
|
3372
|
-
});
|
|
3373
|
-
it('handles no user messages with text/image content gracefully', async () => {
|
|
3374
|
-
const provider = new BedrockModel({
|
|
3375
|
-
guardrailConfig: {
|
|
3376
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3377
|
-
guardrailVersion: '1',
|
|
3378
|
-
guardLatestUserMessage: true,
|
|
3379
|
-
},
|
|
3380
|
-
});
|
|
3381
|
-
// Only assistant message, no user text/image content
|
|
3382
|
-
const messages = [new Message({ role: 'assistant', content: [new TextBlock('Hello!')] })];
|
|
3383
|
-
collectIterator(provider.stream(messages));
|
|
3384
|
-
// Should not throw and should not wrap anything
|
|
3385
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3386
|
-
messages: [
|
|
3387
|
-
{
|
|
3388
|
-
role: 'assistant',
|
|
3389
|
-
content: [{ text: 'Hello!' }],
|
|
3390
|
-
},
|
|
3391
|
-
],
|
|
3392
|
-
}));
|
|
3393
|
-
});
|
|
3394
|
-
it('preserves explicit GuardContentBlock in messages without double-wrapping', async () => {
|
|
3395
|
-
const provider = new BedrockModel({
|
|
3396
|
-
guardrailConfig: {
|
|
3397
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3398
|
-
guardrailVersion: '1',
|
|
3399
|
-
guardLatestUserMessage: true,
|
|
3400
|
-
},
|
|
3401
|
-
});
|
|
3402
|
-
const messages = [
|
|
3403
|
-
new Message({
|
|
3404
|
-
role: 'user',
|
|
3405
|
-
content: [
|
|
3406
|
-
new GuardContentBlock({
|
|
3407
|
-
text: {
|
|
3408
|
-
qualifiers: ['grounding_source'],
|
|
3409
|
-
text: 'Already guarded content',
|
|
3410
|
-
},
|
|
3411
|
-
}),
|
|
3412
|
-
],
|
|
3413
|
-
}),
|
|
3414
|
-
];
|
|
3415
|
-
collectIterator(provider.stream(messages));
|
|
3416
|
-
// Explicit GuardContentBlock should be preserved as-is (no text/image content to wrap)
|
|
3417
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3418
|
-
messages: [
|
|
3419
|
-
{
|
|
3420
|
-
role: 'user',
|
|
3421
|
-
content: [
|
|
3422
|
-
{
|
|
3423
|
-
guardContent: {
|
|
3424
|
-
text: {
|
|
3425
|
-
text: 'Already guarded content',
|
|
3426
|
-
qualifiers: ['grounding_source'],
|
|
3427
|
-
},
|
|
3428
|
-
},
|
|
3429
|
-
},
|
|
3430
|
-
],
|
|
3431
|
-
},
|
|
3432
|
-
],
|
|
3433
|
-
}));
|
|
3434
|
-
});
|
|
3435
|
-
it('wraps all text and image blocks in the latest user message', async () => {
|
|
3436
|
-
const imageBytes = new Uint8Array([5, 6, 7, 8]);
|
|
3437
|
-
const provider = new BedrockModel({
|
|
3438
|
-
guardrailConfig: {
|
|
3439
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3440
|
-
guardrailVersion: '1',
|
|
3441
|
-
guardLatestUserMessage: true,
|
|
3442
|
-
},
|
|
3443
|
-
});
|
|
3444
|
-
const messages = [
|
|
3445
|
-
new Message({
|
|
3446
|
-
role: 'user',
|
|
3447
|
-
content: [
|
|
3448
|
-
new TextBlock('Check this text'),
|
|
3449
|
-
new ImageBlock({
|
|
3450
|
-
format: 'png',
|
|
3451
|
-
source: { bytes: imageBytes },
|
|
3452
|
-
}),
|
|
3453
|
-
new TextBlock('And this text too'),
|
|
3454
|
-
],
|
|
3455
|
-
}),
|
|
3456
|
-
];
|
|
3457
|
-
collectIterator(provider.stream(messages));
|
|
3458
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3459
|
-
messages: [
|
|
3460
|
-
{
|
|
3461
|
-
role: 'user',
|
|
3462
|
-
content: [
|
|
3463
|
-
{
|
|
3464
|
-
guardContent: {
|
|
3465
|
-
text: {
|
|
3466
|
-
text: 'Check this text',
|
|
3467
|
-
},
|
|
3468
|
-
},
|
|
3469
|
-
},
|
|
3470
|
-
{
|
|
3471
|
-
guardContent: {
|
|
3472
|
-
image: {
|
|
3473
|
-
format: 'png',
|
|
3474
|
-
source: { bytes: imageBytes },
|
|
3475
|
-
},
|
|
3476
|
-
},
|
|
3477
|
-
},
|
|
3478
|
-
{
|
|
3479
|
-
guardContent: {
|
|
3480
|
-
text: {
|
|
3481
|
-
text: 'And this text too',
|
|
3482
|
-
},
|
|
3483
|
-
},
|
|
3484
|
-
},
|
|
3485
|
-
],
|
|
3486
|
-
},
|
|
3487
|
-
],
|
|
3488
|
-
}));
|
|
3489
|
-
});
|
|
3490
|
-
it('skips wrapping images with unsupported formats (gif)', async () => {
|
|
3491
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3492
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
3493
|
-
const provider = new BedrockModel({
|
|
3494
|
-
guardrailConfig: {
|
|
3495
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3496
|
-
guardrailVersion: '1',
|
|
3497
|
-
guardLatestUserMessage: true,
|
|
3498
|
-
},
|
|
3499
|
-
});
|
|
3500
|
-
const messages = [
|
|
3501
|
-
new Message({
|
|
3502
|
-
role: 'user',
|
|
3503
|
-
content: [
|
|
3504
|
-
new ImageBlock({
|
|
3505
|
-
format: 'gif',
|
|
3506
|
-
source: { bytes: imageBytes },
|
|
3507
|
-
}),
|
|
3508
|
-
],
|
|
3509
|
-
}),
|
|
3510
|
-
];
|
|
3511
|
-
collectIterator(provider.stream(messages));
|
|
3512
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('image_format=<gif> | format not supported by bedrock guardrails | skipping guardContent wrap');
|
|
3513
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3514
|
-
messages: [
|
|
3515
|
-
{
|
|
3516
|
-
role: 'user',
|
|
3517
|
-
content: [
|
|
3518
|
-
{
|
|
3519
|
-
image: {
|
|
3520
|
-
format: 'gif',
|
|
3521
|
-
source: { bytes: imageBytes },
|
|
3522
|
-
},
|
|
3523
|
-
},
|
|
3524
|
-
],
|
|
3525
|
-
},
|
|
3526
|
-
],
|
|
3527
|
-
}));
|
|
3528
|
-
consoleWarnSpy.mockRestore();
|
|
3529
|
-
});
|
|
3530
|
-
it('skips wrapping images with unsupported formats (webp)', async () => {
|
|
3531
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3532
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
3533
|
-
const provider = new BedrockModel({
|
|
3534
|
-
guardrailConfig: {
|
|
3535
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3536
|
-
guardrailVersion: '1',
|
|
3537
|
-
guardLatestUserMessage: true,
|
|
3538
|
-
},
|
|
3539
|
-
});
|
|
3540
|
-
const messages = [
|
|
3541
|
-
new Message({
|
|
3542
|
-
role: 'user',
|
|
3543
|
-
content: [
|
|
3544
|
-
new ImageBlock({
|
|
3545
|
-
format: 'webp',
|
|
3546
|
-
source: { bytes: imageBytes },
|
|
3547
|
-
}),
|
|
3548
|
-
],
|
|
3549
|
-
}),
|
|
3550
|
-
];
|
|
3551
|
-
collectIterator(provider.stream(messages));
|
|
3552
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('image_format=<webp> | format not supported by bedrock guardrails | skipping guardContent wrap');
|
|
3553
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3554
|
-
messages: [
|
|
3555
|
-
{
|
|
3556
|
-
role: 'user',
|
|
3557
|
-
content: [
|
|
3558
|
-
{
|
|
3559
|
-
image: {
|
|
3560
|
-
format: 'webp',
|
|
3561
|
-
source: { bytes: imageBytes },
|
|
3562
|
-
},
|
|
3563
|
-
},
|
|
3564
|
-
],
|
|
3565
|
-
},
|
|
3566
|
-
],
|
|
3567
|
-
}));
|
|
3568
|
-
consoleWarnSpy.mockRestore();
|
|
3569
|
-
});
|
|
3570
|
-
it('skips wrapping images with S3 source', async () => {
|
|
3571
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3572
|
-
const provider = new BedrockModel({
|
|
3573
|
-
guardrailConfig: {
|
|
3574
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3575
|
-
guardrailVersion: '1',
|
|
3576
|
-
guardLatestUserMessage: true,
|
|
3577
|
-
},
|
|
3578
|
-
});
|
|
3579
|
-
const messages = [
|
|
3580
|
-
new Message({
|
|
3581
|
-
role: 'user',
|
|
3582
|
-
content: [
|
|
3583
|
-
new ImageBlock({
|
|
3584
|
-
format: 'png',
|
|
3585
|
-
source: {
|
|
3586
|
-
location: {
|
|
3587
|
-
type: 's3',
|
|
3588
|
-
uri: 's3://bucket/image.png',
|
|
3589
|
-
},
|
|
3590
|
-
},
|
|
3591
|
-
}),
|
|
3592
|
-
],
|
|
3593
|
-
}),
|
|
3594
|
-
];
|
|
3595
|
-
collectIterator(provider.stream(messages));
|
|
3596
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('source_type=<non-bytes> | image source must be bytes for bedrock guardrails | skipping guardContent wrap');
|
|
3597
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3598
|
-
messages: [
|
|
3599
|
-
{
|
|
3600
|
-
role: 'user',
|
|
3601
|
-
content: [
|
|
3602
|
-
{
|
|
3603
|
-
image: {
|
|
3604
|
-
format: 'png',
|
|
3605
|
-
source: {
|
|
3606
|
-
s3Location: {
|
|
3607
|
-
uri: 's3://bucket/image.png',
|
|
3608
|
-
},
|
|
3609
|
-
},
|
|
3610
|
-
},
|
|
3611
|
-
},
|
|
3612
|
-
],
|
|
3613
|
-
},
|
|
3614
|
-
],
|
|
3615
|
-
}));
|
|
3616
|
-
consoleWarnSpy.mockRestore();
|
|
3617
|
-
});
|
|
3618
|
-
it('skips wrapping images with URL source', async () => {
|
|
3619
|
-
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3620
|
-
const provider = new BedrockModel({
|
|
3621
|
-
guardrailConfig: {
|
|
3622
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3623
|
-
guardrailVersion: '1',
|
|
3624
|
-
guardLatestUserMessage: true,
|
|
3625
|
-
},
|
|
3626
|
-
});
|
|
3627
|
-
const messages = [
|
|
3628
|
-
new Message({
|
|
3629
|
-
role: 'user',
|
|
3630
|
-
content: [
|
|
3631
|
-
new ImageBlock({
|
|
3632
|
-
format: 'jpeg',
|
|
3633
|
-
source: { url: 'https://example.com/image.jpg' },
|
|
3634
|
-
}),
|
|
3635
|
-
],
|
|
3636
|
-
}),
|
|
3637
|
-
];
|
|
3638
|
-
collectIterator(provider.stream(messages));
|
|
3639
|
-
// URL sources return undefined in _formatMediaSource, resulting in source: undefined
|
|
3640
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith('source_type=<imageSourceUrl> | not supported by bedrock | skipping');
|
|
3641
|
-
// The image block still appears but with undefined source (Bedrock will reject this)
|
|
3642
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3643
|
-
messages: [
|
|
3644
|
-
{
|
|
3645
|
-
role: 'user',
|
|
3646
|
-
content: [
|
|
3647
|
-
{
|
|
3648
|
-
image: {
|
|
3649
|
-
format: 'jpeg',
|
|
3650
|
-
source: undefined,
|
|
3651
|
-
},
|
|
3652
|
-
},
|
|
3653
|
-
],
|
|
3654
|
-
},
|
|
3655
|
-
],
|
|
3656
|
-
}));
|
|
3657
|
-
consoleWarnSpy.mockRestore();
|
|
3658
|
-
});
|
|
3659
|
-
it('wraps supported image formats (png and jpeg) with bytes source', async () => {
|
|
3660
|
-
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
3661
|
-
const provider = new BedrockModel({
|
|
3662
|
-
guardrailConfig: {
|
|
3663
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3664
|
-
guardrailVersion: '1',
|
|
3665
|
-
guardLatestUserMessage: true,
|
|
3666
|
-
},
|
|
3667
|
-
});
|
|
3668
|
-
const messages = [
|
|
3669
|
-
new Message({
|
|
3670
|
-
role: 'user',
|
|
3671
|
-
content: [
|
|
3672
|
-
new ImageBlock({
|
|
3673
|
-
format: 'png',
|
|
3674
|
-
source: { bytes: imageBytes },
|
|
3675
|
-
}),
|
|
3676
|
-
new ImageBlock({
|
|
3677
|
-
format: 'jpeg',
|
|
3678
|
-
source: { bytes: imageBytes },
|
|
3679
|
-
}),
|
|
3680
|
-
],
|
|
3681
|
-
}),
|
|
3682
|
-
];
|
|
3683
|
-
collectIterator(provider.stream(messages));
|
|
3684
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3685
|
-
messages: [
|
|
3686
|
-
{
|
|
3687
|
-
role: 'user',
|
|
3688
|
-
content: [
|
|
3689
|
-
{
|
|
3690
|
-
guardContent: {
|
|
3691
|
-
image: {
|
|
3692
|
-
format: 'png',
|
|
3693
|
-
source: { bytes: imageBytes },
|
|
3694
|
-
},
|
|
3695
|
-
},
|
|
3696
|
-
},
|
|
3697
|
-
{
|
|
3698
|
-
guardContent: {
|
|
3699
|
-
image: {
|
|
3700
|
-
format: 'jpeg',
|
|
3701
|
-
source: { bytes: imageBytes },
|
|
3702
|
-
},
|
|
3703
|
-
},
|
|
3704
|
-
},
|
|
3705
|
-
],
|
|
3706
|
-
},
|
|
3707
|
-
],
|
|
3708
|
-
}));
|
|
3709
|
-
});
|
|
3710
|
-
it('does not wrap reasoning or cachePoint blocks', async () => {
|
|
3711
|
-
const provider = new BedrockModel({
|
|
3712
|
-
guardrailConfig: {
|
|
3713
|
-
guardrailIdentifier: 'my-guardrail-id',
|
|
3714
|
-
guardrailVersion: '1',
|
|
3715
|
-
guardLatestUserMessage: true,
|
|
3716
|
-
},
|
|
3717
|
-
});
|
|
3718
|
-
const messages = [
|
|
3719
|
-
new Message({
|
|
3720
|
-
role: 'user',
|
|
3721
|
-
content: [
|
|
3722
|
-
new TextBlock('User message'),
|
|
3723
|
-
new ReasoningBlock({ text: 'thinking...', signature: 'sig' }),
|
|
3724
|
-
new CachePointBlock({ cacheType: 'default' }),
|
|
3725
|
-
],
|
|
3726
|
-
}),
|
|
3727
|
-
];
|
|
3728
|
-
collectIterator(provider.stream(messages));
|
|
3729
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3730
|
-
messages: [
|
|
3731
|
-
{
|
|
3732
|
-
role: 'user',
|
|
3733
|
-
content: [
|
|
3734
|
-
{
|
|
3735
|
-
guardContent: {
|
|
3736
|
-
text: {
|
|
3737
|
-
text: 'User message',
|
|
3738
|
-
},
|
|
3739
|
-
},
|
|
3740
|
-
},
|
|
3741
|
-
{
|
|
3742
|
-
reasoningContent: {
|
|
3743
|
-
reasoningText: {
|
|
3744
|
-
text: 'thinking...',
|
|
3745
|
-
signature: 'sig',
|
|
3746
|
-
},
|
|
3747
|
-
},
|
|
3748
|
-
},
|
|
3749
|
-
{ cachePoint: { type: 'default' } },
|
|
3750
|
-
],
|
|
3751
|
-
},
|
|
3752
|
-
],
|
|
3753
|
-
}));
|
|
3754
|
-
});
|
|
3755
|
-
});
|
|
3756
|
-
});
|
|
3757
|
-
describe('thinking with forced tool choice', () => {
|
|
3758
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
3759
|
-
const provider = new BedrockModel({
|
|
3760
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
3761
|
-
additionalRequestFields: {
|
|
3762
|
-
thinking: { type: 'enabled', budget_tokens: 5000 },
|
|
3763
|
-
some_other_field: 'value',
|
|
3764
|
-
},
|
|
3765
|
-
});
|
|
3766
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
3767
|
-
const toolSpecs = [{ name: 'test_tool', description: 'test' }];
|
|
3768
|
-
it.each([
|
|
3769
|
-
{ name: 'any', toolChoice: { any: {} } },
|
|
3770
|
-
{ name: 'tool', toolChoice: { tool: { name: 'test_tool' } } },
|
|
3771
|
-
])('strips thinking from additional request fields when tool choice is $name', ({ toolChoice }) => {
|
|
3772
|
-
collectIterator(provider.stream(messages, { toolSpecs, toolChoice }));
|
|
3773
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3774
|
-
additionalModelRequestFields: { some_other_field: 'value' },
|
|
3775
|
-
}));
|
|
3776
|
-
});
|
|
3777
|
-
it('preserves thinking when tool choice is auto', () => {
|
|
3778
|
-
collectIterator(provider.stream(messages, { toolSpecs, toolChoice: { auto: {} } }));
|
|
3779
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3780
|
-
additionalModelRequestFields: {
|
|
3781
|
-
thinking: { type: 'enabled', budget_tokens: 5000 },
|
|
3782
|
-
some_other_field: 'value',
|
|
3783
|
-
},
|
|
3784
|
-
}));
|
|
3785
|
-
});
|
|
3786
|
-
it('preserves thinking when no tool choice is provided', () => {
|
|
3787
|
-
collectIterator(provider.stream(messages, { toolSpecs }));
|
|
3788
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3789
|
-
additionalModelRequestFields: {
|
|
3790
|
-
thinking: { type: 'enabled', budget_tokens: 5000 },
|
|
3791
|
-
some_other_field: 'value',
|
|
3792
|
-
},
|
|
3793
|
-
}));
|
|
3794
|
-
});
|
|
3795
|
-
it('omits additionalModelRequestFields when thinking is the only field and tool choice forces tool use', () => {
|
|
3796
|
-
const thinkingOnlyProvider = new BedrockModel({
|
|
3797
|
-
modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',
|
|
3798
|
-
additionalRequestFields: {
|
|
3799
|
-
thinking: { type: 'enabled', budget_tokens: 5000 },
|
|
3800
|
-
},
|
|
3801
|
-
});
|
|
3802
|
-
collectIterator(thinkingOnlyProvider.stream(messages, { toolSpecs, toolChoice: { any: {} } }));
|
|
3803
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.not.objectContaining({
|
|
3804
|
-
additionalModelRequestFields: expect.anything(),
|
|
3805
|
-
}));
|
|
3806
|
-
});
|
|
3807
|
-
});
|
|
3808
|
-
describe('countTokens', () => {
|
|
3809
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('hello')] })];
|
|
3810
|
-
const toolSpecs = [
|
|
3811
|
-
{ name: 'test_tool', description: 'A test tool', inputSchema: { type: 'object', properties: {} } },
|
|
3812
|
-
];
|
|
3813
|
-
beforeEach(() => {
|
|
3814
|
-
vi.clearAllMocks();
|
|
3815
|
-
BedrockModel.clearCountTokensCache();
|
|
3816
|
-
});
|
|
3817
|
-
it('should use heuristic by default when useNativeTokenCount is not set', async () => {
|
|
3818
|
-
const mockSend = vi.fn();
|
|
3819
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3820
|
-
const model = new BedrockModel();
|
|
3821
|
-
const result = await model.countTokens(messages);
|
|
3822
|
-
expect(mockSend).not.toHaveBeenCalled();
|
|
3823
|
-
expect(result).toBe(2); // heuristic: Math.ceil('hello'.length / 4)
|
|
3824
|
-
});
|
|
3825
|
-
it('should return native token count on success', async () => {
|
|
3826
|
-
const mockSend = vi.fn(async () => ({ inputTokens: 42 }));
|
|
3827
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3828
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3829
|
-
const result = await model.countTokens(messages);
|
|
3830
|
-
expect(result).toBe(42);
|
|
3831
|
-
expect(mockSend).toHaveBeenCalledOnce();
|
|
3832
|
-
});
|
|
3833
|
-
it('should include system prompt in request', async () => {
|
|
3834
|
-
const mockSend = vi.fn(async () => ({ inputTokens: 55 }));
|
|
3835
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3836
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3837
|
-
const result = await model.countTokens(messages, { systemPrompt: 'Be helpful.' });
|
|
3838
|
-
expect(result).toBe(55);
|
|
3839
|
-
const commandInput = vi.mocked(CountTokensCommand).mock.calls[0][0];
|
|
3840
|
-
expect(commandInput).toStrictEqual({
|
|
3841
|
-
modelId: expect.any(String),
|
|
3842
|
-
input: {
|
|
3843
|
-
converse: {
|
|
3844
|
-
messages: [{ role: 'user', content: [{ text: 'hello' }] }],
|
|
3845
|
-
system: [{ text: 'Be helpful.' }],
|
|
3846
|
-
},
|
|
3847
|
-
},
|
|
3848
|
-
});
|
|
3849
|
-
});
|
|
3850
|
-
it('should include tool specs in request', async () => {
|
|
3851
|
-
const mockSend = vi.fn(async () => ({ inputTokens: 100 }));
|
|
3852
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3853
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3854
|
-
const result = await model.countTokens(messages, { toolSpecs });
|
|
3855
|
-
expect(result).toBe(100);
|
|
3856
|
-
const commandInput = vi.mocked(CountTokensCommand).mock.calls[0][0];
|
|
3857
|
-
expect(commandInput).toStrictEqual({
|
|
3858
|
-
modelId: expect.any(String),
|
|
3859
|
-
input: {
|
|
3860
|
-
converse: {
|
|
3861
|
-
messages: [{ role: 'user', content: [{ text: 'hello' }] }],
|
|
3862
|
-
toolConfig: {
|
|
3863
|
-
tools: [
|
|
3864
|
-
{
|
|
3865
|
-
toolSpec: {
|
|
3866
|
-
name: 'test_tool',
|
|
3867
|
-
description: 'A test tool',
|
|
3868
|
-
inputSchema: { json: { type: 'object', properties: {} } },
|
|
3869
|
-
},
|
|
3870
|
-
},
|
|
3871
|
-
],
|
|
3872
|
-
},
|
|
3873
|
-
},
|
|
3874
|
-
},
|
|
3875
|
-
});
|
|
3876
|
-
});
|
|
3877
|
-
it('should strip inferenceConfig from request', async () => {
|
|
3878
|
-
const mockSend = vi.fn(async () => ({ inputTokens: 10 }));
|
|
3879
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3880
|
-
const model = new BedrockModel({ maxTokens: 100, useNativeTokenCount: true });
|
|
3881
|
-
await model.countTokens(messages);
|
|
3882
|
-
const commandInput = vi.mocked(CountTokensCommand).mock.calls[0][0];
|
|
3883
|
-
expect(commandInput).toStrictEqual({
|
|
3884
|
-
modelId: expect.any(String),
|
|
3885
|
-
input: {
|
|
3886
|
-
converse: {
|
|
3887
|
-
messages: [{ role: 'user', content: [{ text: 'hello' }] }],
|
|
3888
|
-
},
|
|
3889
|
-
},
|
|
3890
|
-
});
|
|
3891
|
-
});
|
|
3892
|
-
it('should fall back to estimation on API error', async () => {
|
|
3893
|
-
const mockSend = vi.fn(async () => {
|
|
3894
|
-
throw new Error('API error');
|
|
3895
|
-
});
|
|
3896
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3897
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3898
|
-
const result = await model.countTokens(messages);
|
|
3899
|
-
expect(typeof result).toBe('number');
|
|
3900
|
-
expect(result).toBeGreaterThanOrEqual(0);
|
|
3901
|
-
});
|
|
3902
|
-
it('should fall back to estimation on generic exception', async () => {
|
|
3903
|
-
const mockSend = vi.fn(async () => {
|
|
3904
|
-
throw new Error('Connection failed');
|
|
3905
|
-
});
|
|
3906
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3907
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3908
|
-
const result = await model.countTokens(messages);
|
|
3909
|
-
expect(typeof result).toBe('number');
|
|
3910
|
-
expect(result).toBeGreaterThanOrEqual(0);
|
|
3911
|
-
});
|
|
3912
|
-
it('should cache model ID and skip API call when model does not support counting tokens', async () => {
|
|
3913
|
-
const unsupportedError = new Error("The provided model doesn't support counting tokens");
|
|
3914
|
-
unsupportedError.name = 'ValidationException';
|
|
3915
|
-
const mockSend = vi.fn(async () => {
|
|
3916
|
-
throw unsupportedError;
|
|
3917
|
-
});
|
|
3918
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3919
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3920
|
-
// First call: hits API, gets error, caches
|
|
3921
|
-
await model.countTokens(messages);
|
|
3922
|
-
expect(mockSend).toHaveBeenCalledOnce();
|
|
3923
|
-
// Second call: skips API entirely
|
|
3924
|
-
await model.countTokens(messages);
|
|
3925
|
-
expect(mockSend).toHaveBeenCalledOnce();
|
|
3926
|
-
});
|
|
3927
|
-
it('should cache model ID and skip API call on AccessDeniedException', async () => {
|
|
3928
|
-
const accessDeniedError = new Error('User: arn:aws:sts::123456789012:assumed-role/role is not authorized to perform: bedrock:CountTokens');
|
|
3929
|
-
accessDeniedError.name = 'AccessDeniedException';
|
|
3930
|
-
const mockSend = vi.fn(async () => {
|
|
3931
|
-
throw accessDeniedError;
|
|
3932
|
-
});
|
|
3933
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3934
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3935
|
-
// First call: hits API, gets AccessDeniedException, caches
|
|
3936
|
-
await model.countTokens(messages);
|
|
3937
|
-
expect(mockSend).toHaveBeenCalledOnce();
|
|
3938
|
-
// Second call: skips API entirely due to caching
|
|
3939
|
-
await model.countTokens(messages);
|
|
3940
|
-
expect(mockSend).toHaveBeenCalledOnce();
|
|
3941
|
-
});
|
|
3942
|
-
it('should not cache model ID for other errors', async () => {
|
|
3943
|
-
const mockSend = vi.fn(async () => {
|
|
3944
|
-
throw new Error('Transient network error');
|
|
3945
|
-
});
|
|
3946
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3947
|
-
const model = new BedrockModel({ useNativeTokenCount: true });
|
|
3948
|
-
await model.countTokens(messages);
|
|
3949
|
-
expect(mockSend).toHaveBeenCalledTimes(1);
|
|
3950
|
-
// Second call should still attempt the API
|
|
3951
|
-
await model.countTokens(messages);
|
|
3952
|
-
expect(mockSend).toHaveBeenCalledTimes(2);
|
|
3953
|
-
});
|
|
3954
|
-
it('should skip native API and use heuristic when useNativeTokenCount is false', async () => {
|
|
3955
|
-
const mockSend = vi.fn();
|
|
3956
|
-
mockBedrockClientImplementation({ send: mockSend });
|
|
3957
|
-
const model = new BedrockModel({ useNativeTokenCount: false });
|
|
3958
|
-
const result = await model.countTokens(messages);
|
|
3959
|
-
expect(mockSend).not.toHaveBeenCalled();
|
|
3960
|
-
expect(result).toBe(2); // heuristic: Math.ceil('hello'.length / 4)
|
|
3961
|
-
});
|
|
3962
|
-
});
|
|
3963
|
-
});
|
|
3964
|
-
//# sourceMappingURL=bedrock.test.js.map
|