/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as path from 'node:path'; import process from 'node:process'; import { AuthType, ContentGeneratorConfig, createContentGeneratorConfig, } from '../core/contentGenerator.js'; import { PromptRegistry } from '../prompts/prompt-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 { GlobTool } from '../tools/glob.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 { ReadManyFilesTool } from '../tools/read-many-files.js'; import { MemoryTool, setGeminiMdFilename } from '../tools/memoryTool.js'; import { WebSearchTool } from '../tools/web-search.js'; import { GeminiClient } from '../core/client.js'; import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; import { GitService } from '../services/gitService.js'; import { initializeTelemetry, DEFAULT_TELEMETRY_TARGET, DEFAULT_OTLP_ENDPOINT, TelemetryTarget, StartSessionEvent, } from '../telemetry/index.js'; import { DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_GEMINI_FLASH_MODEL, } from './models.js'; import { shouldAttemptBrowserLaunch } from '../utils/browser.js'; import { MCPOAuthConfig } from '../mcp/oauth-provider.js'; import { IdeClient } from '../ide/ide-client.js'; import type { Content } from '@google/genai'; import { FileSystemService, StandardFileSystemService, } from '../services/fileSystemService.js'; import { logCliConfiguration, logIdeConnection } from '../telemetry/loggers.js'; import { IdeConnectionEvent, IdeConnectionType } from '../telemetry/types.js'; // Re-export OAuth config type export type { MCPOAuthConfig }; import { WorkspaceContext } from '../utils/workspaceContext.js'; import { Storage } from './storage.js'; export enum ApprovalMode { DEFAULT = 'default', AUTO_EDIT = 'autoEdit', YOLO = 'yolo', } export interface AccessibilitySettings { disableLoadingPhrases?: boolean; screenReader?: boolean; } export interface BugCommandSettings { urlTemplate: string; } export interface ChatCompressionSettings { contextPercentageThreshold?: number; } export interface SummarizeToolOutputSettings { tokenBudget?: number; } export interface TelemetrySettings { enabled?: boolean; target?: TelemetryTarget; otlpEndpoint?: string; otlpProtocol?: 'grpc' | 'http'; logPrompts?: boolean; outfile?: string; } export interface GeminiCLIExtension { name: string; version: string; isActive: boolean; path: string; } export interface FileFilteringOptions { respectGitIgnore: boolean; respectGeminiIgnore: boolean; } // For memory files export const DEFAULT_MEMORY_FILE_FILTERING_OPTIONS: FileFilteringOptions = { respectGitIgnore: false, respectGeminiIgnore: true, }; // For all other files export const DEFAULT_FILE_FILTERING_OPTIONS: FileFilteringOptions = { respectGitIgnore: true, respectGeminiIgnore: true, }; export class MCPServerConfig { constructor( // For stdio transport readonly command?: string, readonly args?: string[], readonly env?: Record, readonly cwd?: string, // For sse transport readonly url?: string, // For streamable http transport readonly httpUrl?: string, readonly headers?: Record, // For websocket transport readonly tcp?: string, // Common readonly timeout?: number, readonly trust?: boolean, // Metadata readonly description?: string, readonly includeTools?: string[], readonly excludeTools?: string[], readonly extensionName?: string, // OAuth configuration readonly oauth?: MCPOAuthConfig, readonly authProviderType?: AuthProviderType, ) {} } export enum AuthProviderType { DYNAMIC_DISCOVERY = 'dynamic_discovery', GOOGLE_CREDENTIALS = 'google_credentials', } export interface SandboxConfig { command: 'docker' | 'podman' | 'sandbox-exec'; image: string; } export type FlashFallbackHandler = ( currentModel: string, fallbackModel: string, error?: unknown, ) => Promise; export interface ConfigParameters { sessionId: string; embeddingModel?: string; sandbox?: SandboxConfig; targetDir: string; debugMode: boolean; question?: string; fullContext?: boolean; coreTools?: string[]; excludeTools?: string[]; toolDiscoveryCommand?: string; toolCallCommand?: string; mcpServerCommand?: string; mcpServers?: Record; userMemory?: string; geminiMdFileCount?: number; approvalMode?: ApprovalMode; showMemoryUsage?: boolean; contextFileName?: string | string[]; accessibility?: AccessibilitySettings; telemetry?: TelemetrySettings; usageStatisticsEnabled?: boolean; fileFiltering?: { respectGitIgnore?: boolean; respectGeminiIgnore?: boolean; enableRecursiveFileSearch?: boolean; }; checkpointing?: boolean; proxy?: string; cwd: string; fileDiscoveryService?: FileDiscoveryService; includeDirectories?: string[]; bugCommand?: BugCommandSettings; model: string; extensionContextFilePaths?: string[]; maxSessionTurns?: number; experimentalZedIntegration?: boolean; listExtensions?: boolean; extensions?: GeminiCLIExtension[]; blockedMcpServers?: Array<{ name: string; extensionName: string }>; noBrowser?: boolean; summarizeToolOutput?: Record; folderTrustFeature?: boolean; folderTrust?: boolean; ideMode?: boolean; loadMemoryFromIncludeDirectories?: boolean; chatCompression?: ChatCompressionSettings; interactive?: boolean; trustedFolder?: boolean; shouldUseNodePtyShell?: boolean; skipNextSpeakerCheck?: boolean; enablePromptCompletion?: boolean; } export class Config { private toolRegistry!: ToolRegistry; private promptRegistry!: PromptRegistry; private sessionId: string; private fileSystemService: FileSystemService; private contentGeneratorConfig!: ContentGeneratorConfig; private readonly embeddingModel: string; private readonly sandbox: SandboxConfig | undefined; private readonly targetDir: string; private workspaceContext: WorkspaceContext; private readonly debugMode: boolean; private readonly question: string | undefined; private readonly fullContext: boolean; private readonly coreTools: string[] | undefined; private readonly excludeTools: string[] | undefined; private readonly toolDiscoveryCommand: string | undefined; private readonly toolCallCommand: string | undefined; private readonly mcpServerCommand: string | undefined; private readonly mcpServers: Record | undefined; private userMemory: string; private geminiMdFileCount: number; private approvalMode: ApprovalMode; private readonly showMemoryUsage: boolean; private readonly accessibility: AccessibilitySettings; private readonly telemetrySettings: TelemetrySettings; private readonly usageStatisticsEnabled: boolean; private geminiClient!: GeminiClient; private readonly fileFiltering: { respectGitIgnore: boolean; respectGeminiIgnore: boolean; enableRecursiveFileSearch: boolean; }; private fileDiscoveryService: FileDiscoveryService | null = null; private gitService: GitService | undefined = undefined; private readonly checkpointing: boolean; private readonly proxy: string | undefined; private readonly cwd: string; private readonly bugCommand: BugCommandSettings | undefined; private readonly model: string; private readonly extensionContextFilePaths: string[]; private readonly noBrowser: boolean; private readonly folderTrustFeature: boolean; private readonly folderTrust: boolean; private ideMode: boolean; private ideClient: IdeClient; private inFallbackMode = false; private readonly maxSessionTurns: number; private readonly listExtensions: boolean; private readonly _extensions: GeminiCLIExtension[]; private readonly _blockedMcpServers: Array<{ name: string; extensionName: string; }>; flashFallbackHandler?: FlashFallbackHandler; private quotaErrorOccurred: boolean = false; private readonly summarizeToolOutput: | Record | undefined; private readonly experimentalZedIntegration: boolean = false; private readonly loadMemoryFromIncludeDirectories: boolean = false; private readonly chatCompression: ChatCompressionSettings | undefined; private readonly interactive: boolean; private readonly trustedFolder: boolean | undefined; private readonly shouldUseNodePtyShell: boolean; private readonly skipNextSpeakerCheck: boolean; private readonly enablePromptCompletion: boolean = false; private initialized: boolean = false; readonly storage: Storage; constructor(params: ConfigParameters) { this.sessionId = params.sessionId; this.embeddingModel = params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL; this.fileSystemService = new StandardFileSystemService(); this.sandbox = params.sandbox; this.targetDir = path.resolve(params.targetDir); this.workspaceContext = new WorkspaceContext( this.targetDir, params.includeDirectories ?? [], ); this.debugMode = params.debugMode; this.question = params.question; this.fullContext = params.fullContext ?? false; this.coreTools = params.coreTools; this.excludeTools = params.excludeTools; this.toolDiscoveryCommand = params.toolDiscoveryCommand; this.toolCallCommand = params.toolCallCommand; this.mcpServerCommand = params.mcpServerCommand; this.mcpServers = params.mcpServers; this.userMemory = params.userMemory ?? ''; this.geminiMdFileCount = params.geminiMdFileCount ?? 0; this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT; 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, }; this.usageStatisticsEnabled = params.usageStatisticsEnabled ?? true; this.fileFiltering = { respectGitIgnore: params.fileFiltering?.respectGitIgnore ?? true, respectGeminiIgnore: params.fileFiltering?.respectGeminiIgnore ?? true, enableRecursiveFileSearch: params.fileFiltering?.enableRecursiveFileSearch ?? true, }; 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.extensionContextFilePaths = params.extensionContextFilePaths ?? []; this.maxSessionTurns = params.maxSessionTurns ?? -1; this.experimentalZedIntegration = params.experimentalZedIntegration ?? false; this.listExtensions = params.listExtensions ?? false; this._extensions = params.extensions ?? []; this._blockedMcpServers = params.blockedMcpServers ?? []; this.noBrowser = params.noBrowser ?? false; this.summarizeToolOutput = params.summarizeToolOutput; this.folderTrustFeature = params.folderTrustFeature ?? false; this.folderTrust = params.folderTrust ?? false; this.ideMode = params.ideMode ?? false; this.ideClient = IdeClient.getInstance(); this.loadMemoryFromIncludeDirectories = params.loadMemoryFromIncludeDirectories ?? false; this.chatCompression = params.chatCompression; this.interactive = params.interactive ?? false; this.trustedFolder = params.trustedFolder; this.shouldUseNodePtyShell = params.shouldUseNodePtyShell ?? false; this.skipNextSpeakerCheck = params.skipNextSpeakerCheck ?? false; this.storage = new Storage(this.targetDir); this.enablePromptCompletion = params.enablePromptCompletion ?? false; if (params.contextFileName) { setGeminiMdFilename(params.contextFileName); } if (this.telemetrySettings.enabled) { initializeTelemetry(this); } } /** * Must only be called once, throws if called again. */ async initialize(): Promise { if (this.initialized) { throw Error('Config was already initialized'); } this.initialized = true; // Initialize centralized FileDiscoveryService this.getFileService(); if (this.getCheckpointingEnabled()) { await this.getGitService(); } this.promptRegistry = new PromptRegistry(); this.toolRegistry = await this.createToolRegistry(); logCliConfiguration(this, new StartSessionEvent(this, this.toolRegistry)); } async refreshAuth(authMethod: AuthType) { // Save the current conversation history before creating a new client let existingHistory: Content[] = []; if (this.geminiClient && this.geminiClient.isInitialized()) { existingHistory = this.geminiClient.getHistory(); } // Create new content generator config const newContentGeneratorConfig = createContentGeneratorConfig( this, authMethod, ); // Create and initialize new client in local variable first const newGeminiClient = new GeminiClient(this); await newGeminiClient.initialize(newContentGeneratorConfig); // Vertex and Genai have incompatible encryption and sending history with // throughtSignature from Genai to Vertex will fail, we need to strip them const fromGenaiToVertex = this.contentGeneratorConfig?.authType === AuthType.USE_GEMINI && authMethod === AuthType.LOGIN_WITH_GOOGLE; // Only assign to instance properties after successful initialization this.contentGeneratorConfig = newContentGeneratorConfig; this.geminiClient = newGeminiClient; // Restore the conversation history to the new client if (existingHistory.length > 0) { this.geminiClient.setHistory(existingHistory, { stripThoughts: fromGenaiToVertex, }); } // Reset the session flag since we're explicitly changing auth and using default model this.inFallbackMode = false; } getSessionId(): string { return this.sessionId; } setSessionId(sessionId: string): void { this.sessionId = sessionId; } shouldLoadMemoryFromIncludeDirectories(): boolean { return this.loadMemoryFromIncludeDirectories; } getContentGeneratorConfig(): ContentGeneratorConfig { return this.contentGeneratorConfig; } getModel(): string { return this.contentGeneratorConfig?.model || this.model; } setModel(newModel: string): void { if (this.contentGeneratorConfig) { this.contentGeneratorConfig.model = newModel; } } isInFallbackMode(): boolean { return this.inFallbackMode; } setFallbackMode(active: boolean): void { this.inFallbackMode = active; } setFlashFallbackHandler(handler: FlashFallbackHandler): void { this.flashFallbackHandler = handler; } getMaxSessionTurns(): number { return this.maxSessionTurns; } setQuotaErrorOccurred(value: boolean): void { this.quotaErrorOccurred = value; } getQuotaErrorOccurred(): boolean { return this.quotaErrorOccurred; } getEmbeddingModel(): string { return this.embeddingModel; } getSandbox(): SandboxConfig | undefined { return this.sandbox; } isRestrictiveSandbox(): boolean { const sandboxConfig = this.getSandbox(); const seatbeltProfile = process.env['SEATBELT_PROFILE']; return ( !!sandboxConfig && sandboxConfig.command === 'sandbox-exec' && !!seatbeltProfile && seatbeltProfile.startsWith('restrictive-') ); } getTargetDir(): string { return this.targetDir; } getProjectRoot(): string { return this.targetDir; } getWorkspaceContext(): WorkspaceContext { return this.workspaceContext; } getToolRegistry(): ToolRegistry { return this.toolRegistry; } getPromptRegistry(): PromptRegistry { return this.promptRegistry; } getDebugMode(): boolean { return this.debugMode; } getQuestion(): string | undefined { return this.question; } getFullContext(): boolean { return this.fullContext; } getCoreTools(): string[] | undefined { return this.coreTools; } getExcludeTools(): string[] | undefined { return this.excludeTools; } getToolDiscoveryCommand(): string | undefined { return this.toolDiscoveryCommand; } getToolCallCommand(): string | undefined { return this.toolCallCommand; } getMcpServerCommand(): string | undefined { return this.mcpServerCommand; } getMcpServers(): Record | undefined { return this.mcpServers; } getUserMemory(): string { return this.userMemory; } setUserMemory(newUserMemory: string): void { this.userMemory = newUserMemory; } getGeminiMdFileCount(): number { return this.geminiMdFileCount; } setGeminiMdFileCount(count: number): void { this.geminiMdFileCount = count; } getApprovalMode(): ApprovalMode { return this.approvalMode; } setApprovalMode(mode: ApprovalMode): void { this.approvalMode = mode; } getShowMemoryUsage(): boolean { return this.showMemoryUsage; } getAccessibility(): AccessibilitySettings { return this.accessibility; } getTelemetryEnabled(): boolean { return this.telemetrySettings.enabled ?? false; } getTelemetryLogPromptsEnabled(): boolean { return this.telemetrySettings.logPrompts ?? true; } getTelemetryOtlpEndpoint(): string { return this.telemetrySettings.otlpEndpoint ?? DEFAULT_OTLP_ENDPOINT; } getTelemetryOtlpProtocol(): 'grpc' | 'http' { return this.telemetrySettings.otlpProtocol ?? 'grpc'; } getTelemetryTarget(): TelemetryTarget { return this.telemetrySettings.target ?? DEFAULT_TELEMETRY_TARGET; } getTelemetryOutfile(): string | undefined { return this.telemetrySettings.outfile; } getGeminiClient(): GeminiClient { return this.geminiClient; } getEnableRecursiveFileSearch(): boolean { return this.fileFiltering.enableRecursiveFileSearch; } getFileFilteringRespectGitIgnore(): boolean { return this.fileFiltering.respectGitIgnore; } getFileFilteringRespectGeminiIgnore(): boolean { return this.fileFiltering.respectGeminiIgnore; } getFileFilteringOptions(): FileFilteringOptions { return { respectGitIgnore: this.fileFiltering.respectGitIgnore, respectGeminiIgnore: this.fileFiltering.respectGeminiIgnore, }; } getCheckpointingEnabled(): boolean { return this.checkpointing; } getProxy(): string | undefined { return this.proxy; } getWorkingDir(): string { return this.cwd; } getBugCommand(): BugCommandSettings | undefined { return this.bugCommand; } getFileService(): FileDiscoveryService { if (!this.fileDiscoveryService) { this.fileDiscoveryService = new FileDiscoveryService(this.targetDir); } return this.fileDiscoveryService; } getUsageStatisticsEnabled(): boolean { return this.usageStatisticsEnabled; } getExtensionContextFilePaths(): string[] { return this.extensionContextFilePaths; } getExperimentalZedIntegration(): boolean { return this.experimentalZedIntegration; } getListExtensions(): boolean { return this.listExtensions; } getExtensions(): GeminiCLIExtension[] { return this._extensions; } getBlockedMcpServers(): Array<{ name: string; extensionName: string }> { return this._blockedMcpServers; } getNoBrowser(): boolean { return this.noBrowser; } isBrowserLaunchSuppressed(): boolean { return this.getNoBrowser() || !shouldAttemptBrowserLaunch(); } getSummarizeToolOutputConfig(): | Record | undefined { return this.summarizeToolOutput; } getIdeMode(): boolean { return this.ideMode; } getFolderTrustFeature(): boolean { return this.folderTrustFeature; } getFolderTrust(): boolean { return this.folderTrust; } isTrustedFolder(): boolean | undefined { return this.trustedFolder; } setIdeMode(value: boolean): void { this.ideMode = value; } async setIdeModeAndSyncConnection(value: boolean): Promise { this.ideMode = value; if (value) { await this.ideClient.connect(); logIdeConnection(this, new IdeConnectionEvent(IdeConnectionType.SESSION)); } else { await this.ideClient.disconnect(); } } getIdeClient(): IdeClient { return this.ideClient; } /** * Get the current FileSystemService */ getFileSystemService(): FileSystemService { return this.fileSystemService; } /** * Set a custom FileSystemService */ setFileSystemService(fileSystemService: FileSystemService): void { this.fileSystemService = fileSystemService; } getChatCompression(): ChatCompressionSettings | undefined { return this.chatCompression; } isInteractive(): boolean { return this.interactive; } getShouldUseNodePtyShell(): boolean { return this.shouldUseNodePtyShell; } getSkipNextSpeakerCheck(): boolean { return this.skipNextSpeakerCheck; } getScreenReader(): boolean { return this.accessibility.screenReader ?? false; } getEnablePromptCompletion(): boolean { return this.enablePromptCompletion; } async getGitService(): Promise { if (!this.gitService) { this.gitService = new GitService(this.targetDir, this.storage); await this.gitService.initialize(); } return this.gitService; } async createToolRegistry(): Promise { const registry = new ToolRegistry(this); // helper to create & register core tools that are enabled // eslint-disable-next-line @typescript-eslint/no-explicit-any const registerCoreTool = (ToolClass: any, ...args: unknown[]) => { const className = ToolClass.name; const toolName = ToolClass.Name || className; const coreTools = this.getCoreTools(); const excludeTools = this.getExcludeTools(); let isEnabled = false; if (coreTools === undefined) { isEnabled = true; } else { isEnabled = coreTools.some( (tool) => tool === className || tool === toolName || tool.startsWith(`${className}(`) || tool.startsWith(`${toolName}(`), ); } if ( excludeTools?.includes(className) || excludeTools?.includes(toolName) ) { isEnabled = false; } if (isEnabled) { registry.registerTool(new ToolClass(...args)); } }; registerCoreTool(LSTool, this); registerCoreTool(ReadFileTool, this); registerCoreTool(GrepTool, this); registerCoreTool(GlobTool, this); registerCoreTool(EditTool, this); registerCoreTool(WriteFileTool, this); registerCoreTool(WebFetchTool, this); registerCoreTool(ReadManyFilesTool, this); registerCoreTool(ShellTool, this); registerCoreTool(MemoryTool); registerCoreTool(WebSearchTool, this); await registry.discoverAllTools(); return registry; } } // Export model constants for use in CLI export { DEFAULT_GEMINI_FLASH_MODEL };