Metadata-Version: 2.4
Name: whiteroom-bedrock
Version: 0.1.0
Summary: Attaches WhiteRoom session attestation to a boto3 bedrock-runtime client
License: BSL-1.1
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: botocore>=1.34
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: boto3>=1.34; extra == "test"

# whiteroom-bedrock

Client-side half of WhiteRoom's Bedrock adapter (server side lives in
`apps/engine/src/bedrock/`).

An unmodified AWS SDK has no way to prove its identity to WhiteRoom the way
`ANTHROPIC_BASE_URL`/`OPENAI_BASE_URL` callers do with a bearer key — every
Bedrock call is SigV4-signed by the caller's own credentials, and WhiteRoom
discards that signature (it re-signs outbound with its own assumed-role
credentials). Proving who called instead happens once, out of band, by
replaying a signed `sts:GetCallerIdentity` probe to WhiteRoom's `/attest`
endpoint. This package does that handshake and attaches the resulting
session token to every subsequent call, so the rest of your agent code never
has to know WhiteRoom is in the loop.

## Install

```bash
pip install -e sdks/bedrock-python
```

## Test

```bash
pip install -e "sdks/bedrock-python[test]"
pytest sdks/bedrock-python/tests
```

No network access or AWS credentials needed — everything's mocked at the
botocore/urllib layer. `tests/test_attest.py` is the one worth reading
closely: it directly checks the signed probe has `host`,
`x-whiteroom-server-id` and `x-amz-date` in `SignedHeaders`, which is
exactly what the server (`attest.ts`) requires — a change here that broke
that would otherwise only surface as every real attestation failing
against a live proxy.

## Use

```python
import boto3
import whiteroom_bedrock

client = boto3.client(
    "bedrock-runtime",
    region_name="us-east-1",
    endpoint_url="https://proxy.whiteroom.tech",
)
whiteroom_bedrock.install(
    client,
    proxy_base_url="https://proxy.whiteroom.tech",
    server_id="proxy.whiteroom.tech",  # must match the proxy's configured serverId
    region="us-east-1",
)

# Everything from here down is a normal bedrock-runtime client.
response = client.converse(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    messages=[{"role": "user", "content": [{"text": "hi"}]}],
)
```

## What `install()` actually does

- On first call (and again whenever the cached session goes stale), signs a
  throwaway `GetCallerIdentity` request with whatever credentials this
  process already resolves for AWS (env vars, shared config, an instance or
  task role — the standard botocore chain) and POSTs it to `/attest`.
- Attaches the session token WhiteRoom hands back as `x-whiteroom-session`
  on every `invoke_model`/`converse` call, via a `before-send` hook — after
  signing, so it never interferes with the client's own (ultimately
  discarded) signature.
- If the proxy ever answers 403 — the session expired, or WhiteRoom
  restarted and lost its in-memory session table — invalidates the cached
  token and forces exactly one retry, so callers don't see a transient
  session death as a failure.

## Not handled here

- **Streaming.** `invoke_model_with_response_stream` and
  `converse_stream` reach the proxy the same as any other call, but the
  server actively refuses to proxy streaming responses today (see
  `routes.ts`) — you'll get a `ValidationException` back, not a stream.
- **Credential source mismatch.** `region` and credentials are resolved
  from the default botocore chain unless you pass `region=`/
  `botocore_session=` explicitly. If the client you're hooking was built
  with credentials that *don't* come from that chain (e.g. explicit
  `aws_access_key_id=` on `boto3.client(...)`), attest with the same
  credentials by passing a `botocore.session.Session` that resolves them.
