mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Log Model Slash Commands (#9250)
This commit is contained in:
@@ -39,7 +39,20 @@ const renderComponent = (
|
||||
|
||||
const mockConfig = contextValue
|
||||
? ({
|
||||
// --- Functions used by ModelDialog ---
|
||||
getModel: vi.fn(() => DEFAULT_GEMINI_MODEL_AUTO),
|
||||
setModel: vi.fn(),
|
||||
|
||||
// --- Functions used by ClearcutLogger ---
|
||||
getUsageStatisticsEnabled: vi.fn(() => true),
|
||||
getSessionId: vi.fn(() => 'mock-session-id'),
|
||||
getDebugMode: vi.fn(() => false),
|
||||
getContentGeneratorConfig: vi.fn(() => ({ authType: 'mock' })),
|
||||
getUseSmartEdit: vi.fn(() => false),
|
||||
getUseModelRouter: vi.fn(() => false),
|
||||
getProxy: vi.fn(() => undefined),
|
||||
|
||||
// --- Spread test-specific overrides ---
|
||||
...contextValue,
|
||||
} as Config)
|
||||
: undefined;
|
||||
@@ -132,15 +145,15 @@ describe('<ModelDialog />', () => {
|
||||
});
|
||||
|
||||
it('calls config.setModel and onClose when DescriptiveRadioButtonSelect.onSelect is triggered', () => {
|
||||
const mockSetModel = vi.fn();
|
||||
const { props } = renderComponent({}, { setModel: mockSetModel });
|
||||
const { props, mockConfig } = renderComponent({}, {}); // Pass empty object for contextValue
|
||||
|
||||
const childOnSelect = mockedSelect.mock.calls[0][0].onSelect;
|
||||
expect(childOnSelect).toBeDefined();
|
||||
|
||||
childOnSelect(DEFAULT_GEMINI_MODEL);
|
||||
|
||||
expect(mockSetModel).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
|
||||
// Assert against the default mock provided by renderComponent
|
||||
expect(mockConfig?.setModel).toHaveBeenCalledWith(DEFAULT_GEMINI_MODEL);
|
||||
expect(props.onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { DescriptiveRadioButtonSelect } from './shared/DescriptiveRadioButtonSelect.js';
|
||||
import { ConfigContext } from '../contexts/ConfigContext.js';
|
||||
import { ModelSlashCommandEvent } from '@google/gemini-cli-core/src/telemetry/types.js';
|
||||
import { logModelSlashCommand } from '@google/gemini-cli-core/src/telemetry/loggers.js';
|
||||
|
||||
interface ModelDialogProps {
|
||||
onClose: () => void;
|
||||
@@ -71,6 +73,8 @@ export function ModelDialog({ onClose }: ModelDialogProps): React.JSX.Element {
|
||||
(model: string) => {
|
||||
if (config) {
|
||||
config.setModel(model);
|
||||
const event = new ModelSlashCommandEvent(model);
|
||||
logModelSlashCommand(config, event);
|
||||
}
|
||||
onClose();
|
||||
},
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
ExtensionUninstallEvent,
|
||||
ModelRoutingEvent,
|
||||
ExtensionEnableEvent,
|
||||
ModelSlashCommandEvent,
|
||||
ExtensionDisableEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
@@ -69,6 +70,7 @@ export enum EventNames {
|
||||
EXTENSION_UNINSTALL = 'extension_uninstall',
|
||||
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
||||
MODEL_ROUTING = 'model_routing',
|
||||
MODEL_SLASH_COMMAND = 'model_slash_command',
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
@@ -994,6 +996,20 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logModelSlashCommandEvent(event: ModelSlashCommandEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_MODEL_SLASH_COMMAND,
|
||||
value: event.model_name,
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.MODEL_SLASH_COMMAND, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logExtensionDisableEvent(event: ExtensionDisableEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
|
||||
@@ -401,4 +401,7 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs the source of the decision.
|
||||
GEMINI_CLI_ROUTING_DECISION_SOURCE = 101,
|
||||
|
||||
// Logs an event when the user uses the /model command.
|
||||
GEMINI_CLI_MODEL_SLASH_COMMAND = 103,
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export const EVENT_CONTENT_RETRY = 'gemini_cli.chat.content_retry';
|
||||
export const EVENT_CONTENT_RETRY_FAILURE =
|
||||
'gemini_cli.chat.content_retry_failure';
|
||||
export const EVENT_FILE_OPERATION = 'gemini_cli.file_operation';
|
||||
export const EVENT_MODEL_SLASH_COMMAND = 'gemini_cli.slash_command.model';
|
||||
export const METRIC_TOOL_CALL_COUNT = 'gemini_cli.tool.call.count';
|
||||
export const METRIC_TOOL_CALL_LATENCY = 'gemini_cli.tool.call.latency';
|
||||
export const METRIC_API_REQUEST_COUNT = 'gemini_cli.api.request.count';
|
||||
@@ -45,3 +46,5 @@ export const EVENT_MODEL_ROUTING = 'gemini_cli.model_routing';
|
||||
export const METRIC_MODEL_ROUTING_LATENCY = 'gemini_cli.model_routing.latency';
|
||||
export const METRIC_MODEL_ROUTING_FAILURE_COUNT =
|
||||
'gemini_cli.model_routing.failure.count';
|
||||
export const METRIC_MODEL_SLASH_COMMAND_CALL_COUNT =
|
||||
'gemini_cli.slash_command.model.call_count';
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
EVENT_RIPGREP_FALLBACK,
|
||||
EVENT_MODEL_ROUTING,
|
||||
EVENT_EXTENSION_INSTALL,
|
||||
EVENT_MODEL_SLASH_COMMAND,
|
||||
EVENT_EXTENSION_DISABLE,
|
||||
} from './constants.js';
|
||||
import type {
|
||||
@@ -62,6 +63,7 @@ import type {
|
||||
ExtensionEnableEvent,
|
||||
ExtensionUninstallEvent,
|
||||
ExtensionInstallEvent,
|
||||
ModelSlashCommandEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -74,6 +76,7 @@ import {
|
||||
recordContentRetry,
|
||||
recordContentRetryFailure,
|
||||
recordModelRoutingMetrics,
|
||||
recordModelSlashCommand,
|
||||
} from './metrics.js';
|
||||
import { isTelemetrySdkInitialized } from './sdk.js';
|
||||
import type { UiEvent } from './uiTelemetry.js';
|
||||
@@ -700,6 +703,28 @@ export function logModelRouting(
|
||||
recordModelRoutingMetrics(config, event);
|
||||
}
|
||||
|
||||
export function logModelSlashCommand(
|
||||
config: Config,
|
||||
event: ModelSlashCommandEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logModelSlashCommandEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_MODEL_SLASH_COMMAND,
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Model slash command. Model: ${event.model_name}`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
recordModelSlashCommand(config, event);
|
||||
}
|
||||
|
||||
export function logExtensionInstallEvent(
|
||||
config: Config,
|
||||
event: ExtensionInstallEvent,
|
||||
|
||||
@@ -21,9 +21,10 @@ import {
|
||||
METRIC_CONTENT_RETRY_FAILURE_COUNT,
|
||||
METRIC_MODEL_ROUTING_LATENCY,
|
||||
METRIC_MODEL_ROUTING_FAILURE_COUNT,
|
||||
METRIC_MODEL_SLASH_COMMAND_CALL_COUNT,
|
||||
} from './constants.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { ModelRoutingEvent } from './types.js';
|
||||
import type { ModelRoutingEvent, ModelSlashCommandEvent } from './types.js';
|
||||
|
||||
export enum FileOperation {
|
||||
CREATE = 'create',
|
||||
@@ -44,6 +45,7 @@ let contentRetryCounter: Counter | undefined;
|
||||
let contentRetryFailureCounter: Counter | undefined;
|
||||
let modelRoutingLatencyHistogram: Histogram | undefined;
|
||||
let modelRoutingFailureCounter: Counter | undefined;
|
||||
let modelSlashCommandCallCounter: Counter | undefined;
|
||||
let isMetricsInitialized = false;
|
||||
|
||||
function getCommonAttributes(config: Config): Attributes {
|
||||
@@ -130,6 +132,13 @@ export function initializeMetrics(config: Config): void {
|
||||
valueType: ValueType.INT,
|
||||
},
|
||||
);
|
||||
modelSlashCommandCallCounter = meter.createCounter(
|
||||
METRIC_MODEL_SLASH_COMMAND_CALL_COUNT,
|
||||
{
|
||||
description: 'Counts model slash command calls.',
|
||||
valueType: ValueType.INT,
|
||||
},
|
||||
);
|
||||
|
||||
const sessionCounter = meter.createCounter(METRIC_SESSION_COUNT, {
|
||||
description: 'Count of CLI sessions started.',
|
||||
@@ -287,6 +296,17 @@ export function recordContentRetryFailure(config: Config): void {
|
||||
contentRetryFailureCounter.add(1, getCommonAttributes(config));
|
||||
}
|
||||
|
||||
export function recordModelSlashCommand(
|
||||
config: Config,
|
||||
event: ModelSlashCommandEvent,
|
||||
): void {
|
||||
if (!modelSlashCommandCallCounter || !isMetricsInitialized) return;
|
||||
modelSlashCommandCallCounter.add(1, {
|
||||
...getCommonAttributes(config),
|
||||
'slash_command.model.model_name': event.model_name,
|
||||
});
|
||||
}
|
||||
|
||||
export function recordModelRoutingMetrics(
|
||||
config: Config,
|
||||
event: ModelRoutingEvent,
|
||||
|
||||
@@ -567,33 +567,6 @@ export class ModelRoutingEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| StartSessionEvent
|
||||
| EndSessionEvent
|
||||
| UserPromptEvent
|
||||
| ToolCallEvent
|
||||
| ApiRequestEvent
|
||||
| ApiErrorEvent
|
||||
| ApiResponseEvent
|
||||
| FlashFallbackEvent
|
||||
| LoopDetectedEvent
|
||||
| LoopDetectionDisabledEvent
|
||||
| NextSpeakerCheckEvent
|
||||
| KittySequenceOverflowEvent
|
||||
| MalformedJsonResponseEvent
|
||||
| IdeConnectionEvent
|
||||
| ConversationFinishedEvent
|
||||
| SlashCommandEvent
|
||||
| FileOperationEvent
|
||||
| InvalidChunkEvent
|
||||
| ContentRetryEvent
|
||||
| ContentRetryFailureEvent
|
||||
| ExtensionEnableEvent
|
||||
| ExtensionInstallEvent
|
||||
| ExtensionUninstallEvent
|
||||
| ModelRoutingEvent
|
||||
| ToolOutputTruncatedEvent;
|
||||
|
||||
export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_install';
|
||||
'event.timestamp': string;
|
||||
@@ -676,6 +649,46 @@ export class ExtensionEnableEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelSlashCommandEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'model_slash_command';
|
||||
'event.timestamp': string;
|
||||
model_name: string;
|
||||
|
||||
constructor(model_name: string) {
|
||||
this['event.name'] = 'model_slash_command';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.model_name = model_name;
|
||||
}
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| StartSessionEvent
|
||||
| EndSessionEvent
|
||||
| UserPromptEvent
|
||||
| ToolCallEvent
|
||||
| ApiRequestEvent
|
||||
| ApiErrorEvent
|
||||
| ApiResponseEvent
|
||||
| FlashFallbackEvent
|
||||
| LoopDetectedEvent
|
||||
| LoopDetectionDisabledEvent
|
||||
| NextSpeakerCheckEvent
|
||||
| KittySequenceOverflowEvent
|
||||
| MalformedJsonResponseEvent
|
||||
| IdeConnectionEvent
|
||||
| ConversationFinishedEvent
|
||||
| SlashCommandEvent
|
||||
| FileOperationEvent
|
||||
| InvalidChunkEvent
|
||||
| ContentRetryEvent
|
||||
| ContentRetryFailureEvent
|
||||
| ExtensionEnableEvent
|
||||
| ExtensionInstallEvent
|
||||
| ExtensionUninstallEvent
|
||||
| ModelRoutingEvent
|
||||
| ToolOutputTruncatedEvent
|
||||
| ModelSlashCommandEvent;
|
||||
|
||||
export class ExtensionDisableEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_disable';
|
||||
'event.timestamp': string;
|
||||
|
||||
Reference in New Issue
Block a user