fix(quota): fetch quota for paused accounts + simplify exhausted display

- Remove early return for paused accounts in quota fetcher
- Check both auth/ and auth-paused/ directories when reading auth data
- Allows users to see quota status of paused accounts to know when to unpause
- Remove "Exhausted" text in tooltip, just show 0% in red (saves width)
This commit is contained in:
kaitranntt committed 2026-01-25 17:12:45 -05:00
1 parent b2b8a85af6
commit 27e8813cae
3 files changed
+45 -56

No files matched your search

+43 -54
View File
@@ -11,7 +11,7 @@ import { getAuthDir } from './config-generator';
import { CLIProxyProvider } from './types'; import { CLIProxyProvider } from './types';
import { import {
getProviderAccounts, getProviderAccounts,
isAccountPaused, getPausedDir,
setAccountTier, setAccountTier,
type AccountInfo, type AccountInfo,
type AccountTier, type AccountTier,
@@ -242,59 +242,60 @@ async function refreshAccessToken(
* Read auth data from auth file (access token, project_id, expiry status) * Read auth data from auth file (access token, project_id, expiry status)
*/ */
function readAuthData(provider: CLIProxyProvider, accountId: string): AuthData | null { function readAuthData(provider: CLIProxyProvider, accountId: string): AuthData | null {
const authDir = getAuthDir(); // Check both active and paused auth directories (quota needed for paused accounts too)
const authDirs = [getAuthDir(), getPausedDir()];
// Check if auth directory exists
if (!fs.existsSync(authDir)) {
return null;
}
// Sanitize accountId (email) to match auth file naming: @ and . → _ // Sanitize accountId (email) to match auth file naming: @ and . → _
const sanitizedId = sanitizeEmail(accountId); const sanitizedId = sanitizeEmail(accountId);
const prefix = provider === 'agy' ? 'antigravity-' : `${provider}-`; const prefix = provider === 'agy' ? 'antigravity-' : `${provider}-`;
const expectedFile = `${prefix}${sanitizedId}.json`; const expectedFile = `${prefix}${sanitizedId}.json`;
const filePath = path.join(authDir, expectedFile);
// Direct file access (most common case) for (const authDir of authDirs) {
if (fs.existsSync(filePath)) { if (!fs.existsSync(authDir)) continue;
try {
const content = fs.readFileSync(filePath, 'utf-8');
const data = JSON.parse(content) as AntigravityAuthFile;
if (!data.access_token) return null;
return {
accessToken: data.access_token,
refreshToken: data.refresh_token || null,
projectId: data.project_id || null,
isExpired: isTokenExpired(data.expired),
expiresAt: data.expired || null,
};
} catch {
return null;
}
}
// Fallback: scan directory for matching email in file content const filePath = path.join(authDir, expectedFile);
const files = fs.readdirSync(authDir);
for (const file of files) { // Direct file access (most common case)
if (file.startsWith(prefix) && file.endsWith('.json')) { if (fs.existsSync(filePath)) {
const candidatePath = path.join(authDir, file);
try { try {
const content = fs.readFileSync(candidatePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8');
const data = JSON.parse(content) as AntigravityAuthFile; const data = JSON.parse(content) as AntigravityAuthFile;
// Match by email field inside the auth file if (!data.access_token) continue;
if (data.email === accountId && data.access_token) { return {
return { accessToken: data.access_token,
accessToken: data.access_token, refreshToken: data.refresh_token || null,
refreshToken: data.refresh_token || null, projectId: data.project_id || null,
projectId: data.project_id || null, isExpired: isTokenExpired(data.expired),
isExpired: isTokenExpired(data.expired), expiresAt: data.expired || null,
expiresAt: data.expired || null, };
};
}
} catch { } catch {
continue; continue;
} }
} }
// Fallback: scan directory for matching email in file content
const files = fs.readdirSync(authDir);
for (const file of files) {
if (file.startsWith(prefix) && file.endsWith('.json')) {
const candidatePath = path.join(authDir, file);
try {
const content = fs.readFileSync(candidatePath, 'utf-8');
const data = JSON.parse(content) as AntigravityAuthFile;
// Match by email field inside the auth file
if (data.email === accountId && data.access_token) {
return {
accessToken: data.access_token,
refreshToken: data.refresh_token || null,
projectId: data.project_id || null,
isExpired: isTokenExpired(data.expired),
expiresAt: data.expired || null,
};
}
} catch {
continue;
}
}
}
} }
return null; return null;
@@ -554,19 +555,7 @@ export async function fetchAccountQuota(
}; };
} }
// Check if account is paused (token moved to auth-paused/ directory) // Read auth data from auth file (checks both active and paused directories)
if (isAccountPaused(provider, accountId)) {
const error = 'Account is paused';
if (verbose) console.error(`[i] ${error}`);
return {
success: false,
models: [],
lastUpdated: Date.now(),
error,
};
}
// Read auth data from auth file
const authData = readAuthData(provider, accountId); const authData = readAuthData(provider, accountId);
if (!authData) { if (!authData) {
const error = 'Auth file not found for account'; const error = 'Auth file not found for account';
@@ -240,7 +240,7 @@ export function AccountCard({
{m.displayName} {m.displayName}
</span> </span>
<span className={cn('font-mono', m.exhausted && 'text-red-500')}> <span className={cn('font-mono', m.exhausted && 'text-red-500')}>
{m.exhausted ? 'Exhausted' : `${m.percentage}%`} {m.percentage}%
</span> </span>
</div> </div>
))} ))}
@@ -370,7 +370,7 @@ export function AccountItem({
{m.displayName} {m.displayName}
</span> </span>
<span className={cn('font-mono', m.exhausted && 'text-red-500')}> <span className={cn('font-mono', m.exhausted && 'text-red-500')}>
{m.exhausted ? 'Exhausted' : `${m.percentage}%`} {m.percentage}%
</span> </span>
</div> </div>
))} ))}