feat(usage): show OpenRouter credit balance in the usage meter - #182
Merged
Conversation
- failure cache stops queued readers behind the single-flight gate from serially re-spending doomed requests during an outage - secret-store read failures report as an error instead of "no key" - non-finite credit totals are rejected at the schema - configure single-flight keys include the payload so a save can't join a racing remove and claim success - popover refresh gate reads the clock at open time, not render time - interrupted per-environment saves count as failures in the settings toast - stale balances in settings say the last read failed - copy: the credits endpoint needs an OpenRouter management key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The usage meter had no view of OpenRouter spend, even though gateway-routed models bill against that account's prepaid credits.
The server now stores an OpenRouter management key per environment (secret store, never echoed to clients) and reads
GET /api/v1/creditsbehind a process-wide 60-second cache with single-flight and a 10-second failure cache, matching OpenRouter's own cache window. The endpoint rejects regular inference keys, so the settings copy and the 401/403 error both point at openrouter.ai/settings/management-keys. Two new WS RPCs carry it:openRouterCredits.read(read scope) andopenRouterCredits.configure(operate scope). A failed read keeps the last snapshot and reports the reason instead of blanking the number; an unreadable secret store reports that, not "no key".On web/desktop, Settings → Extras → Provider usage gains an opt-in switch (
showOpenRouterCredits, default off) plus a key field that applies to every connected environment and lists each one's stored-key state and current balance. With the switch on, the composer usage popover shows an "OpenRouter credits" row with the remaining balance, refreshed on open at most once a minute; an unconfigured environment points at the settings page, and a server predating the RPC renders nothing.Two review rounds ran (Fable write-capable, then Sol read-only). Fixed from them: request timeout now covers the response body read (a hung body would have wedged the single-flight gate for the process), configure refreshes the read query so settings rows update immediately, failure caching, payload single-flight keys, non-finite payload rejection, open-time clock reads for the popover refresh gate, and honest error reporting in the settings status rows. Deliberately not done: the meter button still only appears when the thread has context-window or provider-usage data (a credits-only meter would render an empty ring), and mobile is not covered.
Focused tests:
apps/server/src/provider/openRouterCredits.test.ts(11 cases: key storage, caching, failure cache, stale retention, hung-body timeout, auth rejection, unreadable store, non-finite payloads).vp checkandvp run typecheckpass.Implemented by Claude Fable 5 in Claude Code, with review rounds by Claude Fable 5 and gpt-5.6-sol.