From 53027af94cc359c2badbe471ed7cfd34b963bb72 Mon Sep 17 00:00:00 2001 From: Adib234 <30782825+Adib234@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:17:29 -0500 Subject: [PATCH] Add telemetry to rewind (#18122) --- .../src/ui/commands/rewindCommand.test.tsx | 2 ++ .../cli/src/ui/commands/rewindCommand.tsx | 5 ++++ .../clearcut-logger/clearcut-logger.ts | 14 ++++++++++ .../clearcut-logger/event-metadata-key.ts | 6 +++++ packages/core/src/telemetry/index.ts | 2 ++ packages/core/src/telemetry/loggers.ts | 20 ++++++++++++++ packages/core/src/telemetry/types.ts | 27 +++++++++++++++++++ 7 files changed, 76 insertions(+) diff --git a/packages/cli/src/ui/commands/rewindCommand.test.tsx b/packages/cli/src/ui/commands/rewindCommand.test.tsx index b0236845bc..529991b07f 100644 --- a/packages/cli/src/ui/commands/rewindCommand.test.tsx +++ b/packages/cli/src/ui/commands/rewindCommand.test.tsx @@ -41,6 +41,8 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { ...actual.coreEvents, emitFeedback: vi.fn(), }, + logRewind: vi.fn(), + RewindEvent: class {}, }; }); diff --git a/packages/cli/src/ui/commands/rewindCommand.tsx b/packages/cli/src/ui/commands/rewindCommand.tsx index f9bd8f3578..d405172661 100644 --- a/packages/cli/src/ui/commands/rewindCommand.tsx +++ b/packages/cli/src/ui/commands/rewindCommand.tsx @@ -19,6 +19,8 @@ import { checkExhaustive, coreEvents, debugLogger, + logRewind, + RewindEvent, type ChatRecordingService, type GeminiClient, } from '@google/gemini-cli-core'; @@ -144,6 +146,9 @@ export const rewindCommand: SlashCommand = { context.ui.removeComponent(); }} onRewind={async (messageId, newText, outcome) => { + if (outcome !== RewindOutcome.Cancel) { + logRewind(config, new RewindEvent(outcome)); + } switch (outcome) { case RewindOutcome.Cancel: context.ui.removeComponent(); diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index cf009307c5..d7c9656234 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -18,6 +18,7 @@ import type { LoopDetectedEvent, NextSpeakerCheckEvent, SlashCommandEvent, + RewindEvent, MalformedJsonResponseEvent, IdeConnectionEvent, ConversationFinishedEvent, @@ -78,6 +79,7 @@ export enum EventNames { LOOP_DETECTION_DISABLED = 'loop_detection_disabled', NEXT_SPEAKER_CHECK = 'next_speaker_check', SLASH_COMMAND = 'slash_command', + REWIND = 'rewind', MALFORMED_JSON_RESPONSE = 'malformed_json_response', IDE_CONNECTION = 'ide_connection', KITTY_SEQUENCE_OVERFLOW = 'kitty_sequence_overflow', @@ -945,6 +947,18 @@ export class ClearcutLogger { this.flushIfNeeded(); } + logRewindEvent(event: RewindEvent): void { + const data: EventValue[] = [ + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_REWIND_OUTCOME, + value: event.outcome, + }, + ]; + + this.enqueueLogEvent(this.createLogEvent(EventNames.REWIND, data)); + this.flushIfNeeded(); + } + logMalformedJsonResponseEvent(event: MalformedJsonResponseEvent): void { const data: EventValue[] = [ { diff --git a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts index 89bf4afb5a..43535f6fa4 100644 --- a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts +++ b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts @@ -544,6 +544,12 @@ export enum EventMetadataKey { GEMINI_CLI_APPROVAL_MODE_DURATION_MS = 143, // ========================================================================== + // Rewind Event Keys + // ========================================================================== + + // Logs the outcome of a rewind operation. + GEMINI_CLI_REWIND_OUTCOME = 144, + // Model Routing Event Keys (Cont.) // ========================================================================== diff --git a/packages/core/src/telemetry/index.ts b/packages/core/src/telemetry/index.ts index 11bc00773f..ee2cf3d41e 100644 --- a/packages/core/src/telemetry/index.ts +++ b/packages/core/src/telemetry/index.ts @@ -46,6 +46,7 @@ export { logExtensionUninstall, logExtensionUpdateEvent, logWebFetchFallbackAttempt, + logRewind, } from './loggers.js'; export type { SlashCommandEvent, ChatCompressionEvent } from './types.js'; export { @@ -62,6 +63,7 @@ export { ToolOutputTruncatedEvent, WebFetchFallbackAttemptEvent, ToolCallDecision, + RewindEvent, } from './types.js'; export { makeSlashCommandEvent, makeChatCompressionEvent } from './types.js'; export type { TelemetryEvent } from './types.js'; diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index ae25424464..b20dac21b2 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -12,6 +12,7 @@ import { EVENT_API_ERROR, EVENT_API_RESPONSE, EVENT_TOOL_CALL, + EVENT_REWIND, } from './types.js'; import type { ApiErrorEvent, @@ -27,6 +28,7 @@ import type { LoopDetectedEvent, LoopDetectionDisabledEvent, SlashCommandEvent, + RewindEvent, ConversationFinishedEvent, ChatCompressionEvent, MalformedJsonResponseEvent, @@ -351,6 +353,24 @@ export function logSlashCommand( }); } +export function logRewind(config: Config, event: RewindEvent): void { + const uiEvent = { + ...event, + 'event.name': EVENT_REWIND, + 'event.timestamp': new Date().toISOString(), + } as UiEvent; + uiTelemetryService.addEvent(uiEvent); + ClearcutLogger.getInstance(config)?.logRewindEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + }); +} + export function logIdeConnection( config: Config, event: IdeConnectionEvent, diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index d10c7e9876..2d98234ee3 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -889,6 +889,32 @@ export enum SlashCommandStatus { ERROR = 'error', } +export const EVENT_REWIND = 'gemini_cli.rewind'; +export class RewindEvent implements BaseTelemetryEvent { + 'event.name': 'rewind'; + 'event.timestamp': string; + outcome: string; + + constructor(outcome: string) { + this['event.name'] = 'rewind'; + this['event.timestamp'] = new Date().toISOString(); + this.outcome = outcome; + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + return { + ...getCommonAttributes(config), + 'event.name': EVENT_REWIND, + 'event.timestamp': this['event.timestamp'], + outcome: this.outcome, + }; + } + + toLogBody(): string { + return `Rewind performed. Outcome: ${this.outcome}.`; + } +} + export const EVENT_CHAT_COMPRESSION = 'gemini_cli.chat_compression'; export interface ChatCompressionEvent extends BaseTelemetryEvent { 'event.name': 'chat_compression'; @@ -1577,6 +1603,7 @@ export type TelemetryEvent = | StartupStatsEvent | WebFetchFallbackAttemptEvent | EditStrategyEvent + | RewindEvent | EditCorrectionEvent; export const EVENT_EXTENSION_DISABLE = 'gemini_cli.extension_disable';