fix(cliproxy): refresh non-default gemini quota tokens

This commit is contained in:
Tam Nhu Tran committed 2026-04-08 23:55:55 -04:00
1 parent 04878f0864
commit b6fb443f57
11 files changed
+207 -71

No files matched your search

+28 -5
View File
@@ -16,12 +16,35 @@ export function sanitizeEmail(email: string): string {
* Check if token is expired based on the expired timestamp.
* Returns false if timestamp is missing or invalid (fail-open for quota display).
*/
export function isTokenExpired(expiredStr?: string): boolean {
if (!expiredStr) return false;
export function getTokenExpiryTimestamp(expiredValue?: string | number | null): number | null {
if (expiredValue === undefined || expiredValue === null || expiredValue === '') {
return null;
}
try {
const expiredDate = new Date(expiredStr);
return expiredDate.getTime() < Date.now();
if (typeof expiredValue === 'number') {
return Number.isFinite(expiredValue) ? expiredValue : null;
}
const trimmed = expiredValue.trim();
if (!trimmed) {
return null;
}
if (/^\d+$/.test(trimmed)) {
const numericTimestamp = Number(trimmed);
return Number.isFinite(numericTimestamp) ? numericTimestamp : null;
}
const expiredDate = new Date(trimmed);
const expiredAt = expiredDate.getTime();
return Number.isNaN(expiredAt) ? null : expiredAt;
} catch {
return false;
return null;
}
}
export function isTokenExpired(expiredValue?: string | number | null): boolean {
const expiredAt = getTokenExpiryTimestamp(expiredValue);
return expiredAt !== null ? expiredAt < Date.now() : false;
}
+17 -10
View File
@@ -9,7 +9,7 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import { getAuthDir } from './config-generator';
import { getProviderAccounts, getPausedDir } from './account-manager';
import { sanitizeEmail, isTokenExpired } from './auth-utils';
import { getTokenExpiryTimestamp, sanitizeEmail, isTokenExpired } from './auth-utils';
import { refreshGeminiToken } from './auth/gemini-token-refresh';
import {
buildGeminiCliBucketsFromParsedBuckets,
@@ -38,7 +38,7 @@ interface GeminiCliAuthData {
accessToken: string;
projectId: string | null;
isExpired: boolean;
expiresAt: string | null;
expiresAt: string | number | null;
}
/** Raw bucket from API response */
@@ -130,17 +130,23 @@ function extractAccessToken(data: Record<string, unknown>): string | null {
* Extract expiry from Gemini auth file data
* Handles both flat (expired) and nested (token.expiry) structures
*/
function extractExpiry(data: Record<string, unknown>): string | null {
function extractExpiry(data: Record<string, unknown>): string | number | null {
// Flat structure: { expired: "..." }
if (typeof data.expired === 'string') {
return data.expired;
}
if (typeof data.expired === 'number') {
return data.expired;
}
// Nested structure: { token: { expiry: "..." } }
if (data.token && typeof data.token === 'object') {
const token = data.token as Record<string, unknown>;
if (typeof token.expiry === 'string') {
return token.expiry;
}
if (typeof token.expiry === 'number') {
return token.expiry;
}
}
return null;
}
@@ -736,19 +742,20 @@ export async function fetchGeminiCliQuota(
// Proactive refresh: refresh if expired OR expiring within 5 minutes
const REFRESH_LEAD_TIME_MS = 5 * 60 * 1000;
const expiresAt = getTokenExpiryTimestamp(authData.expiresAt);
const shouldRefresh =
authData.isExpired ||
!authData.expiresAt ||
new Date(authData.expiresAt).getTime() - Date.now() < REFRESH_LEAD_TIME_MS;
authData.isExpired || expiresAt === null || expiresAt - Date.now() < REFRESH_LEAD_TIME_MS;
let attemptedRefresh = false;
if (shouldRefresh) {
attemptedRefresh = true;
if (verbose)
console.error(
authData.isExpired
? '[i] Token expired, refreshing...'
: '[i] Token expiring soon, proactive refresh...'
);
const refreshResult = await refreshGeminiToken();
const refreshResult = await refreshGeminiToken(accountId);
if (refreshResult.success) {
if (verbose) console.error('[i] Token refreshed successfully');
@@ -776,10 +783,10 @@ export async function fetchGeminiCliQuota(
// First attempt with current token
const result = await fetchWithAuthData(authData, accountId, verbose);
// If 401 error and we haven't refreshed yet, try refresh and retry
if (result.needsReauth && result.error?.includes('expired')) {
// Retry once with an account-scoped refresh when the quota endpoint rejects auth.
if (result.needsReauth && !attemptedRefresh) {
if (verbose) console.error('[i] Got 401, attempting refresh and retry...');
const refreshResult = await refreshGeminiToken();
const refreshResult = await refreshGeminiToken(accountId);
if (refreshResult.success) {
const refreshedAuthData = readGeminiCliAuthData(accountId);
if (refreshedAuthData) {