diff options
| author | Tommaso Sciortino <[email protected]> | 2025-05-30 18:25:47 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-30 18:25:47 -0700 |
| commit | 21fba832d1b4ea7af43fb887d9b2b38fcf8210d0 (patch) | |
| tree | 7200d2fac3a55c385e0a2dac34b5282c942364bc /packages/core/src/config | |
| parent | c81148a0cc8489f657901c2cc7247c0834075e1a (diff) | |
Rename server->core (#638)
Diffstat (limited to 'packages/core/src/config')
| -rw-r--r-- | packages/core/src/config/config.test.ts | 109 | ||||
| -rw-r--r-- | packages/core/src/config/config.ts | 259 |
2 files changed, 368 insertions, 0 deletions
diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts new file mode 100644 index 00000000..f84ad746 --- /dev/null +++ b/packages/core/src/config/config.test.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi, beforeEach /*, afterEach */ } from 'vitest'; // afterEach removed as it was unused +import { Config, createServerConfig, ConfigParameters } from './config.js'; // Adjust import path +import * as path from 'path'; +// import { ToolRegistry } from '../tools/tool-registry'; // ToolRegistry removed as it was unused + +// Mock dependencies that might be called during Config construction or createServerConfig +vi.mock('../tools/tool-registry', () => { + const ToolRegistryMock = vi.fn(); + ToolRegistryMock.prototype.registerTool = vi.fn(); + ToolRegistryMock.prototype.discoverTools = vi.fn(); + ToolRegistryMock.prototype.getAllTools = vi.fn(() => []); // Mock methods if needed + ToolRegistryMock.prototype.getTool = vi.fn(); + ToolRegistryMock.prototype.getFunctionDeclarations = vi.fn(() => []); + return { ToolRegistry: ToolRegistryMock }; +}); + +// Mock individual tools if their constructors are complex or have side effects +vi.mock('../tools/ls'); +vi.mock('../tools/read-file'); +vi.mock('../tools/grep'); +vi.mock('../tools/glob'); +vi.mock('../tools/edit'); +vi.mock('../tools/shell'); +vi.mock('../tools/write-file'); +vi.mock('../tools/web-fetch'); +vi.mock('../tools/read-many-files'); + +describe('Server Config (config.ts)', () => { + const API_KEY = 'server-api-key'; + const MODEL = 'gemini-pro'; + const SANDBOX = false; + const TARGET_DIR = '/path/to/target'; + const DEBUG_MODE = false; + const QUESTION = 'test question'; + const FULL_CONTEXT = false; + const USER_AGENT = 'ServerTestAgent/1.0'; + const USER_MEMORY = 'Test User Memory'; + const baseParams: ConfigParameters = { + apiKey: API_KEY, + model: MODEL, + sandbox: SANDBOX, + targetDir: TARGET_DIR, + debugMode: DEBUG_MODE, + question: QUESTION, + fullContext: FULL_CONTEXT, + userAgent: USER_AGENT, + userMemory: USER_MEMORY, + }; + + beforeEach(() => { + // Reset mocks if necessary + vi.clearAllMocks(); + }); + + it('Config constructor should store userMemory correctly', () => { + const config = new Config(baseParams); + + expect(config.getUserMemory()).toBe(USER_MEMORY); + // Verify other getters if needed + expect(config.getApiKey()).toBe(API_KEY); + expect(config.getModel()).toBe(MODEL); + expect(config.getTargetDir()).toBe(path.resolve(TARGET_DIR)); // Check resolved path + expect(config.getUserAgent()).toBe(USER_AGENT); + }); + + it('Config constructor should default userMemory to empty string if not provided', () => { + const paramsWithoutMemory: ConfigParameters = { ...baseParams }; + delete paramsWithoutMemory.userMemory; + const config = new Config(paramsWithoutMemory); + + expect(config.getUserMemory()).toBe(''); + }); + + it('createServerConfig should pass userMemory to Config constructor', () => { + const config = createServerConfig(baseParams); + + // Check the result of the factory function + expect(config).toBeInstanceOf(Config); + expect(config.getUserMemory()).toBe(USER_MEMORY); + expect(config.getApiKey()).toBe(API_KEY); + expect(config.getUserAgent()).toBe(USER_AGENT); + }); + + it('createServerConfig should default userMemory if omitted', () => { + const paramsWithoutMemory: ConfigParameters = { ...baseParams }; + delete paramsWithoutMemory.userMemory; + const config = createServerConfig(paramsWithoutMemory); + + expect(config).toBeInstanceOf(Config); + expect(config.getUserMemory()).toBe(''); // Should default to empty string + }); + + it('createServerConfig should resolve targetDir', () => { + const relativeDir = './relative/path'; + const expectedResolvedDir = path.resolve(relativeDir); + const paramsWithRelativeDir: ConfigParameters = { + ...baseParams, + targetDir: relativeDir, + }; + const config = createServerConfig(paramsWithRelativeDir); + expect(config.getTargetDir()).toBe(expectedResolvedDir); + }); +}); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts new file mode 100644 index 00000000..0cd7a4fa --- /dev/null +++ b/packages/core/src/config/config.ts @@ -0,0 +1,259 @@ +/** + * @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 } from '../tools/memoryTool.js'; +import { WebSearchTool } from '../tools/web-search.js'; + +export class MCPServerConfig { + constructor( + // For stdio transport + readonly command?: string, + readonly args?: string[], + readonly env?: Record<string, string>, + 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<string, MCPServerConfig>; + userAgent: string; + userMemory?: string; + geminiMdFileCount?: number; + alwaysSkipModificationConfirmation?: boolean; + vertexai?: boolean; + showMemoryUsage?: boolean; +} + +export class Config { + private toolRegistry: ToolRegistry; + 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<string, MCPServerConfig> | undefined; + private readonly userAgent: string; + private userMemory: string; + private geminiMdFileCount: number; + private alwaysSkipModificationConfirmation: boolean; + private readonly vertexai: boolean | undefined; + private readonly showMemoryUsage: boolean; + + 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.alwaysSkipModificationConfirmation = + params.alwaysSkipModificationConfirmation ?? false; + this.vertexai = params.vertexai; + this.showMemoryUsage = params.showMemoryUsage ?? false; + + this.toolRegistry = createToolRegistry(this); + } + + getApiKey(): string { + return this.apiKey; + } + + getModel(): string { + return this.model; + } + + getSandbox(): boolean | string { + return this.sandbox; + } + + getTargetDir(): string { + return this.targetDir; + } + + getToolRegistry(): ToolRegistry { + 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<string, MCPServerConfig> | 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; + } + + getAlwaysSkipModificationConfirmation(): boolean { + return this.alwaysSkipModificationConfirmation; + } + + setAlwaysSkipModificationConfirmation(skip: boolean): void { + this.alwaysSkipModificationConfirmation = skip; + } + + getVertexAI(): boolean | undefined { + return this.vertexai; + } + + getShowMemoryUsage(): boolean { + return this.showMemoryUsage; + } +} + +function findEnvFile(startDir: string): string | null { + let currentDir = path.resolve(startDir); + while (true) { + const envPath = path.join(currentDir, '.env'); + if (fs.existsSync(envPath)) { + return envPath; + } + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir || !parentDir) { + // check ~/.env as fallback + 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): ToolRegistry { + 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); + registerCoreTool(ReadFileTool, targetDir); + registerCoreTool(GrepTool, targetDir); + registerCoreTool(GlobTool, targetDir); + registerCoreTool(EditTool, config); + registerCoreTool(WriteFileTool, config); + registerCoreTool(WebFetchTool, config); + registerCoreTool(ReadManyFilesTool, targetDir); + registerCoreTool(ShellTool, config); + registerCoreTool(MemoryTool); + registerCoreTool(WebSearchTool, config); + registry.discoverTools(); + return registry; +} |
