mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 07:30:52 -07:00
feat(telemetry): add Clearcut instrumentation for AI credits billing events (#22153)
This commit is contained in:
@@ -51,6 +51,12 @@ import { InstallationManager } from '../../utils/installationManager.js';
|
||||
|
||||
import si, { type Systeminformation } from 'systeminformation';
|
||||
import * as os from 'node:os';
|
||||
import {
|
||||
CreditsUsedEvent,
|
||||
OverageOptionSelectedEvent,
|
||||
EmptyWalletMenuShownEvent,
|
||||
CreditPurchaseClickEvent,
|
||||
} from '../billingEvents.js';
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toHaveMetadataValue: ([key, value]: [EventMetadataKey, string]) => R;
|
||||
@@ -1551,4 +1557,99 @@ describe('ClearcutLogger', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logCreditsUsedEvent', () => {
|
||||
it('logs an event with model, consumed, and remaining credits', () => {
|
||||
const { logger } = setup();
|
||||
const event = new CreditsUsedEvent('gemini-3-pro-preview', 10, 490);
|
||||
|
||||
logger?.logCreditsUsedEvent(event);
|
||||
|
||||
const events = getEvents(logger!);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toHaveEventName(EventNames.CREDITS_USED);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
'"gemini-3-pro-preview"',
|
||||
]);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_CREDITS_CONSUMED,
|
||||
'10',
|
||||
]);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_CREDITS_REMAINING,
|
||||
'490',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logOverageOptionSelectedEvent', () => {
|
||||
it('logs an event with model, selected option, and credit balance', () => {
|
||||
const { logger } = setup();
|
||||
const event = new OverageOptionSelectedEvent(
|
||||
'gemini-3-pro-preview',
|
||||
'use_credits',
|
||||
350,
|
||||
);
|
||||
|
||||
logger?.logOverageOptionSelectedEvent(event);
|
||||
|
||||
const events = getEvents(logger!);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toHaveEventName(EventNames.OVERAGE_OPTION_SELECTED);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
'"gemini-3-pro-preview"',
|
||||
]);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_SELECTED_OPTION,
|
||||
'"use_credits"',
|
||||
]);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_CREDIT_BALANCE,
|
||||
'350',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logEmptyWalletMenuShownEvent', () => {
|
||||
it('logs an event with the model', () => {
|
||||
const { logger } = setup();
|
||||
const event = new EmptyWalletMenuShownEvent('gemini-3-pro-preview');
|
||||
|
||||
logger?.logEmptyWalletMenuShownEvent(event);
|
||||
|
||||
const events = getEvents(logger!);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toHaveEventName(EventNames.EMPTY_WALLET_MENU_SHOWN);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
'"gemini-3-pro-preview"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logCreditPurchaseClickEvent', () => {
|
||||
it('logs an event with model and source', () => {
|
||||
const { logger } = setup();
|
||||
const event = new CreditPurchaseClickEvent(
|
||||
'empty_wallet_menu',
|
||||
'gemini-3-pro-preview',
|
||||
);
|
||||
|
||||
logger?.logCreditPurchaseClickEvent(event);
|
||||
|
||||
const events = getEvents(logger!);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toHaveEventName(EventNames.CREDIT_PURCHASE_CLICK);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
'"gemini-3-pro-preview"',
|
||||
]);
|
||||
expect(events[0]).toHaveMetadataValue([
|
||||
EventMetadataKey.GEMINI_CLI_BILLING_PURCHASE_SOURCE,
|
||||
'"empty_wallet_menu"',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,6 +52,12 @@ import type {
|
||||
TokenStorageInitializationEvent,
|
||||
StartupStatsEvent,
|
||||
} from '../types.js';
|
||||
import type {
|
||||
CreditsUsedEvent,
|
||||
OverageOptionSelectedEvent,
|
||||
EmptyWalletMenuShownEvent,
|
||||
CreditPurchaseClickEvent,
|
||||
} from '../billingEvents.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
import { InstallationManager } from '../../utils/installationManager.js';
|
||||
@@ -121,6 +127,10 @@ export enum EventNames {
|
||||
CONSECA_POLICY_GENERATION = 'conseca_policy_generation',
|
||||
CONSECA_VERDICT = 'conseca_verdict',
|
||||
STARTUP_STATS = 'startup_stats',
|
||||
CREDITS_USED = 'credits_used',
|
||||
OVERAGE_OPTION_SELECTED = 'overage_option_selected',
|
||||
EMPTY_WALLET_MENU_SHOWN = 'empty_wallet_menu_shown',
|
||||
CREDIT_PURCHASE_CLICK = 'credit_purchase_click',
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
@@ -1806,6 +1816,84 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// Billing / AI Credits Events
|
||||
// ==========================================================================
|
||||
|
||||
logCreditsUsedEvent(event: CreditsUsedEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_CREDITS_CONSUMED,
|
||||
value: JSON.stringify(event.credits_consumed),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_CREDITS_REMAINING,
|
||||
value: JSON.stringify(event.credits_remaining),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(this.createLogEvent(EventNames.CREDITS_USED, data));
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logOverageOptionSelectedEvent(event: OverageOptionSelectedEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_SELECTED_OPTION,
|
||||
value: JSON.stringify(event.selected_option),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_CREDIT_BALANCE,
|
||||
value: JSON.stringify(event.credit_balance),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.OVERAGE_OPTION_SELECTED, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logEmptyWalletMenuShownEvent(event: EmptyWalletMenuShownEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.EMPTY_WALLET_MENU_SHOWN, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logCreditPurchaseClickEvent(event: CreditPurchaseClickEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_BILLING_PURCHASE_SOURCE,
|
||||
value: JSON.stringify(event.source),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.CREDIT_PURCHASE_CLICK, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default fields to data, and returns a new data array. This fields
|
||||
* should exist on all log events.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Defines valid event metadata keys for Clearcut logging.
|
||||
export enum EventMetadataKey {
|
||||
// Deleted enums: 24
|
||||
// Next ID: 180
|
||||
// Next ID: 191
|
||||
|
||||
GEMINI_CLI_KEY_UNKNOWN = 0,
|
||||
|
||||
@@ -687,4 +687,26 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs the error type for a network retry.
|
||||
GEMINI_CLI_NETWORK_RETRY_ERROR_TYPE = 182,
|
||||
|
||||
// ==========================================================================
|
||||
// Billing / AI Credits Event Keys
|
||||
// ==========================================================================
|
||||
|
||||
// Logs the model associated with a billing event.
|
||||
GEMINI_CLI_BILLING_MODEL = 185,
|
||||
|
||||
// Logs the number of AI credits consumed in a request.
|
||||
GEMINI_CLI_BILLING_CREDITS_CONSUMED = 186,
|
||||
|
||||
// Logs the remaining AI credits after a request.
|
||||
GEMINI_CLI_BILLING_CREDITS_REMAINING = 187,
|
||||
|
||||
// Logs the overage option selected by the user (e.g. use_credits, use_fallback, manage, stop).
|
||||
GEMINI_CLI_BILLING_SELECTED_OPTION = 188,
|
||||
|
||||
// Logs the user's credit balance when the overage menu was shown.
|
||||
GEMINI_CLI_BILLING_CREDIT_BALANCE = 189,
|
||||
|
||||
// Logs the source of a credit purchase click (e.g. overage_menu, empty_wallet_menu, manage).
|
||||
GEMINI_CLI_BILLING_PURCHASE_SOURCE = 190,
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ import { uiTelemetryService, type UiEvent } from './uiTelemetry.js';
|
||||
import { ClearcutLogger } from './clearcut-logger/clearcut-logger.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import type { BillingTelemetryEvent } from './billingEvents.js';
|
||||
import {
|
||||
CreditsUsedEvent,
|
||||
OverageOptionSelectedEvent,
|
||||
EmptyWalletMenuShownEvent,
|
||||
CreditPurchaseClickEvent,
|
||||
} from './billingEvents.js';
|
||||
|
||||
export function logCliConfiguration(
|
||||
config: Config,
|
||||
@@ -877,4 +883,17 @@ export function logBillingEvent(
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
});
|
||||
|
||||
const cc = ClearcutLogger.getInstance(config);
|
||||
if (cc) {
|
||||
if (event instanceof CreditsUsedEvent) {
|
||||
cc.logCreditsUsedEvent(event);
|
||||
} else if (event instanceof OverageOptionSelectedEvent) {
|
||||
cc.logOverageOptionSelectedEvent(event);
|
||||
} else if (event instanceof EmptyWalletMenuShownEvent) {
|
||||
cc.logEmptyWalletMenuShownEvent(event);
|
||||
} else if (event instanceof CreditPurchaseClickEvent) {
|
||||
cc.logCreditPurchaseClickEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user