Commit 52a024d
fix(proxy): strip [1m] model suffix before upstream forwarding (headroomlabs-ai#2027)
## Description
Scopes the `[1m]` context-window tier suffix sanitizer to Anthropic
`/v1/messages` requests only (addresses PR headroomlabs-ai#2027 review feedback). The
original patch applied the rewrite to every buffered compressible
endpoint, which would have silently mutated OpenAI Chat Completions and
OpenAI Responses request model IDs. The `[1m]` marker is an
Anthropic/Claude Code compatibility signal emitted by the Headroom CLI;
the existing Python parity behavior (`sanitize_anthropic_model_id()`) is
Anthropic-specific and must not leak onto OpenAI shapes.
Refactors the helper into
`compression::sanitize_anthropic_model_id_in_body`, drops the dead
`sanitize_model_id` helper in `sse/anthropic.rs`, and adds 8 unit tests
+ 5 wiremock-backed integration tests that pin the scope. All 420
`headroom-proxy` tests pass; `cargo fmt` and `cargo clippy -D warnings`
clean.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- Move `sanitize_request_model_id` out of `proxy.rs` and into
`compression::sanitize_anthropic_model_id_in_body` (Anthropic-specific
name; private `trim_anthropic_model_id_suffix` helper for unit-testable
pure behavior).
- Gate the call site on `CompressibleEndpoint::AnthropicMessages`
**after** classification. The OpenAI Chat Completions and OpenAI
Responses arms get an explicit no-op match so the sanitizer cannot
re-apply to those paths.
- Drop the dead `sanitize_model_id` helper in `sse/anthropic.rs` (it was
`#[allow(dead_code)]` with no callers).
- 8 new unit tests in `compression/mod.rs`: trailing `[1m]` stripped,
Claude-style suffix stripped, no-suffix passthrough (byte-equal),
non-string model, missing `model` field, non-JSON body, `[1m]`
mid-string, and the pure trim helper.
- 5 new integration tests in
`tests/integration_anthropic_model_sanitize.rs` that boot a real Rust
proxy in front of a wiremock upstream.
## Testing
- [x] Unit tests pass (`cargo test -p headroom-proxy` → 420 passed, 35
suites)
- [x] Linting passes (`cargo clippy -p headroom-proxy --tests
--all-features -- -D warnings` clean)
- [x] Type checking passes (`cargo check -p headroom-proxy --tests
--all-features` clean)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ cargo test -p headroom-proxy --test integration_anthropic_model_sanitize
Compiling headroom-proxy v0.x.x
Finished `test` profile [unoptimized + debuginfo] target(s)
Running tests/integration_anthropic_model_sanitize.rs
test anthropic_messages_strips_1m_suffix_glm ... ok
test anthropic_messages_strips_1m_suffix_claude ... ok
test anthropic_messages_passthrough_when_no_suffix ... ok
test openai_chat_completions_passthrough_with_1m_model ... ok
test openai_responses_passthrough_with_1m_model ... ok
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
```text
$ cargo test -p headroom-proxy
test result: ok. 420 passed; 0 failed; 0 ignored; 0 measured; 235 filtered out
finished in 10.93s
```
```text
$ cargo clippy -p headroom-proxy --tests --all-features -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s)
```
## Real Behavior Proof
- **Environment:** macOS 14.x; `rustc` pinned via `rust-toolchain.toml`;
`cargo` 1.x. No network access required (wiremock upstream).
- **Exact command / steps:**
1. `cargo test -p headroom-proxy --test
integration_anthropic_model_sanitize` — confirms `/v1/messages` strips
`glm-5.2[1m]` and `claude-3-7-sonnet[1m]`; confirms
`/v1/chat/completions` and `/v1/responses` leave the body byte-equal
(SHA-256 asserted).
2. `cargo test -p headroom-proxy` — full suite green (420 passed).
3. `cargo clippy -p headroom-proxy --tests --all-features -- -D
warnings` — clean.
4. `cargo fmt -p headroom-proxy --check` — clean.
5. Source inspection of `crates/headroom-proxy/src/proxy.rs` after the
change: the call site is now in a `match endpoint` arm that explicitly
returns `buffered` for the OpenAI variants, so the sanitizer cannot
re-apply to those paths.
- **Observed result:** all 5 new integration tests pass, all 420 crate
tests pass, clippy and fmt clean. The OpenAI tests assert SHA-256 byte
equality on a body whose `model` field ends in `[1m]`; if the sanitizer
were to re-leak onto OpenAI shapes these would fail loudly with a length
delta.
- **Not tested:** a live Anthropic API call (would require real
credentials and is not required to prove the byte-level scope fix). The
Python proxy's `sanitize_anthropic_model_id()` is the documented parity
reference (Python PR headroomlabs-ai#1840, issue headroomlabs-ai#1812).
## 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 (N/A — no
user-facing docs change; the Python proxy's
`sanitize_anthropic_model_id` is the parity reference cited in code
comments)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable (project uses git
log + PR titles; this PR's title follows the conventional commit shape)
## Screenshots (if applicable)
N/A — backend behavior, no UI change.
## Additional Notes
- The OpenAI integration tests rely on a JWT-style `Authorization:
Bearer` header to classify the request as `AuthMode::OAuth` and
short-circuit the PR-E4 `prompt_cache_key` injector. This is the same
control variable the existing `integration_chat_completions.rs` tests
use to isolate dispatcher byte-fidelity from the E4 hook. Comments in
each test explain the relationship.
- The dead helper in `sse/anthropic.rs` is removed, so the diff is net
negative on LoC for the SSE module.
- The Python parity reference is `sanitize_anthropic_model_id()` (Python
PR headroomlabs-ai#1840, issue headroomlabs-ai#1812); the function name and the call-site scope are
the explicit parity contract.
- Branch was rebased onto `upstream/main` (91 commits behind) before
force-push to the fork; conflict-free rebase. The original PR commit and
the fix are the only two commits on the PR.
---------
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Abhishek Mittal <abhishek.mittal@users.noreply.github.com>
Co-authored-by: Tejas Chopra <chopratejas@gmail.com>1 parent 35701ce commit 52a024d
3 files changed
Lines changed: 507 additions & 1 deletion
File tree
- crates/headroom-proxy
- src
- compression
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
88 | 149 | | |
89 | 150 | | |
90 | 151 | | |
| |||
124 | 185 | | |
125 | 186 | | |
126 | 187 | | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
127 | 285 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | | - | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
632 | 632 | | |
633 | 633 | | |
634 | 634 | | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
635 | 655 | | |
636 | 656 | | |
637 | 657 | | |
| |||
0 commit comments