mirror of
https://github.com/JuliusBrussee/caveman.git
synced 2026-09-14 18:02:35 +08:00
f73d98fc8e
* fix(proxy): send x-api-key on compat /v1/messages A named compat mount mapped every credential to Authorization: Bearer. The Anthropic client in Pi sends x-api-key to /v1/messages. OpenCode Go rejects a Bearer header on that path with 401 "Missing API key.". Thus the opencode-go anthropic-messages models failed through the proxy. The named adapter now maps the credential by the wire protocol of the path. A request to /compat/<name>/v1/messages gets x-api-key and a default anthropic-version. Every other path keeps Bearer. The gateway env fallback puts the key in the header that the adapter emitted, so the upstream never gets two credentials. New tests cover the header mapping at the adapter, the gateway fallback, and the full standalone handler with the /w/pi prefix. A test also shows that a user compat.opencode-go entry replaces the built-in upstream on the adapter path. The docs describe the header rule. Fixes #967 Fixes #968 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com> * docs(proxy): add the compat header rule to AGENTS.md proxy/AGENTS.md carries the same auth bullet as proxy/CLAUDE.md. This commit adds the same header rule to both files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com> * fix(proxy): keep a real bearer on compat /v1/messages Review of #969 found two risks. The Anthropic-path remap relabeled a real inbound Bearer token as x-api-key on every named compat mount. This broke the rule that an inbound credential keeps its auth scheme. The remap now skips a real Bearer token. The gateway placeholder is not a real credential, so the remap still moves it to x-api-key, and the env fallback replaces it in that header. The fallback test for x-api-key used a direct map lookup that depends on canonical header keys. It now uses Header.Get. The openai and openai_compatible fallback cases are separate, so each guard list names only the headers that its adapter can emit. New tests show that a real Bearer stays Bearer on /v1/messages at the adapter, in the gateway fallback, and through the standalone handler. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com> --------- Signed-off-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com> Co-authored-by: Zsolt Gaspar <17578237+zsltg@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
8.1 KiB
8.1 KiB
proxy — the byte-safe standalone caveman proxy (commercial, binary-distributed)
A base-URL-swap reverse proxy: match → authenticate → inspect → byte-safe transform → upstream
→ meter. Single-operator, BYOK, zero cloud dependencies. It shares its provider adapters with
the managed gateway (the managed gateway imports them from here). caveman start launches the
caveman-proxy binary.
Layout
providers/— the shared, public byte-safe adapter set:Adapterinterface +Baseembed +UsageScanner/ParseUsageBytes(adapter.go), andanthropic/openai/gemini/azureopenai/bedrock/vertex/openaicompat.ResolveUpstreamURLtakesproviders.RouteContext(no control-plane coupling). Anthropic + OpenAI carry both prefixed and bare routes (/v1/messages,/v1/chat/completions).vertexis a bearer pass-through proxy for Gemini + Claude on Vertex AI (no signing, no custom usage parser).internal/gateway/— the request lifecycle (server.go+proxy.go) behind three injected seams:Authenticator,CredentialResolver,TelemetrySink. Ports the managed loop with the fail-open fix.internal/config/—caveman.yamlloader + BYOK env-key resolution; unknown mode fails closed torecord.internal/store/—~/.caveman/caveman.dbSQLite spend store (modernc.org/sqlite, cgo-free); implementsTelemetrySink.internal/standalone/— wiring: staticAuth, BYOKCreds, adapter set, and the always-on SSRF-guarded client.cmd/caveman-proxy/— binary:serve(default),stats, and content-blindagent-evidence --session --build --plan. Evidence query returns only exact provider usage, request hashes, declared context/plan identity, ordered provider-prefix component hashes, actual transform IDs/counts, and CCR handle; basis is alwaysinferred, verified dollars always zero.
Conventions
- Build/test:
make product-build PRODUCT=proxy/make product-test PRODUCT=proxy. - Tests inject a plain
*http.Clientto reach loopback stubs; the binary uses the SSRF-guarded client. - New provider/optimizer work goes in
providers/(shared) — change it once, both proxies get it.
Gotchas (honesty invariants — correctness, not style)
- byte-safe:
recordmode never transforms; on transform error the ORIGINAL bytes are forwarded (HTTP 200, fail-open) — never a 400. - request-wide opt-out:
x-cave-transforms: caveman.pass-through.v1suppresses every request transform path — compress, pixel, and provider-native — not only compiled plan routes. Tests cover all three modes. - no-fake-savings: standalone records
Basis: "inferred"on every row; it never writesverifiedand never re-projects to a monthly figure. - practice join: local learn sinks carry additive
practice_id; one fail-closed mapping table owns sink→practice and unknown sinks keep"". The historicalsubagent_overusesink is count-only and deliberately has no practice id: spawn count cannot reactivate the retiredcontext-exploration-offloadopportunity or prove any spawn unnecessary. - local trial heuristics are not actuation evidence: a model name never emits
the retired
model-right-sizingid, and provider plus positive cost never emits a cache move because neither proves stable-prefix eligibility. Legacy rows for those identities are hidden at read time. Compression replay reports one trial's local engineestimated_engine_o200kbefore/after shape with zero dollars and low confidence; it is not provider-counted, a rate, an invoice, causal/verified savings, or task-outcome evidence. - Anthropic automatic caching is experimental observation only:
anthropic-automatic-prompt-cacheis a typed, default-off manual policy experiment and may add only Anthropic's top-level 5-minute marker on direct Messages API requests. Managed traffic additionally requires server-attested official Anthropic origin; custom or provenance-unknown origins lose the flag before the adapter. It is mutually exclusive with the explicitanthropic-cache-breakpointstransform and any callercache_control; Bedrock and count-tokens requests stay byte-identical. An applied marker records only its optimizer id plus actual provider usage and cost. It has no practice, recipe, generic mode/candidate activation, ledger tuple, inferred savings, or verified-savings path (cache-only and forged IDs are excluded from the counted-baseline method too); evaluate it by manual paired observation because shared provider cache state can contaminate an A/B. - SSRF always on:
standalone.StandaloneHTTPClientguards every upstream dial (not gated onCAVE_ENV) usingssrf.SelfHostedConfig— NOT ManagedConfig, which ignores the allowlist and would make the escape hatch a silent no-op.CAVE_SSRF_ALLOWLISTopts loopback/private hosts back in (local model servers like Ollama;localhostas an entry covers 127.0.0.0/8 + ::1); metadata/link-local stay blocked in every mode. - Auth scheme is preserved: a key from an inbound
Authorization: BearerkeepsScheme:"bearer"on theproviders.Credential; Anthropic and Gemini forward bearer credentials as bearer credentials (Claude/Gemini OAuth breaks if remapped to an API-key header). BYOK env keys and inboundx-api-keykeep provider API-key mapping. On a named compat mount the header follows the wire protocol of the path:/compat/<name>/v1/messagesgetsx-api-keyplus a defaultanthropic-version, every other path gets Bearer. A real inbound Bearer stays Bearer on every path. OpenCode Go rejects Bearer on/v1/messages(401Missing API key., 2026-09-03). The gateway env fallback puts the key in the header that the adapter emitted, so the upstream never gets two credentials. - fail-closed: unknown route → 404; unknown mode →
record. - subscription AND oauth compression is NOT account-gated: non-PAYG sessions from Claude Code, Codex ChatGPT, Gemini CLI, and other routed clients take live-zone compression with no Caveman account, entitlement, or seat.
CAVEMAN_WRAP_ENTITLEDand everyWrapEntitledfield are deleted, not merely ignored — do not reintroduce them. Exactly four conditions remain, all technical and all fail-closed (liveZoneCompressionAllowed): the operatorsubscription_compressswitch (empty/live_zoneallow,offand any unknown value close it), the adapter must implement schema-awarePrefixStabilizerzones, recovery must run through the agent's own MCPcaveman_retrieve, and a durable prefix cache must be wired. The dedicated Codex/chatgpt/responsesroute uses the OpenAI Responses stabilizer while preserving OAuth andChatGPT-Account-IDheaders; transformed 4xx responses retry once with exact original bytes. Rows are tokens-only —compression_tokens_before/after+estimated_engine_o200k, never compression dollars. LOCAL wrap only; managed gateway non-PAYG behavior is unchanged. - cache safety is byte-stable replacement: a compressed live-zone turn becomes prefix on next request, so same logical message must re-serialize to deterministically identical bytes every time. Replacement is pure function of segment content (deterministic compressor + content-hash CCR marker), held in durable replacement cache and re-substituted below cache floor; new compression stays live-zone-only. Cache miss/write failure forwards original bytes. Cross-turn stability covers anthropic, openai, azureopenai/openaicompat, and gemini through
ExtractStabilizable;bedrockandvertexexpose no compressible blocks and stay pass-through. Anthropic uses declaredcache_control; OpenAI/Gemini use latest-user/latest-tool zones against implicit provider caches. Replacement cache is SQLite spend store and must retainjournal_mode(WAL)+busy_timeout. - pixel mode: S4 lossy text→PNG (
pxpipeport). Default allowlist isclaude-fable-5,gpt-5.6viaCAVE_PIXEL_MODELS; original request is always in CCR before transformed bytes are sent; savings stay inferred-only; any error is byte-identical pass-through. - boundary: this is public code — it must never import the managed-cloud lane.
make check-boundariesenforces it.
See ../../CLAUDE.md (root)