diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 5490afb678b..343889dde50 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -629,7 +629,7 @@ describe('Hierarchical Memory Loading (config.ts) - Placeholder Suite', () => { }, ]; const argv = await parseArguments({} as Settings); - await loadCliConfig(settings, extensions, 'session-id', argv); + await loadCliConfig(settings, extensions, 'session-id', argv, '1.0.0'); expect(ServerConfig.loadServerHierarchicalMemory).toHaveBeenCalledWith( expect.any(String), [], @@ -708,7 +708,7 @@ describe('mergeMcpServers', () => { const originalSettings = JSON.parse(JSON.stringify(settings)); process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - await loadCliConfig(settings, extensions, 'test-session', argv); + await loadCliConfig(settings, extensions, 'test-session', argv, '1.0.0'); expect(settings).toEqual(originalSettings); }); }); @@ -918,7 +918,7 @@ describe('mergeExcludeTools', () => { const originalSettings = JSON.parse(JSON.stringify(settings)); process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - await loadCliConfig(settings, extensions, 'test-session', argv); + await loadCliConfig(settings, extensions, 'test-session', argv, '1.0.0'); expect(settings).toEqual(originalSettings); }); }); @@ -1162,7 +1162,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should allow all MCP servers if the flag is not provided', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig(baseSettings, [], 'test-session', argv); + const config = await loadCliConfig(baseSettings, [], 'test-session', argv, '1.0.0'); expect(config.getMcpServers()).toEqual(baseSettings.mcpServers); }); @@ -1174,7 +1174,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { 'server1', ]; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig(baseSettings, [], 'test-session', argv); + const config = await loadCliConfig(baseSettings, [], 'test-session', argv, '1.0.0'); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, }); @@ -1190,7 +1190,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { 'server3', ]; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig(baseSettings, [], 'test-session', argv); + const config = await loadCliConfig(baseSettings, [], 'test-session', argv, '1.0.0'); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, server3: { url: 'http://localhost:8082' }, @@ -1207,7 +1207,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { 'server4', ]; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig(baseSettings, [], 'test-session', argv); + const config = await loadCliConfig(baseSettings, [], 'test-session', argv, '1.0.0'); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, }); @@ -1216,7 +1216,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should allow no MCP servers if the flag is provided but empty', async () => { process.argv = ['node', 'script.js', '--allowed-mcp-server-names', '']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig(baseSettings, [], 'test-session', argv); + const config = await loadCliConfig(baseSettings, [], 'test-session', argv, '1.0.0'); expect(config.getMcpServers()).toEqual({}); }); @@ -1309,6 +1309,50 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { }); }); +describe('loadCliConfig extensions', () => { + const mockExtensions: Extension[] = [ + { + path: '/path/to/ext1', + config: { name: 'ext1', version: '1.0.0' }, + contextFiles: ['/path/to/ext1.md'], + }, + { + path: '/path/to/ext2', + config: { name: 'ext2', version: '1.0.0' }, + contextFiles: ['/path/to/ext2.md'], + }, + ]; + + it('should not filter extensions if --extensions flag is not used', async () => { + process.argv = ['node', 'script.js']; + const argv = await parseArguments({} as Settings); + const settings: Settings = {}; + const config = await loadCliConfig( + settings, + mockExtensions, + 'test-session', + argv, + ); + expect(config.getExtensionContextFilePaths()).toEqual([ + '/path/to/ext1.md', + '/path/to/ext2.md', + ]); + }); + + it('should filter extensions if --extensions flag is used', async () => { + process.argv = ['node', 'script.js', '--extensions', 'ext1']; + const argv = await parseArguments({} as Settings); + const settings: Settings = {}; + const config = await loadCliConfig( + settings, + mockExtensions, + 'test-session', + argv, + ); + expect(config.getExtensionContextFilePaths()).toEqual(['/path/to/ext1.md']); + }); +}); + describe('loadCliConfig model selection', () => { it('selects a model from settings.json if provided', async () => { process.argv = ['node', 'script.js']; @@ -1758,7 +1802,7 @@ describe('loadCliConfig tool exclusions', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); expect(config.getExcludeTools()).not.toContain('write_file'); @@ -1768,7 +1812,7 @@ describe('loadCliConfig tool exclusions', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); expect(config.getExcludeTools()).not.toContain('write_file'); @@ -1778,7 +1822,7 @@ describe('loadCliConfig tool exclusions', () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '-p', 'test']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getExcludeTools()).toContain('run_shell_command'); expect(config.getExcludeTools()).toContain('replace'); expect(config.getExcludeTools()).toContain('write_file'); @@ -1788,7 +1832,7 @@ describe('loadCliConfig tool exclusions', () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '-p', 'test', '--yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); expect(config.getExcludeTools()).not.toContain('write_file'); @@ -1862,7 +1906,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(true); }); @@ -1870,7 +1914,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '--prompt-interactive', 'test']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(true); }); @@ -1878,7 +1922,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(false); }); @@ -1886,7 +1930,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--prompt', 'test']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(false); }); @@ -1894,7 +1938,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro', 'Hello']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(false); }); @@ -1909,7 +1953,7 @@ describe('loadCliConfig interactive', () => { 'Hello world', ]; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(false); // Verify the question is preserved for one-shot execution expect(argv.prompt).toBe('Hello world'); @@ -1990,7 +2034,7 @@ describe('loadCliConfig interactive', () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.isInteractive()).toBe(true); }); }); @@ -2018,42 +2062,42 @@ describe('loadCliConfig approval mode', () => { it('should default to DEFAULT approval mode when no flags are set', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should set YOLO approval mode when --yolo flag is used', async () => { process.argv = ['node', 'script.js', '--yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); it('should set YOLO approval mode when -y flag is used', async () => { process.argv = ['node', 'script.js', '-y']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); it('should set DEFAULT approval mode when --approval-mode=default', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'default']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should set AUTO_EDIT approval mode when --approval-mode=auto_edit', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'auto_edit']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.AUTO_EDIT); }); it('should set YOLO approval mode when --approval-mode=yolo', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); @@ -2064,14 +2108,14 @@ describe('loadCliConfig approval mode', () => { const argv = await parseArguments({} as Settings); // Manually set yolo to true to simulate what would happen if validation didn't prevent it argv.yolo = true; - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should fall back to --yolo behavior when --approval-mode is not set', async () => { process.argv = ['node', 'script.js', '--yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); @@ -2087,28 +2131,28 @@ describe('loadCliConfig approval mode', () => { it('should override --approval-mode=yolo to DEFAULT', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should override --approval-mode=auto_edit to DEFAULT', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'auto_edit']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should override --yolo flag to DEFAULT', async () => { process.argv = ['node', 'script.js', '--yolo']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should remain DEFAULT when --approval-mode=default', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'default']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); }); @@ -2199,7 +2243,7 @@ describe('Output format', () => { it('should default to TEXT', async () => { process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getOutputFormat()).toBe(OutputFormat.TEXT); }); @@ -2428,7 +2472,7 @@ describe('Telemetry configuration via environment variables', () => { vi.stubEnv('GEMINI_TELEMETRY_ENABLED', '1'); process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getTelemetryEnabled()).toBe(true); }); @@ -2449,7 +2493,7 @@ describe('Telemetry configuration via environment variables', () => { vi.stubEnv('GEMINI_TELEMETRY_LOG_PROMPTS', '1'); process.argv = ['node', 'script.js']; const argv = await parseArguments({} as Settings); - const config = await loadCliConfig({}, [], 'test-session', argv); + const config = await loadCliConfig({}, [], 'test-session', argv, '1.0.0'); expect(config.getTelemetryLogPromptsEnabled()).toBe(true); }); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 1bb1a3b70db..a9578e980f7 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -368,6 +368,7 @@ export async function loadCliConfig( cwd: string = process.cwd(), ): Promise { const debugMode = isDebugMode(argv); + const version = await getCliVersion(); const memoryImportFormat = settings.context?.importFormat || 'tree'; @@ -562,6 +563,7 @@ export async function loadCliConfig( return new Config({ sessionId, + version, embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL, sandbox: sandboxConfig, targetDir: cwd, diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 0546f7cba66..36991506b0d 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -574,6 +574,22 @@ describe('Server Config (config.ts)', () => { }); }); + describe('Version Configuration', () => { + it('should store and return version when provided', () => { + const paramsWithVersion: ConfigParameters = { + ...baseParams, + version: '2.3.4', + }; + const config = new Config(paramsWithVersion); + expect(config.getVersion()).toBe('2.3.4'); + }); + + it('should return undefined when version not provided', () => { + const config = new Config(baseParams); + expect(config.getVersion()).toBeUndefined(); + }); + }); + describe('UseModelRouter Configuration', () => { it('should default useModelRouter to false when not provided', () => { const config = new Config(baseParams); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index baa8bc6175e..686ba3bae5a 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -214,6 +214,7 @@ export interface SandboxConfig { export interface ConfigParameters { sessionId: string; + version?: string; embeddingModel?: string; sandbox?: SandboxConfig; targetDir: string; @@ -291,6 +292,7 @@ export class Config { private promptRegistry!: PromptRegistry; private agentRegistry!: AgentRegistry; private readonly sessionId: string; + private readonly version: string | undefined; private fileSystemService: FileSystemService; private contentGeneratorConfig!: ContentGeneratorConfig; private contentGenerator!: ContentGenerator; @@ -383,6 +385,7 @@ export class Config { constructor(params: ConfigParameters) { this.sessionId = params.sessionId; + this.version = params.version; this.embeddingModel = params.embeddingModel ?? DEFAULT_GEMINI_EMBEDDING_MODEL; this.fileSystemService = new StandardFileSystemService(); @@ -601,6 +604,10 @@ export class Config { return this.sessionId; } + getVersion(): string | undefined { + return this.version; + } + shouldLoadMemoryFromIncludeDirectories(): boolean { return this.loadMemoryFromIncludeDirectories; } diff --git a/packages/core/src/tools/mcp-client-manager.ts b/packages/core/src/tools/mcp-client-manager.ts index ec05563d3de..c1fb8db5805 100644 --- a/packages/core/src/tools/mcp-client-manager.ts +++ b/packages/core/src/tools/mcp-client-manager.ts @@ -64,7 +64,7 @@ export class McpClientManager { this.eventEmitter?.emit('mcp-client-update', this.clients); try { - await client.connect(); + await client.connect(cliConfig); await client.discover(cliConfig); this.eventEmitter?.emit('mcp-client-update', this.clients); } catch (error) { diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts index fe755db7bc6..7a108e62bf0 100644 --- a/packages/core/src/tools/mcp-client.test.ts +++ b/packages/core/src/tools/mcp-client.test.ts @@ -55,6 +55,46 @@ describe('mcp-client', () => { }); describe('McpClient', () => { + it('should create client with correct name and version', () => { + const mockClient = vi.fn(); + vi.mocked(ClientLib.Client).mockImplementation(mockClient); + + new McpClient( + 'test-server', + { command: 'test-command' }, + {} as ToolRegistry, + {} as PromptRegistry, + {} as WorkspaceContext, + false, + '2.1.0', + ); + + expect(mockClient).toHaveBeenCalledWith({ + name: 'gemini-cli', + version: '2.1.0', + }); + }); + + it('should use "unknown" version when not provided', () => { + const mockClient = vi.fn(); + vi.mocked(ClientLib.Client).mockImplementation(mockClient); + + new McpClient( + 'test-server', + { command: 'test-command' }, + {} as ToolRegistry, + {} as PromptRegistry, + {} as WorkspaceContext, + false, + undefined, + ); + + expect(mockClient).toHaveBeenCalledWith({ + name: 'gemini-cli', + version: 'unknown', + }); + }); + it('should discover tools', async () => { const mockedClient = { connect: vi.fn(), @@ -92,6 +132,7 @@ describe('mcp-client', () => { {} as PromptRegistry, workspaceContext, false, + '1.0.0', ); await client.connect(); await client.discover({} as Config); @@ -155,6 +196,7 @@ describe('mcp-client', () => { {} as PromptRegistry, workspaceContext, false, + '1.0.0', ); await client.connect(); await client.discover({} as Config); @@ -195,6 +237,7 @@ describe('mcp-client', () => { {} as PromptRegistry, workspaceContext, false, + '1.0.0', ); await client.connect(); await expect(client.discover({} as Config)).rejects.toThrow( diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index b0e46900a74..b447e3bbc87 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -99,7 +99,7 @@ export class McpClient { /** * Connects to the MCP server. */ - async connect(): Promise { + async connect(cliConfig?: Config): Promise { if (this.status !== MCPServerStatus.DISCONNECTED) { throw new Error( `Can only connect when the client is disconnected, current state is ${this.status}`, @@ -112,6 +112,7 @@ export class McpClient { this.serverConfig, this.debugMode, this.workspaceContext, + cliConfig?.getVersion(), ); const originalOnError = this.client.onerror; this.client.onerror = (error) => { @@ -517,6 +518,7 @@ export async function connectAndDiscover( mcpServerConfig, debugMode, workspaceContext, + cliConfig.getVersion(), ); mcpClient.onerror = (error) => { @@ -752,10 +754,11 @@ export async function connectToMcpServer( mcpServerConfig: MCPServerConfig, debugMode: boolean, workspaceContext: WorkspaceContext, + version?: string, ): Promise { const mcpClient = new Client({ - name: 'gemini-cli-mcp-client', - version: '0.0.1', + name: 'gemini-cli', + version: version || 'unknown', }); mcpClient.registerCapabilities({