feat(cliproxy): add entitlement evidence for gemini and agy

This commit is contained in:
Tam Nhu Tran
2026-04-10 13:17:50 -04:00
parent 70051c2acb
commit bb331ff5d8
14 changed files with 371 additions and 34 deletions
@@ -86,7 +86,9 @@ export function AccountSurfaceCard({
const title = displayEmail || identity.email || accountId;
const normalizedProvider = provider.toLowerCase();
const showTierBadge =
(normalizedProvider === 'agy' || normalizedProvider === 'antigravity') &&
(normalizedProvider === 'agy' ||
normalizedProvider === 'antigravity' ||
normalizedProvider === 'gemini') &&
tier &&
tier !== 'unknown' &&
tier !== 'free';
@@ -21,6 +21,7 @@ import {
type ModelTier,
type UnifiedQuotaResult,
} from '@/lib/utils';
import type { ProviderEntitlementEvidence } from '@/lib/api-client';
interface QuotaTooltipContentProps {
quota: UnifiedQuotaResult | null | undefined;
@@ -77,6 +78,35 @@ function getClaudeWindowDisplayLabel(rateLimitType: string, fallback: string): s
}
}
function renderEntitlementRows(entitlement: ProviderEntitlementEvidence | undefined) {
if (!entitlement) return null;
const rows: Array<{ label: string; value: string | null }> = [];
if (entitlement.rawTierLabel) {
rows.push({ label: 'Tier', value: entitlement.rawTierLabel });
} else if (entitlement.normalizedTier !== 'unknown') {
rows.push({ label: 'Tier', value: entitlement.normalizedTier });
}
if (entitlement.rawTierId) {
rows.push({ label: 'Tier ID', value: entitlement.rawTierId });
}
if (entitlement.accessState !== 'entitled' || entitlement.capacityState !== 'available') {
rows.push({
label: 'State',
value: `${entitlement.accessState.replaceAll('_', ' ')} / ${entitlement.capacityState.replaceAll('_', ' ')}`,
});
}
if (rows.length === 0) return null;
return rows.map((row) => (
<div key={row.label} className="flex justify-between gap-4">
<span className="text-muted-foreground">{row.label}</span>
<span className="font-mono">{row.value}</span>
</div>
));
}
/**
* Renders provider-specific quota tooltip content
* Uses type guards for proper TypeScript narrowing
@@ -132,6 +162,7 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
return (
<div className="text-xs space-y-1.5">
{renderEntitlementRows(quota.entitlement)}
<p className="font-medium">Model Quotas:</p>
{tierOrder.map((tier, idx) => {
const models = groups.get(tier);
@@ -268,10 +299,13 @@ export function QuotaTooltipContent({ quota, resetTime }: QuotaTooltipContentPro
// Gemini provider tooltip
if (isGeminiQuotaResult(quota)) {
const hasBucketResetTime = quota.buckets.some((bucket) => !!bucket.resetTime);
const hasEntitlementTier =
!!quota.entitlement?.rawTierLabel || quota.entitlement?.normalizedTier !== 'unknown';
return (
<div className="text-xs space-y-1.5">
{quota.tierLabel && (
{renderEntitlementRows(quota.entitlement)}
{!hasEntitlementTier && quota.tierLabel && (
<div className="flex justify-between gap-4">
<span className="text-muted-foreground">Tier</span>
<span className="font-mono">{quota.tierLabel}</span>
+26
View File
@@ -562,6 +562,30 @@ export interface QuotaResult {
retryable?: boolean;
/** True if token is expired and needs re-authentication */
needsReauth?: boolean;
/** Richer provider entitlement evidence derived from live/runtime signals */
entitlement?: ProviderEntitlementEvidence;
}
export interface ProviderEntitlementEvidence {
normalizedTier: 'free' | 'pro' | 'ultra' | 'unknown';
rawTierId: string | null;
rawTierLabel: string | null;
source: 'runtime_api' | 'runtime_inference' | 'registry_cache' | 'official_docs';
confidence: 'high' | 'medium' | 'low';
accessState:
| 'entitled'
| 'not_entitled'
| 'capacity_exhausted'
| 'temporarily_unavailable'
| 'unknown';
capacityState:
| 'available'
| 'capacity_exhausted'
| 'rate_limited'
| 'temporarily_unavailable'
| 'unknown';
lastVerifiedAt: number;
notes?: string | null;
}
/** Codex rate limit window */
@@ -725,6 +749,8 @@ export interface GeminiCliQuotaResult {
tierId?: string | null;
/** Available Google One AI credits when reported by the API */
creditBalance?: number | null;
/** Richer provider entitlement evidence derived from live/runtime signals */
entitlement?: ProviderEntitlementEvidence;
/** Timestamp of fetch */
lastUpdated: number;
/** Upstream HTTP status when available */
+20
View File
@@ -771,6 +771,25 @@ export function getQuotaFailureInfo(
const technicalDetail = buildQuotaTechnicalDetail(quota);
const rawDetail = buildQuotaRawDetail(quota, summary, technicalDetail);
const lowerSummary = summary.toLowerCase();
const entitlement = 'entitlement' in quota ? quota.entitlement : undefined;
if (
quota.errorCode === 'capacity_exhausted' ||
entitlement?.capacityState === 'capacity_exhausted' ||
lowerSummary.includes('no capacity available') ||
lowerSummary.includes('capacity exhausted')
) {
return {
label: 'Capacity',
summary,
actionHint:
actionHint ||
'Retry later or switch to another model. This is a temporary provider capacity issue.',
technicalDetail,
rawDetail,
tone: 'warning',
};
}
if (
quota.needsReauth ||
@@ -808,6 +827,7 @@ export function getQuotaFailureInfo(
}
if (
entitlement?.accessState === 'not_entitled' ||
quota.isForbidden ||
quota.httpStatus === 403 ||
errorCode === 'quota_api_forbidden' ||