@strands-agents/sdk 0.6.0 → 0.7.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/README.md +64 -0
- package/dist/src/__fixtures__/agent-helpers.d.ts +37 -4
- package/dist/src/__fixtures__/agent-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/agent-helpers.js +31 -4
- package/dist/src/__fixtures__/agent-helpers.js.map +1 -1
- package/dist/src/__fixtures__/metrics-helpers.d.ts +30 -0
- package/dist/src/__fixtures__/metrics-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/metrics-helpers.js +29 -6
- package/dist/src/__fixtures__/metrics-helpers.js.map +1 -1
- package/dist/src/__fixtures__/mock-message-model.d.ts +8 -4
- package/dist/src/__fixtures__/mock-message-model.d.ts.map +1 -1
- package/dist/src/__fixtures__/mock-message-model.js +13 -7
- package/dist/src/__fixtures__/mock-message-model.js.map +1 -1
- package/dist/src/__fixtures__/mock-meter.d.ts +32 -0
- package/dist/src/__fixtures__/mock-meter.d.ts.map +1 -0
- package/dist/src/__fixtures__/mock-meter.js +47 -0
- package/dist/src/__fixtures__/mock-meter.js.map +1 -0
- package/dist/src/__fixtures__/mock-plugin.d.ts +13 -0
- package/dist/src/__fixtures__/mock-plugin.d.ts.map +1 -0
- package/dist/src/__fixtures__/{mock-hook-provider.js → mock-plugin.js} +8 -5
- package/dist/src/__fixtures__/mock-plugin.js.map +1 -0
- package/dist/src/__fixtures__/tool-helpers.d.ts.map +1 -1
- package/dist/src/__fixtures__/tool-helpers.js +5 -2
- package/dist/src/__fixtures__/tool-helpers.js.map +1 -1
- package/dist/src/__tests__/index.test.js +21 -0
- package/dist/src/__tests__/index.test.js.map +1 -1
- package/dist/src/__tests__/mcp.test.js.map +1 -1
- package/dist/src/__tests__/mime.test.d.ts +2 -0
- package/dist/src/__tests__/mime.test.d.ts.map +1 -0
- package/dist/src/__tests__/mime.test.js +83 -0
- package/dist/src/__tests__/mime.test.js.map +1 -0
- package/dist/src/__tests__/state-store.test.d.ts +2 -0
- package/dist/src/__tests__/state-store.test.d.ts.map +1 -0
- package/dist/src/__tests__/{app-state.test.js → state-store.test.js} +86 -51
- package/dist/src/__tests__/state-store.test.js.map +1 -0
- package/dist/src/a2a/__tests__/a2a-agent.test.d.ts +2 -0
- package/dist/src/a2a/__tests__/a2a-agent.test.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/a2a-agent.test.js +364 -0
- package/dist/src/a2a/__tests__/a2a-agent.test.js.map +1 -0
- package/dist/src/a2a/__tests__/adapters.test.d.ts +2 -0
- package/dist/src/a2a/__tests__/adapters.test.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/adapters.test.js +151 -0
- package/dist/src/a2a/__tests__/adapters.test.js.map +1 -0
- package/dist/src/a2a/__tests__/executor.test.d.ts +2 -0
- package/dist/src/a2a/__tests__/executor.test.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/executor.test.js +196 -0
- package/dist/src/a2a/__tests__/executor.test.js.map +1 -0
- package/dist/src/a2a/__tests__/server.test.d.ts +2 -0
- package/dist/src/a2a/__tests__/server.test.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/server.test.js +51 -0
- package/dist/src/a2a/__tests__/server.test.js.map +1 -0
- package/dist/src/a2a/__tests__/server.test.node.d.ts +2 -0
- package/dist/src/a2a/__tests__/server.test.node.d.ts.map +1 -0
- package/dist/src/a2a/__tests__/server.test.node.js +110 -0
- package/dist/src/a2a/__tests__/server.test.node.js.map +1 -0
- package/dist/src/a2a/a2a-agent.d.ts +132 -0
- package/dist/src/a2a/a2a-agent.d.ts.map +1 -0
- package/dist/src/a2a/a2a-agent.js +255 -0
- package/dist/src/a2a/a2a-agent.js.map +1 -0
- package/dist/src/a2a/adapters.d.ts +27 -0
- package/dist/src/a2a/adapters.d.ts.map +1 -0
- package/dist/src/a2a/adapters.js +175 -0
- package/dist/src/a2a/adapters.js.map +1 -0
- package/dist/src/a2a/events.d.ts +42 -0
- package/dist/src/a2a/events.d.ts.map +1 -0
- package/dist/src/a2a/events.js +35 -0
- package/dist/src/a2a/events.js.map +1 -0
- package/dist/src/a2a/executor.d.ts +57 -0
- package/dist/src/a2a/executor.d.ts.map +1 -0
- package/dist/src/a2a/executor.js +130 -0
- package/dist/src/a2a/executor.js.map +1 -0
- package/dist/src/a2a/express-server.d.ts +67 -0
- package/dist/src/a2a/express-server.d.ts.map +1 -0
- package/dist/src/a2a/express-server.js +95 -0
- package/dist/src/a2a/express-server.js.map +1 -0
- package/dist/src/a2a/index.d.ts +16 -0
- package/dist/src/a2a/index.d.ts.map +1 -0
- package/dist/src/a2a/index.js +16 -0
- package/dist/src/a2a/index.js.map +1 -0
- package/dist/src/a2a/logging.d.ts +8 -0
- package/dist/src/a2a/logging.d.ts.map +1 -0
- package/dist/src/a2a/logging.js +15 -0
- package/dist/src/a2a/logging.js.map +1 -0
- package/dist/src/a2a/server.d.ts +67 -0
- package/dist/src/a2a/server.d.ts.map +1 -0
- package/dist/src/a2a/server.js +67 -0
- package/dist/src/a2a/server.js.map +1 -0
- package/dist/src/agent/__tests__/agent.hook.test.js +87 -51
- package/dist/src/agent/__tests__/agent.hook.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.test.js +87 -10
- package/dist/src/agent/__tests__/agent.test.js.map +1 -1
- package/dist/src/agent/__tests__/agent.tracer.test.js +10 -10
- package/dist/src/agent/__tests__/agent.tracer.test.js.map +1 -1
- package/dist/src/agent/__tests__/snapshot.test.js +11 -11
- package/dist/src/agent/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/agent/agent.d.ts +39 -43
- package/dist/src/agent/agent.d.ts.map +1 -1
- package/dist/src/agent/agent.js +66 -40
- package/dist/src/agent/agent.js.map +1 -1
- package/dist/src/agent/snapshot.d.ts.map +1 -1
- package/dist/src/agent/snapshot.js +3 -2
- package/dist/src/agent/snapshot.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.d.ts +2 -0
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.d.ts.map +1 -0
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js +100 -0
- package/dist/src/conversation-manager/__tests__/conversation-manager.test.js.map +1 -0
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js +26 -10
- package/dist/src/conversation-manager/__tests__/null-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js +84 -21
- package/dist/src/conversation-manager/__tests__/sliding-window-conversation-manager.test.js.map +1 -1
- package/dist/src/conversation-manager/conversation-manager.d.ts +87 -0
- package/dist/src/conversation-manager/conversation-manager.d.ts.map +1 -0
- package/dist/src/conversation-manager/conversation-manager.js +59 -0
- package/dist/src/conversation-manager/conversation-manager.js.map +1 -0
- package/dist/src/conversation-manager/index.d.ts +1 -0
- package/dist/src/conversation-manager/index.d.ts.map +1 -1
- package/dist/src/conversation-manager/index.js +1 -0
- package/dist/src/conversation-manager/index.js.map +1 -1
- package/dist/src/conversation-manager/null-conversation-manager.d.ts +12 -8
- package/dist/src/conversation-manager/null-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/null-conversation-manager.js +13 -7
- package/dist/src/conversation-manager/null-conversation-manager.js.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts +28 -19
- package/dist/src/conversation-manager/sliding-window-conversation-manager.d.ts.map +1 -1
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js +44 -36
- package/dist/src/conversation-manager/sliding-window-conversation-manager.js.map +1 -1
- package/dist/src/hooks/__tests__/registry.test.js +10 -154
- package/dist/src/hooks/__tests__/registry.test.js.map +1 -1
- package/dist/src/hooks/events.d.ts +44 -44
- package/dist/src/hooks/events.d.ts.map +1 -1
- package/dist/src/hooks/events.js +11 -11
- package/dist/src/hooks/events.js.map +1 -1
- package/dist/src/hooks/index.d.ts +3 -3
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +2 -2
- package/dist/src/hooks/registry.d.ts +1 -32
- package/dist/src/hooks/registry.d.ts.map +1 -1
- package/dist/src/hooks/registry.js +1 -47
- package/dist/src/hooks/registry.js.map +1 -1
- package/dist/src/hooks/types.d.ts +0 -31
- package/dist/src/hooks/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +12 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -8
- package/dist/src/index.js.map +1 -1
- package/dist/src/mime.d.ts +24 -0
- package/dist/src/mime.d.ts.map +1 -0
- package/dist/src/mime.js +82 -0
- package/dist/src/mime.js.map +1 -0
- package/dist/src/models/__tests__/anthropic.test.js +78 -1
- package/dist/src/models/__tests__/anthropic.test.js.map +1 -1
- package/dist/src/models/__tests__/bedrock.test.js +1844 -613
- package/dist/src/models/__tests__/bedrock.test.js.map +1 -1
- package/dist/src/models/__tests__/gemini.test.js +100 -1
- package/dist/src/models/__tests__/gemini.test.js.map +1 -1
- package/dist/src/models/__tests__/model.test.js +25 -0
- package/dist/src/models/__tests__/model.test.js.map +1 -1
- package/dist/src/models/__tests__/openai.test.js +184 -32
- package/dist/src/models/__tests__/openai.test.js.map +1 -1
- package/dist/src/models/anthropic.d.ts.map +1 -1
- package/dist/src/models/anthropic.js +6 -7
- package/dist/src/models/anthropic.js.map +1 -1
- package/dist/src/models/bedrock.d.ts +64 -10
- package/dist/src/models/bedrock.d.ts.map +1 -1
- package/dist/src/models/bedrock.js +202 -23
- package/dist/src/models/bedrock.js.map +1 -1
- package/dist/src/models/gemini/adapters.d.ts.map +1 -1
- package/dist/src/models/gemini/adapters.js +65 -14
- package/dist/src/models/gemini/adapters.js.map +1 -1
- package/dist/src/models/model.d.ts +12 -0
- package/dist/src/models/model.d.ts.map +1 -1
- package/dist/src/models/model.js +5 -9
- package/dist/src/models/model.js.map +1 -1
- package/dist/src/models/openai.d.ts +15 -0
- package/dist/src/models/openai.d.ts.map +1 -1
- package/dist/src/models/openai.js +108 -64
- package/dist/src/models/openai.js.map +1 -1
- package/dist/src/models/streaming.d.ts +5 -0
- package/dist/src/models/streaming.d.ts.map +1 -1
- package/dist/src/multiagent/__tests__/events.test.js +24 -7
- package/dist/src/multiagent/__tests__/events.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/graph.test.js +28 -13
- package/dist/src/multiagent/__tests__/graph.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/nodes.test.js +28 -10
- package/dist/src/multiagent/__tests__/nodes.test.js.map +1 -1
- package/dist/src/multiagent/__tests__/swarm.test.js +33 -10
- package/dist/src/multiagent/__tests__/swarm.test.js.map +1 -1
- package/dist/src/multiagent/events.d.ts +48 -14
- package/dist/src/multiagent/events.d.ts.map +1 -1
- package/dist/src/multiagent/events.js +11 -3
- package/dist/src/multiagent/events.js.map +1 -1
- package/dist/src/multiagent/graph.d.ts +21 -11
- package/dist/src/multiagent/graph.d.ts.map +1 -1
- package/dist/src/multiagent/graph.js +35 -29
- package/dist/src/multiagent/graph.js.map +1 -1
- package/dist/src/multiagent/index.d.ts +3 -2
- package/dist/src/multiagent/index.d.ts.map +1 -1
- package/dist/src/multiagent/index.js.map +1 -1
- package/dist/src/multiagent/multiagent.d.ts +41 -0
- package/dist/src/multiagent/multiagent.d.ts.map +1 -0
- package/dist/src/multiagent/multiagent.js +2 -0
- package/dist/src/multiagent/multiagent.js.map +1 -0
- package/dist/src/multiagent/nodes.d.ts +18 -21
- package/dist/src/multiagent/nodes.d.ts.map +1 -1
- package/dist/src/multiagent/nodes.js +27 -11
- package/dist/src/multiagent/nodes.js.map +1 -1
- package/dist/src/multiagent/plugins.d.ts +70 -0
- package/dist/src/multiagent/plugins.d.ts.map +1 -0
- package/dist/src/multiagent/plugins.js +70 -0
- package/dist/src/multiagent/plugins.js.map +1 -0
- package/dist/src/multiagent/state.d.ts +2 -2
- package/dist/src/multiagent/state.d.ts.map +1 -1
- package/dist/src/multiagent/state.js +2 -2
- package/dist/src/multiagent/state.js.map +1 -1
- package/dist/src/multiagent/swarm.d.ts +26 -16
- package/dist/src/multiagent/swarm.d.ts.map +1 -1
- package/dist/src/multiagent/swarm.js +31 -13
- package/dist/src/multiagent/swarm.js.map +1 -1
- package/dist/src/plugins/__tests__/plugin.test.d.ts +2 -0
- package/dist/src/plugins/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/src/plugins/__tests__/plugin.test.js +114 -0
- package/dist/src/plugins/__tests__/plugin.test.js.map +1 -0
- package/dist/src/plugins/__tests__/registry.test.d.ts +2 -0
- package/dist/src/plugins/__tests__/registry.test.d.ts.map +1 -0
- package/dist/src/plugins/__tests__/registry.test.js +147 -0
- package/dist/src/plugins/__tests__/registry.test.js.map +1 -0
- package/dist/src/plugins/index.d.ts +30 -0
- package/dist/src/plugins/index.d.ts.map +1 -0
- package/dist/src/plugins/index.js +30 -0
- package/dist/src/plugins/index.js.map +1 -0
- package/dist/src/plugins/plugin.d.ts +74 -0
- package/dist/src/plugins/plugin.d.ts.map +1 -0
- package/dist/src/plugins/plugin.js +8 -0
- package/dist/src/plugins/plugin.js.map +1 -0
- package/dist/src/plugins/registry.d.ts +25 -0
- package/dist/src/plugins/registry.d.ts.map +1 -0
- package/dist/src/plugins/registry.js +41 -0
- package/dist/src/plugins/registry.js.map +1 -0
- package/dist/src/session/__tests__/session-manager.test.js +74 -92
- package/dist/src/session/__tests__/session-manager.test.js.map +1 -1
- package/dist/src/session/index.d.ts +0 -1
- package/dist/src/session/index.d.ts.map +1 -1
- package/dist/src/session/index.js +0 -1
- package/dist/src/session/index.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +9 -5
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +14 -8
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/session/storage.d.ts +1 -1
- package/dist/src/session/storage.d.ts.map +1 -1
- package/dist/src/session/types.d.ts +2 -2
- package/dist/src/session/types.d.ts.map +1 -1
- package/dist/src/{app-state.d.ts → state-store.d.ts} +11 -11
- package/dist/src/state-store.d.ts.map +1 -0
- package/dist/src/{app-state.js → state-store.js} +8 -7
- package/dist/src/state-store.js.map +1 -0
- package/dist/src/telemetry/__tests__/config.test.js +24 -0
- package/dist/src/telemetry/__tests__/config.test.js.map +1 -1
- package/dist/src/telemetry/__tests__/config.test.node.js +56 -0
- package/dist/src/telemetry/__tests__/config.test.node.js.map +1 -1
- package/dist/src/telemetry/__tests__/meter.test.js +176 -9
- package/dist/src/telemetry/__tests__/meter.test.js.map +1 -1
- package/dist/src/telemetry/__tests__/tracer.test.node.js +123 -2
- package/dist/src/telemetry/__tests__/tracer.test.node.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +72 -12
- package/dist/src/telemetry/config.d.ts.map +1 -1
- package/dist/src/telemetry/config.js +101 -24
- package/dist/src/telemetry/config.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +10 -7
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +9 -6
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/meter.d.ts +23 -4
- package/dist/src/telemetry/meter.d.ts.map +1 -1
- package/dist/src/telemetry/meter.js +78 -5
- package/dist/src/telemetry/meter.js.map +1 -1
- package/dist/src/telemetry/tracer.d.ts +27 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +80 -5
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +2 -0
- package/dist/src/telemetry/types.d.ts.map +1 -1
- package/dist/src/telemetry/utils.d.ts +10 -0
- package/dist/src/telemetry/utils.d.ts.map +1 -0
- package/dist/src/telemetry/utils.js +13 -0
- package/dist/src/telemetry/utils.js.map +1 -0
- package/dist/src/tools/__tests__/tool.test.js +22 -1
- package/dist/src/tools/__tests__/tool.test.js.map +1 -1
- package/dist/src/tools/function-tool.d.ts +11 -1
- package/dist/src/tools/function-tool.d.ts.map +1 -1
- package/dist/src/tools/function-tool.js +64 -3
- package/dist/src/tools/function-tool.js.map +1 -1
- package/dist/src/tools/tool.d.ts +2 -2
- package/dist/src/tools/tool.d.ts.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/types/__tests__/media.test.js +22 -16
- package/dist/src/types/__tests__/media.test.js.map +1 -1
- package/dist/src/types/agent.d.ts +75 -7
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/agent.js.map +1 -1
- package/dist/src/types/media.d.ts +27 -30
- package/dist/src/types/media.d.ts.map +1 -1
- package/dist/src/types/media.js +15 -56
- package/dist/src/types/media.js.map +1 -1
- package/dist/src/types/messages.d.ts +18 -4
- package/dist/src/types/messages.d.ts.map +1 -1
- package/dist/src/types/messages.js +22 -26
- package/dist/src/types/messages.js.map +1 -1
- package/dist/src/types/serializable.d.ts +34 -4
- package/dist/src/types/serializable.d.ts.map +1 -1
- package/dist/src/types/serializable.js +31 -2
- package/dist/src/types/serializable.js.map +1 -1
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js +5 -4
- package/dist/src/vended-tools/bash/__tests__/bash.test.node.js.map +1 -1
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.d.ts.map +1 -0
- package/dist/src/vended-tools/{file_editor → file-editor}/__tests__/file-editor.test.node.js +11 -4
- package/dist/src/vended-tools/file-editor/__tests__/file-editor.test.node.js.map +1 -0
- package/dist/src/vended-tools/{file_editor → file-editor}/file-editor.d.ts +1 -1
- package/dist/src/vended-tools/{file_editor → file-editor}/file-editor.d.ts.map +1 -1
- package/dist/src/vended-tools/{file_editor → file-editor}/file-editor.js +2 -2
- package/dist/src/vended-tools/{file_editor → file-editor}/file-editor.js.map +1 -1
- package/dist/src/vended-tools/{file_editor → file-editor}/index.d.ts.map +1 -1
- package/dist/src/vended-tools/file-editor/index.js.map +1 -0
- package/dist/src/vended-tools/{file_editor → file-editor}/types.d.ts.map +1 -1
- package/dist/src/vended-tools/file-editor/types.js.map +1 -0
- package/dist/src/vended-tools/http-request/__tests__/http-request.test.d.ts.map +1 -0
- package/dist/src/vended-tools/{http_request → http-request}/__tests__/http-request.test.js.map +1 -1
- package/dist/src/vended-tools/{http_request → http-request}/http-request.d.ts.map +1 -1
- package/dist/src/vended-tools/{http_request → http-request}/http-request.js.map +1 -1
- package/dist/src/vended-tools/{http_request → http-request}/index.d.ts.map +1 -1
- package/dist/src/vended-tools/http-request/index.js.map +1 -0
- package/dist/src/vended-tools/{http_request → http-request}/types.d.ts.map +1 -1
- package/dist/src/vended-tools/http-request/types.js.map +1 -0
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js +5 -4
- package/dist/src/vended-tools/notebook/__tests__/notebook.test.js.map +1 -1
- package/dist/src/vended-tools/notebook/notebook.js +2 -2
- package/dist/src/vended-tools/notebook/notebook.js.map +1 -1
- package/package.json +52 -10
- package/dist/src/__fixtures__/mock-hook-provider.d.ts +0 -10
- package/dist/src/__fixtures__/mock-hook-provider.d.ts.map +0 -1
- package/dist/src/__fixtures__/mock-hook-provider.js.map +0 -1
- package/dist/src/__tests__/app-state.test.d.ts +0 -2
- package/dist/src/__tests__/app-state.test.d.ts.map +0 -1
- package/dist/src/__tests__/app-state.test.js.map +0 -1
- package/dist/src/app-state.d.ts.map +0 -1
- package/dist/src/app-state.js.map +0 -1
- package/dist/src/multiagent/base.d.ts +0 -25
- package/dist/src/multiagent/base.d.ts.map +0 -1
- package/dist/src/multiagent/base.js +0 -2
- package/dist/src/multiagent/base.js.map +0 -1
- 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.map +0 -1
- package/dist/src/vended-tools/file_editor/index.js.map +0 -1
- package/dist/src/vended-tools/file_editor/types.js.map +0 -1
- package/dist/src/vended-tools/http_request/__tests__/http-request.test.d.ts.map +0 -1
- package/dist/src/vended-tools/http_request/index.js.map +0 -1
- package/dist/src/vended-tools/http_request/types.js.map +0 -1
- /package/dist/src/vended-tools/{file_editor → file-editor}/__tests__/file-editor.test.node.d.ts +0 -0
- /package/dist/src/vended-tools/{file_editor → file-editor}/index.d.ts +0 -0
- /package/dist/src/vended-tools/{file_editor → file-editor}/index.js +0 -0
- /package/dist/src/vended-tools/{file_editor → file-editor}/types.d.ts +0 -0
- /package/dist/src/vended-tools/{file_editor → file-editor}/types.js +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/__tests__/http-request.test.d.ts +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/__tests__/http-request.test.js +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/http-request.d.ts +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/http-request.js +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/index.d.ts +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/index.js +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/types.d.ts +0 -0
- /package/dist/src/vended-tools/{http_request → http-request}/types.js +0 -0
|
@@ -5,6 +5,7 @@ import { BedrockModel } from '../bedrock.js';
|
|
|
5
5
|
import { ContextWindowOverflowError, ModelThrottledError } from '../../errors.js';
|
|
6
6
|
import { Message, ReasoningBlock, ToolUseBlock, ToolResultBlock, JsonBlock } from '../../types/messages.js';
|
|
7
7
|
import { TextBlock, GuardContentBlock, CachePointBlock } from '../../types/messages.js';
|
|
8
|
+
import { ImageBlock, VideoBlock, DocumentBlock } from '../../types/media.js';
|
|
8
9
|
import { CitationsBlock } from '../../types/citations.js';
|
|
9
10
|
import { collectIterator } from '../../__fixtures__/model-test-helpers.js';
|
|
10
11
|
/**
|
|
@@ -265,13 +266,12 @@ describe('BedrockModel', () => {
|
|
|
265
266
|
it('formats the request to bedrock properly', async () => {
|
|
266
267
|
const provider = new BedrockModel({
|
|
267
268
|
region: 'us-west-2',
|
|
268
|
-
modelId: 'test-model',
|
|
269
|
+
modelId: 'anthropic.claude-test-model',
|
|
269
270
|
maxTokens: 1024,
|
|
270
271
|
temperature: 0.7,
|
|
271
272
|
topP: 0.9,
|
|
272
273
|
stopSequences: ['STOP'],
|
|
273
|
-
|
|
274
|
-
cacheTools: 'default',
|
|
274
|
+
cacheConfig: { strategy: 'auto' },
|
|
275
275
|
additionalResponseFieldPaths: ['Hello!'],
|
|
276
276
|
additionalRequestFields: ['World!'],
|
|
277
277
|
additionalArgs: {
|
|
@@ -297,14 +297,14 @@ describe('BedrockModel', () => {
|
|
|
297
297
|
MyExtraArg: 'ExtraArg',
|
|
298
298
|
additionalModelRequestFields: ['World!'],
|
|
299
299
|
additionalModelResponseFieldPaths: ['Hello!'],
|
|
300
|
-
modelId: 'test-model',
|
|
300
|
+
modelId: 'anthropic.claude-test-model',
|
|
301
301
|
messages: [
|
|
302
302
|
{
|
|
303
303
|
role: 'user',
|
|
304
|
-
content: [{ text: 'Hello' }],
|
|
304
|
+
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
305
305
|
},
|
|
306
306
|
],
|
|
307
|
-
system: [{ text: 'You are a helpful assistant' }
|
|
307
|
+
system: [{ text: 'You are a helpful assistant' }],
|
|
308
308
|
toolConfig: {
|
|
309
309
|
toolChoice: { auto: {} },
|
|
310
310
|
tools: [
|
|
@@ -1044,8 +1044,8 @@ describe('BedrockModel', () => {
|
|
|
1044
1044
|
beforeEach(() => {
|
|
1045
1045
|
vi.clearAllMocks();
|
|
1046
1046
|
});
|
|
1047
|
-
it('
|
|
1048
|
-
const provider = new BedrockModel({
|
|
1047
|
+
it('does not add cache points to string system prompt with cacheConfig', async () => {
|
|
1048
|
+
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1049
1049
|
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1050
1050
|
const options = {
|
|
1051
1051
|
systemPrompt: 'You are a helpful assistant',
|
|
@@ -1056,10 +1056,10 @@ describe('BedrockModel', () => {
|
|
|
1056
1056
|
messages: [
|
|
1057
1057
|
{
|
|
1058
1058
|
role: 'user',
|
|
1059
|
-
content: [{ text: 'Hello' }],
|
|
1059
|
+
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
1060
1060
|
},
|
|
1061
1061
|
],
|
|
1062
|
-
system: [{ text: 'You are a helpful assistant' }
|
|
1062
|
+
system: [{ text: 'You are a helpful assistant' }],
|
|
1063
1063
|
});
|
|
1064
1064
|
});
|
|
1065
1065
|
it('formats array system prompt with text blocks only', async () => {
|
|
@@ -1109,9 +1109,9 @@ describe('BedrockModel', () => {
|
|
|
1109
1109
|
],
|
|
1110
1110
|
});
|
|
1111
1111
|
});
|
|
1112
|
-
it('
|
|
1112
|
+
it('does not warn when array system prompt is provided without cacheConfig', async () => {
|
|
1113
1113
|
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
1114
|
-
const provider = new BedrockModel(
|
|
1114
|
+
const provider = new BedrockModel();
|
|
1115
1115
|
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1116
1116
|
const options = {
|
|
1117
1117
|
systemPrompt: [
|
|
@@ -1120,9 +1120,9 @@ describe('BedrockModel', () => {
|
|
|
1120
1120
|
],
|
|
1121
1121
|
};
|
|
1122
1122
|
collectIterator(provider.stream(messages, options));
|
|
1123
|
-
// Verify warning was logged
|
|
1124
|
-
expect(warnSpy).
|
|
1125
|
-
// Verify array is used as-is
|
|
1123
|
+
// Verify no warning was logged
|
|
1124
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
1125
|
+
// Verify array is used as-is
|
|
1126
1126
|
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1127
1127
|
modelId: 'global.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
|
1128
1128
|
messages: [
|
|
@@ -1135,6 +1135,131 @@ describe('BedrockModel', () => {
|
|
|
1135
1135
|
});
|
|
1136
1136
|
warnSpy.mockRestore();
|
|
1137
1137
|
});
|
|
1138
|
+
it('adds cache point after tools when cacheConfig enabled', async () => {
|
|
1139
|
+
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1140
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1141
|
+
const options = {
|
|
1142
|
+
toolSpecs: [
|
|
1143
|
+
{
|
|
1144
|
+
name: 'calculator',
|
|
1145
|
+
description: 'Calculate',
|
|
1146
|
+
inputSchema: { type: 'object' },
|
|
1147
|
+
},
|
|
1148
|
+
],
|
|
1149
|
+
};
|
|
1150
|
+
collectIterator(provider.stream(messages, options));
|
|
1151
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1152
|
+
modelId: 'global.anthropic.claude-sonnet-4-5-20250929-v1:0',
|
|
1153
|
+
messages: [
|
|
1154
|
+
{
|
|
1155
|
+
role: 'user',
|
|
1156
|
+
content: [{ text: 'Hello' }, { cachePoint: { type: 'default' } }],
|
|
1157
|
+
},
|
|
1158
|
+
],
|
|
1159
|
+
toolConfig: {
|
|
1160
|
+
tools: [
|
|
1161
|
+
{
|
|
1162
|
+
toolSpec: {
|
|
1163
|
+
name: 'calculator',
|
|
1164
|
+
description: 'Calculate',
|
|
1165
|
+
inputSchema: { json: { type: 'object' } },
|
|
1166
|
+
},
|
|
1167
|
+
},
|
|
1168
|
+
{ cachePoint: { type: 'default' } },
|
|
1169
|
+
],
|
|
1170
|
+
},
|
|
1171
|
+
});
|
|
1172
|
+
});
|
|
1173
|
+
it('adds cache points to tools and messages when cacheConfig enabled', async () => {
|
|
1174
|
+
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1175
|
+
const messages = [
|
|
1176
|
+
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1177
|
+
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1178
|
+
];
|
|
1179
|
+
const options = {
|
|
1180
|
+
systemPrompt: 'You are a helpful assistant',
|
|
1181
|
+
toolSpecs: [
|
|
1182
|
+
{
|
|
1183
|
+
name: 'calculator',
|
|
1184
|
+
description: 'Calculate',
|
|
1185
|
+
inputSchema: { type: 'object' },
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
};
|
|
1189
|
+
collectIterator(provider.stream(messages, options));
|
|
1190
|
+
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1191
|
+
expect(call?.system).toStrictEqual([{ text: 'You are a helpful assistant' }]);
|
|
1192
|
+
expect(call?.toolConfig?.tools).toStrictEqual([
|
|
1193
|
+
{
|
|
1194
|
+
toolSpec: {
|
|
1195
|
+
name: 'calculator',
|
|
1196
|
+
description: 'Calculate',
|
|
1197
|
+
inputSchema: { json: { type: 'object' } },
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
{ cachePoint: { type: 'default' } },
|
|
1201
|
+
]);
|
|
1202
|
+
const userMsg = call?.messages?.[0];
|
|
1203
|
+
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1204
|
+
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1205
|
+
const assistantMsg = call?.messages?.[1];
|
|
1206
|
+
const assistantLastBlock = assistantMsg?.content?.[assistantMsg.content.length - 1];
|
|
1207
|
+
expect(assistantLastBlock).not.toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1208
|
+
});
|
|
1209
|
+
it('does not mutate the original messages array', async () => {
|
|
1210
|
+
const provider = new BedrockModel({ cacheConfig: { strategy: 'auto' } });
|
|
1211
|
+
const originalMessages = [
|
|
1212
|
+
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1213
|
+
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1214
|
+
];
|
|
1215
|
+
// Create a deep copy to compare against
|
|
1216
|
+
const messagesCopy = JSON.parse(JSON.stringify(originalMessages));
|
|
1217
|
+
collectIterator(provider.stream(originalMessages));
|
|
1218
|
+
// Verify original messages are unchanged
|
|
1219
|
+
expect(JSON.stringify(originalMessages)).toBe(JSON.stringify(messagesCopy));
|
|
1220
|
+
});
|
|
1221
|
+
it('logs warning and disables caching for non-caching models', async () => {
|
|
1222
|
+
const warnSpy = vi.spyOn(console, 'warn');
|
|
1223
|
+
const provider = new BedrockModel({
|
|
1224
|
+
modelId: 'amazon.titan-text-express-v1',
|
|
1225
|
+
cacheConfig: { strategy: 'auto' },
|
|
1226
|
+
});
|
|
1227
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1228
|
+
const options = {
|
|
1229
|
+
systemPrompt: 'You are a helpful assistant',
|
|
1230
|
+
};
|
|
1231
|
+
collectIterator(provider.stream(messages, options));
|
|
1232
|
+
// Verify warning was logged
|
|
1233
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
1234
|
+
// Verify no cache points were added
|
|
1235
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1236
|
+
modelId: 'amazon.titan-text-express-v1',
|
|
1237
|
+
messages: [
|
|
1238
|
+
{
|
|
1239
|
+
role: 'user',
|
|
1240
|
+
content: [{ text: 'Hello' }],
|
|
1241
|
+
},
|
|
1242
|
+
],
|
|
1243
|
+
system: [{ text: 'You are a helpful assistant' }],
|
|
1244
|
+
});
|
|
1245
|
+
warnSpy.mockRestore();
|
|
1246
|
+
});
|
|
1247
|
+
it('enables caching with anthropic strategy for application inference profiles', async () => {
|
|
1248
|
+
const provider = new BedrockModel({
|
|
1249
|
+
modelId: 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123',
|
|
1250
|
+
cacheConfig: { strategy: 'anthropic' },
|
|
1251
|
+
});
|
|
1252
|
+
const messages = [
|
|
1253
|
+
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
1254
|
+
new Message({ role: 'assistant', content: [new TextBlock('Hi')] }),
|
|
1255
|
+
];
|
|
1256
|
+
collectIterator(provider.stream(messages));
|
|
1257
|
+
const call = mockConverseStreamCommand.mock.lastCall?.[0];
|
|
1258
|
+
// Cache point should be on the user message (index 0)
|
|
1259
|
+
const userMsg = call?.messages?.[0];
|
|
1260
|
+
const lastBlock = userMsg?.content?.[userMsg.content.length - 1];
|
|
1261
|
+
expect(lastBlock).toStrictEqual({ cachePoint: { type: 'default' } });
|
|
1262
|
+
});
|
|
1138
1263
|
it('handles empty array system prompt', async () => {
|
|
1139
1264
|
const provider = new BedrockModel();
|
|
1140
1265
|
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
@@ -1380,123 +1505,687 @@ describe('BedrockModel', () => {
|
|
|
1380
1505
|
});
|
|
1381
1506
|
});
|
|
1382
1507
|
});
|
|
1383
|
-
describe('
|
|
1508
|
+
describe('media blocks in tool results', () => {
|
|
1384
1509
|
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1385
|
-
it('
|
|
1510
|
+
it('formats image block in tool result', async () => {
|
|
1386
1511
|
const provider = new BedrockModel();
|
|
1387
|
-
|
|
1388
|
-
const
|
|
1389
|
-
{
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
title: 'PDF Document',
|
|
1400
|
-
},
|
|
1401
|
-
{
|
|
1402
|
-
location: { type: 'documentChunk', documentIndex: 1, start: 5, end: 8 },
|
|
1403
|
-
source: 'doc-1',
|
|
1404
|
-
sourceContent: [{ text: 'chunk source' }],
|
|
1405
|
-
title: 'Chunked Document',
|
|
1406
|
-
},
|
|
1407
|
-
{
|
|
1408
|
-
location: { type: 'searchResult', searchResultIndex: 0, start: 25, end: 150 },
|
|
1409
|
-
source: 'search-0',
|
|
1410
|
-
sourceContent: [{ text: 'search source' }],
|
|
1411
|
-
title: 'Search Result',
|
|
1412
|
-
},
|
|
1413
|
-
{
|
|
1414
|
-
location: { type: 'web', url: 'https://example.com/doc', domain: 'example.com' },
|
|
1415
|
-
source: 'web-0',
|
|
1416
|
-
sourceContent: [{ text: 'web source' }],
|
|
1417
|
-
title: 'Web Page',
|
|
1418
|
-
},
|
|
1512
|
+
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1513
|
+
const messages = [
|
|
1514
|
+
new Message({
|
|
1515
|
+
role: 'user',
|
|
1516
|
+
content: [
|
|
1517
|
+
new ToolResultBlock({
|
|
1518
|
+
toolUseId: 'tool-1',
|
|
1519
|
+
status: 'success',
|
|
1520
|
+
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1521
|
+
}),
|
|
1522
|
+
],
|
|
1523
|
+
}),
|
|
1419
1524
|
];
|
|
1525
|
+
collectIterator(provider.stream(messages));
|
|
1526
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1527
|
+
messages: [
|
|
1528
|
+
{
|
|
1529
|
+
role: 'user',
|
|
1530
|
+
content: [
|
|
1531
|
+
{
|
|
1532
|
+
toolResult: {
|
|
1533
|
+
toolUseId: 'tool-1',
|
|
1534
|
+
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
1535
|
+
status: 'success',
|
|
1536
|
+
},
|
|
1537
|
+
},
|
|
1538
|
+
],
|
|
1539
|
+
},
|
|
1540
|
+
],
|
|
1541
|
+
}));
|
|
1542
|
+
});
|
|
1543
|
+
it('formats video block in tool result with 3gp format mapping', async () => {
|
|
1544
|
+
const provider = new BedrockModel();
|
|
1545
|
+
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
1420
1546
|
const messages = [
|
|
1421
1547
|
new Message({
|
|
1422
|
-
role: '
|
|
1548
|
+
role: 'user',
|
|
1423
1549
|
content: [
|
|
1424
|
-
new
|
|
1425
|
-
|
|
1426
|
-
|
|
1550
|
+
new ToolResultBlock({
|
|
1551
|
+
toolUseId: 'tool-1',
|
|
1552
|
+
status: 'success',
|
|
1553
|
+
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
1427
1554
|
}),
|
|
1428
1555
|
],
|
|
1429
1556
|
}),
|
|
1557
|
+
];
|
|
1558
|
+
collectIterator(provider.stream(messages));
|
|
1559
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1560
|
+
messages: [
|
|
1561
|
+
{
|
|
1562
|
+
role: 'user',
|
|
1563
|
+
content: [
|
|
1564
|
+
{
|
|
1565
|
+
toolResult: {
|
|
1566
|
+
toolUseId: 'tool-1',
|
|
1567
|
+
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
1568
|
+
status: 'success',
|
|
1569
|
+
},
|
|
1570
|
+
},
|
|
1571
|
+
],
|
|
1572
|
+
},
|
|
1573
|
+
],
|
|
1574
|
+
}));
|
|
1575
|
+
});
|
|
1576
|
+
it('formats document block in tool result', async () => {
|
|
1577
|
+
const provider = new BedrockModel();
|
|
1578
|
+
const docBytes = new Uint8Array([7, 8, 9]);
|
|
1579
|
+
const messages = [
|
|
1430
1580
|
new Message({
|
|
1431
1581
|
role: 'user',
|
|
1432
|
-
content: [
|
|
1582
|
+
content: [
|
|
1583
|
+
new ToolResultBlock({
|
|
1584
|
+
toolUseId: 'tool-1',
|
|
1585
|
+
status: 'success',
|
|
1586
|
+
content: [new DocumentBlock({ name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } })],
|
|
1587
|
+
}),
|
|
1588
|
+
],
|
|
1433
1589
|
}),
|
|
1434
1590
|
];
|
|
1435
1591
|
collectIterator(provider.stream(messages));
|
|
1436
|
-
// Bedrock wire format uses object-key discrimination
|
|
1437
1592
|
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1438
1593
|
messages: [
|
|
1439
1594
|
{
|
|
1440
|
-
role: '
|
|
1595
|
+
role: 'user',
|
|
1441
1596
|
content: [
|
|
1442
1597
|
{
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
source: 'doc-0',
|
|
1448
|
-
sourceContent: [{ text: 'char source' }],
|
|
1449
|
-
title: 'Text Document',
|
|
1450
|
-
},
|
|
1451
|
-
{
|
|
1452
|
-
location: { documentPage: { documentIndex: 0, start: 2, end: 3 } },
|
|
1453
|
-
source: 'doc-0',
|
|
1454
|
-
sourceContent: [{ text: 'page source' }],
|
|
1455
|
-
title: 'PDF Document',
|
|
1456
|
-
},
|
|
1457
|
-
{
|
|
1458
|
-
location: { documentChunk: { documentIndex: 1, start: 5, end: 8 } },
|
|
1459
|
-
source: 'doc-1',
|
|
1460
|
-
sourceContent: [{ text: 'chunk source' }],
|
|
1461
|
-
title: 'Chunked Document',
|
|
1462
|
-
},
|
|
1463
|
-
{
|
|
1464
|
-
location: {
|
|
1465
|
-
searchResultLocation: { searchResultIndex: 0, start: 25, end: 150 },
|
|
1466
|
-
},
|
|
1467
|
-
source: 'search-0',
|
|
1468
|
-
sourceContent: [{ text: 'search source' }],
|
|
1469
|
-
title: 'Search Result',
|
|
1470
|
-
},
|
|
1471
|
-
{
|
|
1472
|
-
location: { web: { url: 'https://example.com/doc', domain: 'example.com' } },
|
|
1473
|
-
source: 'web-0',
|
|
1474
|
-
sourceContent: [{ text: 'web source' }],
|
|
1475
|
-
title: 'Web Page',
|
|
1476
|
-
},
|
|
1477
|
-
],
|
|
1478
|
-
content: [{ text: 'generated text with all citation types' }],
|
|
1598
|
+
toolResult: {
|
|
1599
|
+
toolUseId: 'tool-1',
|
|
1600
|
+
content: [{ document: { name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } } }],
|
|
1601
|
+
status: 'success',
|
|
1479
1602
|
},
|
|
1480
1603
|
},
|
|
1481
1604
|
],
|
|
1482
1605
|
},
|
|
1606
|
+
],
|
|
1607
|
+
}));
|
|
1608
|
+
});
|
|
1609
|
+
it('formats mixed text and media content in tool result', async () => {
|
|
1610
|
+
const provider = new BedrockModel();
|
|
1611
|
+
const imageBytes = new Uint8Array([1, 2]);
|
|
1612
|
+
const messages = [
|
|
1613
|
+
new Message({
|
|
1614
|
+
role: 'user',
|
|
1615
|
+
content: [
|
|
1616
|
+
new ToolResultBlock({
|
|
1617
|
+
toolUseId: 'tool-1',
|
|
1618
|
+
status: 'success',
|
|
1619
|
+
content: [
|
|
1620
|
+
new TextBlock('Here is the image:'),
|
|
1621
|
+
new ImageBlock({ format: 'jpeg', source: { bytes: imageBytes } }),
|
|
1622
|
+
],
|
|
1623
|
+
}),
|
|
1624
|
+
],
|
|
1625
|
+
}),
|
|
1626
|
+
];
|
|
1627
|
+
collectIterator(provider.stream(messages));
|
|
1628
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1629
|
+
messages: [
|
|
1483
1630
|
{
|
|
1484
1631
|
role: 'user',
|
|
1485
|
-
content: [
|
|
1632
|
+
content: [
|
|
1633
|
+
{
|
|
1634
|
+
toolResult: {
|
|
1635
|
+
toolUseId: 'tool-1',
|
|
1636
|
+
content: [
|
|
1637
|
+
{ text: 'Here is the image:' },
|
|
1638
|
+
{ image: { format: 'jpeg', source: { bytes: imageBytes } } },
|
|
1639
|
+
],
|
|
1640
|
+
status: 'success',
|
|
1641
|
+
},
|
|
1642
|
+
},
|
|
1643
|
+
],
|
|
1486
1644
|
},
|
|
1487
1645
|
],
|
|
1488
1646
|
}));
|
|
1489
1647
|
});
|
|
1490
1648
|
});
|
|
1491
|
-
describe('
|
|
1649
|
+
describe('media blocks in messages', () => {
|
|
1492
1650
|
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1651
|
+
it('formats top-level image block', async () => {
|
|
1652
|
+
const provider = new BedrockModel();
|
|
1653
|
+
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1654
|
+
const messages = [
|
|
1655
|
+
new Message({
|
|
1656
|
+
role: 'user',
|
|
1657
|
+
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1658
|
+
}),
|
|
1659
|
+
];
|
|
1660
|
+
collectIterator(provider.stream(messages));
|
|
1661
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1662
|
+
messages: [
|
|
1663
|
+
{
|
|
1498
1664
|
role: 'user',
|
|
1499
|
-
content: [
|
|
1665
|
+
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
1666
|
+
},
|
|
1667
|
+
],
|
|
1668
|
+
}));
|
|
1669
|
+
});
|
|
1670
|
+
it('formats top-level image block with S3 source', async () => {
|
|
1671
|
+
const provider = new BedrockModel();
|
|
1672
|
+
const messages = [
|
|
1673
|
+
new Message({
|
|
1674
|
+
role: 'user',
|
|
1675
|
+
content: [
|
|
1676
|
+
new ImageBlock({ format: 'png', source: { location: { type: 's3', uri: 's3://bucket/image.png' } } }),
|
|
1677
|
+
],
|
|
1678
|
+
}),
|
|
1679
|
+
];
|
|
1680
|
+
collectIterator(provider.stream(messages));
|
|
1681
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1682
|
+
messages: [
|
|
1683
|
+
{
|
|
1684
|
+
role: 'user',
|
|
1685
|
+
content: [{ image: { format: 'png', source: { s3Location: { uri: 's3://bucket/image.png' } } } }],
|
|
1686
|
+
},
|
|
1687
|
+
],
|
|
1688
|
+
}));
|
|
1689
|
+
});
|
|
1690
|
+
it('formats top-level video block with 3gp format mapping', async () => {
|
|
1691
|
+
const provider = new BedrockModel();
|
|
1692
|
+
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
1693
|
+
const messages = [
|
|
1694
|
+
new Message({
|
|
1695
|
+
role: 'user',
|
|
1696
|
+
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
1697
|
+
}),
|
|
1698
|
+
];
|
|
1699
|
+
collectIterator(provider.stream(messages));
|
|
1700
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1701
|
+
messages: [
|
|
1702
|
+
{
|
|
1703
|
+
role: 'user',
|
|
1704
|
+
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
1705
|
+
},
|
|
1706
|
+
],
|
|
1707
|
+
}));
|
|
1708
|
+
});
|
|
1709
|
+
it('formats top-level document block with text source converted to bytes', async () => {
|
|
1710
|
+
const provider = new BedrockModel();
|
|
1711
|
+
const messages = [
|
|
1712
|
+
new Message({
|
|
1713
|
+
role: 'user',
|
|
1714
|
+
content: [new DocumentBlock({ name: 'notes.txt', format: 'txt', source: { text: 'Hello world' } })],
|
|
1715
|
+
}),
|
|
1716
|
+
];
|
|
1717
|
+
collectIterator(provider.stream(messages));
|
|
1718
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1719
|
+
messages: [
|
|
1720
|
+
{
|
|
1721
|
+
role: 'user',
|
|
1722
|
+
content: [
|
|
1723
|
+
{
|
|
1724
|
+
document: {
|
|
1725
|
+
name: 'notes.txt',
|
|
1726
|
+
format: 'txt',
|
|
1727
|
+
source: { bytes: new TextEncoder().encode('Hello world') },
|
|
1728
|
+
},
|
|
1729
|
+
},
|
|
1730
|
+
],
|
|
1731
|
+
},
|
|
1732
|
+
],
|
|
1733
|
+
}));
|
|
1734
|
+
});
|
|
1735
|
+
});
|
|
1736
|
+
describe('citations content block formatting', () => {
|
|
1737
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1738
|
+
it('maps SDK CitationLocation types to Bedrock object-key format through formatting pipeline', async () => {
|
|
1739
|
+
const provider = new BedrockModel();
|
|
1740
|
+
const sdkCitations = [
|
|
1741
|
+
{
|
|
1742
|
+
location: { type: 'documentChar', documentIndex: 0, start: 150, end: 300 },
|
|
1743
|
+
source: 'doc-0',
|
|
1744
|
+
sourceContent: [{ text: 'char source' }],
|
|
1745
|
+
title: 'Text Document',
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
location: { type: 'documentPage', documentIndex: 0, start: 2, end: 3 },
|
|
1749
|
+
source: 'doc-0',
|
|
1750
|
+
sourceContent: [{ text: 'page source' }],
|
|
1751
|
+
title: 'PDF Document',
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
location: { type: 'documentChunk', documentIndex: 1, start: 5, end: 8 },
|
|
1755
|
+
source: 'doc-1',
|
|
1756
|
+
sourceContent: [{ text: 'chunk source' }],
|
|
1757
|
+
title: 'Chunked Document',
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
location: { type: 'searchResult', searchResultIndex: 0, start: 25, end: 150 },
|
|
1761
|
+
source: 'search-0',
|
|
1762
|
+
sourceContent: [{ text: 'search source' }],
|
|
1763
|
+
title: 'Search Result',
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
location: { type: 'web', url: 'https://example.com/doc', domain: 'example.com' },
|
|
1767
|
+
source: 'web-0',
|
|
1768
|
+
sourceContent: [{ text: 'web source' }],
|
|
1769
|
+
title: 'Web Page',
|
|
1770
|
+
},
|
|
1771
|
+
];
|
|
1772
|
+
const messages = [
|
|
1773
|
+
new Message({
|
|
1774
|
+
role: 'assistant',
|
|
1775
|
+
content: [
|
|
1776
|
+
new CitationsBlock({
|
|
1777
|
+
citations: sdkCitations,
|
|
1778
|
+
content: [{ text: 'generated text with all citation types' }],
|
|
1779
|
+
}),
|
|
1780
|
+
],
|
|
1781
|
+
}),
|
|
1782
|
+
new Message({
|
|
1783
|
+
role: 'user',
|
|
1784
|
+
content: [new TextBlock('Follow up')],
|
|
1785
|
+
}),
|
|
1786
|
+
];
|
|
1787
|
+
collectIterator(provider.stream(messages));
|
|
1788
|
+
// Bedrock wire format uses object-key discrimination
|
|
1789
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1790
|
+
messages: [
|
|
1791
|
+
{
|
|
1792
|
+
role: 'assistant',
|
|
1793
|
+
content: [
|
|
1794
|
+
{
|
|
1795
|
+
citationsContent: {
|
|
1796
|
+
citations: [
|
|
1797
|
+
{
|
|
1798
|
+
location: { documentChar: { documentIndex: 0, start: 150, end: 300 } },
|
|
1799
|
+
source: 'doc-0',
|
|
1800
|
+
sourceContent: [{ text: 'char source' }],
|
|
1801
|
+
title: 'Text Document',
|
|
1802
|
+
},
|
|
1803
|
+
{
|
|
1804
|
+
location: { documentPage: { documentIndex: 0, start: 2, end: 3 } },
|
|
1805
|
+
source: 'doc-0',
|
|
1806
|
+
sourceContent: [{ text: 'page source' }],
|
|
1807
|
+
title: 'PDF Document',
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
location: { documentChunk: { documentIndex: 1, start: 5, end: 8 } },
|
|
1811
|
+
source: 'doc-1',
|
|
1812
|
+
sourceContent: [{ text: 'chunk source' }],
|
|
1813
|
+
title: 'Chunked Document',
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
location: {
|
|
1817
|
+
searchResultLocation: { searchResultIndex: 0, start: 25, end: 150 },
|
|
1818
|
+
},
|
|
1819
|
+
source: 'search-0',
|
|
1820
|
+
sourceContent: [{ text: 'search source' }],
|
|
1821
|
+
title: 'Search Result',
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
location: { web: { url: 'https://example.com/doc', domain: 'example.com' } },
|
|
1825
|
+
source: 'web-0',
|
|
1826
|
+
sourceContent: [{ text: 'web source' }],
|
|
1827
|
+
title: 'Web Page',
|
|
1828
|
+
},
|
|
1829
|
+
],
|
|
1830
|
+
content: [{ text: 'generated text with all citation types' }],
|
|
1831
|
+
},
|
|
1832
|
+
},
|
|
1833
|
+
],
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
role: 'user',
|
|
1837
|
+
content: [{ text: 'Follow up' }],
|
|
1838
|
+
},
|
|
1839
|
+
],
|
|
1840
|
+
}));
|
|
1841
|
+
});
|
|
1842
|
+
});
|
|
1843
|
+
describe('media blocks in tool results', () => {
|
|
1844
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1845
|
+
it('formats image block in tool result', async () => {
|
|
1846
|
+
const provider = new BedrockModel();
|
|
1847
|
+
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1848
|
+
const messages = [
|
|
1849
|
+
new Message({
|
|
1850
|
+
role: 'user',
|
|
1851
|
+
content: [
|
|
1852
|
+
new ToolResultBlock({
|
|
1853
|
+
toolUseId: 'tool-1',
|
|
1854
|
+
status: 'success',
|
|
1855
|
+
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1856
|
+
}),
|
|
1857
|
+
],
|
|
1858
|
+
}),
|
|
1859
|
+
];
|
|
1860
|
+
collectIterator(provider.stream(messages));
|
|
1861
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1862
|
+
messages: [
|
|
1863
|
+
{
|
|
1864
|
+
role: 'user',
|
|
1865
|
+
content: [
|
|
1866
|
+
{
|
|
1867
|
+
toolResult: {
|
|
1868
|
+
toolUseId: 'tool-1',
|
|
1869
|
+
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
1870
|
+
status: 'success',
|
|
1871
|
+
},
|
|
1872
|
+
},
|
|
1873
|
+
],
|
|
1874
|
+
},
|
|
1875
|
+
],
|
|
1876
|
+
}));
|
|
1877
|
+
});
|
|
1878
|
+
it('formats video block in tool result with 3gp format mapping', async () => {
|
|
1879
|
+
const provider = new BedrockModel();
|
|
1880
|
+
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
1881
|
+
const messages = [
|
|
1882
|
+
new Message({
|
|
1883
|
+
role: 'user',
|
|
1884
|
+
content: [
|
|
1885
|
+
new ToolResultBlock({
|
|
1886
|
+
toolUseId: 'tool-1',
|
|
1887
|
+
status: 'success',
|
|
1888
|
+
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
1889
|
+
}),
|
|
1890
|
+
],
|
|
1891
|
+
}),
|
|
1892
|
+
];
|
|
1893
|
+
collectIterator(provider.stream(messages));
|
|
1894
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1895
|
+
messages: [
|
|
1896
|
+
{
|
|
1897
|
+
role: 'user',
|
|
1898
|
+
content: [
|
|
1899
|
+
{
|
|
1900
|
+
toolResult: {
|
|
1901
|
+
toolUseId: 'tool-1',
|
|
1902
|
+
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
1903
|
+
status: 'success',
|
|
1904
|
+
},
|
|
1905
|
+
},
|
|
1906
|
+
],
|
|
1907
|
+
},
|
|
1908
|
+
],
|
|
1909
|
+
}));
|
|
1910
|
+
});
|
|
1911
|
+
it('formats document block in tool result', async () => {
|
|
1912
|
+
const provider = new BedrockModel();
|
|
1913
|
+
const docBytes = new Uint8Array([7, 8, 9]);
|
|
1914
|
+
const messages = [
|
|
1915
|
+
new Message({
|
|
1916
|
+
role: 'user',
|
|
1917
|
+
content: [
|
|
1918
|
+
new ToolResultBlock({
|
|
1919
|
+
toolUseId: 'tool-1',
|
|
1920
|
+
status: 'success',
|
|
1921
|
+
content: [new DocumentBlock({ name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } })],
|
|
1922
|
+
}),
|
|
1923
|
+
],
|
|
1924
|
+
}),
|
|
1925
|
+
];
|
|
1926
|
+
collectIterator(provider.stream(messages));
|
|
1927
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1928
|
+
messages: [
|
|
1929
|
+
{
|
|
1930
|
+
role: 'user',
|
|
1931
|
+
content: [
|
|
1932
|
+
{
|
|
1933
|
+
toolResult: {
|
|
1934
|
+
toolUseId: 'tool-1',
|
|
1935
|
+
content: [{ document: { name: 'report.pdf', format: 'pdf', source: { bytes: docBytes } } }],
|
|
1936
|
+
status: 'success',
|
|
1937
|
+
},
|
|
1938
|
+
},
|
|
1939
|
+
],
|
|
1940
|
+
},
|
|
1941
|
+
],
|
|
1942
|
+
}));
|
|
1943
|
+
});
|
|
1944
|
+
it('formats mixed text and media content in tool result', async () => {
|
|
1945
|
+
const provider = new BedrockModel();
|
|
1946
|
+
const imageBytes = new Uint8Array([1, 2]);
|
|
1947
|
+
const messages = [
|
|
1948
|
+
new Message({
|
|
1949
|
+
role: 'user',
|
|
1950
|
+
content: [
|
|
1951
|
+
new ToolResultBlock({
|
|
1952
|
+
toolUseId: 'tool-1',
|
|
1953
|
+
status: 'success',
|
|
1954
|
+
content: [
|
|
1955
|
+
new TextBlock('Here is the image:'),
|
|
1956
|
+
new ImageBlock({ format: 'jpeg', source: { bytes: imageBytes } }),
|
|
1957
|
+
],
|
|
1958
|
+
}),
|
|
1959
|
+
],
|
|
1960
|
+
}),
|
|
1961
|
+
];
|
|
1962
|
+
collectIterator(provider.stream(messages));
|
|
1963
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1964
|
+
messages: [
|
|
1965
|
+
{
|
|
1966
|
+
role: 'user',
|
|
1967
|
+
content: [
|
|
1968
|
+
{
|
|
1969
|
+
toolResult: {
|
|
1970
|
+
toolUseId: 'tool-1',
|
|
1971
|
+
content: [
|
|
1972
|
+
{ text: 'Here is the image:' },
|
|
1973
|
+
{ image: { format: 'jpeg', source: { bytes: imageBytes } } },
|
|
1974
|
+
],
|
|
1975
|
+
status: 'success',
|
|
1976
|
+
},
|
|
1977
|
+
},
|
|
1978
|
+
],
|
|
1979
|
+
},
|
|
1980
|
+
],
|
|
1981
|
+
}));
|
|
1982
|
+
});
|
|
1983
|
+
});
|
|
1984
|
+
describe('media blocks in messages', () => {
|
|
1985
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1986
|
+
it('formats top-level image block', async () => {
|
|
1987
|
+
const provider = new BedrockModel();
|
|
1988
|
+
const imageBytes = new Uint8Array([1, 2, 3]);
|
|
1989
|
+
const messages = [
|
|
1990
|
+
new Message({
|
|
1991
|
+
role: 'user',
|
|
1992
|
+
content: [new ImageBlock({ format: 'png', source: { bytes: imageBytes } })],
|
|
1993
|
+
}),
|
|
1994
|
+
];
|
|
1995
|
+
collectIterator(provider.stream(messages));
|
|
1996
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1997
|
+
messages: [
|
|
1998
|
+
{
|
|
1999
|
+
role: 'user',
|
|
2000
|
+
content: [{ image: { format: 'png', source: { bytes: imageBytes } } }],
|
|
2001
|
+
},
|
|
2002
|
+
],
|
|
2003
|
+
}));
|
|
2004
|
+
});
|
|
2005
|
+
it('formats top-level image block with S3 source', async () => {
|
|
2006
|
+
const provider = new BedrockModel();
|
|
2007
|
+
const messages = [
|
|
2008
|
+
new Message({
|
|
2009
|
+
role: 'user',
|
|
2010
|
+
content: [
|
|
2011
|
+
new ImageBlock({ format: 'png', source: { location: { type: 's3', uri: 's3://bucket/image.png' } } }),
|
|
2012
|
+
],
|
|
2013
|
+
}),
|
|
2014
|
+
];
|
|
2015
|
+
collectIterator(provider.stream(messages));
|
|
2016
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2017
|
+
messages: [
|
|
2018
|
+
{
|
|
2019
|
+
role: 'user',
|
|
2020
|
+
content: [{ image: { format: 'png', source: { s3Location: { uri: 's3://bucket/image.png' } } } }],
|
|
2021
|
+
},
|
|
2022
|
+
],
|
|
2023
|
+
}));
|
|
2024
|
+
});
|
|
2025
|
+
it('formats top-level video block with 3gp format mapping', async () => {
|
|
2026
|
+
const provider = new BedrockModel();
|
|
2027
|
+
const videoBytes = new Uint8Array([4, 5, 6]);
|
|
2028
|
+
const messages = [
|
|
2029
|
+
new Message({
|
|
2030
|
+
role: 'user',
|
|
2031
|
+
content: [new VideoBlock({ format: '3gp', source: { bytes: videoBytes } })],
|
|
2032
|
+
}),
|
|
2033
|
+
];
|
|
2034
|
+
collectIterator(provider.stream(messages));
|
|
2035
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2036
|
+
messages: [
|
|
2037
|
+
{
|
|
2038
|
+
role: 'user',
|
|
2039
|
+
content: [{ video: { format: 'three_gp', source: { bytes: videoBytes } } }],
|
|
2040
|
+
},
|
|
2041
|
+
],
|
|
2042
|
+
}));
|
|
2043
|
+
});
|
|
2044
|
+
it('formats top-level document block with text source converted to bytes', async () => {
|
|
2045
|
+
const provider = new BedrockModel();
|
|
2046
|
+
const messages = [
|
|
2047
|
+
new Message({
|
|
2048
|
+
role: 'user',
|
|
2049
|
+
content: [new DocumentBlock({ name: 'notes.txt', format: 'txt', source: { text: 'Hello world' } })],
|
|
2050
|
+
}),
|
|
2051
|
+
];
|
|
2052
|
+
collectIterator(provider.stream(messages));
|
|
2053
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2054
|
+
messages: [
|
|
2055
|
+
{
|
|
2056
|
+
role: 'user',
|
|
2057
|
+
content: [
|
|
2058
|
+
{
|
|
2059
|
+
document: {
|
|
2060
|
+
name: 'notes.txt',
|
|
2061
|
+
format: 'txt',
|
|
2062
|
+
source: { bytes: new TextEncoder().encode('Hello world') },
|
|
2063
|
+
},
|
|
2064
|
+
},
|
|
2065
|
+
],
|
|
2066
|
+
},
|
|
2067
|
+
],
|
|
2068
|
+
}));
|
|
2069
|
+
});
|
|
2070
|
+
});
|
|
2071
|
+
describe('includeToolResultStatus configuration', async () => {
|
|
2072
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2073
|
+
describe('when includeToolResultStatus is true', () => {
|
|
2074
|
+
it('always includes status field in tool results', async () => {
|
|
2075
|
+
const provider = new BedrockModel({ includeToolResultStatus: true });
|
|
2076
|
+
const messages = [
|
|
2077
|
+
new Message({
|
|
2078
|
+
role: 'user',
|
|
2079
|
+
content: [
|
|
2080
|
+
new ToolResultBlock({
|
|
2081
|
+
toolUseId: 'tool-123',
|
|
2082
|
+
status: 'success',
|
|
2083
|
+
content: [new TextBlock('Result')],
|
|
2084
|
+
}),
|
|
2085
|
+
],
|
|
2086
|
+
}),
|
|
2087
|
+
];
|
|
2088
|
+
collectIterator(provider.stream(messages));
|
|
2089
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2090
|
+
messages: [
|
|
2091
|
+
{
|
|
2092
|
+
content: [
|
|
2093
|
+
{
|
|
2094
|
+
toolResult: {
|
|
2095
|
+
content: [{ text: 'Result' }],
|
|
2096
|
+
status: 'success',
|
|
2097
|
+
toolUseId: 'tool-123',
|
|
2098
|
+
},
|
|
2099
|
+
},
|
|
2100
|
+
],
|
|
2101
|
+
role: 'user',
|
|
2102
|
+
},
|
|
2103
|
+
],
|
|
2104
|
+
modelId: expect.any(String),
|
|
2105
|
+
});
|
|
2106
|
+
});
|
|
2107
|
+
});
|
|
2108
|
+
describe('when includeToolResultStatus is false', () => {
|
|
2109
|
+
it('never includes status field in tool results', async () => {
|
|
2110
|
+
const provider = new BedrockModel({ includeToolResultStatus: false });
|
|
2111
|
+
const messages = [
|
|
2112
|
+
new Message({
|
|
2113
|
+
role: 'user',
|
|
2114
|
+
content: [
|
|
2115
|
+
new ToolResultBlock({
|
|
2116
|
+
toolUseId: 'tool-123',
|
|
2117
|
+
status: 'success',
|
|
2118
|
+
content: [new TextBlock('Result')],
|
|
2119
|
+
}),
|
|
2120
|
+
],
|
|
2121
|
+
}),
|
|
2122
|
+
];
|
|
2123
|
+
collectIterator(provider.stream(messages));
|
|
2124
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2125
|
+
messages: [
|
|
2126
|
+
{
|
|
2127
|
+
content: [
|
|
2128
|
+
{
|
|
2129
|
+
toolResult: {
|
|
2130
|
+
content: [{ text: 'Result' }],
|
|
2131
|
+
toolUseId: 'tool-123',
|
|
2132
|
+
},
|
|
2133
|
+
},
|
|
2134
|
+
],
|
|
2135
|
+
role: 'user',
|
|
2136
|
+
},
|
|
2137
|
+
],
|
|
2138
|
+
modelId: expect.any(String),
|
|
2139
|
+
});
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
describe('when includeToolResultStatus is auto', () => {
|
|
2143
|
+
it('includes status field for Claude models', async () => {
|
|
2144
|
+
const provider = new BedrockModel({
|
|
2145
|
+
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
2146
|
+
includeToolResultStatus: 'auto',
|
|
2147
|
+
});
|
|
2148
|
+
const messages = [
|
|
2149
|
+
new Message({
|
|
2150
|
+
role: 'user',
|
|
2151
|
+
content: [
|
|
2152
|
+
new ToolResultBlock({
|
|
2153
|
+
toolUseId: 'tool-123',
|
|
2154
|
+
status: 'success',
|
|
2155
|
+
content: [new TextBlock('Result')],
|
|
2156
|
+
}),
|
|
2157
|
+
],
|
|
2158
|
+
}),
|
|
2159
|
+
];
|
|
2160
|
+
collectIterator(provider.stream(messages));
|
|
2161
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2162
|
+
messages: [
|
|
2163
|
+
{
|
|
2164
|
+
content: [
|
|
2165
|
+
{
|
|
2166
|
+
toolResult: {
|
|
2167
|
+
content: [{ text: 'Result' }],
|
|
2168
|
+
status: 'success',
|
|
2169
|
+
toolUseId: 'tool-123',
|
|
2170
|
+
},
|
|
2171
|
+
},
|
|
2172
|
+
],
|
|
2173
|
+
role: 'user',
|
|
2174
|
+
},
|
|
2175
|
+
],
|
|
2176
|
+
modelId: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
2177
|
+
});
|
|
2178
|
+
});
|
|
2179
|
+
});
|
|
2180
|
+
describe('when includeToolResultStatus is undefined (default)', () => {
|
|
2181
|
+
it('follows auto logic for non-Claude models', async () => {
|
|
2182
|
+
const provider = new BedrockModel({
|
|
2183
|
+
modelId: 'amazon.nova-lite-v1:0',
|
|
2184
|
+
});
|
|
2185
|
+
const messages = [
|
|
2186
|
+
new Message({
|
|
2187
|
+
role: 'user',
|
|
2188
|
+
content: [
|
|
1500
2189
|
new ToolResultBlock({
|
|
1501
2190
|
toolUseId: 'tool-123',
|
|
1502
2191
|
status: 'success',
|
|
@@ -1506,677 +2195,1219 @@ describe('BedrockModel', () => {
|
|
|
1506
2195
|
}),
|
|
1507
2196
|
];
|
|
1508
2197
|
collectIterator(provider.stream(messages));
|
|
1509
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1510
|
-
messages: [
|
|
1511
|
-
{
|
|
1512
|
-
content: [
|
|
1513
|
-
{
|
|
1514
|
-
toolResult: {
|
|
1515
|
-
content: [{ text: 'Result' }],
|
|
1516
|
-
|
|
1517
|
-
|
|
2198
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
2199
|
+
messages: [
|
|
2200
|
+
{
|
|
2201
|
+
content: [
|
|
2202
|
+
{
|
|
2203
|
+
toolResult: {
|
|
2204
|
+
content: [{ text: 'Result' }],
|
|
2205
|
+
toolUseId: 'tool-123',
|
|
2206
|
+
},
|
|
2207
|
+
},
|
|
2208
|
+
],
|
|
2209
|
+
role: 'user',
|
|
2210
|
+
},
|
|
2211
|
+
],
|
|
2212
|
+
modelId: 'amazon.nova-lite-v1:0',
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
describe('region configuration', () => {
|
|
2218
|
+
beforeEach(() => {
|
|
2219
|
+
vi.clearAllMocks();
|
|
2220
|
+
});
|
|
2221
|
+
it('uses explicit region when provided', async () => {
|
|
2222
|
+
mockBedrockClientImplementation();
|
|
2223
|
+
const provider = new BedrockModel({ region: 'eu-west-1' });
|
|
2224
|
+
// After applyDefaultRegion wraps the config functions, verify they still return the correct value
|
|
2225
|
+
const regionResult = await provider['_client'].config.region();
|
|
2226
|
+
expect(regionResult).toBe('eu-west-1');
|
|
2227
|
+
});
|
|
2228
|
+
it('defaults to us-west-2 when region is missing', async () => {
|
|
2229
|
+
mockBedrockClientImplementation({
|
|
2230
|
+
region: async () => {
|
|
2231
|
+
throw new Error('Region is missing');
|
|
2232
|
+
},
|
|
2233
|
+
useFipsEndpoint: async () => {
|
|
2234
|
+
throw new Error('Region is missing');
|
|
2235
|
+
},
|
|
2236
|
+
});
|
|
2237
|
+
const provider = new BedrockModel();
|
|
2238
|
+
// After applyDefaultRegion wraps the config functions
|
|
2239
|
+
const regionResult = await provider['_client'].config.region();
|
|
2240
|
+
expect(regionResult).toBe('us-west-2');
|
|
2241
|
+
const fipsResult = await provider['_client'].config.useFipsEndpoint();
|
|
2242
|
+
expect(fipsResult).toBe(false);
|
|
2243
|
+
});
|
|
2244
|
+
it('rethrows other region errors', async () => {
|
|
2245
|
+
mockBedrockClientImplementation({
|
|
2246
|
+
region: async () => {
|
|
2247
|
+
throw new Error('Network error');
|
|
2248
|
+
},
|
|
2249
|
+
});
|
|
2250
|
+
const provider = new BedrockModel();
|
|
2251
|
+
// Should rethrow the error
|
|
2252
|
+
await expect(provider['_client'].config.region()).rejects.toThrow('Network error');
|
|
2253
|
+
});
|
|
2254
|
+
});
|
|
2255
|
+
describe('guardrail configuration', () => {
|
|
2256
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2257
|
+
beforeEach(() => {
|
|
2258
|
+
vi.clearAllMocks();
|
|
2259
|
+
});
|
|
2260
|
+
describe('constructor', () => {
|
|
2261
|
+
it('accepts guardrailConfig in options', () => {
|
|
2262
|
+
const provider = new BedrockModel({
|
|
2263
|
+
guardrailConfig: {
|
|
2264
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2265
|
+
guardrailVersion: '1',
|
|
2266
|
+
},
|
|
2267
|
+
});
|
|
2268
|
+
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
2269
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2270
|
+
guardrailVersion: '1',
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2273
|
+
it('accepts guardrailConfig with all options', () => {
|
|
2274
|
+
const provider = new BedrockModel({
|
|
2275
|
+
guardrailConfig: {
|
|
2276
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2277
|
+
guardrailVersion: '1',
|
|
2278
|
+
trace: 'enabled_full',
|
|
2279
|
+
streamProcessingMode: 'sync',
|
|
2280
|
+
redaction: {
|
|
2281
|
+
input: true,
|
|
2282
|
+
inputMessage: '[Custom input redacted.]',
|
|
2283
|
+
output: true,
|
|
2284
|
+
outputMessage: '[Custom output redacted.]',
|
|
2285
|
+
},
|
|
2286
|
+
},
|
|
2287
|
+
});
|
|
2288
|
+
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
2289
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2290
|
+
guardrailVersion: '1',
|
|
2291
|
+
trace: 'enabled_full',
|
|
2292
|
+
streamProcessingMode: 'sync',
|
|
2293
|
+
redaction: {
|
|
2294
|
+
input: true,
|
|
2295
|
+
inputMessage: '[Custom input redacted.]',
|
|
2296
|
+
output: true,
|
|
2297
|
+
outputMessage: '[Custom output redacted.]',
|
|
2298
|
+
},
|
|
2299
|
+
});
|
|
2300
|
+
});
|
|
2301
|
+
});
|
|
2302
|
+
describe('request formatting', () => {
|
|
2303
|
+
it('includes guardrailConfig in request with default trace', async () => {
|
|
2304
|
+
const provider = new BedrockModel({
|
|
2305
|
+
guardrailConfig: {
|
|
2306
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2307
|
+
guardrailVersion: '1',
|
|
2308
|
+
},
|
|
2309
|
+
});
|
|
2310
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2311
|
+
collectIterator(provider.stream(messages));
|
|
2312
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2313
|
+
guardrailConfig: {
|
|
2314
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2315
|
+
guardrailVersion: '1',
|
|
2316
|
+
trace: 'enabled',
|
|
2317
|
+
},
|
|
2318
|
+
}));
|
|
2319
|
+
});
|
|
2320
|
+
it('includes guardrailConfig in request with custom trace', async () => {
|
|
2321
|
+
const provider = new BedrockModel({
|
|
2322
|
+
guardrailConfig: {
|
|
2323
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2324
|
+
guardrailVersion: '1',
|
|
2325
|
+
trace: 'disabled',
|
|
2326
|
+
},
|
|
2327
|
+
});
|
|
2328
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2329
|
+
collectIterator(provider.stream(messages));
|
|
2330
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2331
|
+
guardrailConfig: {
|
|
2332
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2333
|
+
guardrailVersion: '1',
|
|
2334
|
+
trace: 'disabled',
|
|
2335
|
+
},
|
|
2336
|
+
}));
|
|
2337
|
+
});
|
|
2338
|
+
it('includes streamProcessingMode when specified', async () => {
|
|
2339
|
+
const provider = new BedrockModel({
|
|
2340
|
+
guardrailConfig: {
|
|
2341
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2342
|
+
guardrailVersion: '1',
|
|
2343
|
+
streamProcessingMode: 'sync',
|
|
2344
|
+
},
|
|
2345
|
+
});
|
|
2346
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2347
|
+
collectIterator(provider.stream(messages));
|
|
2348
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2349
|
+
guardrailConfig: {
|
|
2350
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2351
|
+
guardrailVersion: '1',
|
|
2352
|
+
trace: 'enabled',
|
|
2353
|
+
streamProcessingMode: 'sync',
|
|
2354
|
+
},
|
|
2355
|
+
}));
|
|
2356
|
+
});
|
|
2357
|
+
it('does not include guardrailConfig when not configured', async () => {
|
|
2358
|
+
const provider = new BedrockModel();
|
|
2359
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2360
|
+
collectIterator(provider.stream(messages));
|
|
2361
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.not.objectContaining({
|
|
2362
|
+
guardrailConfig: expect.anything(),
|
|
2363
|
+
}));
|
|
2364
|
+
});
|
|
2365
|
+
});
|
|
2366
|
+
describe('blocked guardrail detection', () => {
|
|
2367
|
+
it('detects blocked guardrail in inputAssessment', async () => {
|
|
2368
|
+
setupMockSend(async function* () {
|
|
2369
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2370
|
+
yield { contentBlockStart: {} };
|
|
2371
|
+
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2372
|
+
yield { contentBlockStop: {} };
|
|
2373
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2374
|
+
yield {
|
|
2375
|
+
metadata: {
|
|
2376
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2377
|
+
trace: {
|
|
2378
|
+
guardrail: {
|
|
2379
|
+
inputAssessment: {
|
|
2380
|
+
'1234': {
|
|
2381
|
+
topicPolicy: {
|
|
2382
|
+
topics: [{ name: 'Harmful', action: 'BLOCKED', detected: true }],
|
|
2383
|
+
},
|
|
2384
|
+
},
|
|
2385
|
+
},
|
|
2386
|
+
},
|
|
2387
|
+
},
|
|
2388
|
+
},
|
|
2389
|
+
};
|
|
2390
|
+
});
|
|
2391
|
+
const provider = new BedrockModel({
|
|
2392
|
+
guardrailConfig: {
|
|
2393
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2394
|
+
guardrailVersion: '1',
|
|
2395
|
+
},
|
|
2396
|
+
});
|
|
2397
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2398
|
+
const events = await collectIterator(provider.stream(messages));
|
|
2399
|
+
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2400
|
+
expect(redactEvent).toBeDefined();
|
|
2401
|
+
expect(redactEvent).toStrictEqual({
|
|
2402
|
+
type: 'modelRedactionEvent',
|
|
2403
|
+
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
2404
|
+
});
|
|
2405
|
+
});
|
|
2406
|
+
it('detects blocked guardrail in outputAssessments', async () => {
|
|
2407
|
+
setupMockSend(async function* () {
|
|
2408
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2409
|
+
yield { contentBlockStart: {} };
|
|
2410
|
+
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2411
|
+
yield { contentBlockStop: {} };
|
|
2412
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2413
|
+
yield {
|
|
2414
|
+
metadata: {
|
|
2415
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2416
|
+
trace: {
|
|
2417
|
+
guardrail: {
|
|
2418
|
+
outputAssessments: {
|
|
2419
|
+
'1234': {
|
|
2420
|
+
contentPolicy: {
|
|
2421
|
+
filters: [{ type: 'VIOLENCE', action: 'BLOCKED', detected: true }],
|
|
2422
|
+
},
|
|
2423
|
+
},
|
|
2424
|
+
},
|
|
2425
|
+
},
|
|
2426
|
+
},
|
|
2427
|
+
},
|
|
2428
|
+
};
|
|
2429
|
+
});
|
|
2430
|
+
const provider = new BedrockModel({
|
|
2431
|
+
guardrailConfig: {
|
|
2432
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2433
|
+
guardrailVersion: '1',
|
|
2434
|
+
},
|
|
2435
|
+
});
|
|
2436
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2437
|
+
const events = await collectIterator(provider.stream(messages));
|
|
2438
|
+
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2439
|
+
expect(redactEvent).toBeDefined();
|
|
2440
|
+
});
|
|
2441
|
+
it('does not emit redaction events when guardrail not blocked', async () => {
|
|
2442
|
+
setupMockSend(async function* () {
|
|
2443
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2444
|
+
yield { contentBlockStart: {} };
|
|
2445
|
+
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2446
|
+
yield { contentBlockStop: {} };
|
|
2447
|
+
yield { messageStop: { stopReason: 'end_turn' } };
|
|
2448
|
+
yield {
|
|
2449
|
+
metadata: {
|
|
2450
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2451
|
+
trace: {
|
|
2452
|
+
guardrail: {
|
|
2453
|
+
inputAssessment: {
|
|
2454
|
+
'1234': {
|
|
2455
|
+
topicPolicy: {
|
|
2456
|
+
topics: [{ name: 'Safe', action: 'NONE', detected: false }],
|
|
2457
|
+
},
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
},
|
|
2461
|
+
},
|
|
2462
|
+
},
|
|
2463
|
+
};
|
|
2464
|
+
});
|
|
2465
|
+
const provider = new BedrockModel({
|
|
2466
|
+
guardrailConfig: {
|
|
2467
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2468
|
+
guardrailVersion: '1',
|
|
2469
|
+
},
|
|
2470
|
+
});
|
|
2471
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2472
|
+
const events = await collectIterator(provider.stream(messages));
|
|
2473
|
+
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2474
|
+
expect(redactEvent).toBeUndefined();
|
|
2475
|
+
});
|
|
2476
|
+
it('does not emit redaction events without guardrailConfig', async () => {
|
|
2477
|
+
setupMockSend(async function* () {
|
|
2478
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2479
|
+
yield { contentBlockStart: {} };
|
|
2480
|
+
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
2481
|
+
yield { contentBlockStop: {} };
|
|
2482
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2483
|
+
yield {
|
|
2484
|
+
metadata: {
|
|
2485
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2486
|
+
trace: {
|
|
2487
|
+
guardrail: {
|
|
2488
|
+
inputAssessment: {
|
|
2489
|
+
'1234': {
|
|
2490
|
+
topicPolicy: {
|
|
2491
|
+
topics: [{ name: 'Harmful', action: 'BLOCKED', detected: true }],
|
|
2492
|
+
},
|
|
2493
|
+
},
|
|
1518
2494
|
},
|
|
1519
2495
|
},
|
|
1520
|
-
|
|
1521
|
-
|
|
2496
|
+
},
|
|
2497
|
+
},
|
|
2498
|
+
};
|
|
2499
|
+
});
|
|
2500
|
+
const provider = new BedrockModel();
|
|
2501
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2502
|
+
const events = await collectIterator(provider.stream(messages));
|
|
2503
|
+
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
2504
|
+
expect(redactEvent).toBeUndefined();
|
|
2505
|
+
});
|
|
2506
|
+
});
|
|
2507
|
+
describe('redaction event generation', () => {
|
|
2508
|
+
it('emits input redaction with default message', async () => {
|
|
2509
|
+
setupMockSend(async function* () {
|
|
2510
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2511
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2512
|
+
yield {
|
|
2513
|
+
metadata: {
|
|
2514
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2515
|
+
trace: {
|
|
2516
|
+
guardrail: {
|
|
2517
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2518
|
+
},
|
|
2519
|
+
},
|
|
2520
|
+
},
|
|
2521
|
+
};
|
|
2522
|
+
});
|
|
2523
|
+
const provider = new BedrockModel({
|
|
2524
|
+
guardrailConfig: {
|
|
2525
|
+
guardrailIdentifier: 'id',
|
|
2526
|
+
guardrailVersion: '1',
|
|
2527
|
+
},
|
|
2528
|
+
});
|
|
2529
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2530
|
+
expect(events).toContainEqual({
|
|
2531
|
+
type: 'modelRedactionEvent',
|
|
2532
|
+
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
2533
|
+
});
|
|
2534
|
+
});
|
|
2535
|
+
it('emits input redaction with custom message', async () => {
|
|
2536
|
+
setupMockSend(async function* () {
|
|
2537
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2538
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2539
|
+
yield {
|
|
2540
|
+
metadata: {
|
|
2541
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2542
|
+
trace: {
|
|
2543
|
+
guardrail: {
|
|
2544
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2545
|
+
},
|
|
2546
|
+
},
|
|
2547
|
+
},
|
|
2548
|
+
};
|
|
2549
|
+
});
|
|
2550
|
+
const provider = new BedrockModel({
|
|
2551
|
+
guardrailConfig: {
|
|
2552
|
+
guardrailIdentifier: 'id',
|
|
2553
|
+
guardrailVersion: '1',
|
|
2554
|
+
redaction: {
|
|
2555
|
+
inputMessage: '[Custom input message]',
|
|
2556
|
+
},
|
|
2557
|
+
},
|
|
2558
|
+
});
|
|
2559
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2560
|
+
expect(events).toContainEqual({
|
|
2561
|
+
type: 'modelRedactionEvent',
|
|
2562
|
+
inputRedaction: { replaceContent: '[Custom input message]' },
|
|
2563
|
+
});
|
|
2564
|
+
});
|
|
2565
|
+
it('does not emit input redaction when redactInput is false', async () => {
|
|
2566
|
+
setupMockSend(async function* () {
|
|
2567
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2568
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2569
|
+
yield {
|
|
2570
|
+
metadata: {
|
|
2571
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2572
|
+
trace: {
|
|
2573
|
+
guardrail: {
|
|
2574
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2575
|
+
},
|
|
2576
|
+
},
|
|
1522
2577
|
},
|
|
1523
|
-
|
|
1524
|
-
|
|
2578
|
+
};
|
|
2579
|
+
});
|
|
2580
|
+
const provider = new BedrockModel({
|
|
2581
|
+
guardrailConfig: {
|
|
2582
|
+
guardrailIdentifier: 'id',
|
|
2583
|
+
guardrailVersion: '1',
|
|
2584
|
+
redaction: {
|
|
2585
|
+
input: false,
|
|
2586
|
+
},
|
|
2587
|
+
},
|
|
1525
2588
|
});
|
|
2589
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2590
|
+
const inputRedactEvent = events.find((e) => e.type === 'modelRedactionEvent' && 'inputRedaction' in e);
|
|
2591
|
+
expect(inputRedactEvent).toBeUndefined();
|
|
1526
2592
|
});
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
status: 'success',
|
|
1538
|
-
content: [new TextBlock('Result')],
|
|
1539
|
-
}),
|
|
1540
|
-
],
|
|
1541
|
-
}),
|
|
1542
|
-
];
|
|
1543
|
-
collectIterator(provider.stream(messages));
|
|
1544
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith({
|
|
1545
|
-
messages: [
|
|
1546
|
-
{
|
|
1547
|
-
content: [
|
|
1548
|
-
{
|
|
1549
|
-
toolResult: {
|
|
1550
|
-
content: [{ text: 'Result' }],
|
|
1551
|
-
toolUseId: 'tool-123',
|
|
1552
|
-
},
|
|
2593
|
+
it('emits output redaction when redactOutput is true', async () => {
|
|
2594
|
+
setupMockSend(async function* () {
|
|
2595
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2596
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2597
|
+
yield {
|
|
2598
|
+
metadata: {
|
|
2599
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2600
|
+
trace: {
|
|
2601
|
+
guardrail: {
|
|
2602
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
1553
2603
|
},
|
|
1554
|
-
|
|
1555
|
-
role: 'user',
|
|
2604
|
+
},
|
|
1556
2605
|
},
|
|
1557
|
-
|
|
1558
|
-
modelId: expect.any(String),
|
|
2606
|
+
};
|
|
1559
2607
|
});
|
|
1560
|
-
});
|
|
1561
|
-
});
|
|
1562
|
-
describe('when includeToolResultStatus is auto', () => {
|
|
1563
|
-
it('includes status field for Claude models', async () => {
|
|
1564
2608
|
const provider = new BedrockModel({
|
|
1565
|
-
|
|
1566
|
-
|
|
2609
|
+
guardrailConfig: {
|
|
2610
|
+
guardrailIdentifier: 'id',
|
|
2611
|
+
guardrailVersion: '1',
|
|
2612
|
+
redaction: {
|
|
2613
|
+
output: true,
|
|
2614
|
+
},
|
|
2615
|
+
},
|
|
1567
2616
|
});
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
content: [
|
|
1585
|
-
{
|
|
1586
|
-
toolResult: {
|
|
1587
|
-
content: [{ text: 'Result' }],
|
|
1588
|
-
status: 'success',
|
|
1589
|
-
toolUseId: 'tool-123',
|
|
1590
|
-
},
|
|
2617
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2618
|
+
expect(events).toContainEqual({
|
|
2619
|
+
type: 'modelRedactionEvent',
|
|
2620
|
+
outputRedaction: { replaceContent: '[Assistant output redacted.]' },
|
|
2621
|
+
});
|
|
2622
|
+
});
|
|
2623
|
+
it('emits output redaction with custom message', async () => {
|
|
2624
|
+
setupMockSend(async function* () {
|
|
2625
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2626
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2627
|
+
yield {
|
|
2628
|
+
metadata: {
|
|
2629
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2630
|
+
trace: {
|
|
2631
|
+
guardrail: {
|
|
2632
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
1591
2633
|
},
|
|
1592
|
-
|
|
1593
|
-
role: 'user',
|
|
2634
|
+
},
|
|
1594
2635
|
},
|
|
1595
|
-
|
|
1596
|
-
|
|
2636
|
+
};
|
|
2637
|
+
});
|
|
2638
|
+
const provider = new BedrockModel({
|
|
2639
|
+
guardrailConfig: {
|
|
2640
|
+
guardrailIdentifier: 'id',
|
|
2641
|
+
guardrailVersion: '1',
|
|
2642
|
+
redaction: {
|
|
2643
|
+
output: true,
|
|
2644
|
+
outputMessage: '[Custom output message]',
|
|
2645
|
+
},
|
|
2646
|
+
},
|
|
2647
|
+
});
|
|
2648
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2649
|
+
expect(events).toContainEqual({
|
|
2650
|
+
type: 'modelRedactionEvent',
|
|
2651
|
+
outputRedaction: { replaceContent: '[Custom output message]' },
|
|
1597
2652
|
});
|
|
1598
2653
|
});
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
2654
|
+
it('emits both input and output redaction when both are enabled', async () => {
|
|
2655
|
+
setupMockSend(async function* () {
|
|
2656
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2657
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2658
|
+
yield {
|
|
2659
|
+
metadata: {
|
|
2660
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2661
|
+
trace: {
|
|
2662
|
+
guardrail: {
|
|
2663
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2664
|
+
},
|
|
2665
|
+
},
|
|
2666
|
+
},
|
|
2667
|
+
};
|
|
2668
|
+
});
|
|
1602
2669
|
const provider = new BedrockModel({
|
|
1603
|
-
|
|
2670
|
+
guardrailConfig: {
|
|
2671
|
+
guardrailIdentifier: 'id',
|
|
2672
|
+
guardrailVersion: '1',
|
|
2673
|
+
redaction: {
|
|
2674
|
+
input: true,
|
|
2675
|
+
output: true,
|
|
2676
|
+
},
|
|
2677
|
+
},
|
|
1604
2678
|
});
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
2679
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2680
|
+
expect(events).toContainEqual({
|
|
2681
|
+
type: 'modelRedactionEvent',
|
|
2682
|
+
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
2683
|
+
});
|
|
2684
|
+
expect(events).toContainEqual({
|
|
2685
|
+
type: 'modelRedactionEvent',
|
|
2686
|
+
outputRedaction: { replaceContent: '[Assistant output redacted.]' },
|
|
2687
|
+
});
|
|
2688
|
+
});
|
|
2689
|
+
it('includes redactedContent from modelOutput when available', async () => {
|
|
2690
|
+
setupMockSend(async function* () {
|
|
2691
|
+
yield { messageStart: { role: 'assistant' } };
|
|
2692
|
+
yield { contentBlockStart: {} };
|
|
2693
|
+
yield { contentBlockDelta: { delta: { text: 'This content was blocked' } } };
|
|
2694
|
+
yield { contentBlockStop: {} };
|
|
2695
|
+
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
2696
|
+
yield {
|
|
2697
|
+
metadata: {
|
|
2698
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2699
|
+
trace: {
|
|
2700
|
+
guardrail: {
|
|
2701
|
+
modelOutput: ['This content ', 'was blocked'],
|
|
2702
|
+
outputAssessments: {
|
|
2703
|
+
'0': [{ topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } }],
|
|
1626
2704
|
},
|
|
1627
2705
|
},
|
|
1628
|
-
|
|
1629
|
-
role: 'user',
|
|
2706
|
+
},
|
|
1630
2707
|
},
|
|
1631
|
-
|
|
1632
|
-
|
|
2708
|
+
};
|
|
2709
|
+
});
|
|
2710
|
+
const provider = new BedrockModel({
|
|
2711
|
+
guardrailConfig: {
|
|
2712
|
+
guardrailIdentifier: 'id',
|
|
2713
|
+
guardrailVersion: '1',
|
|
2714
|
+
redaction: {
|
|
2715
|
+
output: true,
|
|
2716
|
+
outputMessage: '[Blocked]',
|
|
2717
|
+
},
|
|
2718
|
+
},
|
|
2719
|
+
});
|
|
2720
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2721
|
+
expect(events).toContainEqual({
|
|
2722
|
+
type: 'modelRedactionEvent',
|
|
2723
|
+
outputRedaction: {
|
|
2724
|
+
replaceContent: '[Blocked]',
|
|
2725
|
+
redactedContent: 'This content was blocked',
|
|
2726
|
+
},
|
|
1633
2727
|
});
|
|
1634
2728
|
});
|
|
1635
2729
|
});
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
throw new Error('Region is missing');
|
|
1655
|
-
},
|
|
1656
|
-
});
|
|
1657
|
-
const provider = new BedrockModel();
|
|
1658
|
-
// After applyDefaultRegion wraps the config functions
|
|
1659
|
-
const regionResult = await provider['_client'].config.region();
|
|
1660
|
-
expect(regionResult).toBe('us-west-2');
|
|
1661
|
-
const fipsResult = await provider['_client'].config.useFipsEndpoint();
|
|
1662
|
-
expect(fipsResult).toBe(false);
|
|
1663
|
-
});
|
|
1664
|
-
it('rethrows other region errors', async () => {
|
|
1665
|
-
mockBedrockClientImplementation({
|
|
1666
|
-
region: async () => {
|
|
1667
|
-
throw new Error('Network error');
|
|
1668
|
-
},
|
|
1669
|
-
});
|
|
1670
|
-
const provider = new BedrockModel();
|
|
1671
|
-
// Should rethrow the error
|
|
1672
|
-
await expect(provider['_client'].config.region()).rejects.toThrow('Network error');
|
|
1673
|
-
});
|
|
1674
|
-
});
|
|
1675
|
-
describe('guardrail configuration', () => {
|
|
1676
|
-
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
1677
|
-
beforeEach(() => {
|
|
1678
|
-
vi.clearAllMocks();
|
|
1679
|
-
});
|
|
1680
|
-
describe('constructor', () => {
|
|
1681
|
-
it('accepts guardrailConfig in options', () => {
|
|
2730
|
+
describe('non-streaming mode', () => {
|
|
2731
|
+
it('emits redaction events in non-streaming mode when guardrail blocks', async () => {
|
|
2732
|
+
const mockSend = vi.fn(async () => ({
|
|
2733
|
+
output: {
|
|
2734
|
+
message: {
|
|
2735
|
+
role: 'assistant',
|
|
2736
|
+
content: [{ text: 'Hello' }],
|
|
2737
|
+
},
|
|
2738
|
+
},
|
|
2739
|
+
stopReason: 'guardrail_intervened',
|
|
2740
|
+
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2741
|
+
trace: {
|
|
2742
|
+
guardrail: {
|
|
2743
|
+
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
2744
|
+
},
|
|
2745
|
+
},
|
|
2746
|
+
}));
|
|
2747
|
+
mockBedrockClientImplementation({ send: mockSend });
|
|
1682
2748
|
const provider = new BedrockModel({
|
|
2749
|
+
stream: false,
|
|
1683
2750
|
guardrailConfig: {
|
|
1684
|
-
guardrailIdentifier: '
|
|
2751
|
+
guardrailIdentifier: 'id',
|
|
1685
2752
|
guardrailVersion: '1',
|
|
1686
2753
|
},
|
|
1687
2754
|
});
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2755
|
+
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2756
|
+
expect(events).toContainEqual({
|
|
2757
|
+
type: 'modelRedactionEvent',
|
|
2758
|
+
inputRedaction: { replaceContent: '[User input redacted.]' },
|
|
1691
2759
|
});
|
|
1692
2760
|
});
|
|
1693
|
-
|
|
2761
|
+
});
|
|
2762
|
+
describe('guardLatestUserMessage', () => {
|
|
2763
|
+
const mockConverseStreamCommand = vi.mocked(ConverseStreamCommand);
|
|
2764
|
+
beforeEach(() => {
|
|
2765
|
+
vi.clearAllMocks();
|
|
2766
|
+
});
|
|
2767
|
+
it('accepts guardLatestUserMessage in guardrailConfig', () => {
|
|
1694
2768
|
const provider = new BedrockModel({
|
|
1695
2769
|
guardrailConfig: {
|
|
1696
2770
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1697
2771
|
guardrailVersion: '1',
|
|
1698
|
-
|
|
1699
|
-
streamProcessingMode: 'sync',
|
|
1700
|
-
redaction: {
|
|
1701
|
-
input: true,
|
|
1702
|
-
inputMessage: '[Custom input redacted.]',
|
|
1703
|
-
output: true,
|
|
1704
|
-
outputMessage: '[Custom output redacted.]',
|
|
1705
|
-
},
|
|
2772
|
+
guardLatestUserMessage: true,
|
|
1706
2773
|
},
|
|
1707
2774
|
});
|
|
1708
2775
|
expect(provider.getConfig().guardrailConfig).toStrictEqual({
|
|
1709
2776
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1710
2777
|
guardrailVersion: '1',
|
|
1711
|
-
|
|
1712
|
-
streamProcessingMode: 'sync',
|
|
1713
|
-
redaction: {
|
|
1714
|
-
input: true,
|
|
1715
|
-
inputMessage: '[Custom input redacted.]',
|
|
1716
|
-
output: true,
|
|
1717
|
-
outputMessage: '[Custom output redacted.]',
|
|
1718
|
-
},
|
|
2778
|
+
guardLatestUserMessage: true,
|
|
1719
2779
|
});
|
|
1720
2780
|
});
|
|
1721
|
-
|
|
1722
|
-
describe('request formatting', () => {
|
|
1723
|
-
it('includes guardrailConfig in request with default trace', async () => {
|
|
2781
|
+
it('wraps latest user message text content in guardContent when enabled', async () => {
|
|
1724
2782
|
const provider = new BedrockModel({
|
|
1725
2783
|
guardrailConfig: {
|
|
1726
2784
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1727
2785
|
guardrailVersion: '1',
|
|
2786
|
+
guardLatestUserMessage: true,
|
|
1728
2787
|
},
|
|
1729
2788
|
});
|
|
1730
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2789
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
1731
2790
|
collectIterator(provider.stream(messages));
|
|
1732
2791
|
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
2792
|
+
messages: [
|
|
2793
|
+
{
|
|
2794
|
+
role: 'user',
|
|
2795
|
+
content: [
|
|
2796
|
+
{
|
|
2797
|
+
guardContent: {
|
|
2798
|
+
text: {
|
|
2799
|
+
text: 'Hello world',
|
|
2800
|
+
},
|
|
2801
|
+
},
|
|
2802
|
+
},
|
|
2803
|
+
],
|
|
2804
|
+
},
|
|
2805
|
+
],
|
|
1738
2806
|
}));
|
|
1739
2807
|
});
|
|
1740
|
-
it('
|
|
2808
|
+
it('wraps latest user message image content in guardContent when enabled', async () => {
|
|
2809
|
+
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
1741
2810
|
const provider = new BedrockModel({
|
|
1742
2811
|
guardrailConfig: {
|
|
1743
2812
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1744
2813
|
guardrailVersion: '1',
|
|
1745
|
-
|
|
2814
|
+
guardLatestUserMessage: true,
|
|
1746
2815
|
},
|
|
1747
2816
|
});
|
|
1748
|
-
const messages = [
|
|
2817
|
+
const messages = [
|
|
2818
|
+
new Message({
|
|
2819
|
+
role: 'user',
|
|
2820
|
+
content: [
|
|
2821
|
+
new ImageBlock({
|
|
2822
|
+
format: 'jpeg',
|
|
2823
|
+
source: { bytes: imageBytes },
|
|
2824
|
+
}),
|
|
2825
|
+
],
|
|
2826
|
+
}),
|
|
2827
|
+
];
|
|
1749
2828
|
collectIterator(provider.stream(messages));
|
|
1750
2829
|
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2830
|
+
messages: [
|
|
2831
|
+
{
|
|
2832
|
+
role: 'user',
|
|
2833
|
+
content: [
|
|
2834
|
+
{
|
|
2835
|
+
guardContent: {
|
|
2836
|
+
image: {
|
|
2837
|
+
format: 'jpeg',
|
|
2838
|
+
source: { bytes: imageBytes },
|
|
2839
|
+
},
|
|
2840
|
+
},
|
|
2841
|
+
},
|
|
2842
|
+
],
|
|
2843
|
+
},
|
|
2844
|
+
],
|
|
1756
2845
|
}));
|
|
1757
2846
|
});
|
|
1758
|
-
it('
|
|
2847
|
+
it('does not wrap toolResult messages even though role is user', async () => {
|
|
1759
2848
|
const provider = new BedrockModel({
|
|
1760
2849
|
guardrailConfig: {
|
|
1761
2850
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1762
2851
|
guardrailVersion: '1',
|
|
1763
|
-
|
|
2852
|
+
guardLatestUserMessage: true,
|
|
1764
2853
|
},
|
|
1765
2854
|
});
|
|
1766
|
-
const messages = [
|
|
2855
|
+
const messages = [
|
|
2856
|
+
new Message({ role: 'user', content: [new TextBlock('What is 2+2?')] }),
|
|
2857
|
+
new Message({
|
|
2858
|
+
role: 'assistant',
|
|
2859
|
+
content: [
|
|
2860
|
+
new ToolUseBlock({
|
|
2861
|
+
name: 'calculator',
|
|
2862
|
+
toolUseId: 'tool-123',
|
|
2863
|
+
input: { expression: '2+2' },
|
|
2864
|
+
}),
|
|
2865
|
+
],
|
|
2866
|
+
}),
|
|
2867
|
+
new Message({
|
|
2868
|
+
role: 'user',
|
|
2869
|
+
content: [
|
|
2870
|
+
new ToolResultBlock({
|
|
2871
|
+
toolUseId: 'tool-123',
|
|
2872
|
+
status: 'success',
|
|
2873
|
+
content: [new TextBlock('4')],
|
|
2874
|
+
}),
|
|
2875
|
+
],
|
|
2876
|
+
}),
|
|
2877
|
+
];
|
|
1767
2878
|
collectIterator(provider.stream(messages));
|
|
2879
|
+
// The latest message is a toolResult, but guardContent should wrap the FIRST user message
|
|
2880
|
+
// which contains text, not the toolResult
|
|
1768
2881
|
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2882
|
+
messages: [
|
|
2883
|
+
{
|
|
2884
|
+
role: 'user',
|
|
2885
|
+
content: [
|
|
2886
|
+
{
|
|
2887
|
+
guardContent: {
|
|
2888
|
+
text: {
|
|
2889
|
+
text: 'What is 2+2?',
|
|
2890
|
+
},
|
|
2891
|
+
},
|
|
2892
|
+
},
|
|
2893
|
+
],
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
role: 'assistant',
|
|
2897
|
+
content: [
|
|
2898
|
+
{
|
|
2899
|
+
toolUse: {
|
|
2900
|
+
name: 'calculator',
|
|
2901
|
+
toolUseId: 'tool-123',
|
|
2902
|
+
input: { expression: '2+2' },
|
|
2903
|
+
},
|
|
2904
|
+
},
|
|
2905
|
+
],
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
role: 'user',
|
|
2909
|
+
content: [
|
|
2910
|
+
{
|
|
2911
|
+
toolResult: expect.objectContaining({
|
|
2912
|
+
toolUseId: 'tool-123',
|
|
2913
|
+
}),
|
|
2914
|
+
},
|
|
2915
|
+
],
|
|
2916
|
+
},
|
|
2917
|
+
],
|
|
2918
|
+
}));
|
|
2919
|
+
});
|
|
2920
|
+
it('does not wrap messages when guardLatestUserMessage is false', async () => {
|
|
2921
|
+
const provider = new BedrockModel({
|
|
1769
2922
|
guardrailConfig: {
|
|
1770
2923
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1771
2924
|
guardrailVersion: '1',
|
|
1772
|
-
|
|
1773
|
-
streamProcessingMode: 'sync',
|
|
2925
|
+
guardLatestUserMessage: false,
|
|
1774
2926
|
},
|
|
1775
|
-
})
|
|
1776
|
-
|
|
1777
|
-
it('does not include guardrailConfig when not configured', async () => {
|
|
1778
|
-
const provider = new BedrockModel();
|
|
1779
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
2927
|
+
});
|
|
2928
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
1780
2929
|
collectIterator(provider.stream(messages));
|
|
1781
|
-
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.
|
|
1782
|
-
|
|
2930
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2931
|
+
messages: [
|
|
2932
|
+
{
|
|
2933
|
+
role: 'user',
|
|
2934
|
+
content: [{ text: 'Hello world' }],
|
|
2935
|
+
},
|
|
2936
|
+
],
|
|
1783
2937
|
}));
|
|
1784
2938
|
});
|
|
1785
|
-
|
|
1786
|
-
describe('blocked guardrail detection', () => {
|
|
1787
|
-
it('detects blocked guardrail in inputAssessment', async () => {
|
|
1788
|
-
setupMockSend(async function* () {
|
|
1789
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1790
|
-
yield { contentBlockStart: {} };
|
|
1791
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
1792
|
-
yield { contentBlockStop: {} };
|
|
1793
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
1794
|
-
yield {
|
|
1795
|
-
metadata: {
|
|
1796
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1797
|
-
trace: {
|
|
1798
|
-
guardrail: {
|
|
1799
|
-
inputAssessment: {
|
|
1800
|
-
'1234': {
|
|
1801
|
-
topicPolicy: {
|
|
1802
|
-
topics: [{ name: 'Harmful', action: 'BLOCKED', detected: true }],
|
|
1803
|
-
},
|
|
1804
|
-
},
|
|
1805
|
-
},
|
|
1806
|
-
},
|
|
1807
|
-
},
|
|
1808
|
-
},
|
|
1809
|
-
};
|
|
1810
|
-
});
|
|
2939
|
+
it('does not wrap messages when guardLatestUserMessage is undefined', async () => {
|
|
1811
2940
|
const provider = new BedrockModel({
|
|
1812
2941
|
guardrailConfig: {
|
|
1813
2942
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1814
2943
|
guardrailVersion: '1',
|
|
1815
2944
|
},
|
|
1816
2945
|
});
|
|
1817
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
yield { contentBlockStart: {} };
|
|
1830
|
-
yield { contentBlockDelta: { delta: { text: 'Hello' } } };
|
|
1831
|
-
yield { contentBlockStop: {} };
|
|
1832
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
1833
|
-
yield {
|
|
1834
|
-
metadata: {
|
|
1835
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1836
|
-
trace: {
|
|
1837
|
-
guardrail: {
|
|
1838
|
-
outputAssessments: {
|
|
1839
|
-
'1234': {
|
|
1840
|
-
contentPolicy: {
|
|
1841
|
-
filters: [{ type: 'VIOLENCE', action: 'BLOCKED', detected: true }],
|
|
1842
|
-
},
|
|
1843
|
-
},
|
|
1844
|
-
},
|
|
1845
|
-
},
|
|
1846
|
-
},
|
|
1847
|
-
},
|
|
1848
|
-
};
|
|
1849
|
-
});
|
|
2946
|
+
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello world')] })];
|
|
2947
|
+
collectIterator(provider.stream(messages));
|
|
2948
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2949
|
+
messages: [
|
|
2950
|
+
{
|
|
2951
|
+
role: 'user',
|
|
2952
|
+
content: [{ text: 'Hello world' }],
|
|
2953
|
+
},
|
|
2954
|
+
],
|
|
2955
|
+
}));
|
|
2956
|
+
});
|
|
2957
|
+
it('does not wrap assistant messages', async () => {
|
|
1850
2958
|
const provider = new BedrockModel({
|
|
1851
2959
|
guardrailConfig: {
|
|
1852
2960
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1853
2961
|
guardrailVersion: '1',
|
|
2962
|
+
guardLatestUserMessage: true,
|
|
1854
2963
|
},
|
|
1855
2964
|
});
|
|
1856
|
-
const messages = [
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1871
|
-
trace: {
|
|
1872
|
-
guardrail: {
|
|
1873
|
-
inputAssessment: {
|
|
1874
|
-
'1234': {
|
|
1875
|
-
topicPolicy: {
|
|
1876
|
-
topics: [{ name: 'Safe', action: 'NONE', detected: false }],
|
|
1877
|
-
},
|
|
2965
|
+
const messages = [
|
|
2966
|
+
new Message({ role: 'user', content: [new TextBlock('Hello')] }),
|
|
2967
|
+
new Message({ role: 'assistant', content: [new TextBlock('Hi there!')] }),
|
|
2968
|
+
];
|
|
2969
|
+
collectIterator(provider.stream(messages));
|
|
2970
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
2971
|
+
messages: [
|
|
2972
|
+
{
|
|
2973
|
+
role: 'user',
|
|
2974
|
+
content: [
|
|
2975
|
+
{
|
|
2976
|
+
guardContent: {
|
|
2977
|
+
text: {
|
|
2978
|
+
text: 'Hello',
|
|
1878
2979
|
},
|
|
1879
2980
|
},
|
|
1880
2981
|
},
|
|
1881
|
-
|
|
2982
|
+
],
|
|
1882
2983
|
},
|
|
1883
|
-
|
|
1884
|
-
|
|
2984
|
+
{
|
|
2985
|
+
role: 'assistant',
|
|
2986
|
+
content: [{ text: 'Hi there!' }],
|
|
2987
|
+
},
|
|
2988
|
+
],
|
|
2989
|
+
}));
|
|
2990
|
+
});
|
|
2991
|
+
it('wraps only the last user text/image message in multi-turn conversation', async () => {
|
|
1885
2992
|
const provider = new BedrockModel({
|
|
1886
2993
|
guardrailConfig: {
|
|
1887
2994
|
guardrailIdentifier: 'my-guardrail-id',
|
|
1888
2995
|
guardrailVersion: '1',
|
|
2996
|
+
guardLatestUserMessage: true,
|
|
1889
2997
|
},
|
|
1890
2998
|
});
|
|
1891
|
-
const messages = [
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2999
|
+
const messages = [
|
|
3000
|
+
new Message({ role: 'user', content: [new TextBlock('First message')] }),
|
|
3001
|
+
new Message({ role: 'assistant', content: [new TextBlock('First response')] }),
|
|
3002
|
+
new Message({ role: 'user', content: [new TextBlock('Second message')] }),
|
|
3003
|
+
];
|
|
3004
|
+
collectIterator(provider.stream(messages));
|
|
3005
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3006
|
+
messages: [
|
|
3007
|
+
{
|
|
3008
|
+
role: 'user',
|
|
3009
|
+
content: [{ text: 'First message' }],
|
|
3010
|
+
},
|
|
3011
|
+
{
|
|
3012
|
+
role: 'assistant',
|
|
3013
|
+
content: [{ text: 'First response' }],
|
|
3014
|
+
},
|
|
3015
|
+
{
|
|
3016
|
+
role: 'user',
|
|
3017
|
+
content: [
|
|
3018
|
+
{
|
|
3019
|
+
guardContent: {
|
|
3020
|
+
text: {
|
|
3021
|
+
text: 'Second message',
|
|
1913
3022
|
},
|
|
1914
3023
|
},
|
|
1915
3024
|
},
|
|
1916
|
-
|
|
3025
|
+
],
|
|
1917
3026
|
},
|
|
1918
|
-
|
|
1919
|
-
});
|
|
1920
|
-
const provider = new BedrockModel();
|
|
1921
|
-
const messages = [new Message({ role: 'user', content: [new TextBlock('Hello')] })];
|
|
1922
|
-
const events = await collectIterator(provider.stream(messages));
|
|
1923
|
-
const redactEvent = events.find((e) => e.type === 'modelRedactionEvent');
|
|
1924
|
-
expect(redactEvent).toBeUndefined();
|
|
3027
|
+
],
|
|
3028
|
+
}));
|
|
1925
3029
|
});
|
|
1926
|
-
|
|
1927
|
-
describe('redaction event generation', () => {
|
|
1928
|
-
it('emits input redaction with default message', async () => {
|
|
1929
|
-
setupMockSend(async function* () {
|
|
1930
|
-
yield { messageStart: { role: 'assistant' } };
|
|
1931
|
-
yield { messageStop: { stopReason: 'guardrail_intervened' } };
|
|
1932
|
-
yield {
|
|
1933
|
-
metadata: {
|
|
1934
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
1935
|
-
trace: {
|
|
1936
|
-
guardrail: {
|
|
1937
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
1938
|
-
},
|
|
1939
|
-
},
|
|
1940
|
-
},
|
|
1941
|
-
};
|
|
1942
|
-
});
|
|
3030
|
+
it('handles no user messages with text/image content gracefully', async () => {
|
|
1943
3031
|
const provider = new BedrockModel({
|
|
1944
3032
|
guardrailConfig: {
|
|
1945
|
-
guardrailIdentifier: 'id',
|
|
3033
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
1946
3034
|
guardrailVersion: '1',
|
|
3035
|
+
guardLatestUserMessage: true,
|
|
1947
3036
|
},
|
|
1948
3037
|
});
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
3038
|
+
// Only assistant message, no user text/image content
|
|
3039
|
+
const messages = [new Message({ role: 'assistant', content: [new TextBlock('Hello!')] })];
|
|
3040
|
+
collectIterator(provider.stream(messages));
|
|
3041
|
+
// Should not throw and should not wrap anything
|
|
3042
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3043
|
+
messages: [
|
|
3044
|
+
{
|
|
3045
|
+
role: 'assistant',
|
|
3046
|
+
content: [{ text: 'Hello!' }],
|
|
3047
|
+
},
|
|
3048
|
+
],
|
|
3049
|
+
}));
|
|
1954
3050
|
});
|
|
1955
|
-
it('
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
3051
|
+
it('preserves explicit GuardContentBlock in messages without double-wrapping', async () => {
|
|
3052
|
+
const provider = new BedrockModel({
|
|
3053
|
+
guardrailConfig: {
|
|
3054
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
3055
|
+
guardrailVersion: '1',
|
|
3056
|
+
guardLatestUserMessage: true,
|
|
3057
|
+
},
|
|
3058
|
+
});
|
|
3059
|
+
const messages = [
|
|
3060
|
+
new Message({
|
|
3061
|
+
role: 'user',
|
|
3062
|
+
content: [
|
|
3063
|
+
new GuardContentBlock({
|
|
3064
|
+
text: {
|
|
3065
|
+
qualifiers: ['grounding_source'],
|
|
3066
|
+
text: 'Already guarded content',
|
|
1965
3067
|
},
|
|
1966
|
-
},
|
|
3068
|
+
}),
|
|
3069
|
+
],
|
|
3070
|
+
}),
|
|
3071
|
+
];
|
|
3072
|
+
collectIterator(provider.stream(messages));
|
|
3073
|
+
// Explicit GuardContentBlock should be preserved as-is (no text/image content to wrap)
|
|
3074
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3075
|
+
messages: [
|
|
3076
|
+
{
|
|
3077
|
+
role: 'user',
|
|
3078
|
+
content: [
|
|
3079
|
+
{
|
|
3080
|
+
guardContent: {
|
|
3081
|
+
text: {
|
|
3082
|
+
text: 'Already guarded content',
|
|
3083
|
+
qualifiers: ['grounding_source'],
|
|
3084
|
+
},
|
|
3085
|
+
},
|
|
3086
|
+
},
|
|
3087
|
+
],
|
|
1967
3088
|
},
|
|
1968
|
-
|
|
1969
|
-
});
|
|
3089
|
+
],
|
|
3090
|
+
}));
|
|
3091
|
+
});
|
|
3092
|
+
it('wraps all text and image blocks in the latest user message', async () => {
|
|
3093
|
+
const imageBytes = new Uint8Array([5, 6, 7, 8]);
|
|
1970
3094
|
const provider = new BedrockModel({
|
|
1971
3095
|
guardrailConfig: {
|
|
1972
|
-
guardrailIdentifier: 'id',
|
|
3096
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
1973
3097
|
guardrailVersion: '1',
|
|
1974
|
-
|
|
1975
|
-
inputMessage: '[Custom input message]',
|
|
1976
|
-
},
|
|
3098
|
+
guardLatestUserMessage: true,
|
|
1977
3099
|
},
|
|
1978
3100
|
});
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
3101
|
+
const messages = [
|
|
3102
|
+
new Message({
|
|
3103
|
+
role: 'user',
|
|
3104
|
+
content: [
|
|
3105
|
+
new TextBlock('Check this text'),
|
|
3106
|
+
new ImageBlock({
|
|
3107
|
+
format: 'png',
|
|
3108
|
+
source: { bytes: imageBytes },
|
|
3109
|
+
}),
|
|
3110
|
+
new TextBlock('And this text too'),
|
|
3111
|
+
],
|
|
3112
|
+
}),
|
|
3113
|
+
];
|
|
3114
|
+
collectIterator(provider.stream(messages));
|
|
3115
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3116
|
+
messages: [
|
|
3117
|
+
{
|
|
3118
|
+
role: 'user',
|
|
3119
|
+
content: [
|
|
3120
|
+
{
|
|
3121
|
+
guardContent: {
|
|
3122
|
+
text: {
|
|
3123
|
+
text: 'Check this text',
|
|
3124
|
+
},
|
|
3125
|
+
},
|
|
1995
3126
|
},
|
|
1996
|
-
|
|
3127
|
+
{
|
|
3128
|
+
guardContent: {
|
|
3129
|
+
image: {
|
|
3130
|
+
format: 'png',
|
|
3131
|
+
source: { bytes: imageBytes },
|
|
3132
|
+
},
|
|
3133
|
+
},
|
|
3134
|
+
},
|
|
3135
|
+
{
|
|
3136
|
+
guardContent: {
|
|
3137
|
+
text: {
|
|
3138
|
+
text: 'And this text too',
|
|
3139
|
+
},
|
|
3140
|
+
},
|
|
3141
|
+
},
|
|
3142
|
+
],
|
|
1997
3143
|
},
|
|
1998
|
-
|
|
1999
|
-
});
|
|
3144
|
+
],
|
|
3145
|
+
}));
|
|
3146
|
+
});
|
|
3147
|
+
it('skips wrapping images with unsupported formats (gif)', async () => {
|
|
3148
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3149
|
+
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
2000
3150
|
const provider = new BedrockModel({
|
|
2001
3151
|
guardrailConfig: {
|
|
2002
|
-
guardrailIdentifier: 'id',
|
|
3152
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2003
3153
|
guardrailVersion: '1',
|
|
2004
|
-
|
|
2005
|
-
input: false,
|
|
2006
|
-
},
|
|
3154
|
+
guardLatestUserMessage: true,
|
|
2007
3155
|
},
|
|
2008
3156
|
});
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
3157
|
+
const messages = [
|
|
3158
|
+
new Message({
|
|
3159
|
+
role: 'user',
|
|
3160
|
+
content: [
|
|
3161
|
+
new ImageBlock({
|
|
3162
|
+
format: 'gif',
|
|
3163
|
+
source: { bytes: imageBytes },
|
|
3164
|
+
}),
|
|
3165
|
+
],
|
|
3166
|
+
}),
|
|
3167
|
+
];
|
|
3168
|
+
collectIterator(provider.stream(messages));
|
|
3169
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("Image format 'gif' not supported by Bedrock guardrails, skipping guardContent wrap");
|
|
3170
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3171
|
+
messages: [
|
|
3172
|
+
{
|
|
3173
|
+
role: 'user',
|
|
3174
|
+
content: [
|
|
3175
|
+
{
|
|
3176
|
+
image: {
|
|
3177
|
+
format: 'gif',
|
|
3178
|
+
source: { bytes: imageBytes },
|
|
3179
|
+
},
|
|
2023
3180
|
},
|
|
2024
|
-
|
|
3181
|
+
],
|
|
2025
3182
|
},
|
|
2026
|
-
|
|
2027
|
-
});
|
|
3183
|
+
],
|
|
3184
|
+
}));
|
|
3185
|
+
consoleWarnSpy.mockRestore();
|
|
3186
|
+
});
|
|
3187
|
+
it('skips wrapping images with unsupported formats (webp)', async () => {
|
|
3188
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
3189
|
+
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
2028
3190
|
const provider = new BedrockModel({
|
|
2029
3191
|
guardrailConfig: {
|
|
2030
|
-
guardrailIdentifier: 'id',
|
|
3192
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2031
3193
|
guardrailVersion: '1',
|
|
2032
|
-
|
|
2033
|
-
output: true,
|
|
2034
|
-
},
|
|
3194
|
+
guardLatestUserMessage: true,
|
|
2035
3195
|
},
|
|
2036
3196
|
});
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
3197
|
+
const messages = [
|
|
3198
|
+
new Message({
|
|
3199
|
+
role: 'user',
|
|
3200
|
+
content: [
|
|
3201
|
+
new ImageBlock({
|
|
3202
|
+
format: 'webp',
|
|
3203
|
+
source: { bytes: imageBytes },
|
|
3204
|
+
}),
|
|
3205
|
+
],
|
|
3206
|
+
}),
|
|
3207
|
+
];
|
|
3208
|
+
collectIterator(provider.stream(messages));
|
|
3209
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith("Image format 'webp' not supported by Bedrock guardrails, skipping guardContent wrap");
|
|
3210
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3211
|
+
messages: [
|
|
3212
|
+
{
|
|
3213
|
+
role: 'user',
|
|
3214
|
+
content: [
|
|
3215
|
+
{
|
|
3216
|
+
image: {
|
|
3217
|
+
format: 'webp',
|
|
3218
|
+
source: { bytes: imageBytes },
|
|
3219
|
+
},
|
|
2053
3220
|
},
|
|
2054
|
-
|
|
3221
|
+
],
|
|
2055
3222
|
},
|
|
2056
|
-
|
|
2057
|
-
});
|
|
3223
|
+
],
|
|
3224
|
+
}));
|
|
3225
|
+
consoleWarnSpy.mockRestore();
|
|
3226
|
+
});
|
|
3227
|
+
it('skips wrapping images with S3 source', async () => {
|
|
3228
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
2058
3229
|
const provider = new BedrockModel({
|
|
2059
3230
|
guardrailConfig: {
|
|
2060
|
-
guardrailIdentifier: 'id',
|
|
3231
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2061
3232
|
guardrailVersion: '1',
|
|
2062
|
-
|
|
2063
|
-
output: true,
|
|
2064
|
-
outputMessage: '[Custom output message]',
|
|
2065
|
-
},
|
|
3233
|
+
guardLatestUserMessage: true,
|
|
2066
3234
|
},
|
|
2067
3235
|
});
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
metadata: {
|
|
2080
|
-
usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
2081
|
-
trace: {
|
|
2082
|
-
guardrail: {
|
|
2083
|
-
inputAssessment: { '1': { topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } } },
|
|
3236
|
+
const messages = [
|
|
3237
|
+
new Message({
|
|
3238
|
+
role: 'user',
|
|
3239
|
+
content: [
|
|
3240
|
+
new ImageBlock({
|
|
3241
|
+
format: 'png',
|
|
3242
|
+
source: {
|
|
3243
|
+
location: {
|
|
3244
|
+
type: 's3',
|
|
3245
|
+
uri: 's3://bucket/image.png',
|
|
3246
|
+
},
|
|
2084
3247
|
},
|
|
2085
|
-
},
|
|
3248
|
+
}),
|
|
3249
|
+
],
|
|
3250
|
+
}),
|
|
3251
|
+
];
|
|
3252
|
+
collectIterator(provider.stream(messages));
|
|
3253
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Image source must be bytes for Bedrock guardrails, skipping guardContent wrap');
|
|
3254
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3255
|
+
messages: [
|
|
3256
|
+
{
|
|
3257
|
+
role: 'user',
|
|
3258
|
+
content: [
|
|
3259
|
+
{
|
|
3260
|
+
image: {
|
|
3261
|
+
format: 'png',
|
|
3262
|
+
source: {
|
|
3263
|
+
s3Location: {
|
|
3264
|
+
uri: 's3://bucket/image.png',
|
|
3265
|
+
},
|
|
3266
|
+
},
|
|
3267
|
+
},
|
|
3268
|
+
},
|
|
3269
|
+
],
|
|
2086
3270
|
},
|
|
2087
|
-
|
|
2088
|
-
});
|
|
3271
|
+
],
|
|
3272
|
+
}));
|
|
3273
|
+
consoleWarnSpy.mockRestore();
|
|
3274
|
+
});
|
|
3275
|
+
it('skips wrapping images with URL source', async () => {
|
|
3276
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
2089
3277
|
const provider = new BedrockModel({
|
|
2090
3278
|
guardrailConfig: {
|
|
2091
|
-
guardrailIdentifier: 'id',
|
|
3279
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2092
3280
|
guardrailVersion: '1',
|
|
2093
|
-
|
|
2094
|
-
input: true,
|
|
2095
|
-
output: true,
|
|
2096
|
-
},
|
|
3281
|
+
guardLatestUserMessage: true,
|
|
2097
3282
|
},
|
|
2098
3283
|
});
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
'0': [{ topicPolicy: { topics: [{ action: 'BLOCKED', detected: true }] } }],
|
|
3284
|
+
const messages = [
|
|
3285
|
+
new Message({
|
|
3286
|
+
role: 'user',
|
|
3287
|
+
content: [
|
|
3288
|
+
new ImageBlock({
|
|
3289
|
+
format: 'jpeg',
|
|
3290
|
+
source: { url: 'https://example.com/image.jpg' },
|
|
3291
|
+
}),
|
|
3292
|
+
],
|
|
3293
|
+
}),
|
|
3294
|
+
];
|
|
3295
|
+
collectIterator(provider.stream(messages));
|
|
3296
|
+
// URL sources return undefined in _formatMediaSource, resulting in source: undefined
|
|
3297
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('Ignoring imageSourceUrl content block as its not supported by bedrock');
|
|
3298
|
+
// The image block still appears but with undefined source (Bedrock will reject this)
|
|
3299
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3300
|
+
messages: [
|
|
3301
|
+
{
|
|
3302
|
+
role: 'user',
|
|
3303
|
+
content: [
|
|
3304
|
+
{
|
|
3305
|
+
image: {
|
|
3306
|
+
format: 'jpeg',
|
|
3307
|
+
source: undefined,
|
|
2124
3308
|
},
|
|
2125
3309
|
},
|
|
2126
|
-
|
|
3310
|
+
],
|
|
2127
3311
|
},
|
|
2128
|
-
|
|
2129
|
-
});
|
|
3312
|
+
],
|
|
3313
|
+
}));
|
|
3314
|
+
consoleWarnSpy.mockRestore();
|
|
3315
|
+
});
|
|
3316
|
+
it('wraps supported image formats (png and jpeg) with bytes source', async () => {
|
|
3317
|
+
const imageBytes = new Uint8Array([1, 2, 3, 4]);
|
|
2130
3318
|
const provider = new BedrockModel({
|
|
2131
3319
|
guardrailConfig: {
|
|
2132
|
-
guardrailIdentifier: 'id',
|
|
3320
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2133
3321
|
guardrailVersion: '1',
|
|
2134
|
-
|
|
2135
|
-
output: true,
|
|
2136
|
-
outputMessage: '[Blocked]',
|
|
2137
|
-
},
|
|
2138
|
-
},
|
|
2139
|
-
});
|
|
2140
|
-
const events = await collectIterator(provider.stream([new Message({ role: 'user', content: [new TextBlock('Hello')] })]));
|
|
2141
|
-
expect(events).toContainEqual({
|
|
2142
|
-
type: 'modelRedactionEvent',
|
|
2143
|
-
outputRedaction: {
|
|
2144
|
-
replaceContent: '[Blocked]',
|
|
2145
|
-
redactedContent: 'This content was blocked',
|
|
3322
|
+
guardLatestUserMessage: true,
|
|
2146
3323
|
},
|
|
2147
3324
|
});
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
3325
|
+
const messages = [
|
|
3326
|
+
new Message({
|
|
3327
|
+
role: 'user',
|
|
3328
|
+
content: [
|
|
3329
|
+
new ImageBlock({
|
|
3330
|
+
format: 'png',
|
|
3331
|
+
source: { bytes: imageBytes },
|
|
3332
|
+
}),
|
|
3333
|
+
new ImageBlock({
|
|
3334
|
+
format: 'jpeg',
|
|
3335
|
+
source: { bytes: imageBytes },
|
|
3336
|
+
}),
|
|
3337
|
+
],
|
|
3338
|
+
}),
|
|
3339
|
+
];
|
|
3340
|
+
collectIterator(provider.stream(messages));
|
|
3341
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3342
|
+
messages: [
|
|
3343
|
+
{
|
|
3344
|
+
role: 'user',
|
|
3345
|
+
content: [
|
|
3346
|
+
{
|
|
3347
|
+
guardContent: {
|
|
3348
|
+
image: {
|
|
3349
|
+
format: 'png',
|
|
3350
|
+
source: { bytes: imageBytes },
|
|
3351
|
+
},
|
|
3352
|
+
},
|
|
3353
|
+
},
|
|
3354
|
+
{
|
|
3355
|
+
guardContent: {
|
|
3356
|
+
image: {
|
|
3357
|
+
format: 'jpeg',
|
|
3358
|
+
source: { bytes: imageBytes },
|
|
3359
|
+
},
|
|
3360
|
+
},
|
|
3361
|
+
},
|
|
3362
|
+
],
|
|
2164
3363
|
},
|
|
2165
|
-
|
|
3364
|
+
],
|
|
2166
3365
|
}));
|
|
2167
|
-
|
|
3366
|
+
});
|
|
3367
|
+
it('does not wrap reasoning or cachePoint blocks', async () => {
|
|
2168
3368
|
const provider = new BedrockModel({
|
|
2169
|
-
stream: false,
|
|
2170
3369
|
guardrailConfig: {
|
|
2171
|
-
guardrailIdentifier: 'id',
|
|
3370
|
+
guardrailIdentifier: 'my-guardrail-id',
|
|
2172
3371
|
guardrailVersion: '1',
|
|
3372
|
+
guardLatestUserMessage: true,
|
|
2173
3373
|
},
|
|
2174
3374
|
});
|
|
2175
|
-
const
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
3375
|
+
const messages = [
|
|
3376
|
+
new Message({
|
|
3377
|
+
role: 'user',
|
|
3378
|
+
content: [
|
|
3379
|
+
new TextBlock('User message'),
|
|
3380
|
+
new ReasoningBlock({ text: 'thinking...', signature: 'sig' }),
|
|
3381
|
+
new CachePointBlock({ cacheType: 'default' }),
|
|
3382
|
+
],
|
|
3383
|
+
}),
|
|
3384
|
+
];
|
|
3385
|
+
collectIterator(provider.stream(messages));
|
|
3386
|
+
expect(mockConverseStreamCommand).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
3387
|
+
messages: [
|
|
3388
|
+
{
|
|
3389
|
+
role: 'user',
|
|
3390
|
+
content: [
|
|
3391
|
+
{
|
|
3392
|
+
guardContent: {
|
|
3393
|
+
text: {
|
|
3394
|
+
text: 'User message',
|
|
3395
|
+
},
|
|
3396
|
+
},
|
|
3397
|
+
},
|
|
3398
|
+
{
|
|
3399
|
+
reasoningContent: {
|
|
3400
|
+
reasoningText: {
|
|
3401
|
+
text: 'thinking...',
|
|
3402
|
+
signature: 'sig',
|
|
3403
|
+
},
|
|
3404
|
+
},
|
|
3405
|
+
},
|
|
3406
|
+
{ cachePoint: { type: 'default' } },
|
|
3407
|
+
],
|
|
3408
|
+
},
|
|
3409
|
+
],
|
|
3410
|
+
}));
|
|
2180
3411
|
});
|
|
2181
3412
|
});
|
|
2182
3413
|
});
|