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

@@ -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 {

View File

@@ -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,

View File

@@ -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,
);
}
}