From a974d72399753ac5158451bebc87997771592b62 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 20 Mar 2026 05:51:13 +0000 Subject: [PATCH] chore: revert unnecessary eslint-disable changes and unrelated telemetry removals --- .../src/context/chatCompressionService.ts | 2 ++ packages/core/src/hooks/hookAggregator.ts | 1 + .../core/src/services/loopDetectionService.ts | 5 ++++ .../clearcut-logger/clearcut-logger.ts | 23 ++++++++++++++ packages/core/src/telemetry/index.ts | 2 ++ packages/core/src/telemetry/loggers.ts | 16 ++++++++++ packages/core/src/telemetry/semantic.ts | 2 ++ packages/core/src/telemetry/types.ts | 30 +++++++++++++++++++ .../src/test-utils/mockWorkspaceContext.ts | 1 + packages/core/src/utils/editCorrector.ts | 1 + packages/core/src/utils/googleErrors.ts | 2 +- packages/core/src/utils/oauth-flow.ts | 6 +++- 12 files changed, 89 insertions(+), 2 deletions(-) diff --git a/packages/core/src/context/chatCompressionService.ts b/packages/core/src/context/chatCompressionService.ts index 0f442fb9bf..e88069e367 100644 --- a/packages/core/src/context/chatCompressionService.ts +++ b/packages/core/src/context/chatCompressionService.ts @@ -159,11 +159,13 @@ async function truncateHistoryToBudget( } else if (responseObj && typeof responseObj === 'object') { if ( 'output' in responseObj && + typeof responseObj['output'] === 'string' ) { contentStr = responseObj['output']; } else if ( 'content' in responseObj && + typeof responseObj['content'] === 'string' ) { contentStr = responseObj['content']; diff --git a/packages/core/src/hooks/hookAggregator.ts b/packages/core/src/hooks/hookAggregator.ts index af7f93cfab..71bd9e7a5b 100644 --- a/packages/core/src/hooks/hookAggregator.ts +++ b/packages/core/src/hooks/hookAggregator.ts @@ -362,6 +362,7 @@ export class HookAggregator { // Extract additionalContext from various hook types if ( 'additionalContext' in specific && + typeof specific['additionalContext'] === 'string' ) { contexts.push(specific['additionalContext']); diff --git a/packages/core/src/services/loopDetectionService.ts b/packages/core/src/services/loopDetectionService.ts index d8b72233be..88924db6ed 100644 --- a/packages/core/src/services/loopDetectionService.ts +++ b/packages/core/src/services/loopDetectionService.ts @@ -584,10 +584,12 @@ export class LoopDetectionService { } const flashConfidence = + typeof flashResult['unproductive_state_confidence'] === 'number' ? flashResult['unproductive_state_confidence'] : 0; const flashAnalysis = + typeof flashResult['unproductive_state_analysis'] === 'string' ? flashResult['unproductive_state_analysis'] : ''; @@ -634,11 +636,13 @@ export class LoopDetectionService { const mainModelConfidence = mainModelResult && + typeof mainModelResult['unproductive_state_confidence'] === 'number' ? mainModelResult['unproductive_state_confidence'] : 0; const mainModelAnalysis = mainModelResult && + typeof mainModelResult['unproductive_state_analysis'] === 'string' ? mainModelResult['unproductive_state_analysis'] : undefined; @@ -687,6 +691,7 @@ export class LoopDetectionService { if ( result && + typeof result['unproductive_state_confidence'] === 'number' ) { return result; diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 30231de9be..2915edf712 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -21,6 +21,7 @@ import type { RewindEvent, MalformedJsonResponseEvent, IdeConnectionEvent, + ConversationFinishedEvent, ChatCompressionEvent, FileOperationEvent, InvalidChunkEvent, @@ -1149,6 +1150,28 @@ export class ClearcutLogger { }); } + logConversationFinishedEvent(event: ConversationFinishedEvent): void { + const data: EventValue[] = [ + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID, + value: this.config?.getSessionId() ?? '', + }, + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONVERSATION_TURN_COUNT, + value: JSON.stringify(event.turnCount), + }, + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_APPROVAL_MODE, + value: event.approvalMode, + }, + ]; + + this.enqueueLogEvent( + this.createLogEvent(EventNames.CONVERSATION_FINISHED, data), + ); + this.flushIfNeeded(); + } + logEndSessionEvent(): void { // Flush immediately on session end. this.enqueueLogEvent(this.createLogEvent(EventNames.END_SESSION, [])); diff --git a/packages/core/src/telemetry/index.ts b/packages/core/src/telemetry/index.ts index ff60844656..ea65941e06 100644 --- a/packages/core/src/telemetry/index.ts +++ b/packages/core/src/telemetry/index.ts @@ -38,6 +38,7 @@ export { logApiResponse, logFlashFallback, logSlashCommand, + logConversationFinishedEvent, logChatCompression, logToolOutputTruncated, logExtensionEnable, @@ -65,6 +66,7 @@ export { FlashFallbackEvent, StartSessionEvent, ToolCallEvent, + ConversationFinishedEvent, ToolOutputTruncatedEvent, WebFetchFallbackAttemptEvent, NetworkRetryAttemptEvent, diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 6f582d85b6..a33c8ca200 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -26,6 +26,7 @@ import { type LoopDetectionDisabledEvent, type SlashCommandEvent, type RewindEvent, + type ConversationFinishedEvent, type ChatCompressionEvent, type MalformedJsonResponseEvent, type InvalidChunkEvent, @@ -435,6 +436,21 @@ export function logIdeConnection( }); } +export function logConversationFinishedEvent( + config: Config, + event: ConversationFinishedEvent, +): void { + ClearcutLogger.getInstance(config)?.logConversationFinishedEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + }); +} + export function logChatCompression( config: Config, event: ChatCompressionEvent, diff --git a/packages/core/src/telemetry/semantic.ts b/packages/core/src/telemetry/semantic.ts index cb38502c91..c0bf11f077 100644 --- a/packages/core/src/telemetry/semantic.ts +++ b/packages/core/src/telemetry/semantic.ts @@ -63,6 +63,7 @@ function getStringReferences(parts: AnyPart[]): StringReference[] { }); } } else if (part instanceof GenericPart) { + if (part.type === 'executableCode' && typeof part['code'] === 'string') { refs.push({ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion @@ -73,6 +74,7 @@ function getStringReferences(parts: AnyPart[]): StringReference[] { }); } else if ( part.type === 'codeExecutionResult' && + typeof part['output'] === 'string' ) { refs.push({ diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index 33f94fbe27..9d6cd08c72 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -1184,6 +1184,35 @@ export class IdeConnectionEvent { } } +export const EVENT_CONVERSATION_FINISHED = 'gemini_cli.conversation_finished'; +export class ConversationFinishedEvent { + 'event_name': 'conversation_finished'; + 'event.timestamp': string; // ISO 8601; + approvalMode: ApprovalMode; + turnCount: number; + + constructor(approvalMode: ApprovalMode, turnCount: number) { + this['event_name'] = 'conversation_finished'; + this['event.timestamp'] = new Date().toISOString(); + this.approvalMode = approvalMode; + this.turnCount = turnCount; + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + return { + ...getCommonAttributes(config), + 'event.name': EVENT_CONVERSATION_FINISHED, + 'event.timestamp': this['event.timestamp'], + approvalMode: this.approvalMode, + turnCount: this.turnCount, + }; + } + + toLogBody(): string { + return `Conversation finished.`; + } +} + export const EVENT_FILE_OPERATION = 'gemini_cli.file_operation'; export class FileOperationEvent implements BaseTelemetryEvent { 'event.name': 'file_operation'; @@ -1817,6 +1846,7 @@ export type TelemetryEvent = | NextSpeakerCheckEvent | MalformedJsonResponseEvent | IdeConnectionEvent + | ConversationFinishedEvent | SlashCommandEvent | FileOperationEvent | InvalidChunkEvent diff --git a/packages/core/src/test-utils/mockWorkspaceContext.ts b/packages/core/src/test-utils/mockWorkspaceContext.ts index 67c614e9f5..5c8a3ef9d4 100644 --- a/packages/core/src/test-utils/mockWorkspaceContext.ts +++ b/packages/core/src/test-utils/mockWorkspaceContext.ts @@ -19,6 +19,7 @@ export function createMockWorkspaceContext( ): WorkspaceContext { const allDirs = [rootDir, ...additionalDirs]; + const mockWorkspaceContext = { addDirectory: vi.fn(), getDirectories: vi.fn().mockReturnValue(allDirs), diff --git a/packages/core/src/utils/editCorrector.ts b/packages/core/src/utils/editCorrector.ts index f8ff81b97e..cccd363f8d 100644 --- a/packages/core/src/utils/editCorrector.ts +++ b/packages/core/src/utils/editCorrector.ts @@ -112,6 +112,7 @@ Return ONLY the corrected string in the specified JSON format with the key 'corr if ( result && + typeof result['corrected_string_escaping'] === 'string' && result['corrected_string_escaping'].length > 0 ) { diff --git a/packages/core/src/utils/googleErrors.ts b/packages/core/src/utils/googleErrors.ts index 82d6e56ed0..44ec622285 100644 --- a/packages/core/src/utils/googleErrors.ts +++ b/packages/core/src/utils/googleErrors.ts @@ -231,7 +231,7 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null { } // Basic structural check before casting. // Since the proto definitions are loose, we primarily rely on @type presence. - + if (typeof detailObj['@type'] === 'string') { // We can just cast it; the consumer will have to switch on @type // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion diff --git a/packages/core/src/utils/oauth-flow.ts b/packages/core/src/utils/oauth-flow.ts index f3c19089e2..df1c950c36 100644 --- a/packages/core/src/utils/oauth-flow.ts +++ b/packages/core/src/utils/oauth-flow.ts @@ -361,20 +361,24 @@ async function parseTokenEndpointResponse( data && typeof data === 'object' && 'access_token' in data && + typeof (data as Record)['access_token'] === 'string' ) { const obj = data as Record; const result: OAuthTokenResponse = { access_token: String(obj['access_token']), token_type: + typeof obj['token_type'] === 'string' ? obj['token_type'] : 'Bearer', expires_in: + typeof obj['expires_in'] === 'number' ? obj['expires_in'] : undefined, refresh_token: + typeof obj['refresh_token'] === 'string' ? obj['refresh_token'] : undefined, - + scope: typeof obj['scope'] === 'string' ? obj['scope'] : undefined, }; return result;