diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts index 5656d40ec1..700e67591e 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts @@ -37,7 +37,6 @@ import { AgentTerminateMode } from '../../agents/types.js'; import { GIT_COMMIT_INFO, CLI_VERSION } from '../../generated/git-commit.js'; import { UserAccountManager } from '../../utils/userAccountManager.js'; import { InstallationManager } from '../../utils/installationManager.js'; -import { safeJsonStringify } from '../../utils/safeJsonStringify.js'; interface CustomMatchers { toHaveMetadataValue: ([key, value]: [EventMetadataKey, string]) => R; @@ -260,15 +259,13 @@ describe('ClearcutLogger', () => { const cli_version = CLI_VERSION; const git_commit_hash = GIT_COMMIT_INFO; const prompt_id = 'my-prompt-123'; - const user_settings = safeJsonStringify([ - { smart_edit_enabled: true, model_router_enabled: false }, - ]); // Setup logger with expected values const { logger, loggerConfig } = setup({ lifetimeGoogleAccounts: google_accounts, config: { sessionId: session_id }, }); + vi.spyOn(loggerConfig, 'getContentGeneratorConfig').mockReturnValue({ authType: auth_type, } as ContentGeneratorConfig); @@ -315,7 +312,7 @@ describe('ClearcutLogger', () => { }, { gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS, - value: user_settings, + value: logger?.getConfigJson(), }, ]), ); @@ -346,11 +343,7 @@ describe('ClearcutLogger', () => { }); }); - it('logs the value of config.useSmartEdit and config.useModelRouter', () => { - const user_settings = safeJsonStringify([ - { smart_edit_enabled: true, model_router_enabled: true }, - ]); - + it('logs all user settings', () => { const { logger } = setup({ config: { useSmartEdit: true, useModelRouter: true }, }); @@ -362,7 +355,7 @@ describe('ClearcutLogger', () => { expect(event?.event_metadata[0]).toContainEqual({ gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS, - value: user_settings, + value: logger?.getConfigJson(), }); }); diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index 8afeadbc7d..2325191613 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -42,7 +42,10 @@ import { EventMetadataKey } from './event-metadata-key.js'; import type { Config } from '../../config/config.js'; import { InstallationManager } from '../../utils/installationManager.js'; import { UserAccountManager } from '../../utils/userAccountManager.js'; -import { safeJsonStringify } from '../../utils/safeJsonStringify.js'; +import { + safeJsonStringify, + safeJsonStringifyBooleanValuesOnly, +} from '../../utils/safeJsonStringify.js'; import { FixedDeque } from 'mnemonist'; import { GIT_COMMIT_INFO, CLI_VERSION } from '../../generated/git-commit.js'; import { @@ -1217,12 +1220,7 @@ export class ClearcutLogger { }, { gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS, - value: safeJsonStringify([ - { - smart_edit_enabled: this.config?.getUseSmartEdit() ?? false, - model_router_enabled: this.config?.getUseModelRouter() ?? false, - }, - ]), + value: this.getConfigJson(), }, ]; return [...data, ...defaultLogMetadata]; @@ -1240,6 +1238,12 @@ export class ClearcutLogger { } } + getConfigJson() { + const configJson = safeJsonStringifyBooleanValuesOnly(this.config); + console.debug(configJson); + return safeJsonStringifyBooleanValuesOnly(this.config); + } + shutdown() { this.logEndSessionEvent(); } diff --git a/packages/core/src/utils/safeJsonStringify.ts b/packages/core/src/utils/safeJsonStringify.ts index f439bcea1e..00eeee8cdf 100644 --- a/packages/core/src/utils/safeJsonStringify.ts +++ b/packages/core/src/utils/safeJsonStringify.ts @@ -11,6 +11,8 @@ * @param space - Optional space parameter for formatting (defaults to no formatting) * @returns JSON string with circular references replaced by [Circular] */ +import type { Config } from '../config/config.js'; + export function safeJsonStringify( obj: unknown, space?: string | number, @@ -30,3 +32,37 @@ export function safeJsonStringify( space, ); } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function removeEmptyObjects(data: any): object { + const cleanedObject: { [key: string]: unknown } = {}; + for (const k in data) { + const v = data[k]; + if (v !== null && v !== undefined && typeof v === 'boolean') { + cleanedObject[k] = v; + } + } + + return cleanedObject; +} + +/** + * Safely stringifies an object to JSON, retaining only non-null, Boolean-valued members. + * + * @param obj - The object to stringify + * @returns JSON string with circular references skipped and only non-null, Boolean member values retained. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function safeJsonStringifyBooleanValuesOnly(obj: any): string { + let configSeen = false; + return JSON.stringify(removeEmptyObjects(obj), (key, value) => { + if ((value as Config) !== null && !configSeen) { + configSeen = true; + return value; + } + if (typeof value === 'boolean') { + return value; + } + return ''; + }); +}