Add Gemini CLI v1internal quota support

This commit is contained in:
Mas0nShi
2026-05-22 16:58:04 +08:00
parent 9533bd7043
commit ce02f1ae8c
17 changed files with 985 additions and 52 deletions
@@ -1,6 +1,9 @@
import { describe, expect, it } from 'vitest'
import { getQuotaDisplayText } from '../providerKeyQuota'
import {
getGeminiCliAccountCreditsText,
getQuotaDisplayText,
} from '../providerKeyQuota'
describe('providerKeyQuota', () => {
it('includes Codex Spark quota windows in display text', () => {
@@ -104,4 +107,38 @@ describe('providerKeyQuota', () => {
},
}, 'grok')).toBe('Auto剩余 40.0% (60/150) | Heavy剩余 0.0% (0/20)')
})
it('formats Gemini CLI AI credits from status snapshot and upstream metadata', () => {
expect(getQuotaDisplayText({
status_snapshot: {
quota: {
provider_type: 'gemini_cli',
code: 'ok',
exhausted: false,
credits: {
remaining: 123.5,
consumed: 7,
},
},
},
}, 'gemini_cli')).toBe('AI Credits 剩余 123.5')
expect(getGeminiCliAccountCreditsText({
status_snapshot: {
quota: {
provider_type: 'gemini_cli',
code: 'ok',
exhausted: false,
},
},
upstream_metadata: {
gemini_cli: {
paidTier: {
id: 'g1-pro-tier',
availableCredits: '41.5',
},
},
},
}, 'gemini_cli')).toBe('AI Credits 剩余 41.5')
})
})
+79 -1
View File
@@ -3,10 +3,12 @@ import type {
QuotaStatusSnapshot,
QuotaWindowSnapshot,
} from '@/api/endpoints/types/statusSnapshot'
import type { UpstreamMetadata } from '@/api/endpoints/types/provider'
export interface ProviderKeyQuotaCarrier {
account_quota?: string | null
status_snapshot?: ProviderKeyStatusSnapshot | null
upstream_metadata?: UpstreamMetadata | null
}
function normalizeText(value: unknown): string | null {
@@ -76,6 +78,28 @@ function getQuotaWindow(
return getQuotaWindows(quota).find(window => normalizeText(window.code)?.toLowerCase() === normalizedCode) ?? null
}
function finiteNumber(value: unknown): number | null {
if (typeof value === 'number') return Number.isFinite(value) ? value : null
if (typeof value === 'string') {
const parsed = Number(value.trim())
return Number.isFinite(parsed) ? parsed : null
}
return null
}
function firstFiniteNumber(...values: unknown[]): number | null {
for (const value of values) {
const parsed = finiteNumber(value)
if (parsed != null) return parsed
}
return null
}
function objectValue(value: unknown): Record<string, unknown> | null {
return value && typeof value === 'object' && !Array.isArray(value)
? value as Record<string, unknown>
: null
}
function getQuotaWindowsByScope(
quota: QuotaStatusSnapshot | null | undefined,
scope: string,
@@ -105,6 +129,58 @@ function getQuotaWindowValueText(window: QuotaWindowSnapshot | null | undefined)
return null
}
function getGeminiCliCreditsTextFromQuota(quota: QuotaStatusSnapshot | null | undefined): string | null {
const credits = quota?.credits
if (!credits) return null
const remaining = firstFiniteNumber(credits.remaining, credits.balance)
if (remaining != null) return `AI Credits 剩余 ${formatQuotaValue(remaining)}`
if (credits.unlimited === true) return 'AI Credits 不限量'
if (credits.has_credits === true) return 'AI Credits 可用'
if (credits.has_credits === false) return 'AI Credits 已用尽'
return null
}
export function getGeminiCliAccountCreditsText(
input: ProviderKeyQuotaCarrier,
fallbackProviderType?: string | null,
): string | null {
const quota = getQuotaSnapshot(input)
if (getQuotaProviderType(quota, fallbackProviderType) !== 'gemini_cli') return null
const quotaText = getGeminiCliCreditsTextFromQuota(quota)
if (quotaText) return quotaText
const metadata = input.upstream_metadata?.gemini_cli
const credits = objectValue(metadata?.credits)
const paidTier = objectValue(metadata?.paidTier)
const currentTier = objectValue(metadata?.currentTier)
const remaining = firstFiniteNumber(
credits?.remaining,
credits?.remainingCredits,
credits?.available,
credits?.availableCredits,
credits?.balance,
paidTier?.availableCredits,
paidTier?.remainingCredits,
currentTier?.availableCredits,
currentTier?.remainingCredits,
)
if (remaining != null) return `AI Credits 剩余 ${formatQuotaValue(remaining)}`
const hasCredits = typeof credits?.has_credits === 'boolean'
? credits.has_credits
: typeof paidTier?.hasCredits === 'boolean'
? paidTier.hasCredits
: typeof currentTier?.hasCredits === 'boolean'
? currentTier.hasCredits
: null
if (hasCredits === true) return 'AI Credits 可用'
if (hasCredits === false) return 'AI Credits 已用尽'
const unlimited = credits?.unlimited === true || paidTier?.unlimited === true || currentTier?.unlimited === true
return unlimited ? 'AI Credits 不限量' : null
}
const GROK_QUOTA_MODE_LABELS: Record<string, string> = {
quota_auto: 'Auto',
auto: 'Auto',
@@ -234,6 +310,7 @@ function getAntigravityQuotaText(quota: QuotaStatusSnapshot): string | null {
}
function getGeminiCliQuotaText(quota: QuotaStatusSnapshot): string | null {
const creditsText = getGeminiCliCreditsTextFromQuota(quota)
const modelWindows = getQuotaWindowsByScope(quota, 'model')
const activeCoolingModels = modelWindows
.filter((window) => {
@@ -257,9 +334,10 @@ function getGeminiCliQuotaText(quota: QuotaStatusSnapshot): string | null {
const remainingList = modelWindows
.map(getQuotaWindowRemainingPercent)
.filter((value): value is number => value != null)
if (remainingList.length === 0) return normalizeText(quota.label)
if (remainingList.length === 0) return creditsText || normalizeText(quota.label)
const minimumRemaining = Math.min(...remainingList)
if (creditsText) return creditsText
if (remainingList.length === 1) {
return `剩余 ${formatPercent(minimumRemaining)}`
}