mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 17:11:04 -07:00
feat(billing): implement G1 AI credits overage flow with billing telemetry (#18590)
This commit is contained in:
@@ -16,8 +16,8 @@
|
||||
export interface ErrorInfo {
|
||||
'@type': 'type.googleapis.com/google.rpc.ErrorInfo';
|
||||
reason: string;
|
||||
domain: string;
|
||||
metadata: { [key: string]: string };
|
||||
domain?: string;
|
||||
metadata?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface RetryInfo {
|
||||
|
||||
@@ -313,6 +313,26 @@ describe('classifyGoogleError', () => {
|
||||
expect(result).toBeInstanceOf(TerminalQuotaError);
|
||||
});
|
||||
|
||||
it('should return TerminalQuotaError for INSUFFICIENT_G1_CREDITS_BALANCE without domain', () => {
|
||||
const apiError: GoogleApiError = {
|
||||
code: 429,
|
||||
message: 'Resource has been exhausted (e.g. check quota).',
|
||||
details: [
|
||||
{
|
||||
'@type': 'type.googleapis.com/google.rpc.ErrorInfo',
|
||||
reason: 'INSUFFICIENT_G1_CREDITS_BALANCE',
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.spyOn(errorParser, 'parseGoogleApiError').mockReturnValue(apiError);
|
||||
const result = classifyGoogleError(new Error());
|
||||
expect(result).toBeInstanceOf(TerminalQuotaError);
|
||||
expect((result as TerminalQuotaError).isInsufficientCredits).toBe(true);
|
||||
expect((result as TerminalQuotaError).reason).toBe(
|
||||
'INSUFFICIENT_G1_CREDITS_BALANCE',
|
||||
);
|
||||
});
|
||||
|
||||
it('should prioritize daily limit over retry info', () => {
|
||||
const apiError: GoogleApiError = {
|
||||
code: 429,
|
||||
|
||||
@@ -19,17 +19,24 @@ import { getErrorStatus, ModelNotFoundError } from './httpErrors.js';
|
||||
*/
|
||||
export class TerminalQuotaError extends Error {
|
||||
retryDelayMs?: number;
|
||||
reason?: string;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
override readonly cause: GoogleApiError,
|
||||
retryDelaySeconds?: number,
|
||||
reason?: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'TerminalQuotaError';
|
||||
this.retryDelayMs = retryDelaySeconds
|
||||
? retryDelaySeconds * 1000
|
||||
: undefined;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
get isInsufficientCredits(): boolean {
|
||||
return this.reason === 'INSUFFICIENT_G1_CREDITS_BALANCE';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +128,7 @@ function classifyValidationRequiredError(
|
||||
}
|
||||
|
||||
if (
|
||||
!errorInfo.domain ||
|
||||
!CLOUDCODE_DOMAINS.includes(errorInfo.domain) ||
|
||||
errorInfo.reason !== 'VALIDATION_REQUIRED'
|
||||
) {
|
||||
@@ -293,6 +301,16 @@ export function classifyGoogleError(error: unknown): unknown {
|
||||
}
|
||||
|
||||
if (errorInfo) {
|
||||
// INSUFFICIENT_G1_CREDITS_BALANCE is always terminal, regardless of domain
|
||||
if (errorInfo.reason === 'INSUFFICIENT_G1_CREDITS_BALANCE') {
|
||||
return new TerminalQuotaError(
|
||||
`${googleApiError.message}`,
|
||||
googleApiError,
|
||||
delaySeconds,
|
||||
errorInfo.reason,
|
||||
);
|
||||
}
|
||||
|
||||
// New Cloud Code API quota handling
|
||||
if (errorInfo.domain) {
|
||||
const validDomains = [
|
||||
@@ -313,6 +331,7 @@ export function classifyGoogleError(error: unknown): unknown {
|
||||
`${googleApiError.message}`,
|
||||
googleApiError,
|
||||
delaySeconds,
|
||||
errorInfo.reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user