/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import { HttpsProxyAgent } from 'https-proxy-agent';
import { EventMetadataKey } from './event-metadata-key.js';
import { InstallationManager } from '../../utils/installationManager.js';
import { UserAccountManager } from '../../utils/userAccountManager.js';
import { safeJsonStringify, safeJsonStringifyBooleanValuesOnly, } from '../../utils/safeJsonStringify.js';
import { FixedDeque } from 'mnemonist';
import { GIT_COMMIT_INFO, CLI_VERSION } from '../../generated/git-commit.js';
import { IDE_DEFINITIONS, detectIdeFromEnv, isCloudShell, } from '../../ide/detect-ide.js';
import { debugLogger } from '../../utils/debugLogger.js';
export var EventNames;
(function (EventNames) {
    EventNames["START_SESSION"] = "start_session";
    EventNames["NEW_PROMPT"] = "new_prompt";
    EventNames["TOOL_CALL"] = "tool_call";
    EventNames["FILE_OPERATION"] = "file_operation";
    EventNames["API_REQUEST"] = "api_request";
    EventNames["API_RESPONSE"] = "api_response";
    EventNames["API_ERROR"] = "api_error";
    EventNames["END_SESSION"] = "end_session";
    EventNames["FLASH_FALLBACK"] = "flash_fallback";
    EventNames["RIPGREP_FALLBACK"] = "ripgrep_fallback";
    EventNames["LOOP_DETECTED"] = "loop_detected";
    EventNames["LOOP_DETECTION_DISABLED"] = "loop_detection_disabled";
    EventNames["NEXT_SPEAKER_CHECK"] = "next_speaker_check";
    EventNames["SLASH_COMMAND"] = "slash_command";
    EventNames["MALFORMED_JSON_RESPONSE"] = "malformed_json_response";
    EventNames["IDE_CONNECTION"] = "ide_connection";
    EventNames["KITTY_SEQUENCE_OVERFLOW"] = "kitty_sequence_overflow";
    EventNames["CHAT_COMPRESSION"] = "chat_compression";
    EventNames["CONVERSATION_FINISHED"] = "conversation_finished";
    EventNames["INVALID_CHUNK"] = "invalid_chunk";
    EventNames["CONTENT_RETRY"] = "content_retry";
    EventNames["CONTENT_RETRY_FAILURE"] = "content_retry_failure";
    EventNames["EXTENSION_ENABLE"] = "extension_enable";
    EventNames["EXTENSION_DISABLE"] = "extension_disable";
    EventNames["EXTENSION_INSTALL"] = "extension_install";
    EventNames["EXTENSION_UNINSTALL"] = "extension_uninstall";
    EventNames["EXTENSION_UPDATE"] = "extension_update";
    EventNames["TOOL_OUTPUT_TRUNCATED"] = "tool_output_truncated";
    EventNames["MODEL_ROUTING"] = "model_routing";
    EventNames["MODEL_SLASH_COMMAND"] = "model_slash_command";
    EventNames["SMART_EDIT_STRATEGY"] = "smart_edit_strategy";
    EventNames["SMART_EDIT_CORRECTION"] = "smart_edit_correction";
    EventNames["AGENT_START"] = "agent_start";
    EventNames["AGENT_FINISH"] = "agent_finish";
    EventNames["RECOVERY_ATTEMPT"] = "recovery_attempt";
    EventNames["WEB_FETCH_FALLBACK_ATTEMPT"] = "web_fetch_fallback_attempt";
    EventNames["LLM_LOOP_CHECK"] = "llm_loop_check";
})(EventNames || (EventNames = {}));
/**
 * Determine the surface that the user is currently using.  Surface is effectively the
 * distribution channel in which the user is using Gemini CLI.  Gemini CLI comes bundled
 * w/ Firebase Studio and Cloud Shell.  Users that manually download themselves will
 * likely be "SURFACE_NOT_SET".
 *
 * This is computed based upon a series of environment variables these distribution
 * methods might have in their runtimes.
 */
function determineSurface() {
    if (process.env['SURFACE']) {
        return process.env['SURFACE'];
    }
    else if (isCloudShell()) {
        return IDE_DEFINITIONS.cloudshell.name;
    }
    else if (process.env['GITHUB_SHA']) {
        return 'GitHub';
    }
    else if (process.env['TERM_PROGRAM'] === 'vscode') {
        return detectIdeFromEnv().name || IDE_DEFINITIONS.vscode.name;
    }
    else {
        return 'SURFACE_NOT_SET';
    }
}
/**
 * Determines the GitHub Actions workflow name if the CLI is running in a GitHub Actions environment.
 */
function determineGHWorkflowName() {
    return process.env['GH_WORKFLOW_NAME'];
}
/**
 * Clearcut URL to send logging events to.
 */
const CLEARCUT_URL = 'https://play.googleapis.com/log?format=json&hasfast=true';
/**
 * Interval in which buffered events are sent to clearcut.
 */
const FLUSH_INTERVAL_MS = 1000 * 60;
/**
 * Maximum amount of events to keep in memory. Events added after this amount
 * are dropped until the next flush to clearcut, which happens periodically as
 * defined by {@link FLUSH_INTERVAL_MS}.
 */
const MAX_EVENTS = 1000;
/**
 * Maximum events to retry after a failed clearcut flush
 */
const MAX_RETRY_EVENTS = 100;
// Singleton class for batch posting log events to Clearcut. When a new event comes in, the elapsed time
// is checked and events are flushed to Clearcut if at least a minute has passed since the last flush.
export class ClearcutLogger {
    static instance;
    config;
    sessionData = [];
    promptId = '';
    installationManager;
    userAccountManager;
    /**
     * Queue of pending events that need to be flushed to the server.  New events
     * are added to this queue and then flushed on demand (via `flushToClearcut`)
     */
    events;
    /**
     * The last time that the events were successfully flushed to the server.
     */
    lastFlushTime = Date.now();
    /**
     * the value is true when there is a pending flush happening. This prevents
     * concurrent flush operations.
     */
    flushing = false;
    /**
     * This value is true when a flush was requested during an ongoing flush.
     */
    pendingFlush = false;
    constructor(config) {
        this.config = config;
        this.events = new FixedDeque(Array, MAX_EVENTS);
        this.promptId = config?.getSessionId() ?? '';
        this.installationManager = new InstallationManager();
        this.userAccountManager = new UserAccountManager();
    }
    static getInstance(config) {
        if (config === undefined || !config?.getUsageStatisticsEnabled())
            return undefined;
        if (!ClearcutLogger.instance) {
            ClearcutLogger.instance = new ClearcutLogger(config);
        }
        return ClearcutLogger.instance;
    }
    /** For testing purposes only. */
    static clearInstance() {
        // @ts-expect-error - ClearcutLogger is a singleton, but we need to clear it for tests.
        ClearcutLogger.instance = undefined;
    }
    enqueueHelper(event) {
        // Manually handle overflow for FixedDeque, which throws when full.
        const wasAtCapacity = this.events.size >= MAX_EVENTS;
        if (wasAtCapacity) {
            this.events.shift(); // Evict oldest element to make space.
        }
        this.events.push([
            {
                event_time_ms: Date.now(),
                source_extension_json: safeJsonStringify(event),
            },
        ]);
        if (wasAtCapacity && this.config?.getDebugMode()) {
            debugLogger.debug(`ClearcutLogger: Dropped old event to prevent memory leak (queue size: ${this.events.size})`);
        }
    }
    enqueueLogEvent(event) {
        try {
            this.enqueueHelper(event);
        }
        catch (error) {
            if (this.config?.getDebugMode()) {
                console.error('ClearcutLogger: Failed to enqueue log event.', error);
            }
        }
    }
    async enqueueLogEventAfterExperimentsLoadAsync(event) {
        try {
            this.config?.getExperimentsAsync().then((experiments) => {
                if (experiments) {
                    const exp_id_data = [
                        {
                            gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXPERIMENT_IDS,
                            value: experiments.experimentIds.toString() ?? 'NA',
                        },
                    ];
                    event.event_metadata = [[...event.event_metadata[0], ...exp_id_data]];
                }
                this.enqueueHelper(event);
            });
        }
        catch (error) {
            if (this.config?.getDebugMode()) {
                console.error('ClearcutLogger: Failed to enqueue log event.', error);
            }
        }
    }
    createBasicLogEvent(eventName, data = []) {
        const email = this.userAccountManager.getCachedGoogleAccount();
        const surface = determineSurface();
        const ghWorkflowName = determineGHWorkflowName();
        const baseMetadata = [
            ...data,
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SURFACE,
                value: surface,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_VERSION,
                value: CLI_VERSION,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_GIT_COMMIT_HASH,
                value: GIT_COMMIT_INFO,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_OS,
                value: process.platform,
            },
        ];
        if (ghWorkflowName) {
            baseMetadata.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_GH_WORKFLOW_NAME,
                value: ghWorkflowName,
            });
        }
        const logEvent = {
            console_type: 'GEMINI_CLI',
            application: 102, // GEMINI_CLI
            event_name: eventName,
            event_metadata: [baseMetadata],
        };
        // Should log either email or install ID, not both. See go/cloudmill-1p-oss-instrumentation#define-sessionable-id
        if (email) {
            logEvent.client_email = email;
        }
        else {
            logEvent.client_install_id = this.installationManager.getInstallationId();
        }
        return logEvent;
    }
    createLogEvent(eventName, data = []) {
        if (eventName !== EventNames.START_SESSION) {
            data.push(...this.sessionData);
        }
        const totalAccounts = this.userAccountManager.getLifetimeGoogleAccounts();
        data = this.addDefaultFields(data, totalAccounts);
        return this.createBasicLogEvent(eventName, data);
    }
    flushIfNeeded() {
        if (Date.now() - this.lastFlushTime < FLUSH_INTERVAL_MS) {
            return;
        }
        this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    async flushToClearcut() {
        if (this.flushing) {
            if (this.config?.getDebugMode()) {
                debugLogger.debug('ClearcutLogger: Flush already in progress, marking pending flush.');
            }
            this.pendingFlush = true;
            return Promise.resolve({});
        }
        this.flushing = true;
        if (this.config?.getDebugMode()) {
            debugLogger.log('Flushing log events to Clearcut.');
        }
        const eventsToSend = this.events.toArray();
        this.events.clear();
        const request = [
            {
                log_source_name: 'CONCORD',
                request_time_ms: Date.now(),
                log_event: eventsToSend,
            },
        ];
        let result = {};
        try {
            const response = await fetch(CLEARCUT_URL, {
                method: 'POST',
                body: safeJsonStringify(request),
                headers: {
                    'Content-Type': 'application/json',
                },
            });
            const responseBody = await response.text();
            if (response.status >= 200 && response.status < 300) {
                this.lastFlushTime = Date.now();
                const nextRequestWaitMs = Number(JSON.parse(responseBody)[0]);
                result = {
                    ...result,
                    nextRequestWaitMs,
                };
            }
            else {
                if (this.config?.getDebugMode()) {
                    console.error(`Error flushing log events: HTTP ${response.status}: ${response.statusText}`);
                }
                // Re-queue failed events for retry
                this.requeueFailedEvents(eventsToSend);
            }
        }
        catch (e) {
            if (this.config?.getDebugMode()) {
                console.error('Error flushing log events:', e);
            }
            // Re-queue failed events for retry
            this.requeueFailedEvents(eventsToSend);
        }
        this.flushing = false;
        // If a flush was requested while we were flushing, flush again
        if (this.pendingFlush) {
            this.pendingFlush = false;
            // Fire and forget the pending flush
            this.flushToClearcut().catch((error) => {
                if (this.config?.getDebugMode()) {
                    debugLogger.debug('Error in pending flush to Clearcut:', error);
                }
            });
        }
        return result;
    }
    logStartSessionEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MODEL,
                value: event.model,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EMBEDDING_MODEL,
                value: event.embedding_model,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_SANDBOX,
                value: event.sandbox_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_CORE_TOOLS,
                value: event.core_tools_enabled,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_APPROVAL_MODE,
                value: event.approval_mode,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_API_KEY_ENABLED,
                value: event.api_key_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_VERTEX_API_ENABLED,
                value: event.vertex_ai_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_DEBUG_MODE_ENABLED,
                value: event.debug_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_VERTEX_API_ENABLED,
                value: event.vertex_ai_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_SERVERS,
                value: event.mcp_servers,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_VERTEX_API_ENABLED,
                value: event.vertex_ai_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_TELEMETRY_ENABLED,
                value: event.telemetry_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_TELEMETRY_LOG_USER_PROMPTS_ENABLED,
                value: event.telemetry_log_user_prompts_enabled.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_SERVERS_COUNT,
                value: event.mcp_servers_count
                    ? event.mcp_servers_count.toString()
                    : '',
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_TOOLS_COUNT,
                value: event.mcp_tools_count?.toString() ?? '',
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_MCP_TOOLS,
                value: event.mcp_tools ? event.mcp_tools : '',
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSIONS_COUNT,
                value: event.extensions_count.toString(),
            },
            // We deliberately do not log the names of extensions here, to be safe.
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_START_SESSION_EXTENSION_IDS,
                value: event.extension_ids.toString(),
            },
        ];
        this.sessionData = data;
        // Flush after experiments finish loading from CCPA server
        this.enqueueLogEventAfterExperimentsLoadAsync(this.createLogEvent(EventNames.START_SESSION, data)).then(() => {
            this.flushToClearcut().catch((error) => {
                debugLogger.debug('Error flushing to Clearcut:', error);
            });
        });
    }
    logNewPromptEvent(event) {
        this.promptId = event.prompt_id;
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_PROMPT_LENGTH,
                value: JSON.stringify(event.prompt_length),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.NEW_PROMPT, data));
        this.flushIfNeeded();
    }
    logToolCallEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
                value: JSON.stringify(event.function_name),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_DECISION,
                value: JSON.stringify(event.decision),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_SUCCESS,
                value: JSON.stringify(event.success),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_DURATION_MS,
                value: JSON.stringify(event.duration_ms),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_ERROR_TYPE,
                value: JSON.stringify(event.error_type),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_TYPE,
                value: JSON.stringify(event.tool_type),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_CONTENT_LENGTH,
                value: JSON.stringify(event.content_length),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_MCP_SERVER_NAME,
                value: JSON.stringify(event.mcp_server_name),
            },
        ];
        if (event.metadata) {
            const metadataMapping = {
                model_added_lines: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
                model_removed_lines: EventMetadataKey.GEMINI_CLI_AI_REMOVED_LINES,
                model_added_chars: EventMetadataKey.GEMINI_CLI_AI_ADDED_CHARS,
                model_removed_chars: EventMetadataKey.GEMINI_CLI_AI_REMOVED_CHARS,
                user_added_lines: EventMetadataKey.GEMINI_CLI_USER_ADDED_LINES,
                user_removed_lines: EventMetadataKey.GEMINI_CLI_USER_REMOVED_LINES,
                user_added_chars: EventMetadataKey.GEMINI_CLI_USER_ADDED_CHARS,
                user_removed_chars: EventMetadataKey.GEMINI_CLI_USER_REMOVED_CHARS,
            };
            for (const [key, gemini_cli_key] of Object.entries(metadataMapping)) {
                if (event.metadata[key] !== undefined) {
                    data.push({
                        gemini_cli_key,
                        value: JSON.stringify(event.metadata[key]),
                    });
                }
            }
        }
        if (event.extension_id) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            });
        }
        const logEvent = this.createLogEvent(EventNames.TOOL_CALL, data);
        this.enqueueLogEvent(logEvent);
        this.flushIfNeeded();
    }
    logFileOperationEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
                value: JSON.stringify(event.tool_name),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_TYPE,
                value: JSON.stringify(event.operation),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_LINES,
                value: JSON.stringify(event.lines),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_MIMETYPE,
                value: JSON.stringify(event.mimetype),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_EXTENSION,
                value: JSON.stringify(event.extension),
            },
        ];
        if (event.programming_language) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROGRAMMING_LANGUAGE,
                value: event.programming_language,
            });
        }
        const logEvent = this.createLogEvent(EventNames.FILE_OPERATION, data);
        this.enqueueLogEvent(logEvent);
        this.flushIfNeeded();
    }
    logApiRequestEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_REQUEST_MODEL,
                value: JSON.stringify(event.model),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.API_REQUEST, data));
        this.flushIfNeeded();
    }
    logApiResponseEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_MODEL,
                value: JSON.stringify(event.model),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_STATUS_CODE,
                value: JSON.stringify(event.status_code),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_DURATION_MS,
                value: JSON.stringify(event.duration_ms),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_INPUT_TOKEN_COUNT,
                value: JSON.stringify(event.usage.input_token_count),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_OUTPUT_TOKEN_COUNT,
                value: JSON.stringify(event.usage.output_token_count),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_CACHED_TOKEN_COUNT,
                value: JSON.stringify(event.usage.cached_content_token_count),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_THINKING_TOKEN_COUNT,
                value: JSON.stringify(event.usage.thoughts_token_count),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_TOOL_TOKEN_COUNT,
                value: JSON.stringify(event.usage.tool_token_count),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.API_RESPONSE, data));
        this.flushIfNeeded();
    }
    logApiErrorEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_MODEL,
                value: JSON.stringify(event.model),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_TYPE,
                value: JSON.stringify(event.error_type),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_STATUS_CODE,
                value: JSON.stringify(event.status_code),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_DURATION_MS,
                value: JSON.stringify(event.duration_ms),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.API_ERROR, data));
        this.flushIfNeeded();
    }
    logChatCompressionEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_BEFORE,
                value: `${event.tokens_before}`,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_COMPRESSION_TOKENS_AFTER,
                value: `${event.tokens_after}`,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.CHAT_COMPRESSION, data));
    }
    logFlashFallbackEvent() {
        this.enqueueLogEvent(this.createLogEvent(EventNames.FLASH_FALLBACK, []));
        this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logRipgrepFallbackEvent() {
        this.enqueueLogEvent(this.createLogEvent(EventNames.RIPGREP_FALLBACK, []));
        this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logLoopDetectedEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_LOOP_DETECTED_TYPE,
                value: JSON.stringify(event.loop_type),
            },
        ];
        if (event.confirmed_by_model) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_LOOP_DETECTED_CONFIRMED_BY_MODEL,
                value: event.confirmed_by_model,
            });
        }
        this.enqueueLogEvent(this.createLogEvent(EventNames.LOOP_DETECTED, data));
        this.flushIfNeeded();
    }
    logLoopDetectionDisabledEvent() {
        const data = [];
        this.enqueueLogEvent(this.createLogEvent(EventNames.LOOP_DETECTION_DISABLED, data));
        this.flushIfNeeded();
    }
    logNextSpeakerCheck(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_RESPONSE_FINISH_REASON,
                value: JSON.stringify(event.finish_reason),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_NEXT_SPEAKER_CHECK_RESULT,
                value: JSON.stringify(event.result),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.NEXT_SPEAKER_CHECK, data));
        this.flushIfNeeded();
    }
    logSlashCommandEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SLASH_COMMAND_NAME,
                value: JSON.stringify(event.command),
            },
        ];
        if (event.subcommand) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SLASH_COMMAND_SUBCOMMAND,
                value: JSON.stringify(event.subcommand),
            });
        }
        if (event.status) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SLASH_COMMAND_STATUS,
                value: JSON.stringify(event.status),
            });
        }
        if (event.extension_id) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            });
        }
        this.enqueueLogEvent(this.createLogEvent(EventNames.SLASH_COMMAND, data));
        this.flushIfNeeded();
    }
    logMalformedJsonResponseEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_MALFORMED_JSON_RESPONSE_MODEL,
                value: JSON.stringify(event.model),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.MALFORMED_JSON_RESPONSE, data));
        this.flushIfNeeded();
    }
    logIdeConnectionEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_IDE_CONNECTION_TYPE,
                value: JSON.stringify(event.connection_type),
            },
        ];
        // Flush after experiments finish loading from CCPA server
        this.enqueueLogEventAfterExperimentsLoadAsync(this.createLogEvent(EventNames.START_SESSION, data)).then(() => {
            this.flushToClearcut().catch((error) => {
                debugLogger.debug('Error flushing to Clearcut:', error);
            });
        });
    }
    logConversationFinishedEvent(event) {
        const data = [
            {
                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() {
        // Flush immediately on session end.
        this.enqueueLogEvent(this.createLogEvent(EventNames.END_SESSION, []));
        this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logInvalidChunkEvent(event) {
        const data = [];
        if (event.error_message) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_INVALID_CHUNK_ERROR_MESSAGE,
                value: event.error_message,
            });
        }
        this.enqueueLogEvent(this.createLogEvent(EventNames.INVALID_CHUNK, data));
        this.flushIfNeeded();
    }
    logContentRetryEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_ATTEMPT_NUMBER,
                value: String(event.attempt_number),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_ERROR_TYPE,
                value: event.error_type,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_DELAY_MS,
                value: String(event.retry_delay_ms),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_REQUEST_MODEL,
                value: event.model,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.CONTENT_RETRY, data));
        this.flushIfNeeded();
    }
    logContentRetryFailureEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_FAILURE_TOTAL_ATTEMPTS,
                value: String(event.total_attempts),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_FAILURE_FINAL_ERROR_TYPE,
                value: event.final_error_type,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_REQUEST_MODEL,
                value: event.model,
            },
        ];
        if (event.total_duration_ms) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONTENT_RETRY_FAILURE_TOTAL_DURATION_MS,
                value: String(event.total_duration_ms),
            });
        }
        this.enqueueLogEvent(this.createLogEvent(EventNames.CONTENT_RETRY_FAILURE, data));
        this.flushIfNeeded();
    }
    async logExtensionInstallEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
                value: event.extension_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
                value: event.extension_version,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_SOURCE,
                value: event.extension_source,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_INSTALL_STATUS,
                value: event.status,
            },
        ];
        this.enqueueLogEvent(this.createBasicLogEvent(EventNames.EXTENSION_INSTALL, data));
        await this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    async logExtensionUninstallEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
                value: event.extension_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UNINSTALL_STATUS,
                value: event.status,
            },
        ];
        this.enqueueLogEvent(this.createBasicLogEvent(EventNames.EXTENSION_UNINSTALL, data));
        await this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    async logExtensionUpdateEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
                value: event.extension_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_VERSION,
                value: event.extension_version,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_PREVIOUS_VERSION,
                value: event.extension_previous_version,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_SOURCE,
                value: event.extension_source,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_UPDATE_STATUS,
                value: event.status,
            },
        ];
        this.enqueueLogEvent(this.createBasicLogEvent(EventNames.EXTENSION_UPDATE, data));
        await this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logToolOutputTruncatedEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
                value: JSON.stringify(event.tool_name),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_ORIGINAL_LENGTH,
                value: JSON.stringify(event.original_content_length),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_TRUNCATED_LENGTH,
                value: JSON.stringify(event.truncated_content_length),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_THRESHOLD,
                value: JSON.stringify(event.threshold),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_LINES,
                value: JSON.stringify(event.lines),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.TOOL_OUTPUT_TRUNCATED, data));
        this.flushIfNeeded();
    }
    logModelRoutingEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_ROUTING_DECISION,
                value: event.decision_model,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_ROUTING_DECISION_SOURCE,
                value: event.decision_source,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_ROUTING_LATENCY_MS,
                value: event.routing_latency_ms.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_ROUTING_FAILURE,
                value: event.failed.toString(),
            },
        ];
        if (event.error_message) {
            data.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_ROUTING_FAILURE_REASON,
                value: event.error_message,
            });
        }
        this.enqueueLogEvent(this.createLogEvent(EventNames.MODEL_ROUTING, data));
        this.flushIfNeeded();
    }
    async logExtensionEnableEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
                value: event.extension_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE,
                value: event.setting_scope,
            },
        ];
        this.enqueueLogEvent(this.createBasicLogEvent(EventNames.EXTENSION_ENABLE, data));
        await this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logModelSlashCommandEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_MODEL_SLASH_COMMAND,
                value: event.model_name,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.MODEL_SLASH_COMMAND, data));
        this.flushIfNeeded();
    }
    async logExtensionDisableEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
                value: event.extension_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_ID,
                value: event.extension_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_DISABLE_SETTING_SCOPE,
                value: event.setting_scope,
            },
        ];
        this.enqueueLogEvent(this.createBasicLogEvent(EventNames.EXTENSION_DISABLE, data));
        await this.flushToClearcut().catch((error) => {
            debugLogger.debug('Error flushing to Clearcut:', error);
        });
    }
    logSmartEditStrategyEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SMART_EDIT_STRATEGY,
                value: event.strategy,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.SMART_EDIT_STRATEGY, data));
        this.flushIfNeeded();
    }
    logSmartEditCorrectionEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SMART_EDIT_CORRECTION,
                value: event.correction,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.SMART_EDIT_CORRECTION, data));
        this.flushIfNeeded();
    }
    logAgentStartEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_ID,
                value: event.agent_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_NAME,
                value: event.agent_name,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.AGENT_START, data));
        this.flushIfNeeded();
    }
    logAgentFinishEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_ID,
                value: event.agent_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_NAME,
                value: event.agent_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_DURATION_MS,
                value: event.duration_ms.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_TURN_COUNT,
                value: event.turn_count.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_TERMINATE_REASON,
                value: event.terminate_reason,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.AGENT_FINISH, data));
        this.flushIfNeeded();
    }
    logRecoveryAttemptEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_ID,
                value: event.agent_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_NAME,
                value: event.agent_name,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_RECOVERY_REASON,
                value: event.reason,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_RECOVERY_DURATION_MS,
                value: event.duration_ms.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_RECOVERY_SUCCESS,
                value: event.success.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AGENT_TURN_COUNT,
                value: event.turn_count.toString(),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.RECOVERY_ATTEMPT, data));
        this.flushIfNeeded();
    }
    logWebFetchFallbackAttemptEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_WEB_FETCH_FALLBACK_REASON,
                value: event.reason,
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.WEB_FETCH_FALLBACK_ATTEMPT, data));
        this.flushIfNeeded();
    }
    logLlmLoopCheckEvent(event) {
        const data = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
                value: event.prompt_id,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_FLASH_CONFIDENCE,
                value: event.flash_confidence.toString(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL,
                value: event.main_model,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_LLM_LOOP_CHECK_MAIN_MODEL_CONFIDENCE,
                value: event.main_model_confidence.toString(),
            },
        ];
        this.enqueueLogEvent(this.createLogEvent(EventNames.LLM_LOOP_CHECK, data));
        this.flushIfNeeded();
    }
    /**
     * Adds default fields to data, and returns a new data array.  This fields
     * should exist on all log events.
     */
    addDefaultFields(data, totalAccounts) {
        const defaultLogMetadata = [
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID,
                value: this.config?.getSessionId() ?? '',
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_AUTH_TYPE,
                value: JSON.stringify(this.config?.getContentGeneratorConfig()?.authType),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_GOOGLE_ACCOUNTS_COUNT,
                value: `${totalAccounts}`,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
                value: this.promptId,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_NODE_VERSION,
                value: process.versions.node,
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_SETTINGS,
                value: this.getConfigJson(),
            },
            {
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_INTERACTIVE,
                value: this.config?.isInteractive().toString() ?? 'false',
            },
        ];
        if (this.config?.getExperiments()) {
            defaultLogMetadata.push({
                gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXPERIMENT_IDS,
                value: this.config?.getExperiments()?.experimentIds.toString() ?? 'NA',
            });
        }
        return [...data, ...defaultLogMetadata];
    }
    getProxyAgent() {
        const proxyUrl = this.config?.getProxy();
        if (!proxyUrl)
            return undefined;
        // undici which is widely used in the repo can only support http & https proxy protocol,
        // https://github.com/nodejs/undici/issues/2224
        if (proxyUrl.startsWith('http')) {
            return new HttpsProxyAgent(proxyUrl);
        }
        else {
            throw new Error('Unsupported proxy type');
        }
    }
    getConfigJson() {
        return safeJsonStringifyBooleanValuesOnly(this.config);
    }
    shutdown() {
        this.logEndSessionEvent();
    }
    requeueFailedEvents(eventsToSend) {
        // Add the events back to the front of the queue to be retried, but limit retry queue size
        const eventsToRetry = eventsToSend.slice(-MAX_RETRY_EVENTS); // Keep only the most recent events
        // Log a warning if we're dropping events
        if (eventsToSend.length > MAX_RETRY_EVENTS && this.config?.getDebugMode()) {
            debugLogger.warn(`ClearcutLogger: Dropping ${eventsToSend.length - MAX_RETRY_EVENTS} events due to retry queue limit. Total events: ${eventsToSend.length}, keeping: ${MAX_RETRY_EVENTS}`);
        }
        // Determine how many events can be re-queued
        const availableSpace = MAX_EVENTS - this.events.size;
        const numEventsToRequeue = Math.min(eventsToRetry.length, availableSpace);
        if (numEventsToRequeue === 0) {
            if (this.config?.getDebugMode()) {
                debugLogger.debug(`ClearcutLogger: No events re-queued (queue size: ${this.events.size})`);
            }
            return;
        }
        // Get the most recent events to re-queue
        const eventsToRequeue = eventsToRetry.slice(eventsToRetry.length - numEventsToRequeue);
        // Prepend events to the front of the deque to be retried first.
        // We iterate backwards to maintain the original order of the failed events.
        for (let i = eventsToRequeue.length - 1; i >= 0; i--) {
            this.events.unshift(eventsToRequeue[i]);
        }
        // Clear any potential overflow
        while (this.events.size > MAX_EVENTS) {
            this.events.pop();
        }
        if (this.config?.getDebugMode()) {
            debugLogger.debug(`ClearcutLogger: Re-queued ${numEventsToRequeue} events for retry (queue size: ${this.events.size})`);
        }
    }
}
export const TEST_ONLY = {
    MAX_RETRY_EVENTS,
    MAX_EVENTS,
};
//# sourceMappingURL=clearcut-logger.js.map