fix(proxy): surface CompressionDecision.passthrough_reason in tags

Adds ``CompressionDecision.apply_to_tags(tags)`` — a one-liner mutator
that stamps the passthrough reason into a tags dict for downstream
observability. Each migrated handler now calls
``_decision.apply_to_tags(tags)`` immediately after
``CompressionDecision.decide(...)``. The tags dict flows unchanged
into every downstream ``RequestOutcome(tags=tags, ...)`` construction,
which the funnel surfaces in ``RequestLog.tags`` — same mechanism the
funnel already uses for ``client``.

Dashboards can now slice passthrough traffic by cause:

  * tags["passthrough_reason"] == "bypass_header"
  * tags["passthrough_reason"] == "compression_disabled"
  * tags["passthrough_reason"] == "no_messages"
  * tags["passthrough_reason"] == "license_denied"

No-op when ``should_compress=True`` — compressing requests don't
carry the tag, so absence vs presence is itself the signal.

Bonus fix: ``handle_gemini_count_tokens`` was the one Gemini handler
that never pulled tags out of headers, so its emitted
``RequestOutcome`` reached the dashboard without any of the per-
request slicing keys. Added the missing ``tags = self._extract_tags
(request.headers)`` and threaded ``tags=tags`` into its outcome.

Closes the observability loop opened by PR #477: the four Gemini-
bypass-bug fixes are now visible in the request-log feed the moment
they fire.
This commit is contained in:
chopratejas 2026-05-15 14:26:01 -07:00
parent 1699435ddf
commit e4e28b65f4
9 changed files with 147 additions and 6 deletions

View file

@ -1,6 +1,6 @@
{
"name": "headroom",
"version": "0.21.38",
"version": "0.22.0",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"author": {
"name": "Headroom Contributors",

View file

@ -1,6 +1,6 @@
{
"name": "headroom",
"version": "0.21.38",
"version": "0.22.0",
"description": "Headroom startup hooks for Claude Code and GitHub Copilot CLI.",
"author": {
"name": "Headroom Contributors",