Passive quota-header harvest + account metadata surfacing - #129
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 22 files
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 20 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
cafe0e9 to
0445a98
Compare
|
Thanks for the thorough implementation. I test-merged this on top of the current local routing work and also tested it with #130 stacked above it. I am holding the merge for these issues:
Please keep the token-bound/window-wise merge behavior; that part looks sound. I will re-review after these points and the current routing changes are incorporated. |
0445a98 to
9f1b85a
Compare
|
All four addressed in 9f1b85a.
|
2298a49 to
10bd0de
Compare
|
The four previous findings are addressed, including the profile/sidebar interleaving and the shared-fetch test leak. Two repeated full-suite runs of the #129/#130 stack passed. I found one new release-blocking token race in the fallback profile persistence path.
I reproduced it directly:
Please add a dedicated profile-only state mutation that acquires the state lock, re-reads current state, verifies the expected token fingerprint, and changes only the profile field. A late result for a superseded token should be discarded. Add an exact in-flight fallback hydration versus token-rotation regression. The main-profile path should also reject an older or mismatched result rather than blindly replacing a newer persisted profile. On the paid-fallback policy question: I agree that a fresh, token-bound |
10bd0de to
dadd240
Compare
|
Fixed in New token-fenced The main-profile path additionally keeps a persisted Red-first on your exact interleaving, both directions: the fallback repro (stale snapshot save after rotation) previously restored Full gates: opencode 917, core 58, pi 53, e2e 22, typecheck/lint clean. |
|
The token-fenced profile-only writer fixes the credential rollback. I found one remaining race in the profile-clear operation.
I reproduced the exact state transition:
Please make clears conditional under the state lock. The writer should clear only if the persisted profile is still the stale profile observed by the caller—or, for this specific mismatch path, only if the persisted profile is not already bound to PR #128 is now merged. Please rebase #129 onto current |
dadd240 to
b76cc95
Compare
|
Fixed and rebased onto current Profile-clear race — the delayed-clear now refuses to delete a profile that is already bound to Rebased onto Gates green: typecheck · opencode 949 · core 58 · pi 53 · e2e 22 · lint clean. #130 will be restacked on this head next. |
Harvest anthropic-ratelimit-unified-* headers in the fetch/relay path and normalize them into the shared OAuthQuotaSnapshot alongside poll data; surface account tier/credits/failover metadata in the expanded sidebar. Token-fenced profile-only persistence with delayed-clear protection. Profile persistence is detached from the /claude-quota and /claude-account display path (render immediately, persist fire-and-forget) so an optional tier-label save can never block a command on the account write lock.
b76cc95 to
c47af80
Compare
Anthropic sends
anthropic-ratelimit-unified-*headers on every/v1/messagesresponse, including 200s. This PR harvests them passively so the active account's 5h/7d quota stays fresh between usage-API polls at zero API cost, and surfaces account metadata that was previously invisible.Harvest
packages/core/src/quota-headers.ts: classifier (isQuotaBearingHeaderFrame), normalizer (fraction → integer percent, epoch-seconds → ISO), and a window-class-aware merge that updates only header-owned fields — poll-ownedscoped[](including present-but-empty[]),extraUsage, and poll-derivedbindingWindoware preserved.QuotaManager.pushMainFromHeaders/pushFallbackFromHeaders: token-fingerprint-bound pushes through the existing setters; pushedcheckedAtdefers the next poll through the existing staleness gate (no new scheduler)./v1/messages200 says nothing about the usage endpoint's rate limit), and the main persist path verifies the served token is still live before writing (rotation guard, matching the fallback branch).docs/quota-surfaces.md, follow-up in the backlog.Surfacing (display-only, no routing changes)
credits $100.35/$100.00 · exhausted, severity tone) in/claude-quota+ expanded sidebar, only when the account has the feature enabled.•) — polllimits[].is_activewins over headerrepresentative-claim.Max 20x,Team · Max 5x) viaGET /api/oauth/profile: lazy, once per boot per account, persisted in the state file (never the config), 7-day TTL, silent failure, never fetched in the request hot path.fallback: availableadvisory note (informational; routing still decides by attempt).docs/quota-surfaces.md(included) catalogues all three wire surfaces with live-captured shapes from both a personal Max and a Team account — the test fixtures use those exact captures.Verification
main.quota.source: "headers"in the state file with freshcheckedAt, preservedscoped[], and the debug log line — no usage-API poll involved.relay-worker-miniflare.test.ts+ an e2e test proving harvest stays direct-only under relay config.Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Passively harvest
anthropic-ratelimit-unified-*headers on/v1/messagesto keep 5h/7d quotas fresh without extra polls, and surface account tier and credits across the CLI, account picker, and UI. Relay headers pass through on HTTP/WS; harvesting stays direct‑only behind a client guard, and profile caching is token‑bound with non‑blocking persistence.New Features
core: Header classifier/normalizer/merger (isQuotaBearingHeaderFrame) maps 5h/7d utilization and resets, setsbindingWindowfromrepresentative-claim, parsesfallbackadvisories, converts fractions to percents, and only updates header‑owned fields; poll‑ownedscoped[],extraUsage, idle/boot, and backoff remain poll‑authored.core:QuotaManager.pushMainFromHeaders/pushFallbackFromHeadersmerge header snapshots with a token‑fingerprint guard, replace main 5h/7d windows from headers, share the poll staleness gate (fresh header evidence can license API‑key fallback; stale never does), setcheckedAtto defer the next poll, and preserve poll backoff.core:GET /api/oauth/profilehydration withformatOAuthAccountTier; cached 7 days, token‑bound, and persisted fire‑and‑forget so/claude-quotaand/claude-accountnever wait on the write lock. Money formatter shared betweencoreand TUI.opencode: Wrapped fetch harvests headers with main/fallback attribution; best‑effort and never delays or consumes the response. Tier labels appear in the CLI list, account picker, TUI, and summaries. Surfacing adds credits, a binding‑window marker (•), and afallback: availableadvisory in/claude-quota, the expanded sidebar/TUI, and the account selection dialog; the collapsed sidebar ignores these. Profile state adds delayed‑clear protection.Bug Fixes
core: Window‑aware quota persistence; header/poll freshness by ownership; headers win on equal‑time ties; incomplete frames no longer clobber cached fields; invalid resets no longer discard valid utilization; token‑rotation mismatch drops.opencode: Profile state is token‑bound and only persisted by the dedicated hydration path; generic saves cannot clobber it. Relay eligibility guard corrected and Miniflare confirms HTTP/WS header passthrough while keeping harvest disabled under relay. Malformed money metadata falls back cleanly; quota/refresh/profile state hardened; plugin exports avoid exposing test‑only hooks.Written for commit c47af80. Summary will update on new commits.
Greptile Summary
This PR adds passive quota updates and account metadata across the core and OpenCode surfaces. The main changes are:
/v1/messagesresponses.Confidence Score: 5/5
This looks safe to merge.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Direct messages response] --> B{Quota headers present?} B -->|No| C[Keep current quota state] B -->|Yes| D[Normalize 5h and 7d windows] D --> E[Attribute the served account] E --> F[Verify token fingerprint] F --> G[Merge header-owned fields] G --> H[Preserve poll-owned metadata] H --> I[Update freshness deadline] I --> J[Persist runtime state]%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart LR A[Direct messages response] --> B{Quota headers present?} B -->|No| C[Keep current quota state] B -->|Yes| D[Normalize 5h and 7d windows] D --> E[Attribute the served account] E --> F[Verify token fingerprint] F --> G[Merge header-owned fields] G --> H[Preserve poll-owned metadata] H --> I[Update freshness deadline] I --> J[Persist runtime state]Reviews (15): Last reviewed commit: "feat: passive quota-header harvest + acc..." | Re-trigger Greptile
Context used: