feat: implement G1 AI credits overage flow with billing telemetry

Adds end-to-end support for Google One AI credits in quota exhaustion flows:

- New billing module (packages/core/src/billing/) with credit balance
  checking, overage strategy management, and G1 URL construction
- OverageMenuDialog and EmptyWalletDialog UI components for quota
  exhaustion with credit purchase options
- Credits flow handler extracted to creditsFlowHandler.ts with overage
  menu, empty wallet, and auto-use-credits logic
- Server-side credit tracking: enabledCreditTypes on requests,
  consumed/remaining credits from streaming responses
- Billing telemetry events (overage menu shown, option selected, credits
  used, credit purchase click, API key updated)
- OpenTelemetry metrics for overage option and credit purchase counters
- Credit balance display in /stats command with refresh support
- Settings: general.overageStrategy (ask/always/never) for credit usage
- Error handling: INSUFFICIENT_G1_CREDITS_BALANCE as terminal error
  regardless of domain field presence
- Persistent info message after
This commit is contained in:
Gaurav Ghosh
2026-02-25 03:21:10 -08:00
parent 29e8f2abf4
commit 6836f0e1b2
53 changed files with 3159 additions and 22 deletions
@@ -297,6 +297,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,