ocai-bedrock 0.1.1__tar.gz

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.
@@ -0,0 +1,60 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.so
9
+
10
+ # uv / venv
11
+ .venv/
12
+ uv.lock.bak
13
+
14
+ # Tooling caches
15
+ .pytest_cache/
16
+ .ruff_cache/
17
+ .mypy_cache/
18
+ .import_linter_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # Docs site build output and page cache (zensical build)
23
+ /site/
24
+ /.cache/
25
+
26
+ # Node (future typescript/)
27
+ node_modules/
28
+ *.tsbuildinfo
29
+
30
+ # Go (future go/)
31
+ /go/bin/
32
+
33
+ # Editors / OS. Shared editor settings ARE committed: the multi-root workspace
34
+ # (ocai.code-workspace) and each folder's .vscode/settings.json + extensions.json
35
+ # give Pylance/Ruff the right interpreter per project. Personal files stay local.
36
+ .idea/
37
+ .vscode/*
38
+ !.vscode/settings.json
39
+ !.vscode/extensions.json
40
+ .DS_Store
41
+
42
+ # Per-user agent settings (never commit personal permissions)
43
+ .claude/settings.local.json
44
+ .codex/config.toml
45
+
46
+ # Platform build and browser-test artifacts
47
+ .nuxt/
48
+ .output/
49
+ platform/test-results/
50
+ platform/playwright-report/
51
+
52
+ # Local environment files: the root .env is the active one, .envs/ holds the
53
+ # per-environment variants (`task env -- <ENV>` copies .envs/.env.<ENV> to .env).
54
+ # Never committed: they hold credentials.
55
+ /.env
56
+ /.envs/
57
+ .local/
58
+
59
+ # Task execution fingerprints
60
+ .task/
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to ocai-bedrock are recorded here by the release workflow.
4
+ ## 0.1.1 (2026-11-11)
5
+
6
+ ### Features
7
+
8
+ #### Add an explicit Bedrock InvokeModel host for Anthropic unary calls, native errors,
9
+
10
+ and governed host metadata/guardrail effects through public ocai contracts. Keep
11
+ AWS authentication in ocai-aws and semantic conversion in ocai-anthropic (WU-48).
12
+
13
+ Strengthen package boundary checks for the independent cloud service adapter;
14
+ no kernel runtime or canonical contract changes.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.5
2
+ Name: ocai-bedrock
3
+ Version: 0.1.1
4
+ Summary: Bedrock hosting through public ocai contracts.
5
+ Project-URL: Homepage, https://github.com/ocarinow/ocai
6
+ Project-URL: Source, https://github.com/ocarinow/ocai
7
+ Project-URL: Documentation, https://ocarinow.github.io/ocai/
8
+ Project-URL: Changelog, https://github.com/ocarinow/ocai/blob/main/python/packages/ocai-bedrock/CHANGELOG.md
9
+ Author: ocai maintainers
10
+ License-Expression: FSL-1.1-ALv2
11
+ Requires-Python: >=3.13
12
+ Requires-Dist: ocai<0.2,>=0.1
13
+ Provides-Extra: anthropic
14
+ Requires-Dist: ocai-anthropic<0.2,>=0.1; extra == 'anthropic'
15
+ Provides-Extra: aws
16
+ Requires-Dist: ocai-aws<0.2,>=0.1; extra == 'aws'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # ocai-bedrock
20
+
21
+ Bedrock hosting for ocai, separate from shared AWS authentication and semantic
22
+ provider codecs. This package owns InvokeModel addressing, native host errors and
23
+ response metadata/effects. It performs no discovery and imports no provider SDK.
24
+
25
+ ```python
26
+ import ocai.chat
27
+ from ocai.aws import SigV4Credential
28
+ from ocai.bedrock import Bedrock
29
+
30
+ model = ocai.chat.model(
31
+ "anthropic/us.anthropic.claude-3-5-sonnet-20241022-v2:0",
32
+ host=Bedrock(region="us-east-1"),
33
+ credentials=SigV4Credential(
34
+ access_key="synthetic-access-key", secret_key="synthetic-secret-key",
35
+ region="us-east-1",
36
+ ),
37
+ )
38
+ ```
39
+
40
+ Install the Anthropic codec and AWS credential packages separately or use the
41
+ `anthropic` and `aws` extras. The application supplies credentials explicitly;
42
+ ocai-aws delegates signing to AWS-maintained code. Users do not import its SDK.
43
+
44
+ Qualified unary surface: text, function tools, trait-declared version/beta body
45
+ placement, encoded identifiers, native errors and response metadata/guardrail
46
+ accounting. Explicit profile aliases affect capability lookup only. No model-ID
47
+ heuristic is used. Existing preflight profiles include dialect-wide wildcard
48
+ declarations; those and coverage_complete are not hosted-model qualification
49
+ (G-15). Verify deployment support independently of dialect declarations.
50
+
51
+ `beta` is an explicit tuple of body beta values; it does not silently enable any
52
+ feature. Version/beta conflicts fail before I/O. Binding-level arbitrary headers
53
+ are transmitted as explicit caller data, not qualified as feature relocations.
54
+ Use the host's beta field or trait-declared feature mappings for that purpose.
55
+
56
+ Configure guardrail identifier/version together. Recognized trace assessments
57
+ separate blocked output (ContentFilterError) from masking (reported semantic
58
+ shift). An intervention without sufficient trace is reported as unclassified;
59
+ it is never guessed to be blocked or unmodified. FidelityPolicy can reject those
60
+ shifts. HostEvidence retains native headers/body and host metadata; traces can
61
+ contain original content, so application exporters must redact them. Passive
62
+ observers omit this evidence.
63
+
64
+ Streaming, Converse, media, managed prompts, regional discovery and compatibility
65
+ endpoints are not qualified here. Streaming fails before I/O. Recorded tests prove
66
+ wire behavior without live keys; they do not establish service/account access.
67
+
68
+ | Surface | Recorded qualification |
69
+ |---|---|
70
+ | Text and system instruction | Exact body, canonical text/finish and input/output usage, both drivers |
71
+ | Function tools | Custom tool schema, auto selection, returned call identity/arguments and finish |
72
+ | Routing and beta | Encoded identifier, explicit version and beta placement; no beta feature availability claim |
73
+ | Errors | Header-only throttle/access/model error and body native codes; unknown codes preserved |
74
+ | Guardrails | Explicit blocking, output masking, uncertain intervention, caller fidelity rejection |
75
+ | Metadata | Host body fields and latency/tier headers retained; future host/dialect fields separated |
76
+ | Excluded | Streaming, Converse, media, managed prompts, regional/model discovery, live availability |
77
+
78
+ The recordings are synthetic, hand-authored against the AWS research sources in
79
+ `docs/providers/amazon-bedrock.md` section 16. They reuse the existing Anthropic
80
+ conversion graph. There is no new semantic edge or canonical revision.
@@ -0,0 +1,62 @@
1
+ # ocai-bedrock
2
+
3
+ Bedrock hosting for ocai, separate from shared AWS authentication and semantic
4
+ provider codecs. This package owns InvokeModel addressing, native host errors and
5
+ response metadata/effects. It performs no discovery and imports no provider SDK.
6
+
7
+ ```python
8
+ import ocai.chat
9
+ from ocai.aws import SigV4Credential
10
+ from ocai.bedrock import Bedrock
11
+
12
+ model = ocai.chat.model(
13
+ "anthropic/us.anthropic.claude-3-5-sonnet-20241022-v2:0",
14
+ host=Bedrock(region="us-east-1"),
15
+ credentials=SigV4Credential(
16
+ access_key="synthetic-access-key", secret_key="synthetic-secret-key",
17
+ region="us-east-1",
18
+ ),
19
+ )
20
+ ```
21
+
22
+ Install the Anthropic codec and AWS credential packages separately or use the
23
+ `anthropic` and `aws` extras. The application supplies credentials explicitly;
24
+ ocai-aws delegates signing to AWS-maintained code. Users do not import its SDK.
25
+
26
+ Qualified unary surface: text, function tools, trait-declared version/beta body
27
+ placement, encoded identifiers, native errors and response metadata/guardrail
28
+ accounting. Explicit profile aliases affect capability lookup only. No model-ID
29
+ heuristic is used. Existing preflight profiles include dialect-wide wildcard
30
+ declarations; those and coverage_complete are not hosted-model qualification
31
+ (G-15). Verify deployment support independently of dialect declarations.
32
+
33
+ `beta` is an explicit tuple of body beta values; it does not silently enable any
34
+ feature. Version/beta conflicts fail before I/O. Binding-level arbitrary headers
35
+ are transmitted as explicit caller data, not qualified as feature relocations.
36
+ Use the host's beta field or trait-declared feature mappings for that purpose.
37
+
38
+ Configure guardrail identifier/version together. Recognized trace assessments
39
+ separate blocked output (ContentFilterError) from masking (reported semantic
40
+ shift). An intervention without sufficient trace is reported as unclassified;
41
+ it is never guessed to be blocked or unmodified. FidelityPolicy can reject those
42
+ shifts. HostEvidence retains native headers/body and host metadata; traces can
43
+ contain original content, so application exporters must redact them. Passive
44
+ observers omit this evidence.
45
+
46
+ Streaming, Converse, media, managed prompts, regional discovery and compatibility
47
+ endpoints are not qualified here. Streaming fails before I/O. Recorded tests prove
48
+ wire behavior without live keys; they do not establish service/account access.
49
+
50
+ | Surface | Recorded qualification |
51
+ |---|---|
52
+ | Text and system instruction | Exact body, canonical text/finish and input/output usage, both drivers |
53
+ | Function tools | Custom tool schema, auto selection, returned call identity/arguments and finish |
54
+ | Routing and beta | Encoded identifier, explicit version and beta placement; no beta feature availability claim |
55
+ | Errors | Header-only throttle/access/model error and body native codes; unknown codes preserved |
56
+ | Guardrails | Explicit blocking, output masking, uncertain intervention, caller fidelity rejection |
57
+ | Metadata | Host body fields and latency/tier headers retained; future host/dialect fields separated |
58
+ | Excluded | Streaming, Converse, media, managed prompts, regional/model discovery, live availability |
59
+
60
+ The recordings are synthetic, hand-authored against the AWS research sources in
61
+ `docs/providers/amazon-bedrock.md` section 16. They reuse the existing Anthropic
62
+ conversion graph. There is no new semantic edge or canonical revision.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ocai-bedrock"
7
+ version = "0.1.1"
8
+ description = "Bedrock hosting through public ocai contracts."
9
+ readme = "README.md"
10
+ requires-python = ">=3.13"
11
+ license = "FSL-1.1-ALv2"
12
+ authors = [{ name = "ocai maintainers" }]
13
+ dependencies = ["ocai>=0.1,<0.2"]
14
+
15
+ [project.optional-dependencies]
16
+ aws = ["ocai-aws>=0.1,<0.2"]
17
+ anthropic = ["ocai-anthropic>=0.1,<0.2"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/ocarinow/ocai"
21
+ Source = "https://github.com/ocarinow/ocai"
22
+ Documentation = "https://ocarinow.github.io/ocai/"
23
+ Changelog = "https://github.com/ocarinow/ocai/blob/main/python/packages/ocai-bedrock/CHANGELOG.md"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["src/ocai"]
27
+
28
+ [tool.hatch.build]
29
+ dev-mode-dirs = ["src"]
30
+
31
+ [tool.ocai]
32
+ role = "host"
@@ -0,0 +1,5 @@
1
+ """Bedrock hosting for ocai; credentials and semantic codecs are separate packages."""
2
+
3
+ from ocai.bedrock.hosting import Bedrock
4
+
5
+ __all__ = ["Bedrock"]
@@ -0,0 +1,163 @@
1
+ """External Bedrock hosting; semantic codecs and AWS authentication stay separate."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Mapping
7
+ from copy import deepcopy
8
+ from dataclasses import dataclass
9
+ from dataclasses import field
10
+ from typing import Any
11
+
12
+ from ocai.bedrock.outcomes import error_mapping
13
+ from ocai.bedrock.outcomes import response_mapping
14
+ from ocai.core import UnsupportedFeatureError
15
+ from ocai.wire.hosting import Custom
16
+ from ocai.wire.hosting import EndpointSpec
17
+ from ocai.wire.hosting import EnvelopeTransform
18
+ from ocai.wire.outcomes import HostErrorMapper
19
+ from ocai.wire.outcomes import HostResponseAdapter
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class _InvokeEnvelope:
24
+ spec: EndpointSpec
25
+ beta: tuple[str, ...]
26
+ guardrail_config: Mapping[str, Any] | None
27
+
28
+ def wrap(self, payload: dict[str, Any]) -> dict[str, Any]:
29
+ result = deepcopy(payload)
30
+ if result.get("stream"):
31
+ raise UnsupportedFeatureError("Bedrock streaming is not qualified yet")
32
+ if "model" in result and result["model"] != self.spec.resource:
33
+ raise ValueError("payload model conflicts with the endpoint resource")
34
+ result.pop("model", None)
35
+ result.pop("stream", None)
36
+ additions: dict[str, Any] = dict(self.spec.traits.body_fields("bedrock"))
37
+ if self.beta:
38
+ if "anthropic_beta" in additions and additions["anthropic_beta"] != list(self.beta):
39
+ raise ValueError("conflicting Bedrock beta declarations")
40
+ additions["anthropic_beta"] = list(self.beta)
41
+ if self.guardrail_config is not None:
42
+ additions["amazon-bedrock-guardrailConfig"] = deepcopy(dict(self.guardrail_config))
43
+ for key, value in additions.items():
44
+ if key in result and result[key] != value:
45
+ raise ValueError(f"conflicting Bedrock body declaration: {key}")
46
+ result[key] = value
47
+ return result
48
+
49
+ def unwrap(self, payload: dict[str, Any]) -> dict[str, Any]:
50
+ return payload
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Bedrock:
55
+ """Host the qualified Anthropic Messages unary surface on InvokeModel.
56
+
57
+ Region and credentials are explicit; this value performs no discovery or I/O.
58
+ Pass it to ocai.chat.model with a separately supplied ocai credential. Optional
59
+ endpoint_url is explicit deployment configuration, not a discovery mechanism.
60
+ """
61
+
62
+ region: str
63
+ endpoint_url: str | None = None
64
+ beta: tuple[str, ...] = ()
65
+ guardrail_identifier: str | None = None
66
+ guardrail_version: str | None = None
67
+ guardrail_config: Mapping[str, Any] | None = None
68
+ trace: str | None = None
69
+ performance_latency: str | None = None
70
+ service_tier: str | None = None
71
+ static_headers: Mapping[str, str] = field(default_factory=dict[str, str])
72
+ profile_aliases: Mapping[str, str] = field(default_factory=dict[str, str])
73
+
74
+ def __post_init__(self) -> None:
75
+ """Validate explicit configuration and detach caller-owned maps."""
76
+ if not re.fullmatch(r"[a-z]{2}(?:-[a-z0-9]+)+-[0-9]+", self.region):
77
+ raise ValueError("an explicit AWS region name is required")
78
+ if (self.guardrail_identifier is None) != (self.guardrail_version is None):
79
+ raise ValueError("guardrail identifier and version must be supplied together")
80
+ if self.guardrail_config is not None and self.guardrail_identifier is None:
81
+ raise ValueError("guardrail body configuration requires identifier and version")
82
+ if any(
83
+ key.lower() in {"anthropic-version", "anthropic-beta"} for key in self.static_headers
84
+ ):
85
+ raise ValueError("use declared body versions and beta, not static Anthropic headers")
86
+ object.__setattr__(self, "beta", tuple(self.beta))
87
+ object.__setattr__(self, "static_headers", dict(self.static_headers))
88
+ object.__setattr__(self, "profile_aliases", dict(self.profile_aliases))
89
+ object.__setattr__(self, "guardrail_config", deepcopy(self.guardrail_config))
90
+ self._target()
91
+
92
+ def _target(self) -> Custom:
93
+ suffix = "amazonaws.com.cn" if self.region.startswith("cn-") else "amazonaws.com"
94
+ origin = self.endpoint_url or f"https://bedrock-runtime.{self.region}.{suffix}"
95
+ headers = dict(self.static_headers)
96
+ headers.setdefault("accept", "application/json")
97
+ for name, value in (
98
+ ("x-amzn-bedrock-guardrailidentifier", self.guardrail_identifier),
99
+ ("x-amzn-bedrock-guardrailversion", self.guardrail_version),
100
+ ("x-amzn-bedrock-trace", self.trace),
101
+ ("x-amzn-bedrock-performanceconfig-latency", self.performance_latency),
102
+ ("x-amzn-bedrock-service-tier", self.service_tier),
103
+ ):
104
+ if value is not None:
105
+ if name in headers and headers[name] != value:
106
+ raise ValueError(f"conflicting Bedrock header declaration: {name}")
107
+ headers[name] = value
108
+ return Custom(
109
+ origin,
110
+ paths={"generative": "/model/{resource}/invoke"},
111
+ static_headers=headers,
112
+ profile_aliases=self.profile_aliases,
113
+ )
114
+
115
+ @staticmethod
116
+ def _qualify(spec: EndpointSpec) -> None:
117
+ if spec.op.streaming:
118
+ raise UnsupportedFeatureError("Bedrock streaming is not qualified yet")
119
+ if (spec.dialect.provider, spec.dialect.interface, spec.op.kind) != (
120
+ "anthropic",
121
+ "messages",
122
+ "generative",
123
+ ):
124
+ raise UnsupportedFeatureError("only Anthropic Messages InvokeModel is qualified")
125
+ if spec.traits.host_body_versions.get("bedrock") is None:
126
+ raise UnsupportedFeatureError("dialect has no explicit Bedrock version declaration")
127
+
128
+ def endpoint(self, spec: EndpointSpec) -> str:
129
+ """Place the unchanged resource in one encoded InvokeModel path segment."""
130
+ self._qualify(spec)
131
+ return self._target().endpoint(spec)
132
+
133
+ def headers(self, spec: EndpointSpec) -> dict[str, str]:
134
+ """Use trait-declared relocations and explicit non-secret host headers."""
135
+ self._qualify(spec)
136
+ result = self._target().headers(spec)
137
+ relocated = {key.lower() for key in spec.traits.version_headers}
138
+ relocated.update(
139
+ rule.header for rule in spec.traits.host_feature_body_mappings.get("bedrock", ())
140
+ )
141
+ return {key: value for key, value in result.items() if key.lower() not in relocated}
142
+
143
+ def envelope(self, spec: EndpointSpec) -> EnvelopeTransform:
144
+ """Relocate provider-bound resource/version fields without copying codecs."""
145
+ self._qualify(spec)
146
+ config = self.guardrail_config
147
+ if self.guardrail_identifier is not None and config is None:
148
+ config = dict[str, Any]()
149
+ return _InvokeEnvelope(spec, self.beta, config)
150
+
151
+ def profile_key(self, resource: str) -> str:
152
+ """Use explicit aliases only; a cloud model identifier is not qualification."""
153
+ return self._target().profile_key(resource)
154
+
155
+ def error_mapper(self, spec: EndpointSpec) -> HostErrorMapper:
156
+ """Select public native error adaptation for this qualified operation."""
157
+ self._qualify(spec)
158
+ return error_mapping()
159
+
160
+ def response_adapter(self, spec: EndpointSpec) -> HostResponseAdapter:
161
+ """Select declared unary metadata and guardrail effects."""
162
+ self._qualify(spec)
163
+ return response_mapping()
@@ -0,0 +1,199 @@
1
+ """Bedrock-native outcome mapping behind provider-neutral host contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ocai.convert import DeclaredDisposition
8
+ from ocai.convert.host_effects import HostEffectContract
9
+ from ocai.core import AuthError
10
+ from ocai.core import ContentFilterError
11
+ from ocai.core import Disposition
12
+ from ocai.core import FieldLoss
13
+ from ocai.core import OcaiError
14
+ from ocai.core import RateLimitError
15
+ from ocai.core import SemanticShift
16
+ from ocai.core import TransientError
17
+ from ocai.core.ir import is_json_array
18
+ from ocai.core.ir import is_json_object
19
+ from ocai.wire.http import HttpResponse
20
+ from ocai.wire.outcomes import HostAdaptation
21
+ from ocai.wire.outcomes import HostErrorMapper
22
+ from ocai.wire.outcomes import HostResponseAdapter
23
+
24
+
25
+ def _native_error(error: OcaiError, options: dict[str, Any]) -> OcaiError | None:
26
+ headers = {key.lower(): value for key, value in (error.response_headers or {}).items()}
27
+ raw = error.raw or {}
28
+ native = headers.get("x-amzn-errortype") or raw.get("__type") or raw.get("code")
29
+ if not isinstance(native, str) or not native:
30
+ return None
31
+ name = native.split(":", 1)[0].rsplit("#", 1)[-1]
32
+ error_type: type[OcaiError] | None = None
33
+ if name in {"ThrottlingException", "ModelNotReadyException", "ServiceQuotaExceededException"}:
34
+ error_type = RateLimitError
35
+ elif name in {
36
+ "ServiceUnavailableException",
37
+ "InternalServerException",
38
+ "InternalFailure",
39
+ "ModelTimeoutException",
40
+ "ModelStreamErrorException",
41
+ "ModelErrorException",
42
+ }:
43
+ error_type = TransientError
44
+ elif name in {
45
+ "AccessDeniedException",
46
+ "ExpiredTokenException",
47
+ "UnrecognizedClientException",
48
+ "NotAuthorized",
49
+ "IncompleteSignature",
50
+ "InvalidSignatureException",
51
+ }:
52
+ error_type = AuthError
53
+ if error_type is None:
54
+ error.code = native
55
+ return error
56
+ return error_type("Bedrock request failed", code=native)
57
+
58
+
59
+ def error_mapping() -> HostErrorMapper:
60
+ """Build a detached native-code classifier; no status-only provider guessing."""
61
+ return HostErrorMapper("bedrock.invoke-errors@1", _native_error)
62
+
63
+
64
+ def _assessment_actions(assessment: object) -> set[str]:
65
+ actions: set[str] = set()
66
+ if not is_json_object(assessment):
67
+ return actions
68
+ lanes = {
69
+ "topicPolicy": ("topics",),
70
+ "contentPolicy": ("filters",),
71
+ "wordPolicy": ("customWords", "managedWordLists"),
72
+ "sensitiveInformationPolicy": ("piiEntities", "regexes"),
73
+ }
74
+ for policy, names in lanes.items():
75
+ record = assessment.get(policy)
76
+ if not is_json_object(record):
77
+ continue
78
+ for name in names:
79
+ values = record.get(name)
80
+ if is_json_array(values):
81
+ actions.update(
82
+ value["action"]
83
+ for value in values
84
+ if is_json_object(value) and isinstance(value.get("action"), str)
85
+ )
86
+ return actions
87
+
88
+
89
+ def _actions(trace: object) -> tuple[set[str], set[str]]:
90
+ """Read documented assessment lanes only; modelOutput is never classified."""
91
+ if not is_json_object(trace) or not is_json_object(guardrail := trace.get("guardrail")):
92
+ return set(), set()
93
+ input_actions: set[str] = set()
94
+ input_root = guardrail.get("input")
95
+ if is_json_object(input_root):
96
+ for assessment in input_root.values():
97
+ input_actions.update(_assessment_actions(assessment))
98
+ output_actions: set[str] = set()
99
+ outputs = guardrail.get("outputs")
100
+ for root in outputs if is_json_array(outputs) else []:
101
+ if is_json_object(root):
102
+ for assessment in root.values():
103
+ output_actions.update(_assessment_actions(assessment))
104
+ return input_actions, output_actions
105
+
106
+
107
+ def _response(
108
+ response: HttpResponse, payload: dict[str, Any], options: dict[str, Any]
109
+ ) -> HostAdaptation:
110
+ metadata: dict[str, Any] = {"body": {}, "headers": {}}
111
+ losses: list[FieldLoss] = []
112
+ for key in tuple(payload):
113
+ if key.startswith("amazon-bedrock-"):
114
+ metadata["body"][key] = payload.pop(key)
115
+ losses.append(
116
+ FieldLoss(
117
+ f"body[{key}]", Disposition.RENAMED, note="Preserved in host evidence metadata"
118
+ )
119
+ )
120
+ for key, value in response.headers.items():
121
+ if key.lower() in {
122
+ "x-amzn-bedrock-service-tier",
123
+ "x-amzn-bedrock-performanceconfig-latency",
124
+ }:
125
+ metadata["headers"][key.lower()] = value
126
+ losses.append(
127
+ FieldLoss(
128
+ f"headers[{key.lower()}]",
129
+ Disposition.RENAMED,
130
+ note="Preserved in host evidence metadata",
131
+ )
132
+ )
133
+ action = metadata["body"].get("amazon-bedrock-guardrailAction")
134
+ trace = metadata["body"].get("amazon-bedrock-trace")
135
+ shifts: list[SemanticShift] = []
136
+ error: OcaiError | None = None
137
+ if action == "INTERVENED":
138
+ input_actions, output_actions = _actions(trace)
139
+ if "BLOCKED" in input_actions | output_actions:
140
+ shifts.append(
141
+ SemanticShift(
142
+ "bedrock.guardrail_blocked",
143
+ "response",
144
+ "An explicit guardrail assessment blocked the invocation",
145
+ )
146
+ )
147
+ error = ContentFilterError(
148
+ "Bedrock guardrail blocked the invocation", code="guardrail_intervened"
149
+ )
150
+ elif "ANONYMIZED" in output_actions:
151
+ shifts.append(
152
+ SemanticShift(
153
+ "bedrock.masked_output",
154
+ "response",
155
+ "An explicit guardrail assessment anonymized output",
156
+ )
157
+ )
158
+ else:
159
+ shifts.append(
160
+ SemanticShift(
161
+ "bedrock.guardrail_intervened",
162
+ "response",
163
+ "Intervention is known; its exact effect is unqualified",
164
+ )
165
+ )
166
+ elif action is not None and action != "NONE":
167
+ shifts.append(
168
+ SemanticShift(
169
+ "bedrock.guardrail_intervened",
170
+ "response",
171
+ "Unknown guardrail action preserved without interpreting it",
172
+ )
173
+ )
174
+ return HostAdaptation(payload, metadata, tuple(losses), tuple(shifts), error)
175
+
176
+
177
+ def response_mapping() -> HostResponseAdapter:
178
+ """Declare unary host metadata and effects without changing Anthropic edges."""
179
+ contract = HostEffectContract(
180
+ census={
181
+ "body[]": DeclaredDisposition(
182
+ Disposition.RENAMED,
183
+ "Only amazon-bedrock-* body members move into HostEvidence.metadata",
184
+ ),
185
+ "headers[]": DeclaredDisposition(
186
+ Disposition.RENAMED,
187
+ "Selected service-tier/performance headers copy into HostEvidence.metadata",
188
+ ),
189
+ },
190
+ semantic_shifts=tuple(
191
+ SemanticShift(kind, "response")
192
+ for kind in (
193
+ "bedrock.guardrail_blocked",
194
+ "bedrock.masked_output",
195
+ "bedrock.guardrail_intervened",
196
+ )
197
+ ),
198
+ )
199
+ return HostResponseAdapter("bedrock", "bedrock.invoke-response@1", _response, contract)
File without changes