Skip to content

BAI-319 add session-savings statusline for model routing#109

Closed
imranq2 wants to merge 1048 commits into
imranq2:mainfrom
icanbwell:IQ-BAI-319
Closed

BAI-319 add session-savings statusline for model routing#109
imranq2 wants to merge 1048 commits into
imranq2:mainfrom
icanbwell:IQ-BAI-319

Conversation

@imranq2

@imranq2 imranq2 commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds GET /v1/model-routing/sessions/{session_id}/savings, a read-only endpoint returning a Claude Code session's cumulative cost savings (vs. Anthropic list price) from being routed through this gateway, broken down by model tier. Reads the model-router-sessions rollup collection that usage_tracker.py already writes to (SessionSavingsReader, SessionSavingsRouter), mounted in create_app() alongside the existing CodingModelRouter.
  • Adds scripts/claude_code_statusline.py, a stdlib-only Claude Code statusLine command that calls the new endpoint and renders 💰 $X saved (haiku $... · sonnet $... · opus $...) in the terminal footer, plus a docs/model_routing_guide.md walkthrough for local setup.
  • Deliberately unauthenticated (session_id-as-capability, not a new auth scheme) — see docs/superpowers/specs/2026-07-16-session-savings-statusline-design.md's "Security posture" section for the reasoning; response leaks only an aggregate dollar figure, never PHI/email/account identity.
  • Fixes two pre-existing, unrelated bugs found along the way: stale docstrings in router.py/docs/coding_model_router.md claimed this gateway is deployed per-user/local (it's actually shared multi-tenant), which had been the stated justification for an otherwise-spoofable custom-header user_id fallback — corrected to flag this as a known gap needing a follow-up ticket; and a broken pre-commit-hook (line 12 executed a variable as a command instead of cd-ing into it) that blocked every commit in this repo since the initial commit.

Test plan

  • 26 new tests added across reader/router/wiring/script, all passing (pytest tests/gateway/routers/model_routing/test_session_savings_reader.py tests/gateway/routers/model_routing/test_session_savings_router.py tests/gateway/routers/test_session_savings_wiring.py tests/scripts/test_claude_code_statusline.py -v)
  • Full task-by-task review (spec + quality) plus a final whole-branch review, all clean — see docs/superpowers/plans/2026-07-16-session-savings-statusline.md and .superpowers/sdd/progress.md for the review trail
  • Manual: wire scripts/claude_code_statusline.py into a local ~/.claude/settings.json per the new doc section and confirm the footer renders after a real Claude Code session against this gateway

imranq2 added 30 commits April 21, 2026 07:27
…idation and update snapshot cache methods for improved error handling
…egration and enhance .gitignore for cache management
…n options and enhance comments for better understanding
imranq2 added 28 commits July 14, 2026 14:09
Records the 2026-07-14 investigation into the "0% until auto-compact"
context-usage bug: symptom, root cause in both streaming translators,
why the router's own logging didn't reveal which transport was
actually live, the fix, and which other response paths were audited
and confirmed already safe.
…ransport ran

MODEL_ROUTING_BEDROCK_TRANSPORT now defaults to "native" instead of
"mantle" — "mantle" becomes the manual opt-out instead of the default.
Mantle's own reliability (bare, undiagnosable internal_server_error
500s with no further diagnostic detail, as seen in a services-env
incident) makes native the better default now that both transports
have been proven out in production. Only the literal value "mantle"
(case-insensitive) opts back in; anything else, including a typo,
resolves to "native" rather than silently landing on the less
reliable transport. CodingModelRouter's own constructor default is
left at "mantle" since api.py always passes bedrock_transport
explicitly from this env var — flipping the constructor default too
would have silently changed which path ~20 existing tests exercise
for no behavioral benefit.

Every model-router-usage/model-router-errors record now also carries
a bedrock_transport field ("native"/"mantle") whenever
backend == "aws_bedrock". auth/api_type alone can't disambiguate this
today — native Converse dispatch is also reached via auth="aws",
api_type="openai" routes — which is exactly what made diagnosing the
services-env incident harder than it needed to be. record_error fills
this in automatically from CodingModelRouter._bedrock_transport (no
call-site changes needed); record_usage's callers pass it explicitly,
hardcoded per translator since each usage-tracking wrapper is
transport-specific by construction (_oai_stream_with_usage_tracking is
always Mantle, _converse_stream_with_usage_tracking and
BedrockNativeDispatcher are always native).

Removed the now-redundant MODEL_ROUTING_BEDROCK_TRANSPORT: native
override from docker-compose.yml (native is the default everywhere
now) and updated docs/coding_model_router.md and the original
native-transport design spec to reflect the new default and field.
…Bedrock dispatch paths

- record_usage/session rollup now capture retry_count/total_retries across
  all six dispatch paths (Mantle + native Converse, streaming +
  non-streaming, plus the Anthropic-format Bedrock/passthrough path).
- Native Bedrock Converse streaming now reports sse_event_count, matching
  Mantle's existing behavior - usage records for native-transport streams
  were silently missing this sanity field.
- Dispatch pacing (_pace_bedrock_dispatch) was only wired into the unused
  Anthropic-format Bedrock route; now also applied to the Mantle and native
  Converse paths that actually serve the haiku/sonnet tiers in production,
  so concurrent requests no longer burst against Bedrock unthrottled.
BAI-299 Implement native AWS Bedrock Converse transport
BAI-315 Document Bedrock transport options
BAI-316 Add MODEL_ROUTING_QWEN_ENABLE_THINKING env var
…ey're recorded instead of surfacing as unhandled 500s
BAI-317 Make native Bedrock connect/read timeouts explicit env vars
…nippet

Opening marker used <<< instead of >>>, matching docs/coding_model_router.md convention.
BAI-318 Move model_routing_guide.md from language-model-gateway-configuration
…gn docs

router.py's and coding_model_router.md's docstrings claimed this router is
deployed per-user/local, which justified an otherwise-spoofable custom-header
user_id fallback. That assumption no longer holds (shared multi-tenant
deployment) -- flagged as a known gap rather than silently left stale.

Also fixes a pre-existing bug in pre-commit-hook (line 12 executed
$GIT_PROJECT_ROOT as a command instead of cd-ing into it, breaking every
commit in this repo since the initial commit) and adds the design spec and
implementation plan for a session-savings statusline endpoint, to be
implemented task-by-task on this branch.
format_savings_line() now wraps all field-access and formatting in
try-except, returning None silently on any TypeError, ValueError,
AttributeError, or KeyError. This ensures the statusline command never
prints a raw error string or traceback that would break Claude Code's UI,
regardless of whether the gateway returns wrong field types
(total_savings_usd as string, tier cost_usd as non-numeric, tiers as
non-dict, etc.).

Add 3 test cases covering the previously-crashing malformed-type inputs.
Adds a Prerequisites/Setup/Troubleshooting walkthrough (absolute path,
chmod +x, manual test before wiring into settings.json, restart requirement)
and ties MODEL_ROUTING_GATEWAY_URL to the same gateway_url local var as
ANTHROPIC_BASE_URL in the claude-router() shell function, so the two env
vars can't drift apart when swapping between local and production.
Corrects the collection-name default (usage_sessions -> model-router-sessions)
in SessionSavingsReader/SessionSavingsRouter and their docstrings, plus stale
usage_sessions and curl references in the session-savings-statusline design
doc and plan doc. No runtime behavior change: api.py already passes the
correct env-var-driven collection name to both reader and writer.
@imranq2

imranq2 commented Jul 16, 2026

Copy link
Copy Markdown
Owner Author

Closing: created against the wrong repo by mistake (gh defaulted to this fork instead of icanbwell/language-model-gateway). Correct PR: see icanbwell/language-model-gateway.

@imranq2 imranq2 closed this Jul 16, 2026
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.

3 participants