Skip to content

proxy: native Anthropic/Gemini capture, usage auto-injection, retry classification - #5

Merged
Cro22 merged 1 commit into
masterfrom
feature/proxy-multiprovider
Aug 25, 2026
Merged

proxy: native Anthropic/Gemini capture, usage auto-injection, retry classification#5
Cro22 merged 1 commit into
masterfrom
feature/proxy-multiprovider

Conversation

@Cro22

@Cro22 Cro22 commented Aug 25, 2026

Copy link
Copy Markdown
Owner

Three proxy-layer improvements that close honest limitations the README already documented, plus the schema work they require. All backward compatible; go build + go vet + go test ./... -count=1 green (mirrors CI).

1. Multi-provider usage capture (#1)

parseUsageJSON now detects the OpenAI, Anthropic (/v1/messages) and Gemini (usageMetadata) wire shapes and maps them onto a provider-neutral tokenUsage. Streaming usage is merged last-non-zero-wins so Anthropic's split message_start/message_delta events reconcile like OpenAI's single final block; Gemini's model is read from the URL path.

Anthropic separates cache-read from cache-write (cache_creation_input_tokens, ~1.25× input), which the old Usage model couldn't express. Added a CacheWriteTokens bucket end to end (tracecost.Usage/ModelPrice/Breakdown → pricing loader → pricing.yamlaggregate) so cache writes are billed at their real premium instead of being silently under-counted.

This lets an Anthropic/Gemini agent that can set a base_url point straight at the proxy instead of needing the callback shim.

2. Auto-inject stream_options.include_usage (#2)

OpenAI only emits a streaming usage block when the request opts in. The proxy now injects that flag surgically (preserving every other field), scoped to the OpenAI chat path so it never reaches Anthropic/Gemini. Streamed calls are captured exactly instead of as a zero-token row. Toggle: --inject-usage=false.

3. Retry vs call classification (#3)

The proxy hashes each request body per (scenario, run) and labels a byte-identical repeat trace.KindRetry vs KindInitial — the observable signature of a client-library retry. Surfaced as retries/run in the aggregate. Fan-out is deliberately not claimed (call concurrency isn't visible at the HTTP layer).

Classification and injection both key off the original request body, so record/replay still regenerate identical traces.

Tests

New coverage for Anthropic/Gemini parsing, streaming merge, modelFromPath, injection, retry classification, and cache-write billing. README "honest limitations" updated to match.

…lassification

Three proxy-layer improvements that close honest limitations documented in
the README, plus the schema work they require.

1. Multi-provider usage parsing. parseUsageJSON now detects the OpenAI,
   Anthropic (/v1/messages) and Gemini (usageMetadata) wire shapes and maps
   them onto a provider-neutral tokenUsage. Streaming usage is merged
   last-non-zero-wins so Anthropic's split message_start/message_delta events
   reconcile like OpenAI's single final block. Gemini's model is read from the
   URL path. This lets an Anthropic/Gemini agent point base_url at the proxy
   directly instead of needing the callback shim.

   Anthropic separates cache-read from cache-write (cache_creation_input_tokens,
   ~1.25x input), which the old Usage model couldn't express. Added a
   CacheWriteTokens bucket end to end (trace, cost.Usage/ModelPrice/Breakdown,
   pricing loader, pricing.yaml, aggregate) so cache writes are billed at their
   real premium rather than silently under-counted.

2. Auto-inject stream_options.include_usage on OpenAI streaming chat requests
   that don't opt in, so streamed calls are captured exactly instead of as a
   zero-token row. Surgical merge preserves every other request field; scoped to
   the OpenAI chat path so it never reaches Anthropic/Gemini. Toggle with
   --inject-usage=false.

3. Retry classification. The proxy hashes each request body per (scenario, run)
   and labels a byte-identical repeat trace.KindRetry vs KindInitial — the
   observable signature of a client-library retry. Surfaced as retries/run in
   the aggregate. Fan-out is deliberately not claimed (call concurrency isn't
   visible at the HTTP layer).

Classification and injection key off the original request body, so record/replay
still regenerate identical traces. Tests cover all three plus cache-write
billing; README limitations updated to match.
@Cro22
Cro22 merged commit 23c5b1b into master Aug 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant