/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { inspect } from 'node:util';
import process from 'node:process';
import { AuthType, createContentGenerator, createContentGeneratorConfig, } from '../core/contentGenerator.js';
import { PromptRegistry } from '../prompts/prompt-registry.js';
import { ResourceRegistry } from '../resources/resource-registry.js';
import { ToolRegistry } from '../tools/tool-registry.js';
import { LSTool } from '../tools/ls.js';
import { ReadFileTool } from '../tools/read-file.js';
import { GrepTool } from '../tools/grep.js';
import { canUseRipgrep, RipGrepTool } from '../tools/ripGrep.js';
import { GlobTool } from '../tools/glob.js';
import { ActivateSkillTool } from '../tools/activate-skill.js';
import { EditTool } from '../tools/edit.js';
import { ShellTool } from '../tools/shell.js';
import { WriteFileTool } from '../tools/write-file.js';
import { WebFetchTool } from '../tools/web-fetch.js';
import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js';
import { WebSearchTool } from '../tools/web-search.js';
import { AskUserTool } from '../tools/ask-user.js';
import { ExitPlanModeTool } from '../tools/exit-plan-mode.js';
import { EnterPlanModeTool } from '../tools/enter-plan-mode.js';
import { GeminiClient } from '../core/client.js';
import { BaseLlmClient } from '../core/baseLlmClient.js';
import { LocalLiteRtLmClient } from '../core/localLiteRtLmClient.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
import { GitService } from '../services/gitService.js';
import { initializeTelemetry, DEFAULT_TELEMETRY_TARGET, DEFAULT_OTLP_ENDPOINT, uiTelemetryService, } from '../telemetry/index.js';
import { coreEvents, CoreEvent } from '../utils/events.js';
import { tokenLimit } from '../core/tokenLimits.js';
import { DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_GEMINI_MODEL_AUTO, isAutoModel, isPreviewModel, PREVIEW_GEMINI_FLASH_MODEL, PREVIEW_GEMINI_MODEL, PREVIEW_GEMINI_MODEL_AUTO, resolveModel, } from './models.js';
import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
import { ideContextStore } from '../ide/ideContext.js';
import { WriteTodosTool } from '../tools/write-todos.js';
import { StandardFileSystemService } from '../services/fileSystemService.js';
import { logRipgrepFallback, logFlashFallback, logApprovalModeSwitch, logApprovalModeDuration, } from '../telemetry/loggers.js';
import { RipgrepFallbackEvent, FlashFallbackEvent, ApprovalModeSwitchEvent, ApprovalModeDurationEvent, } from '../telemetry/types.js';
import { ModelAvailabilityService } from '../availability/modelAvailabilityService.js';
import { ModelRouterService } from '../routing/modelRouterService.js';
import { OutputFormat } from '../output/types.js';
import { ModelConfigService } from '../services/modelConfigService.js';
import { DEFAULT_MODEL_CONFIGS } from './defaultModelConfigs.js';
import { ContextManager } from '../services/contextManager.js';
import { WorkspaceContext } from '../utils/workspaceContext.js';
import { Storage } from './storage.js';
import { FileExclusions } from '../utils/ignorePatterns.js';
import { MessageBus } from '../confirmation-bus/message-bus.js';
import { PolicyEngine } from '../policy/policy-engine.js';
import { ApprovalMode, } from '../policy/types.js';
import { HookSystem } from '../hooks/index.js';
import { getCodeAssistServer } from '../code_assist/codeAssist.js';
import { AgentRegistry } from '../agents/registry.js';
import { AcknowledgedAgentsService } from '../agents/acknowledgedAgents.js';
import { setGlobalProxy } from '../utils/fetch.js';
import { SubagentTool } from '../agents/subagent-tool.js';
import { getExperiments } from '../code_assist/experiments/experiments.js';
import { ExperimentFlags } from '../code_assist/experiments/flagNames.js';
import { debugLogger } from '../utils/debugLogger.js';
import { SkillManager } from '../skills/skillManager.js';
import { startupProfiler } from '../telemetry/startupProfiler.js';
import { fetchAdminControls } from '../code_assist/admin/admin_controls.js';
import { isSubpath, resolveToRealPath } from '../utils/paths.js';
import { UserHintService } from './userHintService.js';
import { WORKSPACE_POLICY_TIER } from '../policy/config.js';
import { loadPoliciesFromToml } from '../policy/toml-loader.js';
import { CheckerRunner } from '../safety/checker-runner.js';
import { ContextBuilder } from '../safety/context-builder.js';
import { CheckerRegistry } from '../safety/registry.js';
import { ConsecaSafetyChecker } from '../safety/conseca/conseca.js';
import { DEFAULT_MAX_ATTEMPTS } from '../utils/retry.js';
import { DEFAULT_FILE_FILTERING_OPTIONS, DEFAULT_MEMORY_FILE_FILTERING_OPTIONS, } from './constants.js';
import { DEFAULT_TOOL_PROTECTION_THRESHOLD, DEFAULT_MIN_PRUNABLE_TOKENS_THRESHOLD, DEFAULT_PROTECT_LATEST_TURN, } from '../services/toolOutputMaskingService.js';
import { SimpleExtensionLoader, } from '../utils/extensionLoader.js';
import { McpClientManager } from '../tools/mcp-client-manager.js';
import {} from '../tools/mcp-client.js';
import { getErrorMessage } from '../utils/errors.js';
export { DEFAULT_FILE_FILTERING_OPTIONS, DEFAULT_MEMORY_FILE_FILTERING_OPTIONS, };
export const DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD = 40_000;
export class MCPServerConfig {
    command;
    args;
    env;
    cwd;
    url;
    httpUrl;
    headers;
    tcp;
    type;
    timeout;
    trust;
    description;
    includeTools;
    excludeTools;
    extension;
    oauth;
    authProviderType;
    targetAudience;
    targetServiceAccount;
    constructor(
    // For stdio transport
    command, args, env, cwd, 
    // For sse transport
    url, 
    // For streamable http transport
    httpUrl, headers, 
    // For websocket transport
    tcp, 
    // Transport type (optional, for use with 'url' field)
    // When set to 'http', uses StreamableHTTPClientTransport
    // When set to 'sse', uses SSEClientTransport
    // When omitted, auto-detects transport type
    // Note: 'httpUrl' is deprecated in favor of 'url' + 'type'
    type, 
    // Common
    timeout, trust, 
    // Metadata
    description, includeTools, excludeTools, extension, 
    // OAuth configuration
    oauth, authProviderType, 
    // Service Account Configuration
    /* targetAudience format: CLIENT_ID.apps.googleusercontent.com */
    targetAudience, 
    /* targetServiceAccount format: <service-account-name>@<project-num>.iam.gserviceaccount.com */
    targetServiceAccount) {
        this.command = command;
        this.args = args;
        this.env = env;
        this.cwd = cwd;
        this.url = url;
        this.httpUrl = httpUrl;
        this.headers = headers;
        this.tcp = tcp;
        this.type = type;
        this.timeout = timeout;
        this.trust = trust;
        this.description = description;
        this.includeTools = includeTools;
        this.excludeTools = excludeTools;
        this.extension = extension;
        this.oauth = oauth;
        this.authProviderType = authProviderType;
        this.targetAudience = targetAudience;
        this.targetServiceAccount = targetServiceAccount;
    }
}
export var AuthProviderType;
(function (AuthProviderType) {
    AuthProviderType["DYNAMIC_DISCOVERY"] = "dynamic_discovery";
    AuthProviderType["GOOGLE_CREDENTIALS"] = "google_credentials";
    AuthProviderType["SERVICE_ACCOUNT_IMPERSONATION"] = "service_account_impersonation";
})(AuthProviderType || (AuthProviderType = {}));
export class Config {
    toolRegistry;
    mcpClientManager;
    allowedMcpServers;
    blockedMcpServers;
    allowedEnvironmentVariables;
    blockedEnvironmentVariables;
    enableEnvironmentVariableRedaction;
    promptRegistry;
    resourceRegistry;
    agentRegistry;
    acknowledgedAgentsService;
    skillManager;
    sessionId;
    clientVersion;
    fileSystemService;
    contentGeneratorConfig;
    contentGenerator;
    modelConfigService;
    embeddingModel;
    sandbox;
    targetDir;
    workspaceContext;
    debugMode;
    question;
    enableConseca;
    coreTools;
    /** @deprecated Use Policy Engine instead */
    allowedTools;
    /** @deprecated Use Policy Engine instead */
    excludeTools;
    toolDiscoveryCommand;
    toolCallCommand;
    mcpServerCommand;
    mcpEnabled;
    extensionsEnabled;
    mcpServers;
    mcpEnablementCallbacks;
    userMemory;
    geminiMdFileCount;
    geminiMdFilePaths;
    showMemoryUsage;
    accessibility;
    telemetrySettings;
    usageStatisticsEnabled;
    geminiClient;
    baseLlmClient;
    localLiteRtLmClient;
    modelRouterService;
    modelAvailabilityService;
    fileFiltering;
    fileDiscoveryService = null;
    gitService = undefined;
    checkpointing;
    proxy;
    cwd;
    bugCommand;
    model;
    disableLoopDetection;
    // null = unknown (quota not fetched); true = has access; false = definitively no access
    hasAccessToPreviewModel = null;
    noBrowser;
    folderTrust;
    ideMode;
    _activeModel;
    maxSessionTurns;
    listSessions;
    deleteSession;
    listExtensions;
    _extensionLoader;
    _enabledExtensions;
    enableExtensionReloading;
    fallbackModelHandler;
    validationHandler;
    quotaErrorOccurred = false;
    creditsNotificationShown = false;
    modelQuotas = new Map();
    lastRetrievedQuota;
    lastQuotaFetchTime = 0;
    lastEmittedQuotaRemaining;
    lastEmittedQuotaLimit;
    emitQuotaChangedEvent() {
        const pooled = this.getPooledQuota();
        if (this.lastEmittedQuotaRemaining !== pooled.remaining ||
            this.lastEmittedQuotaLimit !== pooled.limit) {
            this.lastEmittedQuotaRemaining = pooled.remaining;
            this.lastEmittedQuotaLimit = pooled.limit;
            coreEvents.emitQuotaChanged(pooled.remaining, pooled.limit, pooled.resetTime);
        }
    }
    summarizeToolOutput;
    acpMode = false;
    loadMemoryFromIncludeDirectories = false;
    includeDirectoryTree = true;
    importFormat;
    discoveryMaxDirs;
    compressionThreshold;
    /** Public for testing only */
    interactive;
    ptyInfo;
    trustedFolder;
    directWebFetch;
    useRipgrep;
    enableInteractiveShell;
    skipNextSpeakerCheck;
    useBackgroundColor;
    useAlternateBuffer;
    shellExecutionConfig;
    extensionManagement = true;
    truncateToolOutputThreshold;
    compressionTruncationCounter = 0;
    initialized = false;
    initPromise;
    mcpInitializationPromise = null;
    storage;
    fileExclusions;
    eventEmitter;
    useWriteTodos;
    messageBus;
    policyEngine;
    policyUpdateConfirmationRequest;
    outputSettings;
    gemmaModelRouter;
    continueOnFailedApiCall;
    retryFetchErrors;
    maxAttempts;
    enableShellOutputEfficiency;
    shellToolInactivityTimeout;
    fakeResponses;
    recordResponses;
    disableYoloMode;
    rawOutput;
    acceptRawOutputRisk;
    pendingIncludeDirectories;
    enableHooks;
    enableHooksUI;
    toolOutputMasking;
    hooks;
    projectHooks;
    disabledHooks;
    experiments;
    experimentsPromise;
    hookSystem;
    onModelChange;
    onReload;
    billing;
    enableAgents;
    agents;
    enableEventDrivenScheduler;
    skillsSupport;
    disabledSkills;
    adminSkillsEnabled;
    experimentalJitContext;
    disableLLMCorrection;
    planEnabled;
    planModeRoutingEnabled;
    modelSteering;
    contextManager;
    terminalBackground = undefined;
    remoteAdminSettings;
    latestApiRequest;
    lastModeSwitchTime = performance.now();
    userHintService;
    approvedPlanPath;
    constructor(params) {
        this.sessionId = params.sessionId;
        this.clientVersion = params.clientVersion ?? 'unknown';
        this.approvedPlanPath = undefined;
        this.embeddingModel =
            params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL;
        this.fileSystemService = new StandardFileSystemService();
        this.sandbox = params.sandbox;
        this.targetDir = path.resolve(params.targetDir);
        this.folderTrust = params.folderTrust ?? false;
        this.workspaceContext = new WorkspaceContext(this.targetDir, []);
        this.pendingIncludeDirectories = params.includeDirectories ?? [];
        this.debugMode = params.debugMode;
        this.question = params.question;
        this.coreTools = params.coreTools;
        this.allowedTools = params.allowedTools;
        this.excludeTools = params.excludeTools;
        this.toolDiscoveryCommand = params.toolDiscoveryCommand;
        this.toolCallCommand = params.toolCallCommand;
        this.mcpServerCommand = params.mcpServerCommand;
        this.mcpServers = params.mcpServers;
        this.mcpEnablementCallbacks = params.mcpEnablementCallbacks;
        this.mcpEnabled = params.mcpEnabled ?? true;
        this.extensionsEnabled = params.extensionsEnabled ?? true;
        this.allowedMcpServers = params.allowedMcpServers ?? [];
        this.blockedMcpServers = params.blockedMcpServers ?? [];
        this.allowedEnvironmentVariables = params.allowedEnvironmentVariables ?? [];
        this.blockedEnvironmentVariables = params.blockedEnvironmentVariables ?? [];
        this.enableEnvironmentVariableRedaction =
            params.enableEnvironmentVariableRedaction ?? false;
        this.userMemory = params.userMemory ?? '';
        this.geminiMdFileCount = params.geminiMdFileCount ?? 0;
        this.geminiMdFilePaths = params.geminiMdFilePaths ?? [];
        this.showMemoryUsage = params.showMemoryUsage ?? false;
        this.accessibility = params.accessibility ?? {};
        this.telemetrySettings = {
            enabled: params.telemetry?.enabled ?? false,
            target: params.telemetry?.target ?? DEFAULT_TELEMETRY_TARGET,
            otlpEndpoint: params.telemetry?.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT,
            otlpProtocol: params.telemetry?.otlpProtocol,
            logPrompts: params.telemetry?.logPrompts ?? true,
            outfile: params.telemetry?.outfile,
            useCollector: params.telemetry?.useCollector,
            useCliAuth: params.telemetry?.useCliAuth,
        };
        this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true;
        this.fileFiltering = {
            respectGitIgnore: params.fileFiltering?.respectGitIgnore ??
                DEFAULT_FILE_FILTERING_OPTIONS.respectGitIgnore,
            respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ??
                DEFAULT_FILE_FILTERING_OPTIONS.respectGeminiIgnore,
            enableRecursiveFileSearch: params.fileFiltering?.enableRecursiveFileSearch ?? true,
            enableFuzzySearch: params.fileFiltering?.enableFuzzySearch ?? true,
            maxFileCount: params.fileFiltering?.maxFileCount ??
                DEFAULT_FILE_FILTERING_OPTIONS.maxFileCount ??
                20000,
            searchTimeout: params.fileFiltering?.searchTimeout ??
                DEFAULT_FILE_FILTERING_OPTIONS.searchTimeout ??
                5000,
            customIgnoreFilePaths: params.fileFiltering?.customIgnoreFilePaths ?? [],
        };
        this.checkpointing = params.checkpointing ?? false;
        this.proxy = params.proxy;
        this.cwd = params.cwd ?? process.cwd();
        this.fileDiscoveryService = params.fileDiscoveryService ?? null;
        this.bugCommand = params.bugCommand;
        this.model = params.model;
        this.disableLoopDetection = params.disableLoopDetection ?? false;
        this._activeModel = params.model;
        this.enableAgents = params.enableAgents ?? false;
        this.agents = params.agents ?? {};
        this.disableLLMCorrection = params.disableLLMCorrection ?? true;
        this.planEnabled = params.plan ?? true;
        this.planModeRoutingEnabled = params.planSettings?.modelRouting ?? true;
        this.enableEventDrivenScheduler = params.enableEventDrivenScheduler ?? true;
        this.skillsSupport = params.skillsSupport ?? true;
        this.disabledSkills = params.disabledSkills ?? [];
        this.adminSkillsEnabled = params.adminSkillsEnabled ?? true;
        this.modelAvailabilityService = new ModelAvailabilityService();
        this.experimentalJitContext = params.experimentalJitContext ?? false;
        this.modelSteering = params.modelSteering ?? false;
        this.userHintService = new UserHintService(() => this.isModelSteeringEnabled());
        this.toolOutputMasking = {
            enabled: params.toolOutputMasking?.enabled ?? true,
            toolProtectionThreshold: params.toolOutputMasking?.toolProtectionThreshold ??
                DEFAULT_TOOL_PROTECTION_THRESHOLD,
            minPrunableTokensThreshold: params.toolOutputMasking?.minPrunableTokensThreshold ??
                DEFAULT_MIN_PRUNABLE_TOKENS_THRESHOLD,
            protectLatestTurn: params.toolOutputMasking?.protectLatestTurn ??
                DEFAULT_PROTECT_LATEST_TURN,
        };
        this.maxSessionTurns = params.maxSessionTurns ?? -1;
        this.acpMode = params.acpMode ?? false;
        this.listSessions = params.listSessions ?? false;
        this.deleteSession = params.deleteSession;
        this.listExtensions = params.listExtensions ?? false;
        this._extensionLoader =
            params.extensionLoader ?? new SimpleExtensionLoader([]);
        this._enabledExtensions = params.enabledExtensions ?? [];
        this.noBrowser = params.noBrowser ?? false;
        this.summarizeToolOutput = params.summarizeToolOutput;
        this.folderTrust = params.folderTrust ?? false;
        this.ideMode = params.ideMode ?? false;
        this.includeDirectoryTree = params.includeDirectoryTree ?? true;
        this.loadMemoryFromIncludeDirectories =
            params.loadMemoryFromIncludeDirectories ?? false;
        this.importFormat = params.importFormat ?? 'tree';
        this.discoveryMaxDirs = params.discoveryMaxDirs ?? 200;
        this.compressionThreshold = params.compressionThreshold;
        this.interactive = params.interactive ?? false;
        this.ptyInfo = params.ptyInfo ?? 'child_process';
        this.trustedFolder = params.trustedFolder;
        this.directWebFetch = params.directWebFetch ?? false;
        this.useRipgrep = params.useRipgrep ?? true;
        this.useBackgroundColor = params.useBackgroundColor ?? true;
        this.useAlternateBuffer = params.useAlternateBuffer ?? false;
        this.enableInteractiveShell = params.enableInteractiveShell ?? false;
        this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? true;
        this.shellExecutionConfig = {
            terminalWidth: params.shellExecutionConfig?.terminalWidth ?? 80,
            terminalHeight: params.shellExecutionConfig?.terminalHeight ?? 24,
            showColor: params.shellExecutionConfig?.showColor ?? false,
            pager: params.shellExecutionConfig?.pager ?? 'cat',
            sanitizationConfig: this.sanitizationConfig,
        };
        this.truncateToolOutputThreshold =
            params.truncateToolOutputThreshold ??
                DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD;
        // // TODO(joshualitt): Re-evaluate the todo tool for 3 family.
        this.useWriteTodos = isPreviewModel(this.model)
            ? false
            : (params.useWriteTodos ?? true);
        this.enableHooksUI = params.enableHooksUI ?? true;
        this.enableHooks = params.enableHooks ?? true;
        this.disabledHooks = params.disabledHooks ?? [];
        this.continueOnFailedApiCall = params.continueOnFailedApiCall ?? true;
        this.enableShellOutputEfficiency =
            params.enableShellOutputEfficiency ?? true;
        this.shellToolInactivityTimeout =
            (params.shellToolInactivityTimeout ?? 300) * 1000; // 5 minutes
        this.extensionManagement = params.extensionManagement ?? true;
        this.enableExtensionReloading = params.enableExtensionReloading ?? false;
        this.storage = new Storage(this.targetDir, this.sessionId);
        this.storage.setCustomPlansDir(params.planSettings?.directory);
        this.fakeResponses = params.fakeResponses;
        this.recordResponses = params.recordResponses;
        this.fileExclusions = new FileExclusions(this);
        this.eventEmitter = params.eventEmitter;
        this.enableConseca = params.enableConseca ?? false;
        // Initialize Safety Infrastructure
        const contextBuilder = new ContextBuilder(this);
        const checkersPath = this.targetDir;
        // The checkersPath  is used to resolve external checkers. Since we do not have any external checkers currently, it is set to the targetDir.
        const checkerRegistry = new CheckerRegistry(checkersPath);
        const checkerRunner = new CheckerRunner(contextBuilder, checkerRegistry, {
            checkersPath,
            timeout: 30000, // 30 seconds to allow for LLM-based checkers
        });
        this.policyUpdateConfirmationRequest =
            params.policyUpdateConfirmationRequest;
        this.policyEngine = new PolicyEngine({
            ...params.policyEngineConfig,
            approvalMode: params.approvalMode ?? params.policyEngineConfig?.approvalMode,
        }, checkerRunner);
        // Register Conseca if enabled
        if (this.enableConseca) {
            debugLogger.log('[SAFETY] Registering Conseca Safety Checker');
            ConsecaSafetyChecker.getInstance().setConfig(this);
        }
        this.messageBus = new MessageBus(this.policyEngine, this.debugMode);
        this.acknowledgedAgentsService = new AcknowledgedAgentsService();
        this.skillManager = new SkillManager();
        this.outputSettings = {
            format: params.output?.format ?? OutputFormat.TEXT,
        };
        this.gemmaModelRouter = {
            enabled: params.gemmaModelRouter?.enabled ?? false,
            classifier: {
                host: params.gemmaModelRouter?.classifier?.host ?? 'http://localhost:9379',
                model: params.gemmaModelRouter?.classifier?.model ?? 'gemma3-1b-gpu-custom',
            },
        };
        this.retryFetchErrors = params.retryFetchErrors ?? false;
        this.maxAttempts = Math.min(params.maxAttempts ?? DEFAULT_MAX_ATTEMPTS, DEFAULT_MAX_ATTEMPTS);
        this.disableYoloMode = params.disableYoloMode ?? false;
        this.rawOutput = params.rawOutput ?? false;
        this.acceptRawOutputRisk = params.acceptRawOutputRisk ?? false;
        if (params.hooks) {
            this.hooks = params.hooks;
        }
        if (params.projectHooks) {
            this.projectHooks = params.projectHooks;
        }
        this.experiments = params.experiments;
        this.onModelChange = params.onModelChange;
        this.onReload = params.onReload;
        this.billing = {
            overageStrategy: params.billing?.overageStrategy ?? 'ask',
        };
        if (params.contextFileName) {
            setGeminiMdFilename(params.contextFileName);
        }
        if (this.telemetrySettings.enabled) {
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
            initializeTelemetry(this);
        }
        const proxy = this.getProxy();
        if (proxy) {
            try {
                setGlobalProxy(proxy);
            }
            catch (error) {
                coreEvents.emitFeedback('error', 'Invalid proxy configuration detected. Check debug drawer for more details (F12)', error);
            }
        }
        this.geminiClient = new GeminiClient(this);
        this.modelRouterService = new ModelRouterService(this);
        // HACK: The settings loading logic doesn't currently merge the default
        // generation config with the user's settings. This means if a user provides
        // any `generation` settings (e.g., just `overrides`), the default `aliases`
        // are lost. This hack manually merges the default aliases back in if they
        // are missing from the user's config.
        // TODO(12593): Fix the settings loading logic to properly merge defaults and
        // remove this hack.
        let modelConfigServiceConfig = params.modelConfigServiceConfig;
        if (modelConfigServiceConfig) {
            if (!modelConfigServiceConfig.aliases) {
                modelConfigServiceConfig = {
                    ...modelConfigServiceConfig,
                    aliases: DEFAULT_MODEL_CONFIGS.aliases,
                };
            }
            if (!modelConfigServiceConfig.overrides) {
                modelConfigServiceConfig = {
                    ...modelConfigServiceConfig,
                    overrides: DEFAULT_MODEL_CONFIGS.overrides,
                };
            }
        }
        this.modelConfigService = new ModelConfigService(modelConfigServiceConfig ?? DEFAULT_MODEL_CONFIGS);
    }
    isInitialized() {
        return this.initialized;
    }
    /**
     * Dedups initialization requests using a shared promise that is only resolved
     * once.
     */
    async initialize() {
        if (this.initPromise) {
            return this.initPromise;
        }
        this.initPromise = this._initialize();
        return this.initPromise;
    }
    async _initialize() {
        await this.storage.initialize();
        // Add pending directories to workspace context
        for (const dir of this.pendingIncludeDirectories) {
            this.workspaceContext.addDirectory(dir);
        }
        // Add plans directory to workspace context for plan file storage
        if (this.planEnabled) {
            const plansDir = this.storage.getPlansDir();
            try {
                await fs.promises.access(plansDir);
                this.workspaceContext.addDirectory(plansDir);
            }
            catch {
                // Directory does not exist yet, so we don't add it to the workspace context.
                // It will be created when the first plan is written. Since custom plan
                // directories must be within the project root, they are automatically
                // covered by the project-wide file discovery once created.
            }
        }
        // Initialize centralized FileDiscoveryService
        const discoverToolsHandle = startupProfiler.start('discover_tools');
        this.getFileService();
        if (this.getCheckpointingEnabled()) {
            await this.getGitService();
        }
        this.promptRegistry = new PromptRegistry();
        this.resourceRegistry = new ResourceRegistry();
        this.agentRegistry = new AgentRegistry(this);
        await this.agentRegistry.initialize();
        coreEvents.on(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
        this.toolRegistry = await this.createToolRegistry();
        discoverToolsHandle?.end();
        this.mcpClientManager = new McpClientManager(this.clientVersion, this.toolRegistry, this, this.eventEmitter);
        // We do not await this promise so that the CLI can start up even if
        // MCP servers are slow to connect.
        this.mcpInitializationPromise = Promise.allSettled([
            this.mcpClientManager.startConfiguredMcpServers(),
            this.getExtensionLoader().start(this),
        ]).then((results) => {
            for (const result of results) {
                if (result.status === 'rejected') {
                    debugLogger.error('Error initializing MCP clients:', result.reason);
                }
            }
        });
        if (!this.interactive || this.acpMode) {
            await this.mcpInitializationPromise;
        }
        if (this.skillsSupport) {
            this.getSkillManager().setAdminSettings(this.adminSkillsEnabled);
            if (this.adminSkillsEnabled) {
                await this.getSkillManager().discoverSkills(this.storage, this.getExtensions(), this.isTrustedFolder());
                this.getSkillManager().setDisabledSkills(this.disabledSkills);
                // Re-register ActivateSkillTool to update its schema with the discovered enabled skill enums
                if (this.getSkillManager().getSkills().length > 0) {
                    this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
                    this.getToolRegistry().registerTool(new ActivateSkillTool(this, this.messageBus));
                }
            }
        }
        // Initialize hook system if enabled
        if (this.getEnableHooks()) {
            this.hookSystem = new HookSystem(this);
            await this.hookSystem.initialize();
        }
        if (this.experimentalJitContext) {
            this.contextManager = new ContextManager(this);
            await this.contextManager.refresh();
        }
        await this.geminiClient.initialize();
        this.initialized = true;
    }
    getContentGenerator() {
        return this.contentGenerator;
    }
    async refreshAuth(authMethod, apiKey, baseUrl, customHeaders) {
        // Reset availability service when switching auth
        this.modelAvailabilityService.reset();
        // Vertex and Genai have incompatible encryption and sending history with
        // thoughtSignature from Genai to Vertex will fail, we need to strip them
        if (this.contentGeneratorConfig?.authType === AuthType.USE_GEMINI &&
            authMethod !== AuthType.USE_GEMINI) {
            // Restore the conversation history to the new client
            this.geminiClient.stripThoughtsFromHistory();
        }
        // Reset availability status when switching auth (e.g. from limited key to OAuth)
        this.modelAvailabilityService.reset();
        // Clear stale authType to ensure getGemini31LaunchedSync doesn't return stale results
        // during the transition.
        if (this.contentGeneratorConfig) {
            this.contentGeneratorConfig.authType = undefined;
        }
        const newContentGeneratorConfig = await createContentGeneratorConfig(this, authMethod, apiKey, baseUrl, customHeaders);
        this.contentGenerator = await createContentGenerator(newContentGeneratorConfig, this, this.getSessionId());
        // Only assign to instance properties after successful initialization
        this.contentGeneratorConfig = newContentGeneratorConfig;
        // Initialize BaseLlmClient now that the ContentGenerator is available
        this.baseLlmClient = new BaseLlmClient(this.contentGenerator, this);
        const codeAssistServer = getCodeAssistServer(this);
        if (codeAssistServer?.projectId) {
            await this.refreshUserQuota();
        }
        this.experimentsPromise = getExperiments(codeAssistServer)
            .then((experiments) => {
            this.setExperiments(experiments);
        })
            .catch((e) => {
            debugLogger.error('Failed to fetch experiments', e);
        });
        const authType = this.contentGeneratorConfig.authType;
        if (authType === AuthType.USE_GEMINI ||
            authType === AuthType.USE_VERTEX_AI) {
            this.setHasAccessToPreviewModel(true);
        }
        // Only reset when we have explicit "no access" (hasAccessToPreviewModel === false).
        // When null (quota not fetched) or true, we preserve the saved model.
        if (isPreviewModel(this.model) && this.hasAccessToPreviewModel === false) {
            this.setModel(DEFAULT_GEMINI_MODEL_AUTO);
        }
        // Fetch admin controls
        await this.ensureExperimentsLoaded();
        const adminControlsEnabled = this.experiments?.flags[ExperimentFlags.ENABLE_ADMIN_CONTROLS]
            ?.boolValue ?? false;
        const adminControls = await fetchAdminControls(codeAssistServer, this.getRemoteAdminSettings(), adminControlsEnabled, (newSettings) => {
            this.setRemoteAdminSettings(newSettings);
            coreEvents.emitAdminSettingsChanged();
        });
        this.setRemoteAdminSettings(adminControls);
    }
    async getExperimentsAsync() {
        if (this.experiments) {
            return this.experiments;
        }
        const codeAssistServer = getCodeAssistServer(this);
        return getExperiments(codeAssistServer);
    }
    getUserTier() {
        return this.contentGenerator?.userTier;
    }
    getUserTierName() {
        return this.contentGenerator?.userTierName;
    }
    getUserPaidTier() {
        return this.contentGenerator?.paidTier;
    }
    /**
     * Provides access to the BaseLlmClient for stateless LLM operations.
     */
    getBaseLlmClient() {
        if (!this.baseLlmClient) {
            // Handle cases where initialization might be deferred or authentication failed
            if (this.contentGenerator) {
                this.baseLlmClient = new BaseLlmClient(this.getContentGenerator(), this);
            }
            else {
                throw new Error('BaseLlmClient not initialized. Ensure authentication has occurred and ContentGenerator is ready.');
            }
        }
        return this.baseLlmClient;
    }
    getLocalLiteRtLmClient() {
        if (!this.localLiteRtLmClient) {
            this.localLiteRtLmClient = new LocalLiteRtLmClient(this);
        }
        return this.localLiteRtLmClient;
    }
    getSessionId() {
        return this.sessionId;
    }
    setSessionId(sessionId) {
        this.sessionId = sessionId;
    }
    setTerminalBackground(terminalBackground) {
        this.terminalBackground = terminalBackground;
    }
    getTerminalBackground() {
        return this.terminalBackground;
    }
    getLatestApiRequest() {
        return this.latestApiRequest;
    }
    setLatestApiRequest(req) {
        this.latestApiRequest = req;
    }
    getRemoteAdminSettings() {
        return this.remoteAdminSettings;
    }
    setRemoteAdminSettings(settings) {
        this.remoteAdminSettings = settings;
    }
    shouldLoadMemoryFromIncludeDirectories() {
        return this.loadMemoryFromIncludeDirectories;
    }
    getIncludeDirectoryTree() {
        return this.includeDirectoryTree;
    }
    getImportFormat() {
        return this.importFormat;
    }
    getDiscoveryMaxDirs() {
        return this.discoveryMaxDirs;
    }
    getContentGeneratorConfig() {
        return this.contentGeneratorConfig;
    }
    getModel() {
        return this.model;
    }
    getDisableLoopDetection() {
        return this.disableLoopDetection ?? false;
    }
    setModel(newModel, isTemporary = true) {
        if (this.model !== newModel || this._activeModel !== newModel) {
            this.model = newModel;
            // When the user explicitly sets a model, that becomes the active model.
            this._activeModel = newModel;
            coreEvents.emitModelChanged(newModel);
            if (this.onModelChange && !isTemporary) {
                this.onModelChange(newModel);
            }
        }
        this.modelAvailabilityService.reset();
    }
    activateFallbackMode(model) {
        this.setModel(model, true);
        const authType = this.getContentGeneratorConfig()?.authType;
        if (authType) {
            logFlashFallback(this, new FlashFallbackEvent(authType));
        }
    }
    getActiveModel() {
        return this._activeModel ?? this.model;
    }
    setActiveModel(model) {
        if (this._activeModel !== model) {
            this._activeModel = model;
        }
    }
    setFallbackModelHandler(handler) {
        this.fallbackModelHandler = handler;
    }
    getFallbackModelHandler() {
        return this.fallbackModelHandler;
    }
    setValidationHandler(handler) {
        this.validationHandler = handler;
    }
    getValidationHandler() {
        return this.validationHandler;
    }
    resetTurn() {
        this.modelAvailabilityService.resetTurn();
    }
    /** Resets billing state (overageStrategy, creditsNotificationShown) once per user prompt. */
    resetBillingTurnState(overageStrategy) {
        this.creditsNotificationShown = false;
        this.billing.overageStrategy = overageStrategy ?? 'ask';
    }
    getMaxSessionTurns() {
        return this.maxSessionTurns;
    }
    setQuotaErrorOccurred(value) {
        this.quotaErrorOccurred = value;
    }
    getQuotaErrorOccurred() {
        return this.quotaErrorOccurred;
    }
    setCreditsNotificationShown(value) {
        this.creditsNotificationShown = value;
    }
    getCreditsNotificationShown() {
        return this.creditsNotificationShown;
    }
    setQuota(remaining, limit, modelId) {
        const activeModel = modelId ?? this.getActiveModel();
        if (remaining !== undefined && limit !== undefined) {
            const current = this.modelQuotas.get(activeModel);
            if (!current ||
                current.remaining !== remaining ||
                current.limit !== limit) {
                this.modelQuotas.set(activeModel, { remaining, limit });
                this.emitQuotaChangedEvent();
            }
        }
    }
    getPooledQuota() {
        const model = this.getModel();
        if (!isAutoModel(model)) {
            return {};
        }
        const isPreview = model === PREVIEW_GEMINI_MODEL_AUTO ||
            isPreviewModel(this.getActiveModel());
        const proModel = isPreview ? PREVIEW_GEMINI_MODEL : DEFAULT_GEMINI_MODEL;
        const flashModel = isPreview
            ? PREVIEW_GEMINI_FLASH_MODEL
            : DEFAULT_GEMINI_FLASH_MODEL;
        const proQuota = this.modelQuotas.get(proModel);
        const flashQuota = this.modelQuotas.get(flashModel);
        if (proQuota || flashQuota) {
            // For reset time, take the one that is furthest in the future (most conservative)
            const resetTime = [proQuota?.resetTime, flashQuota?.resetTime]
                .filter((t) => !!t)
                .sort()
                .reverse()[0];
            return {
                remaining: (proQuota?.remaining ?? 0) + (flashQuota?.remaining ?? 0),
                limit: (proQuota?.limit ?? 0) + (flashQuota?.limit ?? 0),
                resetTime,
            };
        }
        return {};
    }
    getQuotaRemaining() {
        const pooled = this.getPooledQuota();
        if (pooled.remaining !== undefined) {
            return pooled.remaining;
        }
        const primaryModel = resolveModel(this.getModel(), this.getGemini31LaunchedSync());
        return this.modelQuotas.get(primaryModel)?.remaining;
    }
    getQuotaLimit() {
        const pooled = this.getPooledQuota();
        if (pooled.limit !== undefined) {
            return pooled.limit;
        }
        const primaryModel = resolveModel(this.getModel(), this.getGemini31LaunchedSync());
        return this.modelQuotas.get(primaryModel)?.limit;
    }
    getQuotaResetTime() {
        const pooled = this.getPooledQuota();
        if (pooled.resetTime !== undefined) {
            return pooled.resetTime;
        }
        const primaryModel = resolveModel(this.getModel(), this.getGemini31LaunchedSync());
        return this.modelQuotas.get(primaryModel)?.resetTime;
    }
    getEmbeddingModel() {
        return this.embeddingModel;
    }
    getSandbox() {
        return this.sandbox;
    }
    isRestrictiveSandbox() {
        const sandboxConfig = this.getSandbox();
        const seatbeltProfile = process.env['SEATBELT_PROFILE'];
        return (!!sandboxConfig &&
            sandboxConfig.command === 'sandbox-exec' &&
            !!seatbeltProfile &&
            (seatbeltProfile.startsWith('restrictive-') ||
                seatbeltProfile.startsWith('strict-')));
    }
    getTargetDir() {
        return this.targetDir;
    }
    getProjectRoot() {
        return this.targetDir;
    }
    getWorkspaceContext() {
        return this.workspaceContext;
    }
    getAgentRegistry() {
        return this.agentRegistry;
    }
    getAcknowledgedAgentsService() {
        return this.acknowledgedAgentsService;
    }
    getToolRegistry() {
        return this.toolRegistry;
    }
    getPromptRegistry() {
        return this.promptRegistry;
    }
    getSkillManager() {
        return this.skillManager;
    }
    getResourceRegistry() {
        return this.resourceRegistry;
    }
    getDebugMode() {
        return this.debugMode;
    }
    getQuestion() {
        return this.question;
    }
    getHasAccessToPreviewModel() {
        return this.hasAccessToPreviewModel !== false;
    }
    setHasAccessToPreviewModel(hasAccess) {
        this.hasAccessToPreviewModel = hasAccess;
    }
    async refreshAvailableCredits() {
        const codeAssistServer = getCodeAssistServer(this);
        if (!codeAssistServer) {
            return;
        }
        try {
            await codeAssistServer.refreshAvailableCredits();
        }
        catch {
            // Non-fatal: proceed even if refresh fails.
            // The actual credit balance will be verified server-side.
        }
    }
    async refreshUserQuota() {
        const codeAssistServer = getCodeAssistServer(this);
        if (!codeAssistServer || !codeAssistServer.projectId) {
            return undefined;
        }
        try {
            const quota = await codeAssistServer.retrieveUserQuota({
                project: codeAssistServer.projectId,
            });
            if (quota.buckets) {
                this.lastRetrievedQuota = quota;
                this.lastQuotaFetchTime = Date.now();
                for (const bucket of quota.buckets) {
                    if (bucket.modelId &&
                        bucket.remainingAmount &&
                        bucket.remainingFraction != null) {
                        const remaining = parseInt(bucket.remainingAmount, 10);
                        const limit = bucket.remainingFraction > 0
                            ? Math.round(remaining / bucket.remainingFraction)
                            : (this.modelQuotas.get(bucket.modelId)?.limit ?? 0);
                        if (!isNaN(remaining) && Number.isFinite(limit) && limit > 0) {
                            this.modelQuotas.set(bucket.modelId, {
                                remaining,
                                limit,
                                resetTime: bucket.resetTime,
                            });
                        }
                    }
                }
                this.emitQuotaChangedEvent();
            }
            const hasAccess = quota.buckets?.some((b) => b.modelId && isPreviewModel(b.modelId)) ??
                false;
            this.setHasAccessToPreviewModel(hasAccess);
            return quota;
        }
        catch (e) {
            debugLogger.debug('Failed to retrieve user quota', e);
            return undefined;
        }
    }
    async refreshUserQuotaIfStale(staleMs = 30_000) {
        const now = Date.now();
        if (now - this.lastQuotaFetchTime > staleMs) {
            return this.refreshUserQuota();
        }
        return this.lastRetrievedQuota;
    }
    getLastRetrievedQuota() {
        return this.lastRetrievedQuota;
    }
    getRemainingQuotaForModel(modelId) {
        const bucket = this.lastRetrievedQuota?.buckets?.find((b) => b.modelId === modelId);
        if (!bucket)
            return undefined;
        return {
            remainingAmount: bucket.remainingAmount
                ? parseInt(bucket.remainingAmount, 10)
                : undefined,
            remainingFraction: bucket.remainingFraction,
            resetTime: bucket.resetTime,
        };
    }
    getCoreTools() {
        return this.coreTools;
    }
    getAllowedTools() {
        return this.allowedTools;
    }
    /**
     * All the excluded tools from static configuration, loaded extensions, or
     * other sources (like the Policy Engine).
     *
     * May change over time.
     */
    getExcludeTools(toolMetadata, allToolNames) {
        // Right now this is present for backward compatibility with settings.json exclude
        const excludeToolsSet = new Set([...(this.excludeTools ?? [])]);
        for (const extension of this.getExtensionLoader().getExtensions()) {
            if (!extension.isActive) {
                continue;
            }
            for (const tool of extension.excludeTools || []) {
                excludeToolsSet.add(tool);
            }
        }
        const policyExclusions = this.policyEngine.getExcludedTools(toolMetadata, allToolNames);
        for (const tool of policyExclusions) {
            excludeToolsSet.add(tool);
        }
        return excludeToolsSet;
    }
    getToolDiscoveryCommand() {
        return this.toolDiscoveryCommand;
    }
    getToolCallCommand() {
        return this.toolCallCommand;
    }
    getMcpServerCommand() {
        return this.mcpServerCommand;
    }
    /**
     * The user configured MCP servers (via gemini settings files).
     *
     * Does NOT include mcp servers configured by extensions.
     */
    getMcpServers() {
        return this.mcpServers;
    }
    getMcpEnabled() {
        return this.mcpEnabled;
    }
    getMcpEnablementCallbacks() {
        return this.mcpEnablementCallbacks;
    }
    getExtensionsEnabled() {
        return this.extensionsEnabled;
    }
    getMcpClientManager() {
        return this.mcpClientManager;
    }
    setUserInteractedWithMcp() {
        this.mcpClientManager?.setUserInteractedWithMcp();
    }
    /** @deprecated Use getMcpClientManager().getLastError() directly */
    getLastMcpError(serverName) {
        return this.mcpClientManager?.getLastError(serverName);
    }
    emitMcpDiagnostic(severity, message, error, serverName) {
        if (this.mcpClientManager) {
            this.mcpClientManager.emitDiagnostic(severity, message, error, serverName);
        }
        else {
            coreEvents.emitFeedback(severity, message, error);
        }
    }
    getAllowedMcpServers() {
        return this.allowedMcpServers;
    }
    getBlockedMcpServers() {
        return this.blockedMcpServers;
    }
    get sanitizationConfig() {
        return {
            allowedEnvironmentVariables: this.allowedEnvironmentVariables,
            blockedEnvironmentVariables: this.blockedEnvironmentVariables,
            enableEnvironmentVariableRedaction: this.enableEnvironmentVariableRedaction,
        };
    }
    setMcpServers(mcpServers) {
        this.mcpServers = mcpServers;
    }
    getUserMemory() {
        if (this.experimentalJitContext && this.contextManager) {
            return {
                global: this.contextManager.getGlobalMemory(),
                extension: this.contextManager.getExtensionMemory(),
                project: this.contextManager.getEnvironmentMemory(),
            };
        }
        return this.userMemory;
    }
    /**
     * Refreshes the MCP context, including memory, tools, and system instructions.
     */
    async refreshMcpContext() {
        if (this.experimentalJitContext && this.contextManager) {
            await this.contextManager.refresh();
        }
        else {
            const { refreshServerHierarchicalMemory } = await import('../utils/memoryDiscovery.js');
            await refreshServerHierarchicalMemory(this);
        }
        if (this.geminiClient?.isInitialized()) {
            await this.geminiClient.setTools();
            this.geminiClient.updateSystemInstruction();
        }
    }
    setUserMemory(newUserMemory) {
        this.userMemory = newUserMemory;
    }
    getGlobalMemory() {
        return this.contextManager?.getGlobalMemory() ?? '';
    }
    getEnvironmentMemory() {
        return this.contextManager?.getEnvironmentMemory() ?? '';
    }
    getContextManager() {
        return this.contextManager;
    }
    isJitContextEnabled() {
        return this.experimentalJitContext;
    }
    isModelSteeringEnabled() {
        return this.modelSteering;
    }
    getToolOutputMaskingEnabled() {
        return this.toolOutputMasking.enabled;
    }
    async getToolOutputMaskingConfig() {
        await this.ensureExperimentsLoaded();
        const remoteProtection = this.experiments?.flags[ExperimentFlags.MASKING_PROTECTION_THRESHOLD]
            ?.intValue;
        const remotePrunable = this.experiments?.flags[ExperimentFlags.MASKING_PRUNABLE_THRESHOLD]
            ?.intValue;
        const remoteProtectLatest = this.experiments?.flags[ExperimentFlags.MASKING_PROTECT_LATEST_TURN]
            ?.boolValue;
        const parsedProtection = remoteProtection
            ? parseInt(remoteProtection, 10)
            : undefined;
        const parsedPrunable = remotePrunable
            ? parseInt(remotePrunable, 10)
            : undefined;
        return {
            enabled: this.toolOutputMasking.enabled,
            toolProtectionThreshold: parsedProtection !== undefined && !isNaN(parsedProtection)
                ? parsedProtection
                : this.toolOutputMasking.toolProtectionThreshold,
            minPrunableTokensThreshold: parsedPrunable !== undefined && !isNaN(parsedPrunable)
                ? parsedPrunable
                : this.toolOutputMasking.minPrunableTokensThreshold,
            protectLatestTurn: remoteProtectLatest ?? this.toolOutputMasking.protectLatestTurn,
        };
    }
    getGeminiMdFileCount() {
        if (this.experimentalJitContext && this.contextManager) {
            return this.contextManager.getLoadedPaths().size;
        }
        return this.geminiMdFileCount;
    }
    setGeminiMdFileCount(count) {
        this.geminiMdFileCount = count;
    }
    getGeminiMdFilePaths() {
        if (this.experimentalJitContext && this.contextManager) {
            return Array.from(this.contextManager.getLoadedPaths());
        }
        return this.geminiMdFilePaths;
    }
    setGeminiMdFilePaths(paths) {
        this.geminiMdFilePaths = paths;
    }
    getApprovalMode() {
        return this.policyEngine.getApprovalMode();
    }
    getPolicyUpdateConfirmationRequest() {
        return this.policyUpdateConfirmationRequest;
    }
    /**
     * Hot-loads workspace policies from the specified directory into the active policy engine.
     * This allows applying newly accepted policies without requiring an application restart.
     *
     * @param policyDir The directory containing the workspace policy TOML files.
     */
    async loadWorkspacePolicies(policyDir) {
        const { rules, checkers } = await loadPoliciesFromToml([policyDir], () => WORKSPACE_POLICY_TIER);
        // Clear existing workspace policies to prevent duplicates/stale rules
        this.policyEngine.removeRulesByTier(WORKSPACE_POLICY_TIER);
        this.policyEngine.removeCheckersByTier(WORKSPACE_POLICY_TIER);
        for (const rule of rules) {
            this.policyEngine.addRule(rule);
        }
        for (const checker of checkers) {
            this.policyEngine.addChecker(checker);
        }
        this.policyUpdateConfirmationRequest = undefined;
        debugLogger.debug(`Workspace policies loaded from: ${policyDir}`);
    }
    setApprovalMode(mode) {
        if (!this.isTrustedFolder() && mode !== ApprovalMode.DEFAULT) {
            throw new Error('Cannot enable privileged approval modes in an untrusted folder.');
        }
        const currentMode = this.getApprovalMode();
        if (currentMode !== mode) {
            this.logCurrentModeDuration(currentMode);
            logApprovalModeSwitch(this, new ApprovalModeSwitchEvent(currentMode, mode));
        }
        this.policyEngine.setApprovalMode(mode);
        const isPlanModeTransition = currentMode !== mode &&
            (currentMode === ApprovalMode.PLAN || mode === ApprovalMode.PLAN);
        const isYoloModeTransition = currentMode !== mode &&
            (currentMode === ApprovalMode.YOLO || mode === ApprovalMode.YOLO);
        if (isPlanModeTransition || isYoloModeTransition) {
            if (this.geminiClient?.isInitialized()) {
                this.geminiClient.setTools().catch((err) => {
                    debugLogger.error('Failed to update tools', err);
                });
            }
            this.updateSystemInstructionIfInitialized();
        }
    }
    /**
     * Logs the duration of the current approval mode.
     */
    logCurrentModeDuration(mode) {
        const now = performance.now();
        const duration = now - this.lastModeSwitchTime;
        if (duration > 0) {
            logApprovalModeDuration(this, new ApprovalModeDurationEvent(mode, duration));
        }
        this.lastModeSwitchTime = now;
    }
    isYoloModeDisabled() {
        return this.disableYoloMode || !this.isTrustedFolder();
    }
    getRawOutput() {
        return this.rawOutput;
    }
    getAcceptRawOutputRisk() {
        return this.acceptRawOutputRisk;
    }
    getPendingIncludeDirectories() {
        return this.pendingIncludeDirectories;
    }
    clearPendingIncludeDirectories() {
        this.pendingIncludeDirectories = [];
    }
    getShowMemoryUsage() {
        return this.showMemoryUsage;
    }
    getAccessibility() {
        return this.accessibility;
    }
    getTelemetryEnabled() {
        return this.telemetrySettings.enabled ?? false;
    }
    getTelemetryLogPromptsEnabled() {
        return this.telemetrySettings.logPrompts ?? true;
    }
    getTelemetryOtlpEndpoint() {
        return this.telemetrySettings.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT;
    }
    getTelemetryOtlpProtocol() {
        return this.telemetrySettings.otlpProtocol ?? 'grpc';
    }
    getTelemetryTarget() {
        return this.telemetrySettings.target ?? DEFAULT_TELEMETRY_TARGET;
    }
    getTelemetryOutfile() {
        return this.telemetrySettings.outfile;
    }
    getBillingSettings() {
        return this.billing;
    }
    /**
     * Updates the overage strategy at runtime.
     * Used to switch from 'ask' to 'always' after the user accepts credits
     * via the overage dialog, so subsequent API calls auto-include credits.
     */
    setOverageStrategy(strategy) {
        this.billing.overageStrategy = strategy;
    }
    getTelemetryUseCollector() {
        return this.telemetrySettings.useCollector ?? false;
    }
    getTelemetryUseCliAuth() {
        return this.telemetrySettings.useCliAuth ?? false;
    }
    getGeminiClient() {
        return this.geminiClient;
    }
    /**
     * Updates the system instruction with the latest user memory.
     * Whenever the user memory (GEMINI.md files) is updated.
     */
    updateSystemInstructionIfInitialized() {
        const geminiClient = this.getGeminiClient();
        if (geminiClient?.isInitialized()) {
            geminiClient.updateSystemInstruction();
        }
    }
    getModelRouterService() {
        return this.modelRouterService;
    }
    getModelAvailabilityService() {
        return this.modelAvailabilityService;
    }
    getEnableRecursiveFileSearch() {
        return this.fileFiltering.enableRecursiveFileSearch;
    }
    getFileFilteringEnableFuzzySearch() {
        return this.fileFiltering.enableFuzzySearch;
    }
    getFileFilteringRespectGitIgnore() {
        return this.fileFiltering.respectGitIgnore;
    }
    getFileFilteringRespectGeminiIgnore() {
        return this.fileFiltering.respectGeminiIgnore;
    }
    getCustomIgnoreFilePaths() {
        return this.fileFiltering.customIgnoreFilePaths;
    }
    getFileFilteringOptions() {
        return {
            respectGitIgnore: this.fileFiltering.respectGitIgnore,
            respectGeminiIgnore: this.fileFiltering.respectGeminiIgnore,
            maxFileCount: this.fileFiltering.maxFileCount,
            searchTimeout: this.fileFiltering.searchTimeout,
            customIgnoreFilePaths: this.fileFiltering.customIgnoreFilePaths,
        };
    }
    /**
     * Gets custom file exclusion patterns from configuration.
     * TODO: This is a placeholder implementation. In the future, this could
     * read from settings files, CLI arguments, or environment variables.
     */
    getCustomExcludes() {
        // Placeholder implementation - returns empty array for now
        // Future implementation could read from:
        // - User settings file
        // - Project-specific configuration
        // - Environment variables
        // - CLI arguments
        return [];
    }
    getCheckpointingEnabled() {
        return this.checkpointing;
    }
    getProxy() {
        return this.proxy;
    }
    getWorkingDir() {
        return this.cwd;
    }
    getBugCommand() {
        return this.bugCommand;
    }
    getFileService() {
        if (!this.fileDiscoveryService) {
            this.fileDiscoveryService = new FileDiscoveryService(this.targetDir, {
                respectGitIgnore: this.fileFiltering.respectGitIgnore,
                respectGeminiIgnore: this.fileFiltering.respectGeminiIgnore,
                customIgnoreFilePaths: this.fileFiltering.customIgnoreFilePaths,
            });
        }
        return this.fileDiscoveryService;
    }
    getUsageStatisticsEnabled() {
        return this.usageStatisticsEnabled;
    }
    getAcpMode() {
        return this.acpMode;
    }
    async waitForMcpInit() {
        if (this.mcpInitializationPromise) {
            await this.mcpInitializationPromise;
        }
    }
    getListExtensions() {
        return this.listExtensions;
    }
    getListSessions() {
        return this.listSessions;
    }
    getDeleteSession() {
        return this.deleteSession;
    }
    getExtensionManagement() {
        return this.extensionManagement;
    }
    getExtensions() {
        return this._extensionLoader.getExtensions();
    }
    getExtensionLoader() {
        return this._extensionLoader;
    }
    // The list of explicitly enabled extensions, if any were given, may contain
    // the string "none".
    getEnabledExtensions() {
        return this._enabledExtensions;
    }
    getEnableExtensionReloading() {
        return this.enableExtensionReloading;
    }
    getDisableLLMCorrection() {
        return this.disableLLMCorrection;
    }
    isPlanEnabled() {
        return this.planEnabled;
    }
    getApprovedPlanPath() {
        return this.approvedPlanPath;
    }
    getDirectWebFetch() {
        return this.directWebFetch;
    }
    setApprovedPlanPath(path) {
        this.approvedPlanPath = path;
    }
    isAgentsEnabled() {
        return this.enableAgents;
    }
    isEventDrivenSchedulerEnabled() {
        return this.enableEventDrivenScheduler;
    }
    getNoBrowser() {
        return this.noBrowser;
    }
    getAgentsSettings() {
        return this.agents;
    }
    isBrowserLaunchSuppressed() {
        return this.getNoBrowser() || !shouldAttemptBrowserLaunch();
    }
    getSummarizeToolOutputConfig() {
        return this.summarizeToolOutput;
    }
    getIdeMode() {
        return this.ideMode;
    }
    /**
     * Returns 'true' if the folder trust feature is enabled.
     */
    getFolderTrust() {
        return this.folderTrust;
    }
    /**
     * Returns 'true' if the workspace is considered "trusted".
     * 'false' for untrusted.
     */
    isTrustedFolder() {
        const context = ideContextStore.get();
        if (context?.workspaceState?.isTrusted !== undefined) {
            return context.workspaceState.isTrusted;
        }
        // Default to untrusted if folder trust is enabled and no explicit value is set.
        return this.folderTrust ? (this.trustedFolder ?? false) : true;
    }
    setIdeMode(value) {
        this.ideMode = value;
    }
    /**
     * Get the current FileSystemService
     */
    getFileSystemService() {
        return this.fileSystemService;
    }
    /**
     * Checks if a given absolute path is allowed for file system operations.
     * A path is allowed if it's within the workspace context or the project's temporary directory.
     *
     * @param absolutePath The absolute path to check.
     * @returns true if the path is allowed, false otherwise.
     */
    isPathAllowed(absolutePath) {
        const resolvedPath = resolveToRealPath(absolutePath);
        const workspaceContext = this.getWorkspaceContext();
        if (workspaceContext.isPathWithinWorkspace(resolvedPath)) {
            return true;
        }
        const projectTempDir = this.storage.getProjectTempDir();
        const resolvedTempDir = resolveToRealPath(projectTempDir);
        return isSubpath(resolvedTempDir, resolvedPath);
    }
    /**
     * Validates if a path is allowed and returns a detailed error message if not.
     *
     * @param absolutePath The absolute path to validate.
     * @param checkType The type of access to check ('read' or 'write'). Defaults to 'write' for safety.
     * @returns An error message string if the path is disallowed, null otherwise.
     */
    validatePathAccess(absolutePath, checkType = 'write') {
        // For read operations, check read-only paths first
        if (checkType === 'read') {
            if (this.getWorkspaceContext().isPathReadable(absolutePath)) {
                return null;
            }
        }
        // Then check standard allowed paths (Workspace + Temp)
        // This covers 'write' checks and acts as a fallback/temp-dir check for 'read'
        if (this.isPathAllowed(absolutePath)) {
            return null;
        }
        const workspaceDirs = this.getWorkspaceContext().getDirectories();
        const projectTempDir = this.storage.getProjectTempDir();
        return `Path not in workspace: Attempted path "${absolutePath}" resolves outside the allowed workspace directories: ${workspaceDirs.join(', ')} or the project temp directory: ${projectTempDir}`;
    }
    /**
     * Set a custom FileSystemService
     */
    setFileSystemService(fileSystemService) {
        this.fileSystemService = fileSystemService;
    }
    async getCompressionThreshold() {
        if (this.compressionThreshold) {
            return this.compressionThreshold;
        }
        await this.ensureExperimentsLoaded();
        const remoteThreshold = this.experiments?.flags[ExperimentFlags.CONTEXT_COMPRESSION_THRESHOLD]
            ?.floatValue;
        if (remoteThreshold === 0) {
            return undefined;
        }
        return remoteThreshold;
    }
    async getUserCaching() {
        await this.ensureExperimentsLoaded();
        return this.experiments?.flags[ExperimentFlags.USER_CACHING]?.boolValue;
    }
    async getPlanModeRoutingEnabled() {
        return this.planModeRoutingEnabled;
    }
    async getNumericalRoutingEnabled() {
        await this.ensureExperimentsLoaded();
        return !!this.experiments?.flags[ExperimentFlags.ENABLE_NUMERICAL_ROUTING]
            ?.boolValue;
    }
    async getClassifierThreshold() {
        await this.ensureExperimentsLoaded();
        const flag = this.experiments?.flags[ExperimentFlags.CLASSIFIER_THRESHOLD];
        if (flag?.intValue !== undefined) {
            return parseInt(flag.intValue, 10);
        }
        return flag?.floatValue;
    }
    async getBannerTextNoCapacityIssues() {
        await this.ensureExperimentsLoaded();
        return (this.experiments?.flags[ExperimentFlags.BANNER_TEXT_NO_CAPACITY_ISSUES]
            ?.stringValue ?? '');
    }
    async getBannerTextCapacityIssues() {
        await this.ensureExperimentsLoaded();
        return (this.experiments?.flags[ExperimentFlags.BANNER_TEXT_CAPACITY_ISSUES]
            ?.stringValue ?? '');
    }
    /**
     * Returns whether Gemini 3.1 has been launched.
     * This method is async and ensures that experiments are loaded before returning the result.
     */
    async getGemini31Launched() {
        await this.ensureExperimentsLoaded();
        return this.getGemini31LaunchedSync();
    }
    /**
     * Returns whether Gemini 3.1 has been launched.
     *
     * Note: This method should only be called after startup, once experiments have been loaded.
     * If you need to call this during startup or from an async context, use
     * getGemini31Launched instead.
     */
    getGemini31LaunchedSync() {
        const authType = this.contentGeneratorConfig?.authType;
        if (authType === AuthType.USE_GEMINI ||
            authType === AuthType.USE_VERTEX_AI) {
            return true;
        }
        return (this.experiments?.flags[ExperimentFlags.GEMINI_3_1_PRO_LAUNCHED]
            ?.boolValue ?? false);
    }
    async ensureExperimentsLoaded() {
        if (!this.experimentsPromise) {
            return;
        }
        try {
            await this.experimentsPromise;
        }
        catch (e) {
            debugLogger.debug('Failed to fetch experiments', e);
        }
    }
    isInteractiveShellEnabled() {
        return (this.interactive &&
            this.ptyInfo !== 'child_process' &&
            this.enableInteractiveShell);
    }
    isSkillsSupportEnabled() {
        return this.skillsSupport;
    }
    /**
     * Reloads skills by re-discovering them from extensions and local directories.
     */
    async reloadSkills() {
        if (!this.skillsSupport) {
            return;
        }
        if (this.onReload) {
            const refreshed = await this.onReload();
            this.disabledSkills = refreshed.disabledSkills ?? [];
            this.getSkillManager().setAdminSettings(refreshed.adminSkillsEnabled ?? this.adminSkillsEnabled);
        }
        if (this.getSkillManager().isAdminEnabled()) {
            await this.getSkillManager().discoverSkills(this.storage, this.getExtensions(), this.isTrustedFolder());
            this.getSkillManager().setDisabledSkills(this.disabledSkills);
            // Re-register ActivateSkillTool to update its schema with the newly discovered skills
            if (this.getSkillManager().getSkills().length > 0) {
                this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
                this.getToolRegistry().registerTool(new ActivateSkillTool(this, this.messageBus));
            }
            else {
                this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
            }
        }
        else {
            this.getSkillManager().clearSkills();
            this.getToolRegistry().unregisterTool(ActivateSkillTool.Name);
        }
        // Notify the client that system instructions might need updating
        this.updateSystemInstructionIfInitialized();
    }
    /**
     * Reloads agent settings.
     */
    async reloadAgents() {
        if (this.onReload) {
            const refreshed = await this.onReload();
            if (refreshed.agents) {
                this.agents = refreshed.agents;
            }
        }
    }
    isInteractive() {
        return this.interactive;
    }
    getUseRipgrep() {
        return this.useRipgrep;
    }
    getUseBackgroundColor() {
        return this.useBackgroundColor;
    }
    getUseAlternateBuffer() {
        return this.useAlternateBuffer;
    }
    getEnableInteractiveShell() {
        return this.enableInteractiveShell;
    }
    getSkipNextSpeakerCheck() {
        return this.skipNextSpeakerCheck;
    }
    getContinueOnFailedApiCall() {
        return this.continueOnFailedApiCall;
    }
    getRetryFetchErrors() {
        return this.retryFetchErrors;
    }
    getMaxAttempts() {
        return this.maxAttempts;
    }
    getEnableShellOutputEfficiency() {
        return this.enableShellOutputEfficiency;
    }
    getShellToolInactivityTimeout() {
        return this.shellToolInactivityTimeout;
    }
    getShellExecutionConfig() {
        return this.shellExecutionConfig;
    }
    setShellExecutionConfig(config) {
        this.shellExecutionConfig = {
            terminalWidth: config.terminalWidth ?? this.shellExecutionConfig.terminalWidth,
            terminalHeight: config.terminalHeight ?? this.shellExecutionConfig.terminalHeight,
            showColor: config.showColor ?? this.shellExecutionConfig.showColor,
            pager: config.pager ?? this.shellExecutionConfig.pager,
            sanitizationConfig: config.sanitizationConfig ??
                this.shellExecutionConfig.sanitizationConfig,
        };
    }
    getScreenReader() {
        return this.accessibility.screenReader ?? false;
    }
    getTruncateToolOutputThreshold() {
        return Math.min(
        // Estimate remaining context window in characters (1 token ~= 4 chars).
        4 *
            (tokenLimit(this.model) - uiTelemetryService.getLastPromptTokenCount()), this.truncateToolOutputThreshold);
    }
    getNextCompressionTruncationId() {
        return ++this.compressionTruncationCounter;
    }
    getUseWriteTodos() {
        return this.useWriteTodos;
    }
    getOutputFormat() {
        return this.outputSettings?.format
            ? this.outputSettings.format
            : OutputFormat.TEXT;
    }
    async getGitService() {
        if (!this.gitService) {
            this.gitService = new GitService(this.targetDir, this.storage);
            await this.gitService.initialize();
        }
        return this.gitService;
    }
    getFileExclusions() {
        return this.fileExclusions;
    }
    getMessageBus() {
        return this.messageBus;
    }
    getPolicyEngine() {
        return this.policyEngine;
    }
    getEnableHooks() {
        return this.enableHooks;
    }
    getEnableHooksUI() {
        return this.enableHooksUI;
    }
    getGemmaModelRouterEnabled() {
        return this.gemmaModelRouter.enabled ?? false;
    }
    getGemmaModelRouterSettings() {
        return this.gemmaModelRouter;
    }
    /**
     * Get override settings for a specific agent.
     * Reads from agents.overrides.<agentName>.
     */
    getAgentOverride(agentName) {
        return this.getAgentsSettings()?.overrides?.[agentName];
    }
    /**
     * Get browser agent configuration.
     * Combines generic AgentOverride fields with browser-specific customConfig.
     * This is the canonical way to access browser agent settings.
     */
    getBrowserAgentConfig() {
        const override = this.getAgentOverride('browser_agent');
        const customConfig = this.getAgentsSettings()?.browser ?? {};
        return {
            enabled: override?.enabled ?? false,
            model: override?.modelConfig?.model,
            customConfig: {
                sessionMode: customConfig.sessionMode ?? 'persistent',
                headless: customConfig.headless ?? false,
                profilePath: customConfig.profilePath,
                visualModel: customConfig.visualModel,
            },
        };
    }
    async createToolRegistry() {
        const registry = new ToolRegistry(this, this.messageBus);
        // helper to create & register core tools that are enabled
        const maybeRegister = (toolClass, registerFn) => {
            const className = toolClass.name;
            const toolName = toolClass.Name || className;
            const coreTools = this.getCoreTools();
            // On some platforms, the className can be minified to _ClassName.
            const normalizedClassName = className.replace(/^_+/, '');
            let isEnabled = true; // Enabled by default if coreTools is not set.
            if (coreTools) {
                isEnabled = coreTools.some((tool) => tool === toolName ||
                    tool === normalizedClassName ||
                    tool.startsWith(`${toolName}(`) ||
                    tool.startsWith(`${normalizedClassName}(`));
            }
            if (isEnabled) {
                registerFn();
            }
        };
        maybeRegister(LSTool, () => registry.registerTool(new LSTool(this, this.messageBus)));
        maybeRegister(ReadFileTool, () => registry.registerTool(new ReadFileTool(this, this.messageBus)));
        if (this.getUseRipgrep()) {
            let useRipgrep = false;
            let errorString = undefined;
            try {
                useRipgrep = await canUseRipgrep();
            }
            catch (error) {
                errorString = String(error);
            }
            if (useRipgrep) {
                maybeRegister(RipGrepTool, () => registry.registerTool(new RipGrepTool(this, this.messageBus)));
            }
            else {
                logRipgrepFallback(this, new RipgrepFallbackEvent(errorString));
                maybeRegister(GrepTool, () => registry.registerTool(new GrepTool(this, this.messageBus)));
            }
        }
        else {
            maybeRegister(GrepTool, () => registry.registerTool(new GrepTool(this, this.messageBus)));
        }
        maybeRegister(GlobTool, () => registry.registerTool(new GlobTool(this, this.messageBus)));
        maybeRegister(ActivateSkillTool, () => registry.registerTool(new ActivateSkillTool(this, this.messageBus)));
        maybeRegister(EditTool, () => registry.registerTool(new EditTool(this, this.messageBus)));
        maybeRegister(WriteFileTool, () => registry.registerTool(new WriteFileTool(this, this.messageBus)));
        maybeRegister(WebFetchTool, () => registry.registerTool(new WebFetchTool(this, this.messageBus)));
        maybeRegister(ShellTool, () => registry.registerTool(new ShellTool(this, this.messageBus)));
        maybeRegister(MemoryTool, () => registry.registerTool(new MemoryTool(this.messageBus)));
        maybeRegister(WebSearchTool, () => registry.registerTool(new WebSearchTool(this, this.messageBus)));
        maybeRegister(AskUserTool, () => registry.registerTool(new AskUserTool(this.messageBus)));
        if (this.getUseWriteTodos()) {
            maybeRegister(WriteTodosTool, () => registry.registerTool(new WriteTodosTool(this.messageBus)));
        }
        if (this.isPlanEnabled()) {
            maybeRegister(ExitPlanModeTool, () => registry.registerTool(new ExitPlanModeTool(this, this.messageBus)));
            maybeRegister(EnterPlanModeTool, () => registry.registerTool(new EnterPlanModeTool(this, this.messageBus)));
        }
        // Register Subagents as Tools
        this.registerSubAgentTools(registry);
        await registry.discoverAllTools();
        registry.sortTools();
        return registry;
    }
    /**
     * Registers SubAgentTools for all available agents.
     */
    registerSubAgentTools(registry) {
        const agentsOverrides = this.getAgentsSettings().overrides ?? {};
        if (this.isAgentsEnabled() ||
            agentsOverrides['codebase_investigator']?.enabled !== false ||
            agentsOverrides['cli_help']?.enabled !== false) {
            const definitions = this.agentRegistry.getAllDefinitions();
            for (const definition of definitions) {
                try {
                    const tool = new SubagentTool(definition, this, this.getMessageBus());
                    registry.registerTool(tool);
                }
                catch (e) {
                    debugLogger.warn(`Failed to register tool for agent ${definition.name}: ${getErrorMessage(e)}`);
                }
            }
        }
    }
    /**
     * Get the hook system instance
     */
    getHookSystem() {
        return this.hookSystem;
    }
    /**
     * Get hooks configuration
     */
    getHooks() {
        return this.hooks;
    }
    /**
     * Get project-specific hooks configuration
     */
    getProjectHooks() {
        return this.projectHooks;
    }
    /**
     * Update the list of disabled hooks dynamically.
     * This is used to keep the running system in sync with settings changes
     * without risk of loading new hook definitions into memory.
     */
    updateDisabledHooks(disabledHooks) {
        this.disabledHooks = disabledHooks;
    }
    /**
     * Get disabled hooks list
     */
    getDisabledHooks() {
        return this.disabledHooks;
    }
    /**
     * Get experiments configuration
     */
    getExperiments() {
        return this.experiments;
    }
    /**
     * Set experiments configuration
     */
    setExperiments(experiments) {
        this.experiments = experiments;
        const flagSummaries = Object.entries(experiments.flags ?? {})
            .sort(([a], [b]) => a.localeCompare(b))
            .map(([flagId, flag]) => {
            const summary = { flagId };
            if (flag.boolValue !== undefined) {
                summary['boolValue'] = flag.boolValue;
            }
            if (flag.floatValue !== undefined) {
                summary['floatValue'] = flag.floatValue;
            }
            if (flag.intValue !== undefined) {
                summary['intValue'] = flag.intValue;
            }
            if (flag.stringValue !== undefined) {
                summary['stringValue'] = flag.stringValue;
            }
            const int32Length = flag.int32ListValue?.values?.length ?? 0;
            if (int32Length > 0) {
                summary['int32ListLength'] = int32Length;
            }
            const stringListLength = flag.stringListValue?.values?.length ?? 0;
            if (stringListLength > 0) {
                summary['stringListLength'] = stringListLength;
            }
            return summary;
        });
        const summary = {
            experimentIds: experiments.experimentIds ?? [],
            flags: flagSummaries,
        };
        const summaryString = inspect(summary, {
            depth: null,
            maxArrayLength: null,
            maxStringLength: null,
            breakLength: 80,
            compact: false,
        });
        debugLogger.debug('Experiments loaded', summaryString);
    }
    onAgentsRefreshed = async () => {
        if (this.toolRegistry) {
            this.registerSubAgentTools(this.toolRegistry);
        }
        // Propagate updates to the active chat session
        const client = this.getGeminiClient();
        if (client?.isInitialized()) {
            await client.setTools();
            client.updateSystemInstruction();
        }
        else {
            debugLogger.debug('[Config] GeminiClient not initialized; skipping live prompt/tool refresh.');
        }
    };
    /**
     * Disposes of resources and removes event listeners.
     */
    async dispose() {
        this.logCurrentModeDuration(this.getApprovalMode());
        coreEvents.off(CoreEvent.AgentsRefreshed, this.onAgentsRefreshed);
        this.agentRegistry?.dispose();
        this.geminiClient?.dispose();
        if (this.mcpClientManager) {
            await this.mcpClientManager.stop();
        }
    }
}
// Export model constants for use in CLI
export { DEFAULT_GEMINI_FLASH_MODEL };
//# sourceMappingURL=config.js.map