Skip to content

feat(codex): add subscription usage provider#6

Closed
PedroMendes-AE wants to merge 4 commits into
ogrodev:mainfrom
PedroMendes-AE:task/005-provider-codex
Closed

feat(codex): add subscription usage provider#6
PedroMendes-AE wants to merge 4 commits into
ogrodev:mainfrom
PedroMendes-AE:task/005-provider-codex

Conversation

@PedroMendes-AE
Copy link
Copy Markdown

@PedroMendes-AE PedroMendes-AE commented Jun 3, 2026

Summary

  • Adds Codex subscription usage via per-account local OAuth discovery.
  • Hardens shipping gaps: no Codex refresh downscope, bounded HTTP probes, read-only WAL fallback, overflow-safe timestamps.
  • Adds deterministic OMP discovery coverage and Vitest UI state coverage for siloed stale/error handling.

Verification

  • make check
  • make tasks
  • make deps
  • CI=true make qa
  • pre-push cargo test --workspace

Notes

  • Pushed from fork because current GitHub token has read-only permission on ogrodev/MLT.

Summary by cubic

Adds Codex usage tracking and multi-account discovery, so each Codex and Claude Code login appears as its own source with siloed usage, stale, and error states. Completes task 005 by reusing local OAuth logins from Oh My Pi and vendor CLIs and refreshing tokens safely.

  • New Features

    • Codex provider: reuse ~/.codex/auth.json, fetch chatgpt.com/backend-api/wham/usage, parse session/weekly windows, and send ChatGPT-Account-Id.
    • Multi-account discovery: read Oh My Pi per-profile SQLite (agent.db) and vendor stores, dedupe by account id, and expose each account as codex:<id> / claude-code:<id>.
    • Per-account sources: deterministic account_source_id and account_cache_key, with per-provider usage/errors kept fully siloed in the UI.
    • App/UI: generic fetch_usage(id), per-provider error event payloads, switcher labels disambiguated by account email.
  • Refactors

    • Shared providers::oauth::OAuthRefresher for reused-login providers; refreshed tokens cached under our keychain, never written back to vendor stores.
    • Claude Code adopts the multi-account model; scope guard relaxed when scopes are unknown so the endpoint decides.
    • Bounded HTTP probes with a 15s default timeout; source descriptors now own strings.
    • Tests and tooling: add vitest UI state tests and ui-test to make check; add rusqlite for read-only Oh My Pi discovery.

Written for commit 15da8e1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Codex (ChatGPT/OpenAI) provider for subscription usage tracking
    • Implemented multi-account discovery from local OAuth stores for supported providers
    • Enhanced UI to display and manage usage per discovered account with isolated state
  • Improvements

    • Added explicit HTTP request timeout handling (15 seconds)
    • Refactored OAuth token refresh into reusable shared mechanism
    • Updated error reporting to include per-provider context

ogrodev added 4 commits June 2, 2026 17:56
Reuse the Codex CLI's ~/.codex/auth.json OAuth login and poll the private
chatgpt.com/backend-api/wham/usage endpoint, rendering Codex's session and
weekly windows next to Claude's in the popover.

core:
- codex provider: lossy wham/usage parser (primary/secondary windows
  classified by length, additive model-specific limits), JWT-claims account
  identity, ChatGPT-Account-Id header, 401/429 handling.
- generalize the Claude token refresher into a shared
  providers::oauth::OAuthRefresher (configurable endpoint/client/cache/scope)
  used by both Claude and Codex; add OAuthTokens.account_id.
- register Codex in the source catalog.

adapters:
- CodexCredentials (auth.json reader + metadata-only presence; expiry from
  the access token's JWT exp), codex_strategy wiring, LocalSourceProbe entry,
  and a [ignore]-style codex_live example.

app:
- generic fetch_usage(id) command + codex in fetch_for; the usage-error event
  now carries the provider id so a failure is siloed to its own tile.

ui:
- per-provider usage/error state: each provider shows only its own windows,
  keeps last-known values with a stale banner on a failed fetch, and never
  renders another provider's data. Existing providers are unchanged.

Tests use recorded fixtures + fakes (no live accounts, no Keychain prompts);
the live check is the [ignore]d codex_live example. Completes task 005.
Codex logins aren't only in ~/.codex/auth.json — when Codex is driven through Oh My Pi,
each profile keeps its own OAuth token in a per-profile SQLite store
(~/.omp[/profiles/*]/agent/agent.db, provider 'openai-codex'). Single-path discovery missed
all of them and only ever found the (often stale) CLI file. Discover both stores, dedupe by
ChatGPT account id (freshest token wins), and surface each distinct account as its own
connectable source.

core:
- sources: SourceDescriptor owns its strings; add CodexAccount + codex_account_descriptor +
  codex_source_id; discover_sources/active_sources take the discovered accounts and treat them
  as present-by-discovery (no file probe), seeding each account's email from discovery.
- codex: account_cache_key(account_id) namespaces each account's refreshed-token cache
  (oauth.codex.<id>) so two logins never collide; the catalog and the strategy derive the key
  from one place.

adapters:
- codex: enumerate the Codex CLI auth.json + every Oh My Pi profile DB (read-only rusqlite),
  dedupe by account id, and resolve per-account credentials (freshest token, re-read each load
  so MLT always uses Oh My Pi's latest token). Never writes either store.

app:
- per-account routing: fetch_for handles codex:<id>; descriptor_for / discover_all / active_all
  thread the discovered accounts through every command and the refresh loop; disconnect purges
  only the targeted account's namespaced token.

ui:
- per-account tiles: reportsUsage covers codex:<id>; the switcher disambiguates the otherwise
  identical \"Codex\" tabs by account email; Codex gets its own icon. Each account's usage,
  identity, and stale/error state stay siloed by its unique source id.

Verified live against two real logins (a personal Plus and a Team account) through the
[ignore]-style codex_live example: both discovered, both fetch their own windows. Unit tests
cover parsing, dedup, dynamic discovery, and per-account disconnect; no live accounts in tests.
…he same multi-account treatment

The multi-account model built for Codex was Codex-specific. Generalize it into one shared
mechanism and apply it to Claude Code, so every reused-login (OAuth-subscription) provider gets
the same treatment — and a new one plugs in with a registry row plus a strategy, no bespoke code.

core:
- CodexAccount -> DiscoveredAccount { base, account_id, email, origin }; codex_source_id /
  codex_account_descriptor -> account_source_id / account_cache_key / account_descriptor, driven
  by an ACCOUNT_PROVIDERS registry (codex, claude-code). discover_sources/active_sources expand
  any provider's accounts uniformly.
- claude: the usage scope guard now fires only when scopes are *known and insufficient*; an
  Oh My Pi credential omits scopes, so such a token is trusted and the endpoint is the authority.

adapters:
- new accounts.rs: the provider-agnostic Oh My Pi reader (one PROVIDERS table maps base ->
  Oh My Pi provider id), dedup, and AccountCredentials shared by every provider. codex.rs keeps
  only its CLI auth.json reader + strategy; claude.rs adds claude_account_strategy. anthropic
  OAuth logins are read the same way Codex's are.

app + ui:
- descriptor_for / fetch_for route any <base>:<account_id> (codex:, claude-code:) to the right
  strategy; the front-end recognizes per-account ids and disambiguates same-named tabs by email.

docs:
- ADR 0019 records the pattern; the shared Definition of Done now requires every reused-login
  provider to use it (so the next provider is multi-account by default).

Verified live (accounts_live example): 4 accounts discovered — 2 Codex + 2 Claude Code, across
the gmail and bigshotpictures logins — each fetching its own windows through its provider's real
strategy. Tests cover the generic discovery, dedup, per-account disconnect, and the relaxed scope
guard; no live accounts in tests.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abd8fd2c-bac3-427d-82ab-13b677afde7e

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5d8df and 15da8e1.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • Makefile
  • crates/adapters/Cargo.toml
  • crates/adapters/examples/accounts_live.rs
  • crates/adapters/src/accounts.rs
  • crates/adapters/src/claude.rs
  • crates/adapters/src/codex.rs
  • crates/adapters/src/http.rs
  • crates/adapters/src/lib.rs
  • crates/adapters/src/sources.rs
  • crates/core/Cargo.toml
  • crates/core/src/domain.rs
  • crates/core/src/providers/claude.rs
  • crates/core/src/providers/codex.rs
  • crates/core/src/providers/mod.rs
  • crates/core/src/providers/oauth.rs
  • crates/core/src/providers/testdata/codex_usage.json
  • crates/core/src/sources.rs
  • docs/PRD.md
  • docs/adr/0019-multi-account-discovery.md
  • docs/adr/README.md
  • docs/research/PROVIDERS.md
  • docs/tasks/005-provider-codex.md
  • docs/tasks/README.md
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/src/lib.rs
  • src/lib/usage.ts
  • src/lib/usageState.test.ts
  • src/lib/usageState.ts
  • src/routes/+page.svelte

📝 Walkthrough

Walkthrough

This PR implements multi-account OAuth credential discovery from local stores (Oh My Pi profiles, Codex CLI), introduces a reusable OAuth token refresh framework, adds complete Codex provider support with usage parsing, and refactors the Tauri backend and SvelteKit frontend to support per-provider usage snapshots with per-account source isolation.

Changes

Multi-account OAuth discovery and Codex provider integration

Layer / File(s) Summary
OAuth token model enrichment
crates/core/src/domain.rs
Add optional account_id field to OAuthTokens with #[serde(default)] for backward-compatible deserialization of cached tokens from before this field existed.
Core sources types and discovery signatures
crates/core/src/sources.rs (types & signatures)
Introduce DiscoveredAccount type and per-account helpers (account_source_id, account_cache_key, account_descriptor); change discover_sources and active_sources signatures to accept &[DiscoveredAccount] for multi-account provider expansion. Change oauth_cache_key field from Option<&'static str> to Option<String> to support dynamic per-account namespacing.
OAuth refresh framework
crates/core/src/providers/oauth.rs
Implement OAuthRefresher wrapping a bootstrap credential source with MLT's SecretStore cache. Fast-path fresh vendor tokens without cache reads; refresh and cache only when stale; use overflow-safe expiry arithmetic; support optional scope in refresh requests; carry forward subscription/account id. Comprehensive test suite covers cache-skip behavior, refresh + caching, freshest-source selection, and scope handling.
Codex provider implementation
crates/core/src/providers/codex.rs
Implement lossy JSON parser (parse_usage) for Codex usage endpoint, best-effort JWT claim extraction for identity, token expiry parsing, and CodexStrategy as a FetchStrategy that loads OAuth tokens, builds authenticated headers with optional account id, maps HTTP statuses (200→success, 429→rate-limited, 401/403→re-auth, others→error), and caches resolved identity per provider. Extensive unit tests cover parsing resilience, JWT decoding, identity caching, and HTTP behavior.
Multi-account credential discovery
crates/adapters/src/accounts.rs
Implement discovered_accounts() and AccountCredentials source to enumerate OAuth credentials from Oh My Pi profile SQLite databases (read-only with URI fallback) and optionally Codex CLI auth.json. Deduplicate by account_id while keeping freshest token (by expires_ms). Parse Oh My Pi JSON blobs extracting access/refresh/accountId/email; fall back to JWT-derived expiry when missing. Comprehensive tests validate credential parsing, deduplication per account, and multi-profile discovery.
Codex adapter discovery and strategy building
crates/adapters/src/codex.rs
Implement Codex CLI account discovery reading CODEX_HOME/auth.json, extracting OAuth logins only, parsing snake_case and camelCase token fields, deriving expiry from access token JWT exp. Provide codex_strategy() builder that wires per-account credential sources, HTTP/clock ports, keychain-backed OAuthRefresher, and per-account cache keys. Include detect_user_agent() for codex --version parsing with fallback. Unit tests cover JWT parsing, auth.json shapes, and user-agent detection.
Claude adapter OAuth refresh refactoring
crates/adapters/src/claude.rs
Update Claude adapter to use shared OAuthRefresher instead of custom refresher. Set account_id: None for Claude Code tokens. Build per-account strategy using AccountCredentials bootstrap source with account_cache_key(base, id) for per-account refresh/caching isolation.
Adapter HTTP timeout and module exports
crates/adapters/src/http.rs, crates/adapters/src/lib.rs, crates/adapters/src/sources.rs
Add explicit DEFAULT_HTTP_TIMEOUT (15 seconds) to HTTP requests. Expand public adapter modules to declare accounts and codex; update re-exports to include discovered_accounts, codex_strategy, and exclude detect_user_agent from claude re-exports. Add documentation note that Codex logins are discovered rather than probed.
Core sources discover/active with discovered accounts
crates/core/src/sources.rs (discovery logic & tests)
Enhance discover_sources to iterate discovered accounts alongside catalog, mark accounts present-by-construction, apply consent/labels, resolve identity with email fallback. Enhance active_sources to include discovered accounts when consent is enabled, skipping presence probing for them. Update unit tests to pass accounts argument and validate per-account descriptor namespacing and isolation.
Tauri integration: helpers and generic fetch_usage
crates/adapters/.., src-tauri/src/lib.rs
Add descriptor_for(id) helper to resolve per-account synthesized Codex descriptors; discover_all(sources, probe, consent) to enumerate catalog + discovered accounts; active_all(sources, probe, consent) to compute pollable providers. Replace fetch_claude_usage with generic fetch_usage(id) gated on active state. Update source listing, connect/disconnect, and label-setting flows to use discover_all. Wire per-provider usage-error events with provider id and message. Add serde dependency for event payloads.
Tauri usage fetch routing for per-account providers
src-tauri/src/lib.rs
Implement provider-id pattern routing: claude-code standalone, <base>:<account_id> per-account. Construct per-account strategies for Codex and Claude using synthesized descriptors. Wire background refresh loop to active_all.
Frontend usage state helpers
src/lib/usageState.ts
Introduce Tone type and UsageRecords container (snapshots/errors keyed by provider id). Export predicates (sourceActive, reportsUsage), helpers for countdown text and window keys, and derived state (selectedAccount from snapshot/source, connectionState mapping snapshot status to UI label/tone).
Usage command wrapper and error event types
src/lib/usage.ts
Replace fetchClaudeUsage() with parameterized fetchUsage(id); introduce UsageErrorEvent interface (provider + message); update onUsageError to receive structured events instead of raw strings.
Usage state test suite
src/lib/usageState.test.ts
Comprehensive Vitest suite validating per-provider snapshot/error isolation, connection state rendering, source activation routing, window key uniqueness, reset countdown formatting, and single-provider clearance.
Frontend usage page refactor: per-provider UI
src/routes/+page.svelte
Refactor from single-provider snapshot/error state to per-provider usageRecords store. Add tabLabel(source) helper for display text disambiguation. Update provider icon routing for claude-code:* and codex:* patterns. Implement fetchConnectedUsage(sources) to fetch and record all connected/reporting providers. Derive per-provider selected snapshot/error, update UI panels to show resetCountdown, stale/error indicators, and "tracking coming later" message for non-reporting providers.
Dependency and CI updates
Makefile, package.json, crates/adapters/Cargo.toml, crates/core/Cargo.toml, src-tauri/Cargo.toml
Add rusqlite bundled feature for SQLite reads; add parking_lot dev-dependency for test fakes; add serde for Tauri payload types; add ui-test make target running pnpm test; add test npm script running vitest run.
ADR 0019: Multi-account discovery decision
docs/adr/0019-multi-account-discovery.md, docs/adr/README.md
Document multi-account discovery model with per-account source ID scheme (<base>:<account_id>), cache/consent/identity namespacing, discovery from local Oh My Pi + vendor stores, deduplication by freshest token, immediate presence marking with deferred fetch gating (ADR 0012), and read-only refresh into MLT keychain. Record consequences including Oh My Pi schema coupling and scope-guard behavior changes.
Task completion and shared done criteria
docs/tasks/005-provider-codex.md, docs/tasks/README.md
Mark Codex task 005 done with all acceptance criteria checked. Add shared Definition of Done bullet clarifying that reused-login OAuth providers must default to multi-account behavior via shared ACCOUNT_PROVIDERS/PROVIDERS registration and per-account discovery/consent/identity/cache-key namespacing.
Research and PRD updates
docs/research/PROVIDERS.md, docs/PRD.md
Update Codex research to specify read-only auth.json (refreshed tokens stored in app keychain only). Mark Codex provider as done in PRD delivery checklist.
Accounts discovery example program
crates/adapters/examples/accounts_live.rs
Add example demonstrating discovery of all local OAuth accounts, loading identity store, and fetching per-account usage via appropriate provider strategies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ogrodev/MLT#2: Establishes the base local source discovery model (consent + presence gates) that this PR extends with per-account sources and multi-provider expansion logic.
  • ogrodev/MLT#4: Parallel UI refactoring to support multi-provider usage state and per-source snapshots, directly complementing this PR's backend per-account source isolation.
  • ogrodev/MLT#5: Builds on this PR's per-account disconnect logic to implement surgical purging of individual Codex account cached tokens/consent/identity.

Poem

🐰 Whiskers twitch with joy—
Accounts hop in from Oh My Pi,
OAuth tokens refresh,
Each provider tracked alone,
Multi-account magic at last!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@PedroMendes-AE
Copy link
Copy Markdown
Author

Closing this fork-based PR; recreating from the ogrodev-owned branch.

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.

2 participants