fix(cliproxy): surface quota metadata in CLI

This commit is contained in:
Tam Nhu Tran
2026-03-26 17:13:11 -04:00
parent 4d49d13931
commit 19f6b3c4e6
4 changed files with 227 additions and 6 deletions
+7
View File
@@ -743,5 +743,12 @@ export async function fetchAllGeminiCliQuotas(
return results;
}
export const __testExports = {
sanitizeGeminiCliErrorDetail,
extractGeminiCliNestedMessage,
parseGeminiCliErrorBody,
buildGeminiCliForbiddenActionHint,
};
// Export for testing
export { resolveGeminiCliProjectId, buildGeminiCliBuckets };
+79 -5
View File
@@ -27,6 +27,7 @@ import type {
ClaudeQuotaResult,
GeminiCliQuotaResult,
GhcpQuotaResult,
QuotaErrorMetadata,
} from '../../cliproxy/quota-types';
import { isOnCooldown } from '../../cliproxy/quota-manager';
import { CLIProxyProvider } from '../../cliproxy/types';
@@ -96,6 +97,75 @@ function formatResetTimeISO(isoTime: string): string {
return formatResetTime(seconds);
}
interface QuotaFailureDisplayEntry {
tone: 'error' | 'info' | 'dim';
text: string;
}
function getQuotaFailureDisplayEntries(
quota: QuotaErrorMetadata & {
error?: string;
}
): QuotaFailureDisplayEntry[] {
const entries: QuotaFailureDisplayEntry[] = [
{
tone: 'error',
text: quota.error || 'Failed to fetch quota',
},
];
if (quota.actionHint) {
entries.push({
tone: 'info',
text: quota.actionHint,
});
}
const diagnostics: string[] = [];
if (typeof quota.httpStatus === 'number') {
diagnostics.push(`HTTP ${quota.httpStatus}`);
}
if (quota.errorCode) {
diagnostics.push(`Code: ${quota.errorCode}`);
}
if (quota.retryable) {
diagnostics.push('Retryable');
}
if (diagnostics.length > 0) {
entries.push({
tone: 'dim',
text: diagnostics.join(' | '),
});
}
const normalizedError = quota.error?.trim();
const normalizedDetail = quota.errorDetail?.trim();
if (normalizedDetail && normalizedDetail !== normalizedError) {
entries.push({
tone: 'dim',
text: `Detail: ${normalizedDetail}`,
});
}
return entries;
}
function displayQuotaFailure(
quota: QuotaErrorMetadata & {
error?: string;
}
): void {
for (const entry of getQuotaFailureDisplayEntries(quota)) {
const rendered =
entry.tone === 'error'
? color(entry.text, 'error')
: entry.tone === 'info'
? info(entry.text)
: dim(entry.text);
console.log(` ${rendered}`);
}
}
function formatAbsoluteResetTime(isoTime: string): string | null {
if (!isoTime) return null;
const resetDate = new Date(isoTime);
@@ -318,7 +388,7 @@ function displayCodexQuotaSection(results: { account: string; quota: CodexQuotaR
if (!quota.success) {
console.log(` ${fail(account)}${defaultMark}`);
console.log(` ${color(quota.error || 'Failed to fetch quota', 'error')}`);
displayQuotaFailure(quota);
console.log('');
continue;
}
@@ -473,7 +543,7 @@ function displayClaudeQuotaSection(results: { account: string; quota: ClaudeQuot
if (!quota.success) {
console.log(` ${fail(account)}${defaultMark}`);
console.log(` ${color(quota.error || 'Failed to fetch quota', 'error')}`);
displayQuotaFailure(quota);
console.log('');
continue;
}
@@ -550,7 +620,7 @@ function displayGeminiCliQuotaSection(
if (!quota.success) {
console.log(` ${fail(account)}${defaultMark}`);
console.log(` ${color(quota.error || 'Failed to fetch quota', 'error')}`);
displayQuotaFailure(quota);
console.log('');
continue;
}
@@ -601,7 +671,7 @@ function displayGhcpQuotaSection(results: { account: string; quota: GhcpQuotaRes
if (!quota.success) {
console.log(` ${fail(account)}${defaultMark}`);
console.log(` ${color(quota.error || 'Failed to fetch quota', 'error')}`);
displayQuotaFailure(quota);
console.log('');
continue;
}
@@ -697,6 +767,10 @@ const QUOTA_PROVIDER_RUNTIME: Record<QuotaSupportedProvider, QuotaProviderRuntim
},
};
export const __testExports = {
getQuotaFailureDisplayEntries,
};
export async function handleQuotaStatus(
verbose = false,
providerFilter: QuotaSupportedProvider | 'all' = 'all'
@@ -771,7 +845,7 @@ export async function handleDoctor(verbose = false): Promise<void> {
if (!quota.success) {
console.log(` ${fail(accountLabel)}${defaultBadge}`);
console.log(` ${color(quota.error || 'Failed to fetch quota', 'error')}`);
displayQuotaFailure(quota);
if (quota.isUnprovisioned) {
console.log(
` ${warn('Account not provisioned - open Gemini Code Assist in IDE first')}`