diff --git a/src/components/quota/quotaConfigs.ts b/src/components/quota/quotaConfigs.ts index abdac0e..e9765aa 100644 --- a/src/components/quota/quotaConfigs.ts +++ b/src/components/quota/quotaConfigs.ts @@ -28,6 +28,7 @@ import { GEMINI_CLI_QUOTA_URL, GEMINI_CLI_REQUEST_HEADERS, normalizeAuthIndexValue, + normalizeGeminiCliModelId, normalizeNumberValue, normalizePlanType, normalizeQuotaFraction, @@ -368,7 +369,7 @@ const fetchGeminiCliQuota = async ( const parsedBuckets = buckets .map((bucket) => { - const modelId = normalizeStringValue(bucket.modelId ?? bucket.model_id); + const modelId = normalizeGeminiCliModelId(bucket.modelId ?? bucket.model_id); if (!modelId) return null; const tokenType = normalizeStringValue(bucket.tokenType ?? bucket.token_type); const remainingFractionRaw = normalizeQuotaFraction( diff --git a/src/utils/quota/builders.ts b/src/utils/quota/builders.ts index 39d325f..95351af 100644 --- a/src/utils/quota/builders.ts +++ b/src/utils/quota/builders.ts @@ -10,7 +10,11 @@ import type { GeminiCliParsedBucket, GeminiCliQuotaBucketState, } from '@/types'; -import { ANTIGRAVITY_QUOTA_GROUPS, GEMINI_CLI_GROUP_LOOKUP } from './constants'; +import { + ANTIGRAVITY_QUOTA_GROUPS, + GEMINI_CLI_GROUP_LOOKUP, + GEMINI_CLI_GROUP_ORDER, +} from './constants'; import { normalizeQuotaFraction } from './parsers'; import { isIgnoredGeminiCliModel } from './validators'; @@ -92,24 +96,40 @@ export function buildGeminiCliQuotaBuckets( } }); - return Array.from(grouped.values()).map((bucket) => { - const uniqueModelIds = Array.from(new Set(bucket.modelIds)); - const preferred = bucket.preferredBucket; - const remainingFraction = preferred - ? preferred.remainingFraction - : bucket.fallbackRemainingFraction; - const remainingAmount = preferred ? preferred.remainingAmount : bucket.fallbackRemainingAmount; - const resetTime = preferred ? preferred.resetTime : bucket.fallbackResetTime; - return { - id: bucket.id, - label: bucket.label, - remainingFraction, - remainingAmount, - resetTime, - tokenType: bucket.tokenType, - modelIds: uniqueModelIds, - }; - }); + const toGroupOrder = (bucket: GeminiCliQuotaBucketGroup): number => { + const tokenSuffix = bucket.tokenType ? `-${bucket.tokenType}` : ''; + const groupId = bucket.id.endsWith(tokenSuffix) + ? bucket.id.slice(0, bucket.id.length - tokenSuffix.length) + : bucket.id; + return GEMINI_CLI_GROUP_ORDER.get(groupId) ?? Number.MAX_SAFE_INTEGER; + }; + + return Array.from(grouped.values()) + .sort((a, b) => { + const orderDiff = toGroupOrder(a) - toGroupOrder(b); + if (orderDiff !== 0) return orderDiff; + const tokenTypeA = a.tokenType ?? ''; + const tokenTypeB = b.tokenType ?? ''; + return tokenTypeA.localeCompare(tokenTypeB); + }) + .map((bucket) => { + const uniqueModelIds = Array.from(new Set(bucket.modelIds)); + const preferred = bucket.preferredBucket; + const remainingFraction = preferred + ? preferred.remainingFraction + : bucket.fallbackRemainingFraction; + const remainingAmount = preferred ? preferred.remainingAmount : bucket.fallbackRemainingAmount; + const resetTime = preferred ? preferred.resetTime : bucket.fallbackResetTime; + return { + id: bucket.id, + label: bucket.label, + remainingFraction, + remainingAmount, + resetTime, + tokenType: bucket.tokenType, + modelIds: uniqueModelIds, + }; + }); } export function getAntigravityQuotaInfo(entry?: AntigravityQuotaInfo): { diff --git a/src/utils/quota/constants.ts b/src/utils/quota/constants.ts index 330af65..f9b8e1d 100644 --- a/src/utils/quota/constants.ts +++ b/src/utils/quota/constants.ts @@ -119,11 +119,17 @@ export const GEMINI_CLI_REQUEST_HEADERS = { }; export const GEMINI_CLI_QUOTA_GROUPS: GeminiCliQuotaGroupDefinition[] = [ + { + id: 'gemini-flash-lite-series', + label: 'Gemini Flash Lite Series', + preferredModelId: 'gemini-2.5-flash-lite', + modelIds: ['gemini-2.5-flash-lite'], + }, { id: 'gemini-flash-series', label: 'Gemini Flash Series', preferredModelId: 'gemini-3-flash-preview', - modelIds: ['gemini-3-flash-preview', 'gemini-2.5-flash', 'gemini-2.5-flash-lite'], + modelIds: ['gemini-3-flash-preview', 'gemini-2.5-flash'], }, { id: 'gemini-pro-series', @@ -133,6 +139,10 @@ export const GEMINI_CLI_QUOTA_GROUPS: GeminiCliQuotaGroupDefinition[] = [ }, ]; +export const GEMINI_CLI_GROUP_ORDER = new Map( + GEMINI_CLI_QUOTA_GROUPS.map((group, index) => [group.id, index] as const) +); + export const GEMINI_CLI_GROUP_LOOKUP = new Map( GEMINI_CLI_QUOTA_GROUPS.flatMap((group) => group.modelIds.map((modelId) => [modelId, group] as const) diff --git a/src/utils/quota/parsers.ts b/src/utils/quota/parsers.ts index 2383833..748a3a9 100644 --- a/src/utils/quota/parsers.ts +++ b/src/utils/quota/parsers.ts @@ -4,6 +4,8 @@ import type { CodexUsagePayload, GeminiCliQuotaPayload } from '@/types'; +const GEMINI_CLI_MODEL_SUFFIX = '_vertex'; + export function normalizeAuthIndexValue(value: unknown): string | null { if (typeof value === 'number' && Number.isFinite(value)) { return value.toString(); @@ -26,6 +28,15 @@ export function normalizeStringValue(value: unknown): string | null { return null; } +export function normalizeGeminiCliModelId(value: unknown): string | null { + const modelId = normalizeStringValue(value); + if (!modelId) return null; + if (modelId.endsWith(GEMINI_CLI_MODEL_SUFFIX)) { + return modelId.slice(0, -GEMINI_CLI_MODEL_SUFFIX.length); + } + return modelId; +} + export function normalizeNumberValue(value: unknown): number | null { if (typeof value === 'number' && Number.isFinite(value)) return value; if (typeof value === 'string') {