Skip to content

feat(bedrock): cross-region + Converse compression; bundle proxy binary in images#999

Merged
JerrettDavis merged 7 commits into
headroomlabs-ai:mainfrom
ysheikh2:feat/bedrock-compression-eventstream-fixes
Jun 16, 2026
Merged

feat(bedrock): cross-region + Converse compression; bundle proxy binary in images#999
JerrettDavis merged 7 commits into
headroomlabs-ai:mainfrom
ysheikh2:feat/bedrock-compression-eventstream-fixes

Conversation

@ysheikh2

@ysheikh2 ysheikh2 commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Description

The native Bedrock path (Phase D) compresses + signs Anthropic-on-Bedrock requests, but
two real-world cases slipped through, and the native binary that powers it was never
shipped. This PR closes those gaps as a focused set of give-backs.

Aligns with the Rust migration plan (see below).

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change
  • Documentation update
  • Performance improvement
  • Code refactoring (no functional changes)

Changes Made

  • Cross-region inference-profile detection via a new bedrock::vendor module
    (canonical_vendor()), following the design proposed in [FEATURE] Bedrock-native compression parity for non-Anthropic vendors (Amazon Nova, ZAI/GLM, MiniMax, Kimi) + cross-region inference profile fix #953: strip a known geo prefix
    (eu./us./apac./global.) then match the canonical vendor. Geo-prefixed Anthropic
    profiles (eu.anthropic.…) now get live-zone compression instead of being silently
    skipped; geo-prefixed non-Anthropic vendors stay correctly excluded.
  • Converse-body compression (two parts):
    1. run_anthropic_compression no longer bails to passthrough when the body lacks an
      InvokeModel anthropic_version envelope; envelope re-emit stays gated on successful parse.
    2. The live-zone dispatcher now recognizes Bedrock Converse content blocks. Converse
      blocks carry no type discriminator (the variant is the key: {"text": …} vs
      Anthropic's {"type":"text","text":…}), so real Converse user-message text was still
      passing through uncompressed. A typeless block whose text is a JSON string now routes
      through the same surgical text path. Anthropic blocks always carry type, so the
      Anthropic path is byte-for-byte unchanged; non-text Converse blocks ({"image":…},
      {"toolUse":…}) stay unrecognized and no-op.
  • Correct /converse upstream routing: the non-streaming handler resolved the upstream
    action from a hard-coded "invoke", so /converse requests were forwarded to Bedrock's
    /invoke endpoint. It now resolves the action from the inbound path (extract_invoke_action),
    mirroring the streaming handler's extract_streaming_action. SigV4 signs the same URL it
    forwards, so the signature stays consistent.
  • aws-config sso feature: SSO profiles now resolve through the default credential
    chain for SigV4 — the credential chain in docs/bedrock.md already promised SSO; this
    makes the code match.
  • Ship the headroom-proxy binary in published images (Dockerfile): built in the
    builder stage (--locked, with the cargo registry cache mounted at CARGO_HOME) and
    copied into both the debian and distroless runtime images.
  • Docs (docs/bedrock.md): document cross-region inference profiles and a "Running the
    proxy" section. AWS credentials mount at /home/nonroot/.aws (the default nonroot image
    home) where the SDK looks for ~/.aws, with a note on the root-image alternative.

Related issues

Alignment with the Rust migration plan

Per docs/spec/022-rust-migration.md, the migration is proxy-first: headroom-proxy is
the deployable Rust artifact, native routes replace Python passthroughs one at a time
(Stage 4 = provider expansion, Bedrock included), and the binary is meant to be "built,
tested, and released together with the Python package." Two ways this PR advances that:

  • The binary-in-images change makes the codebase do what the spec already states (ship the
    artifact) — closing the gap that forced downstreams to build from source.
  • Hardening the native Bedrock route (cross-region, Converse routing + body compression) is
    exactly the Stage-4 provider-expansion work, keeping the native path at parity with real
    traffic so it can be the default rather than a passthrough.

Testing

  • Unit tests pass (cargo test -p headroom-core -p headroom-proxy — full suites, 0 failures)
  • Linting passes (cargo clippy -p headroom-core -p headroom-proxy --all-targets -- -D warnings)
  • Formatting passes (cargo fmt -- --check)
  • New tests added — bedrock::vendor (foundation + inference-profile matching),
    extract_invoke_action + converse upstream URL, and live-zone Converse text-block routing
    (block_has_string_text_field, converse-vs-anthropic dispatch equivalence).
  • Manual testing performed

Test Output

$ cargo test -p headroom-core -p headroom-proxy   # all suites: ok, 0 failed
$ cargo clippy -p headroom-core -p headroom-proxy --all-targets -- -D warnings   # Finished, no warnings
$ cargo fmt -- --check                            # clean
# image validation (local, proxy/code extras):
$ docker build --target runtime ...      # debian: /usr/local/bin/headroom-proxy, --help OK
$ docker build --target runtime-slim ... # distroless: binary links + --help OK

Real Behavior Proof

  • Environment: native Bedrock proxy against bedrock-runtime.eu-west-2, SSO profile, model
    eu.anthropic.claude-haiku-4-5-20251001-v1:0.
  • Exact command / steps: POST a large multi-turn Converse body to
    /model/eu.anthropic.claude-haiku-4-5-20251001-v1:0/converse; separately build the
    runtime + runtime-slim targets and run /usr/local/bin/headroom-proxy --help.
  • Observed result: before — bedrock_compression_skipped (geo-prefixed id not recognized),
    forwarded uncompressed to the wrong /invoke upstream; after — geo-prefixed id recognized,
    /converse forwarded to the /converse upstream, live-zone dispatcher compresses the
    Converse user-message text, measurable token savings. Images contain a runnable
    headroom-proxy in both variants.
  • Not tested: non-Anthropic vendor compression parity ([FEATURE] Bedrock-native compression parity for non-Anthropic vendors (Amazon Nova, ZAI/GLM, MiniMax, Kimi) + cross-region inference profile fix #953 follow-up); Converse
    toolResult nested-text compression (follow-up — only top-level Converse text blocks
    compress today).

Review Readiness

  • I have performed a self-review
  • This PR is ready for human review

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective
  • New and existing unit tests pass locally with my changes
  • I have updated the CHANGELOG.md if applicable

Additional Notes

  • An earlier revision flipped the EventStream Accept default (*/*/absent → passthrough);
    dropped*/* is what most clients (incl. reqwest and the proxy's own metrics tests)
    send while expecting SSE, so forcing passthrough breaks the standard SSE path.
  • The binary build adds the native-proxy compile to the image build; happy to gate it behind
    a build arg if maintainers prefer it opt-in.
  • Addressed a Copilot review round: corrected the /converse upstream routing, the stale
    run_anthropic_compression comment, the Dockerfile cargo cache mount + --locked, and the
    nonroot AWS-credentials docs example.

@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

PR governance

This PR follows the template and is marked ready for human review.

@github-actions github-actions Bot added the status: needs author action Pull request body or readiness checklist still needs author updates label Jun 14, 2026
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ysheikh2 ysheikh2 force-pushed the feat/bedrock-compression-eventstream-fixes branch from 345955e to 476333d Compare June 15, 2026 06:14
@ysheikh2 ysheikh2 changed the title feat(bedrock): native compression for cross-region inference profiles, Converse bodies & EventStream defaults feat(bedrock): compress cross-region inference profiles and Converse bodies Jun 15, 2026
@github-actions github-actions Bot added status: ci failing Required or reported CI checks are failing and removed status: needs author action Pull request body or readiness checklist still needs author updates status: ci failing Required or reported CI checks are failing labels Jun 15, 2026
@ysheikh2 ysheikh2 changed the title feat(bedrock): compress cross-region inference profiles and Converse bodies feat(bedrock): cross-region + Converse compression; bundle proxy binary in images Jun 15, 2026
@ysheikh2 ysheikh2 marked this pull request as ready for review June 15, 2026 06:49
@github-actions github-actions Bot added the status: ready for review Pull request body is complete and the author marked it ready for human review label Jun 15, 2026
@ysheikh2 ysheikh2 marked this pull request as draft June 15, 2026 08:19
@github-actions github-actions Bot added status: ci failing Required or reported CI checks are failing and removed status: ready for review Pull request body is complete and the author marked it ready for human review status: ci failing Required or reported CI checks are failing labels Jun 15, 2026
@ysheikh2 ysheikh2 marked this pull request as ready for review June 15, 2026 09:13
@github-actions github-actions Bot added status: needs author action Pull request body or readiness checklist still needs author updates status: ready for review Pull request body is complete and the author marked it ready for human review status: ci failing Required or reported CI checks are failing and removed status: needs author action Pull request body or readiness checklist still needs author updates status: ready for review Pull request body is complete and the author marked it ready for human review labels Jun 15, 2026
ysheikh2 added a commit to ysheikh2/headroom that referenced this pull request Jun 15, 2026
Cross-region inference-profile detection, Converse-body compression,
SSO credential fix, and headroom-proxy binary in published images.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added status: ready for review Pull request body is complete and the author marked it ready for human review and removed status: ci failing Required or reported CI checks are failing labels Jun 15, 2026
ysheikh2 added a commit to ysheikh2/headroom that referenced this pull request Jun 15, 2026
Cross-region inference-profile detection, Converse-body compression,
SSO credential fix, and headroom-proxy binary in published images.
@ysheikh2 ysheikh2 force-pushed the feat/bedrock-compression-eventstream-fixes branch from b2161c3 to 598c3a2 Compare June 15, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens and extends the Rust headroom-proxy Bedrock-native path by improving model-id vendor detection (including cross-region inference profiles), broadening request-body compression to cover Converse-shaped bodies, enabling AWS SSO credential resolution, and shipping the headroom-proxy binary inside the published Docker images. It also updates Bedrock operator docs and the changelog to reflect the new capabilities.

Changes:

  • Add canonical Bedrock vendor resolution (bedrock::vendor) and use it to recognize geo-prefixed Anthropic inference profiles.
  • Adjust Bedrock Anthropic compression to proceed even when the body isn’t a parseable InvokeModel envelope (while gating envelope re-emit on successful parse).
  • Enable aws-config SSO support and bundle the headroom-proxy binary into runtime and runtime-slim Docker images; update docs + changelog accordingly.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
docs/bedrock.md Adds “Running the proxy” instructions and documents cross-region inference-profile handling.
Dockerfile Builds headroom-proxy in the builder stage and copies it into runtime images.
crates/headroom-proxy/src/bedrock/vendor.rs New module to resolve canonical vendor from geo-prefixed model IDs + tests.
crates/headroom-proxy/src/bedrock/mod.rs Exposes the new vendor module.
crates/headroom-proxy/src/bedrock/invoke.rs Uses canonical vendor check and adjusts envelope-parse behavior for compression/re-emit.
crates/headroom-proxy/src/bedrock/invoke_streaming.rs Uses canonical vendor check and adjusts envelope-parse behavior for streaming compression/re-emit.
CHANGELOG.md Documents the new Bedrock features, Docker packaging change, and SSO fix.
Cargo.toml Enables aws-config sso feature.
Cargo.lock Locks new AWS SDK deps pulled in by SSO support.

Comment thread crates/headroom-proxy/src/bedrock/invoke.rs Outdated
Comment thread Dockerfile Outdated
Comment thread docs/bedrock.md
Comment thread crates/headroom-proxy/src/bedrock/invoke.rs
ysheikh2 added 6 commits June 16, 2026 09:49
…bodies

Two gaps in the native Bedrock compression path, plus an SSO build fix:

1. Cross-region inference-profile detection (addresses part of headroomlabs-ai#953). Anthropic
   models were detected by the bare `anthropic.` prefix only, so geo-prefixed
   inference-profile IDs (`eu.anthropic.`, `us.anthropic.`, `global.anthropic.`)
   silently skipped live-zone compression. Add `is_anthropic_model_id()` matching
   both the prefix and the `.anthropic.` segment, used by invoke + invoke_streaming.

2. Converse-body compression. `run_anthropic_compression` returned the body
   unchanged whenever it was not a parseable InvokeModel envelope, so Converse-shaped
   bodies were never compressed. Gate the envelope re-emit on a successful parse and
   fall through to generic Anthropic compression otherwise.

3. aws-config `sso` feature so SSO profiles resolve through the default credential
   chain for SigV4 signing.

Tests: add anthropic_model_id_matches_cross_region_inference_profiles. Full
headroom-proxy suite (225 lib + all integration tests) passes; rustfmt + clippy clean.
The published images already run "the proxy" and carry the `proxy` extra,
but only shipped the Python `headroom` CLI — the native Rust `headroom-proxy`
reverse proxy (SigV4 Bedrock + live-zone compression) was built in CI yet
never included in any image or release artifact (issue headroomlabs-ai#976).

Build the `headroom-proxy` binary in the builder stage and COPY it into both
the debian (`runtime-slim-base`) and distroless (`runtime-slim`) runtime
images at `/usr/local/bin/headroom-proxy`. Verified it is present and runs
(`--help`) in both the `:code` (debian) and distroless `slim` variants.

This lets operators front the Python proxy with the native Rust path from a
single published image instead of building it from source.
…ed binary

Keep the operator guide consistent with the code changes in this PR:

- "Supported model IDs" now documents that geo-prefixed cross-region inference
  profiles (`eu.anthropic.`, `us.anthropic.`, `apac.anthropic.`, `global.anthropic.`)
  are matched as Anthropic-shape, alongside the bare `anthropic.` prefix — and that
  geo-prefixed non-Anthropic vendors still skip compression.
- New "Running the proxy" section: the `headroom-proxy` binary now ships in the
  published images at `/usr/local/bin/headroom-proxy`, so operators can run the native
  Bedrock surface straight from a published tag (or front the Python proxy with it)
  instead of building from source.
Replace the per-handler `is_anthropic_model_id` (and its `.contains(".anthropic.")`
heuristic) with a shared `bedrock::vendor` module, following the design proposed in
issue headroomlabs-ai#953: strip a known cross-region inference-profile geo prefix (`eu.`, `us.`,
`apac.`, `global.`) then take the leading dot-segment as the canonical vendor.

This is more precise than a substring match (an unrelated `something.anthropic.x` id is
not mistaken for an Anthropic profile) and gives a single reusable vendor resolver for the
non-Anthropic vendor-parity follow-up in headroomlabs-ai#953. Tests move to `bedrock::vendor`; docs
updated to describe the strip-then-match rule.
Cross-region inference-profile detection, Converse-body compression,
SSO credential fix, and headroom-proxy binary in published images.
…ment

- Route /converse to the upstream Converse endpoint: handle_invoke now
  resolves the action from the inbound path via extract_invoke_action,
  mirroring invoke_streaming::extract_streaming_action, instead of the
  hard-coded "invoke". Adds unit tests for path resolution and URL build.
- Dockerfile: point the cargo registry cache mount at CARGO_HOME
  (/usr/local/cargo/registry) so it is actually effective, and add
  --locked for reproducible builds.
- docs/bedrock.md: mount AWS creds at /home/nonroot/.aws (the default
  nonroot image home) instead of /root/.aws; note the root alternative.
- invoke.rs: correct the stale run_anthropic_compression comment that
  claimed parse-failure bodies are forwarded verbatim.
@ysheikh2 ysheikh2 force-pushed the feat/bedrock-compression-eventstream-fixes branch from 598c3a2 to 783516b Compare June 16, 2026 07:54
The live-zone dispatcher only recognized Anthropic content blocks
(`{"type":"text","text":…}`). Bedrock Converse blocks carry no
`type` discriminator — the variant is the key (`{"text":…}`), so
real Converse user-message text was passing through uncompressed even
after the parse-gate was relaxed.

Route a typeless block whose `text` field is a JSON string through the
same surgical text path. Anthropic blocks always carry `type`, so the
Anthropic path is byte-for-byte unchanged; non-text Converse blocks
(`{"image":…}`, `{"toolUse":…}`) stay unrecognized and no-op.

Adds unit tests asserting Converse `{text}` blocks dispatch identically
to Anthropic text blocks and that non-text blocks are not misrouted.
@ysheikh2

Copy link
Copy Markdown
Contributor Author

ok pushed the review fixes and rebased on latest main. ready for another look. whats changed since last time:

  • /converse was getting forwarded to the /invoke upstream because the handler hardcoded the action. now it reads the action off the inbound path like the streaming handler already does, so converse goes to converse. sigv4 signs the same url we forward so no mismatch
  • found converse bodies weren't actually compressing either, their content blocks carry no type field so the live-zone dispatcher skipped them. it recognizes the typeless converse {text} shape now, so converse user text actually compresses (anthropic path is untouched since those always carry type)
  • dockerfile cache mount was pointing at the wrong path (CARGO_HOME is /usr/local/cargo), fixed + added --locked
  • docs: aws creds mount at /home/nonroot/.aws since the image is nonroot by default
  • fixed the stale comment in run_anthropic_compression

added tests for all of it (extract_invoke_action + converse url, the converse text-block routing, and the non-text blocks staying no-op). fmt, clippy -D warnings and cargo test --workspace all pass locally and CI's green (just the native docker e2e still finishing).

@JerrettDavis JerrettDavis merged commit 0dc2e1c into headroomlabs-ai:main Jun 16, 2026
46 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 16, 2026
chopratejas pushed a commit that referenced this pull request Jun 16, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>0.26.0</summary>

##
[0.26.0](v0.25.0...v0.26.0)
(2026-06-16)


### Features

* add Copilot BYOK provider wrapper utilities and CLI support
([#1041](#1041))
([e67ee2a](e67ee2a))
* add dashboard agent usage stats
([#814](#814))
([6d3f39f](6d3f39f))
* Add support for Mistral Vibe CLI
([#935](#935))
([0932b8b](0932b8b))
* attribute reread waste to over-compression via marker check
([#901](#901))
([f928576](f928576))
* **bedrock:** cross-region + Converse compression; bundle proxy binary
in images ([#999](#999))
([0dc2e1c](0dc2e1c))
* **dashboard:** surface compression-vs-cache net impact in Prefix Cache
panel ([#913](#913))
([2a4d300](2a4d300))
* **evals:** adversarial-input robustness grid for compressors
([#918](#918))
([5939004](5939004))
* **parser:** detect re-issued identical tool calls as reread waste
([#909](#909))
([7d4ae86](7d4ae86))
* **policy:** batch deep edits through one cache-bust
([#856](#856) P3a)
([#1015](#1015))
([c2e52fe](c2e52fe))
* **policy:** consume net-cost mutation gate in ContentRouter
([#856](#856) P2)
([#905](#905))
([553ade4](553ade4))
* **proxy:** compress AWS Bedrock InvokeModel requests via configurable
upstream ([#720](#720))
([7edb27a](7edb27a))


### Bug Fixes

* **anthropic:** strip styled Claude model ids
([#651](#651))
([0c5c89d](0c5c89d))
* **anyllm:** forward openai api_base/api_key to the any-llm backend
([#942](#942))
([#954](#954))
([a7ee8a6](a7ee8a6))
* **cache:** guard None exemplar embeddings in dynamic detector
([#950](#950))
([1ec9320](1ec9320))
* **cache:** name the missing piece in semantic detector guard
([#1018](#1018))
([3b0bcee](3b0bcee))
* **ci:** check out repo in PR Governance label job
([#1021](#1021))
([4558bc2](4558bc2))
* **ci:** make PR governance advisory
([#1047](#1047))
([74dff94](74dff94))
* **codex:** compute waste signals on the OpenAI Responses path
([#898](#898))
([b9e2761](b9e2761))
* **codex:** poll /wham/usage for subscription limits (handshake no
longer sends x-codex-* headers)
([#924](#924))
([8c00f71](8c00f71))
* **codex:** PR health label check state
([#986](#986))
([99c874d](99c874d))
* **codex:** retag thread providers so history menu stays whole across
the proxy boundary
([#1034](#1034))
([74ae781](74ae781))
* **codex:** write canonical hooks feature flag and migrate deprecated
codex_hooks ([#743](#743))
([dff6a19](dff6a19))
* **compression:** convert tree-sitter byte offsets to char offsets
([#892](#892))
([b1f700f](b1f700f))
* **compression:** correct JSON array item counting and entropy gate
([#887](#887))
([d6f0f0f](d6f0f0f))
* **compression:** keep container bodies compressible in code handler
([#890](#890))
([16ed73b](16ed73b))
* **compression:** measure short-value threshold on payload, not token
([#889](#889))
([65b0e8c](65b0e8c))
* **compression:** use thread-local tree-sitter parsers in code handler
([#893](#893))
([6cdb846](6cdb846))
* **gemini:** surface functionResponse payloads to waste-signal
detection ([#897](#897))
([9b0c840](9b0c840))
* **learn:** decode directory names with spaces in Windows project paths
([#997](#997))
([#1027](#1027))
([2d3701b](2d3701b))
* **learn:** scan subagent and workflow transcripts
([#1045](#1045))
([0ddd4ed](0ddd4ed))
* **openclaw:** declare headroom_retrieve tool contract
([#947](#947))
([7c8c909](7c8c909))
* **policy:** correct warm-cache penalty in net_mutation_gain to (S +
dT) ([#903](#903))
([0632eba](0632eba))
* **proxy:** add native Bedrock converse-stream route
([#917](#917))
([b08ec15](b08ec15))
* **proxy:** keep codex image-generation WS turns alive through the
relay ([#1000](#1000))
([7dbbb40](7dbbb40))
* **proxy:** make budget enforcement actually work
([#885](#885))
([a14ab45](a14ab45))
* **proxy:** read RTK gain stats globally by default
([#957](#957))
([b70fccb](b70fccb))
* route v1internal code assist requests to cloudcode-pa.googleapis…
([#821](#821))
([e20f16b](e20f16b))
* **serena:** stop the Serena dashboard popup and make --no-serena
actually disable Serena
([#1003](#1003))
([919379a](919379a))
* support Copilot Business subscription auth
([#641](#641))
([0b4a4bd](0b4a4bd))
* wire HEADROOM_EXCLUDE_TOOLS / HEADROOM_TOOL_PROFILES into Click proxy
entrypoint ([#943](#943))
([9b7b436](9b7b436))
* **wrap:** avoid duplicate top-level keys when injecting codex provider
([#884](#884))
([dd22cfd](dd22cfd))


### Code Refactoring

* DRY cache logic, add thread safety, fix Bash exclusion
([#704](#704))
([e36fccd](e36fccd))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Epicism pushed a commit to Epicism/headroom that referenced this pull request Jun 17, 2026
…ry in images (headroomlabs-ai#999)

## Description

The native Bedrock path (Phase D) compresses + signs
Anthropic-on-Bedrock requests, but
two real-world cases slipped through, and the native binary that powers
it was never
shipped. This PR closes those gaps as a focused set of give-backs.

Aligns with the Rust migration plan (see below).

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- **Cross-region inference-profile detection** via a new
`bedrock::vendor` module
(`canonical_vendor()`), following the design proposed in headroomlabs-ai#953: strip a
known geo prefix
(`eu.`/`us.`/`apac.`/`global.`) then match the canonical vendor.
Geo-prefixed Anthropic
profiles (`eu.anthropic.…`) now get live-zone compression instead of
being silently
  skipped; geo-prefixed non-Anthropic vendors stay correctly excluded.
- **Converse-body compression (two parts)**:
1. `run_anthropic_compression` no longer bails to passthrough when the
body lacks an
InvokeModel `anthropic_version` envelope; envelope re-emit stays gated
on successful parse.
2. The **live-zone dispatcher now recognizes Bedrock Converse content
blocks**. Converse
blocks carry no `type` discriminator (the variant is the key: `{"text":
…}` vs
Anthropic's `{"type":"text","text":…}`), so real Converse user-message
text was still
passing through uncompressed. A typeless block whose `text` is a JSON
string now routes
through the same surgical text path. Anthropic blocks always carry
`type`, so the
Anthropic path is byte-for-byte unchanged; non-text Converse blocks
(`{"image":…}`,
     `{"toolUse":…}`) stay unrecognized and no-op.
- **Correct `/converse` upstream routing**: the non-streaming handler
resolved the upstream
action from a hard-coded `"invoke"`, so `/converse` requests were
forwarded to Bedrock's
`/invoke` endpoint. It now resolves the action from the inbound path
(`extract_invoke_action`),
mirroring the streaming handler's `extract_streaming_action`. SigV4
signs the same URL it
  forwards, so the signature stays consistent.
- **`aws-config` `sso` feature**: SSO profiles now resolve through the
default credential
chain for SigV4 — the credential chain in `docs/bedrock.md` already
promised SSO; this
  makes the code match.
- **Ship the `headroom-proxy` binary in published images**
(`Dockerfile`): built in the
builder stage (`--locked`, with the cargo registry cache mounted at
`CARGO_HOME`) and
  copied into both the debian and distroless runtime images.
- **Docs** (`docs/bedrock.md`): document cross-region inference profiles
and a "Running the
proxy" section. AWS credentials mount at `/home/nonroot/.aws` (the
default nonroot image
home) where the SDK looks for `~/.aws`, with a note on the root-image
alternative.

## Related issues

- Closes headroomlabs-ai#976 — ship the `headroom-proxy` binary in published images
(this PR implements
  the exact fix proposed there).
- Addresses the **cross-region inference-profile** half of headroomlabs-ai#953 via its
proposed
`canonical_vendor()` design. Non-Anthropic vendor compression parity
(Nova/GLM/MiniMax/
Kimi) is the natural follow-up — `bedrock::vendor` is the shared
resolver it can build on.
- Extends the native Bedrock InvokeModel compression requested in headroomlabs-ai#734
(the Bedrock slice of
  headroomlabs-ai#510) to cross-region profiles and Converse bodies.
- Partially enables headroomlabs-ai#181 (native, Python-free packaging): the native
binary now ships in the
  images, though full Python-free distribution remains out of scope.

## Alignment with the Rust migration plan

Per `docs/spec/022-rust-migration.md`, the migration is **proxy-first**:
`headroom-proxy` is
the deployable Rust artifact, native routes replace Python passthroughs
one at a time
(Stage 4 = provider expansion, Bedrock included), and the binary is
meant to be "built,
tested, and **released together with the Python package**." Two ways
this PR advances that:

- The binary-in-images change makes the codebase do what the spec
already states (ship the
artifact) — closing the gap that forced downstreams to build from
source.
- Hardening the native Bedrock route (cross-region, Converse routing +
body compression) is
exactly the Stage-4 provider-expansion work, keeping the native path at
parity with real
  traffic so it can be the default rather than a passthrough.

## Testing

- [x] Unit tests pass (`cargo test -p headroom-core -p headroom-proxy` —
full suites, 0 failures)
- [x] Linting passes (`cargo clippy -p headroom-core -p headroom-proxy
--all-targets -- -D warnings`)
- [x] Formatting passes (`cargo fmt -- --check`)
- [x] New tests added — `bedrock::vendor` (foundation +
inference-profile matching),
`extract_invoke_action` + converse upstream URL, and live-zone Converse
text-block routing
(`block_has_string_text_field`, converse-vs-anthropic dispatch
equivalence).
- [x] Manual testing performed

### Test Output

```text
$ cargo test -p headroom-core -p headroom-proxy   # all suites: ok, 0 failed
$ cargo clippy -p headroom-core -p headroom-proxy --all-targets -- -D warnings   # Finished, no warnings
$ cargo fmt -- --check                            # clean
# image validation (local, proxy/code extras):
$ docker build --target runtime ...      # debian: /usr/local/bin/headroom-proxy, --help OK
$ docker build --target runtime-slim ... # distroless: binary links + --help OK
```

## Real Behavior Proof

- Environment: native Bedrock proxy against `bedrock-runtime.eu-west-2`,
SSO profile, model
  `eu.anthropic.claude-haiku-4-5-20251001-v1:0`.
- Exact command / steps: POST a large multi-turn Converse body to
`/model/eu.anthropic.claude-haiku-4-5-20251001-v1:0/converse`;
separately build the
`runtime` + `runtime-slim` targets and run
`/usr/local/bin/headroom-proxy --help`.
- Observed result: before — `bedrock_compression_skipped` (geo-prefixed
id not recognized),
forwarded uncompressed to the wrong `/invoke` upstream; after —
geo-prefixed id recognized,
`/converse` forwarded to the `/converse` upstream, live-zone dispatcher
compresses the
Converse user-message text, measurable token savings. Images contain a
runnable
  `headroom-proxy` in both variants.
- Not tested: non-Anthropic vendor compression parity (headroomlabs-ai#953 follow-up);
Converse
`toolResult` nested-text compression (follow-up — only top-level
Converse text blocks
  compress today).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- An earlier revision flipped the EventStream `Accept` default
(`*/*`/absent → passthrough);
**dropped** — `*/*` is what most clients (incl. reqwest and the proxy's
own metrics tests)
send while expecting SSE, so forcing passthrough breaks the standard SSE
path.
- The binary build adds the native-proxy compile to the image build;
happy to gate it behind
  a build arg if maintainers prefer it opt-in.
- Addressed a Copilot review round: corrected the `/converse` upstream
routing, the stale
`run_anthropic_compression` comment, the Dockerfile cargo cache mount +
`--locked`, and the
  nonroot AWS-credentials docs example.
studyzy pushed a commit to studyzy/headroom that referenced this pull request Jun 24, 2026
…ry in images (headroomlabs-ai#999)

## Description

The native Bedrock path (Phase D) compresses + signs
Anthropic-on-Bedrock requests, but
two real-world cases slipped through, and the native binary that powers
it was never
shipped. This PR closes those gaps as a focused set of give-backs.

Aligns with the Rust migration plan (see below).

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)
- [x] New feature (non-breaking change that adds functionality)
- [ ] Breaking change
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- **Cross-region inference-profile detection** via a new
`bedrock::vendor` module
(`canonical_vendor()`), following the design proposed in headroomlabs-ai#953: strip a
known geo prefix
(`eu.`/`us.`/`apac.`/`global.`) then match the canonical vendor.
Geo-prefixed Anthropic
profiles (`eu.anthropic.…`) now get live-zone compression instead of
being silently
  skipped; geo-prefixed non-Anthropic vendors stay correctly excluded.
- **Converse-body compression (two parts)**:
1. `run_anthropic_compression` no longer bails to passthrough when the
body lacks an
InvokeModel `anthropic_version` envelope; envelope re-emit stays gated
on successful parse.
2. The **live-zone dispatcher now recognizes Bedrock Converse content
blocks**. Converse
blocks carry no `type` discriminator (the variant is the key: `{"text":
…}` vs
Anthropic's `{"type":"text","text":…}`), so real Converse user-message
text was still
passing through uncompressed. A typeless block whose `text` is a JSON
string now routes
through the same surgical text path. Anthropic blocks always carry
`type`, so the
Anthropic path is byte-for-byte unchanged; non-text Converse blocks
(`{"image":…}`,
     `{"toolUse":…}`) stay unrecognized and no-op.
- **Correct `/converse` upstream routing**: the non-streaming handler
resolved the upstream
action from a hard-coded `"invoke"`, so `/converse` requests were
forwarded to Bedrock's
`/invoke` endpoint. It now resolves the action from the inbound path
(`extract_invoke_action`),
mirroring the streaming handler's `extract_streaming_action`. SigV4
signs the same URL it
  forwards, so the signature stays consistent.
- **`aws-config` `sso` feature**: SSO profiles now resolve through the
default credential
chain for SigV4 — the credential chain in `docs/bedrock.md` already
promised SSO; this
  makes the code match.
- **Ship the `headroom-proxy` binary in published images**
(`Dockerfile`): built in the
builder stage (`--locked`, with the cargo registry cache mounted at
`CARGO_HOME`) and
  copied into both the debian and distroless runtime images.
- **Docs** (`docs/bedrock.md`): document cross-region inference profiles
and a "Running the
proxy" section. AWS credentials mount at `/home/nonroot/.aws` (the
default nonroot image
home) where the SDK looks for `~/.aws`, with a note on the root-image
alternative.

## Related issues

- Closes headroomlabs-ai#976 — ship the `headroom-proxy` binary in published images
(this PR implements
  the exact fix proposed there).
- Addresses the **cross-region inference-profile** half of headroomlabs-ai#953 via its
proposed
`canonical_vendor()` design. Non-Anthropic vendor compression parity
(Nova/GLM/MiniMax/
Kimi) is the natural follow-up — `bedrock::vendor` is the shared
resolver it can build on.
- Extends the native Bedrock InvokeModel compression requested in headroomlabs-ai#734
(the Bedrock slice of
  headroomlabs-ai#510) to cross-region profiles and Converse bodies.
- Partially enables headroomlabs-ai#181 (native, Python-free packaging): the native
binary now ships in the
  images, though full Python-free distribution remains out of scope.

## Alignment with the Rust migration plan

Per `docs/spec/022-rust-migration.md`, the migration is **proxy-first**:
`headroom-proxy` is
the deployable Rust artifact, native routes replace Python passthroughs
one at a time
(Stage 4 = provider expansion, Bedrock included), and the binary is
meant to be "built,
tested, and **released together with the Python package**." Two ways
this PR advances that:

- The binary-in-images change makes the codebase do what the spec
already states (ship the
artifact) — closing the gap that forced downstreams to build from
source.
- Hardening the native Bedrock route (cross-region, Converse routing +
body compression) is
exactly the Stage-4 provider-expansion work, keeping the native path at
parity with real
  traffic so it can be the default rather than a passthrough.

## Testing

- [x] Unit tests pass (`cargo test -p headroom-core -p headroom-proxy` —
full suites, 0 failures)
- [x] Linting passes (`cargo clippy -p headroom-core -p headroom-proxy
--all-targets -- -D warnings`)
- [x] Formatting passes (`cargo fmt -- --check`)
- [x] New tests added — `bedrock::vendor` (foundation +
inference-profile matching),
`extract_invoke_action` + converse upstream URL, and live-zone Converse
text-block routing
(`block_has_string_text_field`, converse-vs-anthropic dispatch
equivalence).
- [x] Manual testing performed

### Test Output

```text
$ cargo test -p headroom-core -p headroom-proxy   # all suites: ok, 0 failed
$ cargo clippy -p headroom-core -p headroom-proxy --all-targets -- -D warnings   # Finished, no warnings
$ cargo fmt -- --check                            # clean
# image validation (local, proxy/code extras):
$ docker build --target runtime ...      # debian: /usr/local/bin/headroom-proxy, --help OK
$ docker build --target runtime-slim ... # distroless: binary links + --help OK
```

## Real Behavior Proof

- Environment: native Bedrock proxy against `bedrock-runtime.eu-west-2`,
SSO profile, model
  `eu.anthropic.claude-haiku-4-5-20251001-v1:0`.
- Exact command / steps: POST a large multi-turn Converse body to
`/model/eu.anthropic.claude-haiku-4-5-20251001-v1:0/converse`;
separately build the
`runtime` + `runtime-slim` targets and run
`/usr/local/bin/headroom-proxy --help`.
- Observed result: before — `bedrock_compression_skipped` (geo-prefixed
id not recognized),
forwarded uncompressed to the wrong `/invoke` upstream; after —
geo-prefixed id recognized,
`/converse` forwarded to the `/converse` upstream, live-zone dispatcher
compresses the
Converse user-message text, measurable token savings. Images contain a
runnable
  `headroom-proxy` in both variants.
- Not tested: non-Anthropic vendor compression parity (headroomlabs-ai#953 follow-up);
Converse
`toolResult` nested-text compression (follow-up — only top-level
Converse text blocks
  compress today).

## Review Readiness

- [x] I have performed a self-review
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective
- [x] New and existing unit tests pass locally with my changes
- [x] I have updated the CHANGELOG.md if applicable

## Additional Notes

- An earlier revision flipped the EventStream `Accept` default
(`*/*`/absent → passthrough);
**dropped** — `*/*` is what most clients (incl. reqwest and the proxy's
own metrics tests)
send while expecting SSE, so forcing passthrough breaks the standard SSE
path.
- The binary build adds the native-proxy compile to the image build;
happy to gate it behind
  a build arg if maintainers prefer it opt-in.
- Addressed a Copilot review round: corrected the `/converse` upstream
routing, the stale
`run_anthropic_compression` comment, the Dockerfile cargo cache mount +
`--locked`, and the
  nonroot AWS-credentials docs example.
studyzy pushed a commit to studyzy/headroom that referenced this pull request Jun 24, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>0.26.0</summary>

##
[0.26.0](headroomlabs-ai/headroom@v0.25.0...v0.26.0)
(2026-06-16)


### Features

* add Copilot BYOK provider wrapper utilities and CLI support
([headroomlabs-ai#1041](headroomlabs-ai#1041))
([e67ee2a](headroomlabs-ai@e67ee2a))
* add dashboard agent usage stats
([headroomlabs-ai#814](headroomlabs-ai#814))
([6d3f39f](headroomlabs-ai@6d3f39f))
* Add support for Mistral Vibe CLI
([headroomlabs-ai#935](headroomlabs-ai#935))
([0932b8b](headroomlabs-ai@0932b8b))
* attribute reread waste to over-compression via marker check
([headroomlabs-ai#901](headroomlabs-ai#901))
([f928576](headroomlabs-ai@f928576))
* **bedrock:** cross-region + Converse compression; bundle proxy binary
in images ([headroomlabs-ai#999](headroomlabs-ai#999))
([0dc2e1c](headroomlabs-ai@0dc2e1c))
* **dashboard:** surface compression-vs-cache net impact in Prefix Cache
panel ([headroomlabs-ai#913](headroomlabs-ai#913))
([2a4d300](headroomlabs-ai@2a4d300))
* **evals:** adversarial-input robustness grid for compressors
([headroomlabs-ai#918](headroomlabs-ai#918))
([5939004](headroomlabs-ai@5939004))
* **parser:** detect re-issued identical tool calls as reread waste
([headroomlabs-ai#909](headroomlabs-ai#909))
([7d4ae86](headroomlabs-ai@7d4ae86))
* **policy:** batch deep edits through one cache-bust
([headroomlabs-ai#856](headroomlabs-ai#856) P3a)
([headroomlabs-ai#1015](headroomlabs-ai#1015))
([c2e52fe](headroomlabs-ai@c2e52fe))
* **policy:** consume net-cost mutation gate in ContentRouter
([headroomlabs-ai#856](headroomlabs-ai#856) P2)
([headroomlabs-ai#905](headroomlabs-ai#905))
([553ade4](headroomlabs-ai@553ade4))
* **proxy:** compress AWS Bedrock InvokeModel requests via configurable
upstream ([headroomlabs-ai#720](headroomlabs-ai#720))
([7edb27a](headroomlabs-ai@7edb27a))


### Bug Fixes

* **anthropic:** strip styled Claude model ids
([headroomlabs-ai#651](headroomlabs-ai#651))
([0c5c89d](headroomlabs-ai@0c5c89d))
* **anyllm:** forward openai api_base/api_key to the any-llm backend
([headroomlabs-ai#942](headroomlabs-ai#942))
([headroomlabs-ai#954](headroomlabs-ai#954))
([a7ee8a6](headroomlabs-ai@a7ee8a6))
* **cache:** guard None exemplar embeddings in dynamic detector
([headroomlabs-ai#950](headroomlabs-ai#950))
([1ec9320](headroomlabs-ai@1ec9320))
* **cache:** name the missing piece in semantic detector guard
([headroomlabs-ai#1018](headroomlabs-ai#1018))
([3b0bcee](headroomlabs-ai@3b0bcee))
* **ci:** check out repo in PR Governance label job
([headroomlabs-ai#1021](headroomlabs-ai#1021))
([4558bc2](headroomlabs-ai@4558bc2))
* **ci:** make PR governance advisory
([headroomlabs-ai#1047](headroomlabs-ai#1047))
([74dff94](headroomlabs-ai@74dff94))
* **codex:** compute waste signals on the OpenAI Responses path
([headroomlabs-ai#898](headroomlabs-ai#898))
([b9e2761](headroomlabs-ai@b9e2761))
* **codex:** poll /wham/usage for subscription limits (handshake no
longer sends x-codex-* headers)
([headroomlabs-ai#924](headroomlabs-ai#924))
([8c00f71](headroomlabs-ai@8c00f71))
* **codex:** PR health label check state
([headroomlabs-ai#986](headroomlabs-ai#986))
([99c874d](headroomlabs-ai@99c874d))
* **codex:** retag thread providers so history menu stays whole across
the proxy boundary
([headroomlabs-ai#1034](headroomlabs-ai#1034))
([74ae781](headroomlabs-ai@74ae781))
* **codex:** write canonical hooks feature flag and migrate deprecated
codex_hooks ([headroomlabs-ai#743](headroomlabs-ai#743))
([dff6a19](headroomlabs-ai@dff6a19))
* **compression:** convert tree-sitter byte offsets to char offsets
([headroomlabs-ai#892](headroomlabs-ai#892))
([b1f700f](headroomlabs-ai@b1f700f))
* **compression:** correct JSON array item counting and entropy gate
([headroomlabs-ai#887](headroomlabs-ai#887))
([d6f0f0f](headroomlabs-ai@d6f0f0f))
* **compression:** keep container bodies compressible in code handler
([headroomlabs-ai#890](headroomlabs-ai#890))
([16ed73b](headroomlabs-ai@16ed73b))
* **compression:** measure short-value threshold on payload, not token
([headroomlabs-ai#889](headroomlabs-ai#889))
([65b0e8c](headroomlabs-ai@65b0e8c))
* **compression:** use thread-local tree-sitter parsers in code handler
([headroomlabs-ai#893](headroomlabs-ai#893))
([6cdb846](headroomlabs-ai@6cdb846))
* **gemini:** surface functionResponse payloads to waste-signal
detection ([headroomlabs-ai#897](headroomlabs-ai#897))
([9b0c840](headroomlabs-ai@9b0c840))
* **learn:** decode directory names with spaces in Windows project paths
([headroomlabs-ai#997](headroomlabs-ai#997))
([headroomlabs-ai#1027](headroomlabs-ai#1027))
([2d3701b](headroomlabs-ai@2d3701b))
* **learn:** scan subagent and workflow transcripts
([headroomlabs-ai#1045](headroomlabs-ai#1045))
([0ddd4ed](headroomlabs-ai@0ddd4ed))
* **openclaw:** declare headroom_retrieve tool contract
([headroomlabs-ai#947](headroomlabs-ai#947))
([7c8c909](headroomlabs-ai@7c8c909))
* **policy:** correct warm-cache penalty in net_mutation_gain to (S +
dT) ([headroomlabs-ai#903](headroomlabs-ai#903))
([0632eba](headroomlabs-ai@0632eba))
* **proxy:** add native Bedrock converse-stream route
([headroomlabs-ai#917](headroomlabs-ai#917))
([b08ec15](headroomlabs-ai@b08ec15))
* **proxy:** keep codex image-generation WS turns alive through the
relay ([headroomlabs-ai#1000](headroomlabs-ai#1000))
([7dbbb40](headroomlabs-ai@7dbbb40))
* **proxy:** make budget enforcement actually work
([headroomlabs-ai#885](headroomlabs-ai#885))
([a14ab45](headroomlabs-ai@a14ab45))
* **proxy:** read RTK gain stats globally by default
([headroomlabs-ai#957](headroomlabs-ai#957))
([b70fccb](headroomlabs-ai@b70fccb))
* route v1internal code assist requests to cloudcode-pa.googleapis…
([headroomlabs-ai#821](headroomlabs-ai#821))
([e20f16b](headroomlabs-ai@e20f16b))
* **serena:** stop the Serena dashboard popup and make --no-serena
actually disable Serena
([headroomlabs-ai#1003](headroomlabs-ai#1003))
([919379a](headroomlabs-ai@919379a))
* support Copilot Business subscription auth
([headroomlabs-ai#641](headroomlabs-ai#641))
([0b4a4bd](headroomlabs-ai@0b4a4bd))
* wire HEADROOM_EXCLUDE_TOOLS / HEADROOM_TOOL_PROFILES into Click proxy
entrypoint ([headroomlabs-ai#943](headroomlabs-ai#943))
([9b7b436](headroomlabs-ai@9b7b436))
* **wrap:** avoid duplicate top-level keys when injecting codex provider
([headroomlabs-ai#884](headroomlabs-ai#884))
([dd22cfd](headroomlabs-ai@dd22cfd))


### Code Refactoring

* DRY cache logic, add thread safety, fix Bash exclusion
([headroomlabs-ai#704](headroomlabs-ai#704))
([e36fccd](headroomlabs-ai@e36fccd))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: ready for review Pull request body is complete and the author marked it ready for human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Include headroom-proxy binary in published images

3 participants