From bf9ca33c18efcb5c071016ceae78ba27ecc277bb Mon Sep 17 00:00:00 2001 From: Abhi <43648792+abhipatel12@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:11:38 +0900 Subject: [PATCH] feat(telemetry): add keychain availability and token storage metrics (#18971) --- packages/core/src/code_assist/oauth2.test.ts | 8 +++ .../hybrid-token-storage.test.ts | 14 ++++ .../mcp/token-storage/hybrid-token-storage.ts | 12 ++++ .../keychain-token-storage.test.ts | 15 +++-- .../token-storage/keychain-token-storage.ts | 13 ++++ .../clearcut-logger/clearcut-logger.ts | 38 +++++++++++ .../clearcut-logger/event-metadata-key.ts | 16 ++++- packages/core/src/telemetry/loggers.ts | 38 +++++++++++ packages/core/src/telemetry/metrics.test.ts | 66 ++++++++++++++++++- packages/core/src/telemetry/metrics.ts | 54 +++++++++++++++ packages/core/src/telemetry/sdk.ts | 49 ++++++++++++++ packages/core/src/telemetry/types.ts | 57 ++++++++++++++++ packages/core/src/utils/events.ts | 16 +++++ 13 files changed, 388 insertions(+), 8 deletions(-) diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts index 2cdbdad3cb..e23f86fe6e 100644 --- a/packages/core/src/code_assist/oauth2.test.ts +++ b/packages/core/src/code_assist/oauth2.test.ts @@ -79,6 +79,14 @@ vi.mock('./oauth-credential-storage.js', () => ({ }, })); +vi.mock('../mcp/token-storage/hybrid-token-storage.js', () => ({ + HybridTokenStorage: vi.fn(() => ({ + getCredentials: vi.fn(), + setCredentials: vi.fn(), + deleteCredentials: vi.fn(), + })), +})); + const mockConfig = { getNoBrowser: () => false, getProxy: () => 'http://test.proxy.com:8080', diff --git a/packages/core/src/mcp/token-storage/hybrid-token-storage.test.ts b/packages/core/src/mcp/token-storage/hybrid-token-storage.test.ts index 5303d84770..88d7d5c6ee 100644 --- a/packages/core/src/mcp/token-storage/hybrid-token-storage.test.ts +++ b/packages/core/src/mcp/token-storage/hybrid-token-storage.test.ts @@ -22,6 +22,20 @@ vi.mock('./keychain-token-storage.js', () => ({ })), })); +vi.mock('../../code_assist/oauth-credential-storage.js', () => ({ + OAuthCredentialStorage: { + saveCredentials: vi.fn(), + loadCredentials: vi.fn(), + clearCredentials: vi.fn(), + }, +})); + +vi.mock('../../core/apiKeyCredentialStorage.js', () => ({ + loadApiKey: vi.fn(), + saveApiKey: vi.fn(), + clearApiKey: vi.fn(), +})); + vi.mock('./file-token-storage.js', () => ({ FileTokenStorage: vi.fn().mockImplementation(() => ({ getCredentials: vi.fn(), diff --git a/packages/core/src/mcp/token-storage/hybrid-token-storage.ts b/packages/core/src/mcp/token-storage/hybrid-token-storage.ts index d5e798cab4..3bda6050e6 100644 --- a/packages/core/src/mcp/token-storage/hybrid-token-storage.ts +++ b/packages/core/src/mcp/token-storage/hybrid-token-storage.ts @@ -8,6 +8,8 @@ import { BaseTokenStorage } from './base-token-storage.js'; import { FileTokenStorage } from './file-token-storage.js'; import type { TokenStorage, OAuthCredentials } from './types.js'; import { TokenStorageType } from './types.js'; +import { coreEvents } from '../../utils/events.js'; +import { TokenStorageInitializationEvent } from '../../telemetry/types.js'; const FORCE_FILE_STORAGE_ENV_VAR = 'GEMINI_FORCE_FILE_STORAGE'; @@ -34,6 +36,11 @@ export class HybridTokenStorage extends BaseTokenStorage { if (isAvailable) { this.storage = keychainStorage; this.storageType = TokenStorageType.KEYCHAIN; + + coreEvents.emitTelemetryTokenStorageType( + new TokenStorageInitializationEvent('keychain', forceFileStorage), + ); + return this.storage; } } catch (_e) { @@ -43,6 +50,11 @@ export class HybridTokenStorage extends BaseTokenStorage { this.storage = new FileTokenStorage(this.serviceName); this.storageType = TokenStorageType.ENCRYPTED_FILE; + + coreEvents.emitTelemetryTokenStorageType( + new TokenStorageInitializationEvent('encrypted_file', forceFileStorage), + ); + return this.storage; } diff --git a/packages/core/src/mcp/token-storage/keychain-token-storage.test.ts b/packages/core/src/mcp/token-storage/keychain-token-storage.test.ts index 632387e23b..8b402ff7cd 100644 --- a/packages/core/src/mcp/token-storage/keychain-token-storage.test.ts +++ b/packages/core/src/mcp/token-storage/keychain-token-storage.test.ts @@ -25,15 +25,20 @@ vi.mock('keytar', () => ({ default: mockKeytar, })); -vi.mock('node:crypto', () => ({ - randomBytes: vi.fn(() => ({ - toString: vi.fn(() => mockCryptoRandomBytesString), - })), -})); +vi.mock('node:crypto', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + randomBytes: vi.fn(() => ({ + toString: vi.fn(() => mockCryptoRandomBytesString), + })), + }; +}); vi.mock('../../utils/events.js', () => ({ coreEvents: { emitFeedback: vi.fn(), + emitTelemetryKeychainAvailability: vi.fn(), }, })); diff --git a/packages/core/src/mcp/token-storage/keychain-token-storage.ts b/packages/core/src/mcp/token-storage/keychain-token-storage.ts index a06e44fb1d..8936823196 100644 --- a/packages/core/src/mcp/token-storage/keychain-token-storage.ts +++ b/packages/core/src/mcp/token-storage/keychain-token-storage.ts @@ -8,6 +8,7 @@ import * as crypto from 'node:crypto'; import { BaseTokenStorage } from './base-token-storage.js'; import type { OAuthCredentials, SecretStorage } from './types.js'; import { coreEvents } from '../../utils/events.js'; +import { KeychainAvailabilityEvent } from '../../telemetry/types.js'; interface Keytar { getPassword(service: string, account: string): Promise; @@ -263,9 +264,21 @@ export class KeychainTokenStorage const success = deleted && retrieved === testPassword; this.keychainAvailable = success; + + coreEvents.emitTelemetryKeychainAvailability( + new KeychainAvailabilityEvent(success), + ); + return success; } catch (_error) { this.keychainAvailable = false; + + // Do not log the raw error message to avoid potential PII leaks + // (e.g. from OS-level error messages containing file paths) + coreEvents.emitTelemetryKeychainAvailability( + new KeychainAvailabilityEvent(false), + ); + return false; } } diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 570725318a..a6a8b6d228 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -47,6 +47,8 @@ import type { ApprovalModeDurationEvent, PlanExecutionEvent, ToolOutputMaskingEvent, + KeychainAvailabilityEvent, + TokenStorageInitializationEvent, } from '../types.js'; import { EventMetadataKey } from './event-metadata-key.js'; import type { Config } from '../../config/config.js'; @@ -111,6 +113,8 @@ export enum EventNames { APPROVAL_MODE_DURATION = 'approval_mode_duration', PLAN_EXECUTION = 'plan_execution', TOOL_OUTPUT_MASKING = 'tool_output_masking', + KEYCHAIN_AVAILABILITY = 'keychain_availability', + TOKEN_STORAGE_INITIALIZATION = 'token_storage_initialization', } export interface LogResponse { @@ -1613,6 +1617,40 @@ export class ClearcutLogger { this.flushIfNeeded(); } + logKeychainAvailabilityEvent(event: KeychainAvailabilityEvent): void { + const data: EventValue[] = [ + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_KEYCHAIN_AVAILABLE, + value: JSON.stringify(event.available), + }, + ]; + + this.enqueueLogEvent( + this.createLogEvent(EventNames.KEYCHAIN_AVAILABILITY, data), + ); + this.flushIfNeeded(); + } + + logTokenStorageInitializationEvent( + event: TokenStorageInitializationEvent, + ): void { + const data: EventValue[] = [ + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOKEN_STORAGE_TYPE, + value: event.type, + }, + { + gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOKEN_STORAGE_FORCED, + value: JSON.stringify(event.forced), + }, + ]; + + this.enqueueLogEvent( + this.createLogEvent(EventNames.TOKEN_STORAGE_INITIALIZATION, data), + ); + this.flushIfNeeded(); + } + /** * Adds default fields to data, and returns a new data array. This fields * should exist on all log events. 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 8934db5570..fc7edc6dff 100644 --- a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts +++ b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts @@ -7,7 +7,7 @@ // Defines valid event metadata keys for Clearcut logging. export enum EventMetadataKey { // Deleted enums: 24 - // Next ID: 156 + // Next ID: 159 GEMINI_CLI_KEY_UNKNOWN = 0, @@ -578,7 +578,6 @@ export enum EventMetadataKey { // Logs the total prunable tokens identified at the trigger point. GEMINI_CLI_TOOL_OUTPUT_MASKING_TOTAL_PRUNABLE_TOKENS = 151, - // ========================================================================== // Ask User Stats Event Keys // ========================================================================== @@ -593,4 +592,17 @@ export enum EventMetadataKey { // Logs the number of questions answered in the ask_user tool. GEMINI_CLI_ASK_USER_ANSWER_COUNT = 155, + + // ========================================================================== + // Keychain & Token Storage Event Keys + // ========================================================================== + + // Logs whether the keychain is available. + GEMINI_CLI_KEYCHAIN_AVAILABLE = 156, + + // Logs the type of token storage initialized. + GEMINI_CLI_TOKEN_STORAGE_TYPE = 157, + + // Logs whether the token storage type was forced by an environment variable. + GEMINI_CLI_TOKEN_STORAGE_FORCED = 158, } diff --git a/packages/core/src/telemetry/loggers.ts b/packages/core/src/telemetry/loggers.ts index 4699d50d70..bbc47cfac8 100644 --- a/packages/core/src/telemetry/loggers.ts +++ b/packages/core/src/telemetry/loggers.ts @@ -57,6 +57,8 @@ import type { LlmLoopCheckEvent, PlanExecutionEvent, ToolOutputMaskingEvent, + KeychainAvailabilityEvent, + TokenStorageInitializationEvent, } from './types.js'; import { recordApiErrorMetrics, @@ -76,6 +78,8 @@ import { recordLinesChanged, recordHookCallMetrics, recordPlanExecution, + recordKeychainAvailability, + recordTokenStorageInitialization, } from './metrics.js'; import { bufferTelemetryEvent } from './sdk.js'; import type { UiEvent } from './uiTelemetry.js'; @@ -805,3 +809,37 @@ export function logStartupStats( }); }); } + +export function logKeychainAvailability( + config: Config, + event: KeychainAvailabilityEvent, +): void { + ClearcutLogger.getInstance(config)?.logKeychainAvailabilityEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + + recordKeychainAvailability(config, event); + }); +} + +export function logTokenStorageInitialization( + config: Config, + event: TokenStorageInitializationEvent, +): void { + ClearcutLogger.getInstance(config)?.logTokenStorageInitializationEvent(event); + bufferTelemetryEvent(() => { + const logger = logs.getLogger(SERVICE_NAME); + const logRecord: LogRecord = { + body: event.toLogBody(), + attributes: event.toOpenTelemetryAttributes(config), + }; + logger.emit(logRecord); + + recordTokenStorageInitialization(config, event); + }); +} diff --git a/packages/core/src/telemetry/metrics.test.ts b/packages/core/src/telemetry/metrics.test.ts index b395674b28..ccfe1000ba 100644 --- a/packages/core/src/telemetry/metrics.test.ts +++ b/packages/core/src/telemetry/metrics.test.ts @@ -20,7 +20,12 @@ import { ApiRequestPhase, } from './metrics.js'; import { makeFakeConfig } from '../test-utils/config.js'; -import { ModelRoutingEvent, AgentFinishEvent } from './types.js'; +import { + ModelRoutingEvent, + AgentFinishEvent, + KeychainAvailabilityEvent, + TokenStorageInitializationEvent, +} from './types.js'; import { AgentTerminateMode } from '../agents/types.js'; const mockCounterAddFn: Mock< @@ -97,6 +102,8 @@ describe('Telemetry Metrics', () => { let recordLinesChangedModule: typeof import('./metrics.js').recordLinesChanged; let recordSlowRenderModule: typeof import('./metrics.js').recordSlowRender; let recordPlanExecutionModule: typeof import('./metrics.js').recordPlanExecution; + let recordKeychainAvailabilityModule: typeof import('./metrics.js').recordKeychainAvailability; + let recordTokenStorageInitializationModule: typeof import('./metrics.js').recordTokenStorageInitialization; beforeEach(async () => { vi.resetModules(); @@ -142,6 +149,10 @@ describe('Telemetry Metrics', () => { recordLinesChangedModule = metricsJsModule.recordLinesChanged; recordSlowRenderModule = metricsJsModule.recordSlowRender; recordPlanExecutionModule = metricsJsModule.recordPlanExecution; + recordKeychainAvailabilityModule = + metricsJsModule.recordKeychainAvailability; + recordTokenStorageInitializationModule = + metricsJsModule.recordTokenStorageInitialization; const otelApiModule = await import('@opentelemetry/api'); @@ -1485,4 +1496,57 @@ describe('Telemetry Metrics', () => { }); }); }); + + describe('Keychain and Token Storage Metrics', () => { + describe('recordKeychainAvailability', () => { + it('should not record metrics if not initialized', () => { + const config = makeFakeConfig({}); + const event = new KeychainAvailabilityEvent(true); + recordKeychainAvailabilityModule(config, event); + expect(mockCounterAddFn).not.toHaveBeenCalled(); + }); + + it('should record keychain availability when initialized', () => { + const config = makeFakeConfig({}); + initializeMetricsModule(config); + mockCounterAddFn.mockClear(); + + const event = new KeychainAvailabilityEvent(true); + recordKeychainAvailabilityModule(config, event); + + expect(mockCounterAddFn).toHaveBeenCalledWith(1, { + 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', + available: true, + }); + }); + }); + + describe('recordTokenStorageInitialization', () => { + it('should not record metrics if not initialized', () => { + const config = makeFakeConfig({}); + const event = new TokenStorageInitializationEvent('hybrid', false); + recordTokenStorageInitializationModule(config, event); + expect(mockCounterAddFn).not.toHaveBeenCalled(); + }); + + it('should record token storage initialization when initialized', () => { + const config = makeFakeConfig({}); + initializeMetricsModule(config); + mockCounterAddFn.mockClear(); + + const event = new TokenStorageInitializationEvent('keychain', true); + recordTokenStorageInitializationModule(config, event); + + expect(mockCounterAddFn).toHaveBeenCalledWith(1, { + 'session.id': 'test-session-id', + 'installation.id': 'test-installation-id', + 'user.email': 'test@example.com', + type: 'keychain', + forced: true, + }); + }); + }); + }); }); diff --git a/packages/core/src/telemetry/metrics.ts b/packages/core/src/telemetry/metrics.ts index 73234f8daf..5d8dd90bd1 100644 --- a/packages/core/src/telemetry/metrics.ts +++ b/packages/core/src/telemetry/metrics.ts @@ -13,6 +13,8 @@ import type { ModelSlashCommandEvent, AgentFinishEvent, RecoveryAttemptEvent, + KeychainAvailabilityEvent, + TokenStorageInitializationEvent, } from './types.js'; import { AuthType } from '../core/contentGenerator.js'; import { getCommonAttributes } from './telemetryAttributes.js'; @@ -37,6 +39,8 @@ const MODEL_SLASH_COMMAND_CALL_COUNT = 'gemini_cli.slash_command.model.call_count'; const EVENT_HOOK_CALL_COUNT = 'gemini_cli.hook_call.count'; const EVENT_HOOK_CALL_LATENCY = 'gemini_cli.hook_call.latency'; +const KEYCHAIN_AVAILABILITY_COUNT = 'gemini_cli.keychain.availability.count'; +const TOKEN_STORAGE_TYPE_COUNT = 'gemini_cli.token_storage.type.count'; // Agent Metrics const AGENT_RUN_COUNT = 'gemini_cli.agent.run.count'; @@ -236,6 +240,25 @@ const COUNTER_DEFINITIONS = { success: boolean; }, }, + [KEYCHAIN_AVAILABILITY_COUNT]: { + description: 'Counts keychain availability checks.', + valueType: ValueType.INT, + assign: (c: Counter) => (keychainAvailabilityCounter = c), + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + attributes: {} as { + available: boolean; + }, + }, + [TOKEN_STORAGE_TYPE_COUNT]: { + description: 'Counts token storage type initializations.', + valueType: ValueType.INT, + assign: (c: Counter) => (tokenStorageTypeCounter = c), + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + attributes: {} as { + type: string; + forced: boolean; + }, + }, } as const; const HISTOGRAM_DEFINITIONS = { @@ -572,6 +595,8 @@ let planExecutionCounter: Counter | undefined; let slowRenderHistogram: Histogram | undefined; let hookCallCounter: Counter | undefined; let hookCallLatencyHistogram: Histogram | undefined; +let keychainAvailabilityCounter: Counter | undefined; +let tokenStorageTypeCounter: Counter | undefined; // OpenTelemetry GenAI Semantic Convention Metrics let genAiClientTokenUsageHistogram: Histogram | undefined; @@ -1279,3 +1304,32 @@ export function recordHookCallMetrics( hookCallCounter.add(1, metricAttributes); hookCallLatencyHistogram.record(durationMs, metricAttributes); } + +/** + * Records a metric for keychain availability. + */ +export function recordKeychainAvailability( + config: Config, + event: KeychainAvailabilityEvent, +): void { + if (!keychainAvailabilityCounter || !isMetricsInitialized) return; + keychainAvailabilityCounter.add(1, { + ...baseMetricDefinition.getCommonAttributes(config), + available: event.available, + }); +} + +/** + * Records a metric for token storage type initialization. + */ +export function recordTokenStorageInitialization( + config: Config, + event: TokenStorageInitializationEvent, +): void { + if (!tokenStorageTypeCounter || !isMetricsInitialized) return; + tokenStorageTypeCounter.add(1, { + ...baseMetricDefinition.getCommonAttributes(config), + type: event.type, + forced: event.forced, + }); +} diff --git a/packages/core/src/telemetry/sdk.ts b/packages/core/src/telemetry/sdk.ts index 5753cc5359..66b89523db 100644 --- a/packages/core/src/telemetry/sdk.ts +++ b/packages/core/src/telemetry/sdk.ts @@ -53,6 +53,15 @@ import { import { TelemetryTarget } from './index.js'; import { debugLogger } from '../utils/debugLogger.js'; import { authEvents } from '../code_assist/oauth2.js'; +import { coreEvents, CoreEvent } from '../utils/events.js'; +import { + logKeychainAvailability, + logTokenStorageInitialization, +} from './loggers.js'; +import type { + KeychainAvailabilityEvent, + TokenStorageInitializationEvent, +} from './types.js'; // For troubleshooting, set the log level to DiagLogLevel.DEBUG class DiagLoggerAdapter { @@ -86,6 +95,12 @@ let telemetryInitialized = false; let callbackRegistered = false; let authListener: ((newCredentials: JWTInput) => Promise) | undefined = undefined; +let keychainAvailabilityListener: + | ((event: KeychainAvailabilityEvent) => void) + | undefined = undefined; +let tokenStorageTypeListener: + | ((event: TokenStorageInitializationEvent) => void) + | undefined = undefined; const telemetryBuffer: Array<() => void | Promise> = []; let activeTelemetryEmail: string | undefined; @@ -196,6 +211,26 @@ export async function initializeTelemetry( 'session.id': config.getSessionId(), }); + if (!keychainAvailabilityListener) { + keychainAvailabilityListener = (event: KeychainAvailabilityEvent) => { + logKeychainAvailability(config, event); + }; + coreEvents.on( + CoreEvent.TelemetryKeychainAvailability, + keychainAvailabilityListener, + ); + } + + if (!tokenStorageTypeListener) { + tokenStorageTypeListener = (event: TokenStorageInitializationEvent) => { + logTokenStorageInitialization(config, event); + }; + coreEvents.on( + CoreEvent.TelemetryTokenStorageType, + tokenStorageTypeListener, + ); + } + const otlpEndpoint = config.getTelemetryOtlpEndpoint(); const otlpProtocol = config.getTelemetryOtlpProtocol(); const telemetryTarget = config.getTelemetryTarget(); @@ -376,6 +411,20 @@ export async function shutdownTelemetry( authEvents.off('post_auth', authListener); authListener = undefined; } + if (keychainAvailabilityListener) { + coreEvents.off( + CoreEvent.TelemetryKeychainAvailability, + keychainAvailabilityListener, + ); + keychainAvailabilityListener = undefined; + } + if (tokenStorageTypeListener) { + coreEvents.off( + CoreEvent.TelemetryTokenStorageType, + tokenStorageTypeListener, + ); + tokenStorageTypeListener = undefined; + } callbackRegistered = false; activeTelemetryEmail = undefined; } diff --git a/packages/core/src/telemetry/types.ts b/packages/core/src/telemetry/types.ts index c46255744a..54cca4f61f 100644 --- a/packages/core/src/telemetry/types.ts +++ b/packages/core/src/telemetry/types.ts @@ -2111,3 +2111,60 @@ export class HookCallEvent implements BaseTelemetryEvent { return `Hook call ${hookId} ${status} in ${this.duration_ms}ms`; } } + +export const EVENT_KEYCHAIN_AVAILABILITY = 'gemini_cli.keychain.availability'; +export class KeychainAvailabilityEvent implements BaseTelemetryEvent { + 'event.name': 'keychain_availability'; + 'event.timestamp': string; + available: boolean; + + constructor(available: boolean) { + this['event.name'] = 'keychain_availability'; + this['event.timestamp'] = new Date().toISOString(); + this.available = available; + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + const attributes: LogAttributes = { + ...getCommonAttributes(config), + 'event.name': EVENT_KEYCHAIN_AVAILABILITY, + 'event.timestamp': this['event.timestamp'], + available: this.available, + }; + return attributes; + } + + toLogBody(): string { + return `Keychain availability: ${this.available}`; + } +} + +export const EVENT_TOKEN_STORAGE_INITIALIZATION = + 'gemini_cli.token_storage.initialization'; +export class TokenStorageInitializationEvent implements BaseTelemetryEvent { + 'event.name': 'token_storage_initialization'; + 'event.timestamp': string; + type: string; + forced: boolean; + + constructor(type: string, forced: boolean) { + this['event.name'] = 'token_storage_initialization'; + this['event.timestamp'] = new Date().toISOString(); + this.type = type; + this.forced = forced; + } + + toOpenTelemetryAttributes(config: Config): LogAttributes { + return { + ...getCommonAttributes(config), + 'event.name': EVENT_TOKEN_STORAGE_INITIALIZATION, + 'event.timestamp': this['event.timestamp'], + type: this.type, + forced: this.forced, + }; + } + + toLogBody(): string { + return `Token storage initialized. Type: ${this.type}. Forced: ${this.forced}`; + } +} diff --git a/packages/core/src/utils/events.ts b/packages/core/src/utils/events.ts index 8784da07a2..014c2eec7a 100644 --- a/packages/core/src/utils/events.ts +++ b/packages/core/src/utils/events.ts @@ -9,6 +9,10 @@ import type { AgentDefinition } from '../agents/types.js'; import type { McpClient } from '../tools/mcp-client.js'; import type { ExtensionEvents } from './extensionLoader.js'; import type { EditorType } from './editor.js'; +import type { + TokenStorageInitializationEvent, + KeychainAvailabilityEvent, +} from '../telemetry/types.js'; /** * Defines the severity level for user-facing feedback. @@ -168,6 +172,8 @@ export enum CoreEvent { EditorSelected = 'editor-selected', SlashCommandConflicts = 'slash-command-conflicts', QuotaChanged = 'quota-changed', + TelemetryKeychainAvailability = 'telemetry-keychain-availability', + TelemetryTokenStorageType = 'telemetry-token-storage-type', } /** @@ -198,6 +204,8 @@ export interface CoreEvents extends ExtensionEvents { [CoreEvent.RequestEditorSelection]: never[]; [CoreEvent.EditorSelected]: [EditorSelectedPayload]; [CoreEvent.SlashCommandConflicts]: [SlashCommandConflictsPayload]; + [CoreEvent.TelemetryKeychainAvailability]: [KeychainAvailabilityEvent]; + [CoreEvent.TelemetryTokenStorageType]: [TokenStorageInitializationEvent]; } type EventBacklogItem = { @@ -367,6 +375,14 @@ export class CoreEventEmitter extends EventEmitter { ); } } + + emitTelemetryKeychainAvailability(event: KeychainAvailabilityEvent): void { + this._emitOrQueue(CoreEvent.TelemetryKeychainAvailability, event); + } + + emitTelemetryTokenStorageType(event: TokenStorageInitializationEvent): void { + this._emitOrQueue(CoreEvent.TelemetryTokenStorageType, event); + } } export const coreEvents = new CoreEventEmitter();