/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as dotenv from 'dotenv'; import * as fs from 'node:fs'; import * as path from 'node:path'; import process from 'node:process'; import * as os from 'node:os'; 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 { GEMINI_CONFIG_DIR as GEMINI_DIR } from '../tools/memoryTool.js'; import { FileDiscoveryService } from '../services/fileDiscoveryService.js'; export enum ApprovalMode { DEFAULT = 'default', AUTO_EDIT = 'autoEdit', YOLO = 'yolo', } export interface AccessibilitySettings { disableLoadingPhrases?: boolean; } 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, // Common readonly timeout?: number, readonly trust?: boolean, ) {} } export interface ConfigParameters { apiKey: string; model: string; sandbox: boolean | string; targetDir: string; debugMode: boolean; question?: string; fullContext?: boolean; coreTools?: string[]; toolDiscoveryCommand?: string; toolCallCommand?: string; mcpServerCommand?: string; mcpServers?: Record; userAgent: string; userMemory?: string; geminiMdFileCount?: number; approvalMode?: ApprovalMode; vertexai?: boolean; showMemoryUsage?: boolean; contextFileName?: string; accessibility?: AccessibilitySettings; fileFilteringRespectGitIgnore?: boolean; fileFilteringAllowBuildArtifacts?: boolean; } export class Config { private toolRegistry: Promise; private readonly apiKey: string; private readonly model: string; private readonly sandbox: boolean | string; private readonly targetDir: string; private readonly debugMode: boolean; private readonly question: string | undefined; private readonly fullContext: boolean; private readonly coreTools: string[] | undefined; private readonly toolDiscoveryCommand: string | undefined; private readonly toolCallCommand: string | undefined; private readonly mcpServerCommand: string | undefined; private readonly mcpServers: Record | undefined; private readonly userAgent: string; private userMemory: string; private geminiMdFileCount: number; private approvalMode: ApprovalMode; private readonly vertexai: boolean | undefined; private readonly showMemoryUsage: boolean; private readonly accessibility: AccessibilitySettings; private readonly geminiClient: GeminiClient; private readonly fileFilteringRespectGitIgnore: boolean; private readonly fileFilteringAllowBuildArtifacts: boolean; private fileDiscoveryService: FileDiscoveryService | null = null; constructor(params: ConfigParameters) { this.apiKey = params.apiKey; this.model = params.model; this.sandbox = params.sandbox; this.targetDir = path.resolve(params.targetDir); this.debugMode = params.debugMode; this.question = params.question; this.fullContext = params.fullContext ?? false; this.coreTools = params.coreTools; this.toolDiscoveryCommand = params.toolDiscoveryCommand; this.toolCallCommand = params.toolCallCommand; this.mcpServerCommand = params.mcpServerCommand; this.mcpServers = params.mcpServers; this.userAgent = params.userAgent; this.userMemory = params.userMemory ?? ''; this.geminiMdFileCount = params.geminiMdFileCount ?? 0; this.approvalMode = params.approvalMode ?? ApprovalMode.DEFAULT; this.vertexai = params.vertexai; this.showMemoryUsage = params.showMemoryUsage ?? false; this.accessibility = params.accessibility ?? {}; this.fileFilteringRespectGitIgnore = params.fileFilteringRespectGitIgnore ?? true; this.fileFilteringAllowBuildArtifacts = params.fileFilteringAllowBuildArtifacts ?? false; if (params.contextFileName) { setGeminiMdFilename(params.contextFileName); } this.toolRegistry = createToolRegistry(this); this.geminiClient = new GeminiClient(this); } getApiKey(): string { return this.apiKey; } getModel(): string { return this.model; } getSandbox(): boolean | string { return this.sandbox; } getTargetDir(): string { return this.targetDir; } async getToolRegistry(): Promise { return this.toolRegistry; } getDebugMode(): boolean { return this.debugMode; } getQuestion(): string | undefined { return this.question; } getFullContext(): boolean { return this.fullContext; } getCoreTools(): string[] | undefined { return this.coreTools; } getToolDiscoveryCommand(): string | undefined { return this.toolDiscoveryCommand; } getToolCallCommand(): string | undefined { return this.toolCallCommand; } getMcpServerCommand(): string | undefined { return this.mcpServerCommand; } getMcpServers(): Record | undefined { return this.mcpServers; } getUserAgent(): string { return this.userAgent; } 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; } getVertexAI(): boolean | undefined { return this.vertexai; } getShowMemoryUsage(): boolean { return this.showMemoryUsage; } getAccessibility(): AccessibilitySettings { return this.accessibility; } getGeminiClient(): GeminiClient { return this.geminiClient; } getFileFilteringRespectGitIgnore(): boolean { return this.fileFilteringRespectGitIgnore; } getFileFilteringAllowBuildArtifacts(): boolean { return this.fileFilteringAllowBuildArtifacts; } async getFileService(): Promise { if (!this.fileDiscoveryService) { this.fileDiscoveryService = new FileDiscoveryService(this.targetDir); await this.fileDiscoveryService.initialize({ respectGitIgnore: this.fileFilteringRespectGitIgnore, includeBuildArtifacts: this.fileFilteringAllowBuildArtifacts, }); } return this.fileDiscoveryService; } } function findEnvFile(startDir: string): string | null { let currentDir = path.resolve(startDir); while (true) { // prefer gemini-specific .env under GEMINI_DIR const geminiEnvPath = path.join(currentDir, GEMINI_DIR, '.env'); if (fs.existsSync(geminiEnvPath)) { return geminiEnvPath; } const envPath = path.join(currentDir, '.env'); if (fs.existsSync(envPath)) { return envPath; } const parentDir = path.dirname(currentDir); if (parentDir === currentDir || !parentDir) { // check .env under home as fallback, again preferring gemini-specific .env const homeGeminiEnvPath = path.join(os.homedir(), GEMINI_DIR, '.env'); if (fs.existsSync(homeGeminiEnvPath)) { return homeGeminiEnvPath; } const homeEnvPath = path.join(os.homedir(), '.env'); if (fs.existsSync(homeEnvPath)) { return homeEnvPath; } return null; } currentDir = parentDir; } } export function loadEnvironment(): void { const envFilePath = findEnvFile(process.cwd()); if (!envFilePath) { return; } dotenv.config({ path: envFilePath }); } export function createServerConfig(params: ConfigParameters): Config { return new Config({ ...params, targetDir: path.resolve(params.targetDir), // Ensure targetDir is resolved userAgent: params.userAgent ?? 'GeminiCLI/unknown', // Default user agent }); } export function createToolRegistry(config: Config): Promise { const registry = new ToolRegistry(config); const targetDir = config.getTargetDir(); const tools = config.getCoreTools() ? new Set(config.getCoreTools()) : undefined; // helper to create & register core tools that are enabled // eslint-disable-next-line @typescript-eslint/no-explicit-any const registerCoreTool = (ToolClass: any, ...args: unknown[]) => { // check both the tool name (.Name) and the class name (.name) if (!tools || tools.has(ToolClass.Name) || tools.has(ToolClass.name)) { registry.registerTool(new ToolClass(...args)); } }; registerCoreTool(LSTool, targetDir, config); registerCoreTool(ReadFileTool, targetDir); registerCoreTool(GrepTool, targetDir); registerCoreTool(GlobTool, targetDir, config); registerCoreTool(EditTool, config); registerCoreTool(WriteFileTool, config); registerCoreTool(WebFetchTool, config); registerCoreTool(ReadManyFilesTool, targetDir, config); registerCoreTool(ShellTool, config); registerCoreTool(MemoryTool); registerCoreTool(WebSearchTool, config); return (async () => { await registry.discoverTools(); return registry; })(); }