Partial implementation of #181, scoped down to the smallest demoable piece.
In scope
- In-process Axum module inside
tracevault-server, mounted at /proxy/anthropic/{*path}
- User points their tool's
ANTHROPIC_BASE_URL at <tv-server>/proxy/anthropic and uses their TV session token as x-api-key
- Proxy swaps the TV token for the user's stored Anthropic key (via
auth_sessions lookup → user_anthropic_keys table → AES-GCM decrypt) and forwards the request to https://api.anthropic.com/{path} byte-for-byte
- Per-user Anthropic key, configured in a new
/me/proxy/ page (status-only GET, key never returned)
- SSE passthrough via
reqwest::Response::bytes_stream() — no event parsing
- Anthropic-shaped error envelope for proxy-originated failures; upstream errors pass through verbatim
- Allow-list header forwarding (
content-type, accept, anthropic-version, anthropic-beta); host / cookie / authorization / x-forwarded-* stripped
tracevault proxy info CLI helper prints the proxy base URL and credentials path
Explicitly deferred to follow-up slices/milestones
- Event capture / trace persistence (will be a tee + parse stage on the existing byte stream)
- Model routing / named routes / dispatching
- Org-level keys and admin key management
- OpenAI proxy support
- Cost attribution, rate limiting, quotas
- Dedicated long-lived proxy tokens (current slice uses existing
auth_sessions; expiry causes silent 401s — documented limitation)
Verification
- Rust unit tests + mocked-upstream integration tests on every PR
- Real-Anthropic integration test under
#[ignore], run locally before merge
- Manual smoke: GSD2 driving the local proxy through a full coding session
Parent issue: #181
Partial implementation of #181, scoped down to the smallest demoable piece.
In scope
tracevault-server, mounted at/proxy/anthropic/{*path}ANTHROPIC_BASE_URLat<tv-server>/proxy/anthropicand uses their TV session token asx-api-keyauth_sessionslookup →user_anthropic_keystable → AES-GCM decrypt) and forwards the request tohttps://api.anthropic.com/{path}byte-for-byte/me/proxy/page (status-only GET, key never returned)reqwest::Response::bytes_stream()— no event parsingcontent-type,accept,anthropic-version,anthropic-beta);host/cookie/authorization/x-forwarded-*strippedtracevault proxy infoCLI helper prints the proxy base URL and credentials pathExplicitly deferred to follow-up slices/milestones
auth_sessions; expiry causes silent 401s — documented limitation)Verification
#[ignore], run locally before mergeParent issue: #181