feat(openrouter): add API usage/credit provider (task 006)#8
Conversation
Read OpenRouter credit/usage with the key stored by task 003 and render it as
a normalized credit window in the popover (task 006).
- core: OpenRouterStrategy (API-key) reads the key via the SecretStore port and
polls GET /api/v1/key; pure parse_key_info/parse_credits (lossy, ADR 0015) +
usage_windows map to one Custom credit window. Prepaid credit has no reset, so
the window carries no countdown (stated honestly).
- The account-wide /credits call is only the fallback bound: it is fetched solely
when the key has no usable spend cap, so a normal capped key no longer makes a
recurring (often 403) round-trip on every poll.
- adapters: openrouter_strategy() builder + hand-run openrouter_live example.
- app: route `openrouter` through usage_route/fetch_for; reportsUsage('openrouter').
Status/error mapping (401/403/429/5xx/transport) feeds the existing stale/error
UI; data stays siloed to its own tile (no account identity invented).
Tests: 26 core unit tests (lossy parsing; window selection incl. non-positive
cap and zero-credits fall-through; 100% clamp; no-reset invariant; bearer header;
capped key skips /credits; status mapping). make check green.
📝 WalkthroughWalkthroughThis PR completes OpenRouter usage reading by implementing a full fetch strategy that polls key spending caps and account credits, wiring it through the adapter layer with keychain-backed secret storage, integrating it into the Tauri app's usage routing and dispatch, updating frontend state to track OpenRouter as a reporting provider, and marking all acceptance criteria as complete in the documentation. ChangesOpenRouter usage fetching and integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/core/src/providers/openrouter.rs`:
- Around line 214-223: The code currently collapses any failure from
self.http.send(bearer_get(...)) into None, which hides real fetch/transport
errors; change the credits acquisition to surface operational failures as
FetchError while only mapping an explicit management-key refusal to None.
Specifically, update the logic around the credits variable (the
key_cap(key_info.limit) branch and the match on self.http.send) so that: on
Ok(resp) with status 200 call parse_credits and return Some(parsed); on Ok(resp)
with a management-key refusal status (e.g., 403/401) return None; on any other
non-200 status or transport error return a FetchError variant (propagate the
error) so callers like usage_windows() can mark the provider Stale/Error. Ensure
you reuse parse_credits, bearer_get, and the existing FetchError type to
represent surfaced failures rather than returning None for all errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ed75c9e4-8834-4a89-8594-3058426ab1ca
📒 Files selected for processing (9)
crates/adapters/examples/openrouter_live.rscrates/adapters/src/lib.rscrates/adapters/src/openrouter.rscrates/core/src/providers/openrouter.rsdocs/PRD.mddocs/tasks/006-provider-openrouter.mdsrc-tauri/src/lib.rssrc/lib/usageState.test.tssrc/lib/usageState.ts
…ing "no limit" The credits fallback collapsed every non-200 (and transport) outcome into None, so a 5xx or network failure on /api/v1/credits rendered a confident "No spending limit" (Status::Ok) — hiding a real outage and, for a paying no-cap key, asserting a balance state we never actually read. Now only the expected management-key refusal degrades gracefully: - 200 -> parse_credits (best-effort Option, unchanged) - 401 | 403 -> None (normal inference key can't read /credits; fall back) - 429 -> FetchError::RateLimited - other / 5xx -> FetchError::Upstream; transport errors propagate via `?` Genuine failures now surface as Stale/Error (last-known retained) per ADR 0015. Reuses bearer_get/parse_credits/FetchError. Adds tests for the 5xx and transport cases; the 403-refusal and 200-balance paths are unchanged.
Summary
Task 006 — OpenRouter API usage. Read OpenRouter credit/usage with the key stored by task 003 and render it as a normalized credit window in the popover.
crates/core/src/providers/openrouter.rs):OpenRouterStrategy(API-key) reads the key via theSecretStoreport and pollsGET /api/v1/key; pureparse_key_info/parse_credits(lossy, ADR 0015) +usage_windowsmap to oneCustomcredit window. Prepaid credit has no reset, so the window carries no countdown — stated honestly./creditscall is the fallback bound only: fetched solely when the key has no usable spend cap, so a normal capped key no longer makes a recurring (often 403) round-trip on every poll.openrouter_strategy()builder + hand-runopenrouter_liveexample.openrouterthroughusage_route/fetch_for;reportsUsage('openrouter'). Status/error mapping (401/403/429/5xx/transport) feeds the existing stale/error UI; data stays siloed to its own tile (no account identity invented).Acceptance criteria (task 006) — all met
Review
Ran the packaged thermo review agents before finalizing:
Fixes applied from the reviews:
/creditscall on absence of a usable key cap (avoids a recurring 403 every poll); extracted a sharedkey_cappredicate so the gate and window selection can't drift./key, and an assertion that a capped key skips/credits.is_finite()symmetry on the credits branch;FixedClock→FakeClock(sibling convention);validate_keynow routes through the sharedbearer_get.Verification
cargo test -p mlt-core openrouter— 26 passedmake check— green (clippy-D warnings, core-purity,cargo test --workspace, cargo-deny, biome, svelte-check, vitest)make coverage—mlt-core94.97% lines (openrouter.rs97%)Summary by cubic
Adds OpenRouter API usage/credit tracking via the stored API key and shows it as a single normalized credit window in the popover. Calls
/creditsonly when the key has no spend cap; operational failures on/creditsnow surface as errors instead of showing “No spending limit.”New Features
OpenRouterStrategypollsGET /api/v1/keyfor limit/usage; best-effortGET /api/v1/creditsonly if no per-key cap. Lossyparse_key_info/parse_credits;usage_windowsmap to one Custom window with percent used and no invented reset.openrouter_strategy()andopenrouter_liveexample; app wiring routesopenrouterviausage_route/fetch_for;reportsUsage('openrouter'); errors (401/403/429/5xx/transport) feed stale/error UI; data stays siloed; no account identity. Meets task 006.Bug Fixes
/creditshandling: 200 → parsed; 401/403 → graceful fallback; 429 → RateLimited; 5xx/transport → Upstream error. Prevents falsely showing “No spending limit” during outages.Written for commit f59ddd5. Summary will update on new commits.
Summary by CodeRabbit