-
Notifications
You must be signed in to change notification settings - Fork 12
systems oauth usage tracker
Active contributors: Nik, Ran
The OAuth usage tracker fetches quota windows for connected Claude and Codex OAuth accounts and publishes them to the "OAuth Quota Usage" section of the settings window. It talks to the provider usage endpoints directly over HTTPS using the stored OAuth tokens, so it has no dependency on the codex CLI or any external binary. The Claude path additionally refreshes expired access tokens in place before reading usage.
It is implemented entirely in src/Sources/OAuthUsageTracker.swift.
src/Sources/
OAuthUsageTracker.swift # OAuthUsageTracker + OAuthUsageWindow + OAuthAccountUsage
SettingsView.swift # owns the refresh button and renders the windows
AuthStatus.swift # AuthManager / AuthAccount consumed by refresh(...)
Credential files live under ~/.cli-proxy-api/ (one JSON file per account). The Claude refresh path writes updated tokens back into those same files.
| Type | Role |
|---|---|
OAuthUsageWindow |
One quota window (title, usedPercent, resetText, resetDate). Exposes remainingPercent = max(0, 100 - usedPercent). Identifiable + Equatable. |
OAuthAccountUsage |
Per-account result: id, provider (ServiceType), email, isLoading, windows, error, updatedAt. |
OAuthUsageTracker |
@MainActor ObservableObject. Publishes accounts and isRefreshing; owns the in-flight refreshTask. |
OAuthUsageParsing |
Private constants: 15 s request timeout and the allowed character set for form-URL-encoding the refresh token. |
refresh(codexAccounts:claudeAccounts:) is the single entry point. It cancels any prior refreshTask, filters each provider's accounts down to enabled and non-expired (!isDisabled && !isExpired), and short-circuits to an empty list when nothing qualifies. It then publishes loading placeholders, flips isRefreshing to true, and launches a withTaskGroup that fetches every account in parallel. Results are collected, sorted by provider then email (case-insensitive), and published back on the main actor unless the task was cancelled.
sequenceDiagram
participant SV as SettingsView
participant T as OAuthUsageTracker
participant G as TaskGroup
participant CX as chatgpt.com (Codex)
participant CL as api.anthropic.com (Claude)
participant F as ~/.cli-proxy-api files
SV->>T: refresh(codexAccounts, claudeAccounts)
T->>T: cancel prior refreshTask
T->>T: filter enabled + non-expired
T->>SV: publish loading placeholders, isRefreshing = true
T->>G: withTaskGroup
par per Codex account
G->>CX: GET /backend-api/wham/usage (Bearer + ChatGPT-Account-Id)
CX-->>G: rate_limit JSON
and per Claude account
G->>F: read token, check expiry
opt expired
G->>CL: POST platform.claude.com /v1/oauth/token (refresh)
CL-->>G: new access/refresh
G->>F: write tokens atomically
end
G->>CL: GET /api/oauth/usage (anthropic-beta oauth-2025-04-20)
CL-->>G: bucket JSON (401/403 -> retry after refresh)
end
G-->>T: [OAuthAccountUsage]
T->>T: sort by provider, then email
T->>SV: publish accounts, isRefreshing = false
fetchCodexUsage reads the access_token from the account file and issues GET https://chatgpt.com/backend-api/wham/usage with an Authorization: Bearer header, Accept: application/json, a codex-cli user agent, and the ChatGPT-Account-Id header when account_id is present. parseCodexWindows reads rate_limit.primary_window as the "5-hour" window and rate_limit.secondary_window as the "Weekly" window. If the rate_limit shape is missing it falls back to parseGenericWindows, which flattens nested dictionaries and heuristically labels windows ("5-hour", "Weekly", "Full", "Session") from key/value text and percent/reset fields.
fetchClaudeUsage reads the token and checks the expired timestamp. If expired, it calls refreshClaudeTokens, which POSTs to https://platform.claude.com/v1/oauth/token with client_id, grant_type=refresh_token, and the form-URL-encoded refresh token. On success it writes the new access_token, refresh_token, recomputed expired (now + expires_in), and last_refresh back into the credential file atomically (.write(to:options:[.atomic])). It then calls executeClaudeUsageRequest, which issues GET https://api.anthropic.com/api/oauth/usage with anthropic-beta: oauth-2025-04-20. A 401/403 response triggers retryClaudeUsageAfterRefresh, which forces a token refresh and retries once. parseClaudeWindows reads named buckets: five_hour ("5-hour"), seven_day ("Weekly"), seven_day_opus, seven_day_sonnet, seven_day_oauth_apps, seven_day_cowork, and seven_day_omelette, reading each bucket's utilization and resets_at.
-
numberValuecoercesDouble,Int,NSNumber, or numericString(stripping%) toDouble. - Percent normalization treats any value
<= 1as a fraction and multiplies by 100. -
resetText(for:)renders both a relative ("in 3 hours") and absolute (medium date + short time) form. -
parseISO8601Dateaccepts timestamps with or without fractional seconds. -
remainingPercenton each window is100 - usedPercent, clamped at zero, and is what the settings UI displays as "% left".
-
SettingsViewowns the refresh button (refreshOAuthUsage) and renders results throughoauthUsageDashboard/oauthUsageAccountRow/usageWindowRow. It also refreshes automatically on appear and whenever the enabled Codex/Claude account signature changes. -
AuthManager(src/Sources/AuthStatus.swift) supplies theAuthAccountarrays passed intorefresh(...); only enabled, non-expired accounts are queried. -
Credential files: the Claude refresh path writes refreshed tokens back to the per-account JSON under
~/.cli-proxy-api/, so a usage refresh can quietly extend a session's access token.
- Add or relabel Claude buckets: edit the
knownBucketslist inparseClaudeWindows. - Change Codex window mapping: edit
parseCodexWindows/codexWindow, or the heuristics inparseGenericWindows/windowTitle/windowRank. - Adjust request timeout or token encoding: edit
OAuthUsageParsing. - Change refresh filtering or sort order: edit
refresh(codexAccounts:claudeAccounts:). - Change token-refresh behavior (endpoint, client id, written fields): edit
refreshClaudeTokens.
| File | Role |
|---|---|
src/Sources/OAuthUsageTracker.swift |
Tracker, structs, fetch/parse helpers, Claude token refresh. |
src/Sources/SettingsView.swift |
Refresh button, dashboard rendering, auto-refresh triggers. |
src/Sources/AuthStatus.swift |
AuthManager / AuthAccount source for account lists. |