feat(billing): implement G1 AI credits overage flow with billing telemetry (#18590)

This commit is contained in:
Gaurav
2026-02-27 10:15:06 -08:00
committed by GitHub
parent fdd844b405
commit b2d6844f9b
55 changed files with 3182 additions and 23 deletions

View File

@@ -39,11 +39,18 @@ describe('statsCommand', () => {
mockContext.session.stats.sessionStartTime = startTime;
});
it('should display general session stats when run with no subcommand', () => {
it('should display general session stats when run with no subcommand', async () => {
if (!statsCommand.action) throw new Error('Command has no action');
// eslint-disable-next-line @typescript-eslint/no-floating-promises
statsCommand.action(mockContext, '');
mockContext.services.config = {
refreshUserQuota: vi.fn(),
refreshAvailableCredits: vi.fn(),
getUserTierName: vi.fn(),
getUserPaidTier: vi.fn(),
getModel: vi.fn(),
} as unknown as Config;
await statsCommand.action(mockContext, '');
const expectedDuration = formatDuration(
endTime.getTime() - startTime.getTime(),
@@ -55,6 +62,7 @@ describe('statsCommand', () => {
tier: undefined,
userEmail: 'mock@example.com',
currentModel: undefined,
creditBalance: undefined,
});
});
@@ -78,6 +86,8 @@ describe('statsCommand', () => {
getQuotaRemaining: mockGetQuotaRemaining,
getQuotaLimit: mockGetQuotaLimit,
getQuotaResetTime: mockGetQuotaResetTime,
getUserPaidTier: vi.fn(),
refreshAvailableCredits: vi.fn(),
} as unknown as Config;
await statsCommand.action(mockContext, '');

View File

@@ -11,7 +11,10 @@ import type {
} from '../types.js';
import { MessageType } from '../types.js';
import { formatDuration } from '../utils/formatters.js';
import { UserAccountManager } from '@google/gemini-cli-core';
import {
UserAccountManager,
getG1CreditBalance,
} from '@google/gemini-cli-core';
import {
type CommandContext,
type SlashCommand,
@@ -27,8 +30,10 @@ function getUserIdentity(context: CommandContext) {
const userEmail = cachedAccount ?? undefined;
const tier = context.services.config?.getUserTierName();
const paidTier = context.services.config?.getUserPaidTier();
const creditBalance = getG1CreditBalance(paidTier) ?? undefined;
return { selectedAuthType, userEmail, tier };
return { selectedAuthType, userEmail, tier, creditBalance };
}
async function defaultSessionView(context: CommandContext) {
@@ -43,7 +48,8 @@ async function defaultSessionView(context: CommandContext) {
}
const wallDuration = now.getTime() - sessionStartTime.getTime();
const { selectedAuthType, userEmail, tier } = getUserIdentity(context);
const { selectedAuthType, userEmail, tier, creditBalance } =
getUserIdentity(context);
const currentModel = context.services.config?.getModel();
const statsItem: HistoryItemStats = {
@@ -53,10 +59,14 @@ async function defaultSessionView(context: CommandContext) {
userEmail,
tier,
currentModel,
creditBalance,
};
if (context.services.config) {
const quota = await context.services.config.refreshUserQuota();
const [quota] = await Promise.all([
context.services.config.refreshUserQuota(),
context.services.config.refreshAvailableCredits(),
]);
if (quota) {
statsItem.quotas = quota;
statsItem.pooledRemaining = context.services.config.getQuotaRemaining();