feat(quota): gemini fetcher, grok token refresh, claude cred merge fix - #2665
feat(quota): gemini fetcher, grok token refresh, claude cred merge fix#2665romeoju01 wants to merge 1 commit into
Conversation
- claude: merge refreshed oauth into the raw credentials document instead of the zod-parsed shape; the schema parse stripped sibling keys (mcpOAuth, expiresAt, scopes) on write-back, degrading the CLI login - grok: skip expired auth.json tokens (expires_at) and refresh them via the CLI's OIDC flow (discovery + refresh_token grant), persisting the rotated tokens back to the same file; GROK_AUTH_FILE env override; billing-period-end detail - gemini: new fetcher for Code Assist (retrieveUserQuota per-model buckets); refreshes expired oauth_creds.json via the CLI's installed-app OAuth client, resolved at runtime from the local gemini-cli bundle (GEMINI_OAUTH_CLIENT_ID/SECRET env overrides)
| const target = entry.topKey ? raw[entry.topKey] : raw; | ||
| if (!target || typeof target !== "object" || Array.isArray(target)) return null; | ||
| const record = target as Record<string, unknown>; | ||
| record["key"] = tok.access_token; |
There was a problem hiding this comment.
Refreshed token stored in wrong field
When a legacy flat auth record contains an expired access_token, refresh writes the replacement to key while subsequent reads continue to prefer the stale access_token, causing later Grok quota requests to report usage as unavailable.
| record["key"] = tok.access_token; | |
| if (typeof record["access_token"] === "string") { | |
| record["access_token"] = tok.access_token; | |
| } else { | |
| record["key"] = tok.access_token; | |
| } |
| if (cachedOAuthClient !== undefined) return cachedOAuthClient; | ||
| cachedOAuthClient = null; |
There was a problem hiding this comment.
Failed OAuth discovery stays cached
An unsuccessful OAuth-client lookup stores null in module state, so installing or repairing Gemini CLI later does not restore credential refresh until the long-running daemon restarts.
| if (cachedOAuthClient !== undefined) return cachedOAuthClient; | |
| cachedOAuthClient = null; | |
| if (cachedOAuthClient) return cachedOAuthClient; | |
| cachedOAuthClient = null; |
|
| Filename | Overview |
|---|---|
| packages/server/src/services/quota-fetcher/manifest.ts | Registers the new Gemini quota provider through the existing provider manifest. |
| packages/server/src/services/quota-fetcher/providers/claude.ts | Preserves unknown top-level Claude credential fields by merging refreshed OAuth data into the raw document. |
| packages/server/src/services/quota-fetcher/providers/gemini.ts | Adds Gemini quota and token-refresh support, but a failed OAuth-client lookup is cached for the daemon lifetime. |
| packages/server/src/services/quota-fetcher/providers/grok.ts | Adds expiry-aware Grok token selection and refresh, but legacy flat credentials revert to their stale access token after refresh. |
Sequence Diagram
sequenceDiagram
participant S as Quota service
participant F as Credential file
participant O as OAuth/OIDC server
participant Q as Provider quota API
S->>F: Read access token and expiry
alt Token expired
S->>O: Refresh token grant
O-->>S: Rotated access/refresh tokens
S->>F: Persist refreshed credentials
end
S->>Q: Fetch quota with access token
Q-->>S: Usage buckets or balances
Reviews (1): Last reviewed commit: "feat(quota): gemini fetcher, grok token ..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0151ac1ba8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| existing && typeof existing === "object" && !Array.isArray(existing) | ||
| ? (existing as Record<string, unknown>) | ||
| : {}; | ||
| record["claudeAiOauth"] = oauth; |
There was a problem hiding this comment.
Preserve nested Claude OAuth metadata
When claudeAiOauth contains metadata such as expiresAt or scopes, oauth has already passed through ClaudeCredentialsSchema, which strips those unknown nested fields. Replacing the entire raw claudeAiOauth object here therefore still deletes that metadata during every token refresh, despite preserving root-level siblings; merge the refreshed token fields into the existing raw nested object instead.
Useful? React with 👍 / 👎.
| record["key"] = tok.access_token; | ||
| if (tok.refresh_token) record["refresh_token"] = tok.refresh_token; |
There was a problem hiding this comment.
Persist refreshed Grok tokens to the original field
For a supported auth entry that stores its token as access_token, entryFromValue gives that field precedence over key, but refresh writes only key. The current request succeeds with the returned token, while the next read selects the old access_token and treats it as fresh because expires_at was advanced, causing billing requests to fail until another refresh is attempted; update the field that supplied the token or update both representations.
Useful? React with 👍 / 👎.
| const nvmRoot = join(homedir(), ".nvm", "versions", "node"); | ||
| try { | ||
| if (existsSync(nvmRoot)) { | ||
| for (const ver of readdirSync(nvmRoot)) { | ||
| dirs.push(join(nvmRoot, ver, "lib", "node_modules", "@google", "gemini-cli", "bundle")); | ||
| } |
There was a problem hiding this comment.
Search the configured npx installation for Gemini OAuth
The repository's configured Gemini command is npx -y @google/gemini-cli@0.52.0 (packages/app/src/data/acp-provider-catalog.ts:188), but this lookup searches only an APPDATA global installation and NVM global installations. An npx-managed package resides in npm's execution cache, so after its access token expires, the default Paseo installation cannot discover the CLI OAuth client and returns unavailable unless users manually supply undocumented override variables.
Useful? React with 👍 / 👎.
Summary
Three independent quota-fetcher fixes, found while running the fetchers against real CLI credential files on Windows + WSL:
efresh_token\ grant) and persists the rotated tokens back to the same file (write-back is required — xAI rotates refresh tokens). Also adds \GROK_AUTH_FILE\ env override and a billing-period-end detail.
etrieveUserQuota\ per-model buckets (remainingFraction/resetTime) as usage windows. Refreshes expired \oauth_creds.json\ via the gemini-cli's installed-app OAuth client, which is resolved at runtime from the local gemini-cli bundle (or \GEMINI_OAUTH_CLIENT_ID/\GEMINI_OAUTH_CLIENT_SECRET\ env) so no client credentials are committed. Project from \GOOGLE_CLOUD_PROJECT/\GOOGLE_CLOUD_QUOTA_PROJECT\ or constructor option; reports a descriptive error when unset.
Testing
ode_modules, untouched by this PR).