Gemini CLI源码解析:深入工具系统的实现细节
之前的文章介绍过主控Agent以及上下文实现的细节,除了主控Agent和上下文管理外,工具实现也是Agentic的一个重要环节。
核心架构设计
1. 工具接口(Tool Interface)
所有工具都必须实现Tool接口,这是整个系统的基础:
export interface Tool<TParams = unknown, TResult extends ToolResult = ToolResult> { name: string; // 工具的唯一标识符 displayName: string; // 用户友好的显示名称 description: string; // 工具功能描述 icon: Icon; // 显示图标 schema: FunctionDeclaration; // JSON Schema 参数定义 // 核心方法 validateToolParams(params: TParams): string | null; getDescription(params: TParams): string; shouldConfirmExecute(params: TParams, abortSignal: AbortSignal): Promise<ToolCallConfirmationDetails | false>; execute(params: TParams, signal: AbortSignal, updateOutput?: (output: string) => void): Promise<TResult>; }
这个接口设计的巧妙之处在于: ● 类型安全:通过泛型确保参数和返回值的类型正确性 ● 参数验证:内置验证机制,确保输入数据的有效性 ● 用户确认:对于危险操作,可以要求用户确认 ● 可中断性:支持通过 AbortSignal 取消长时间运行的操作
2. 基础工具类(BaseTool)
为了减少重复代码,系统提供了BaseTool抽象类:
export abstract class BaseTool<TParams = unknown, TResult extends ToolResult = ToolResult> implements Tool<TParams, TResult> { constructor( readonly name: string, readonly displayName: string, readonly description: string, readonly icon: Icon, readonly parameterSchema: Schema, readonly isOutputMarkdown: boolean = true, readonly canUpdateOutput: boolean = false, ) {} // 自动生成 schema get schema(): FunctionDeclaration { return { name: this.name, description: this.description, parameters: this.parameterSchema, }; } // 子类必须实现的抽象方法 abstract execute(params: TParams, signal: AbortSignal, updateOutput?: (output: string) => void): Promise<TResult>; }
3. 工具结果(ToolResult)
每个工具执行后都返回标准化的结果:
export interface ToolResult { summary?: string; // 操作摘要 llmContent: PartListUnion; // 发送给 LLM 的内容 returnDisplay: ToolResultDisplay; // 显示给用户的内容 }
这种设计分离了"AI 需要知道的"和"用户需要看到的",让系统更加灵活。
工具注册与发现机制
工具注册表(ToolRegistry)
ToolRegistry是整个工具系统的中央管理器:
export class ToolRegistry { private tools: Map<string, Tool> = new Map(); // 注册内置工具 registerTool(tool: Tool): void { this.tools.set(tool.name, tool); } // 动态发现工具(外部工具) async discoverAllTools(): Promise<void> { await this.discoverAndRegisterToolsFromCommand(); await discoverMcpTools(/* ... */); } }
动态工具发现
系统支持两种动态工具发现机制:
1. 命令行发现
通过配置toolDiscoveryCommand,系统可以执行命令来发现自定义工具:
class DiscoveredTool extends BaseTool<ToolParams, ToolResult> { async execute(params: ToolParams): Promise<ToolResult> { const callCommand = this.config.getToolCallCommand()!; const child = spawn(callCommand, [this.name]); child.stdin.write(JSON.stringify(params)); // ... 处理执行结果 } }
2. MCP 服务器发现
支持 Model Context Protocol (MCP) 服务器,实现更复杂的工具集成:
class DiscoveredMCPTool extends BaseTool<ToolParams, ToolResult> { constructor( private mcpTool: CallableTool, public readonly serverName: string, private serverToolName: string, // ... ) { // 工具名称会加上服务器前缀,如:serverAlias__actualToolName super(`${serverName}__${serverToolName}`, /* ... */); } }
内置工具详解
让我们看看几个核心的内置工具是如何实现的:
1. 文件读取工具
export class ReadFileTool extends BaseTool<ReadFileToolParams, ToolResult> { static readonly Name: string = 'read_file'; constructor(private config: Config) { super( ReadFileTool.Name, 'ReadFile', 'Reads and returns the content of a specified file from the local filesystem...', Icon.FileSearch, { properties: { absolute_path: { description: "The absolute path to the file to read...", type: Type.STRING, }, offset: { /* 起始行号 */ }, limit: { /* 读取行数 */ }, }, required: ['absolute_path'], type: Type.OBJECT, }, ); } validateToolParams(params: ReadFileToolParams): string | null { // 验证路径是否为绝对路径 if (!path.isAbsolute(params.absolute_path)) { return `File path must be absolute, but was relative: ${params.absolute_path}`; } // 验证路径是否在允许的根目录内 if (!isWithinRoot(params.absolute_path, this.config.getTargetDir())) { return `File path must be within the root directory`; } // 检查是否被 .geminiignore 忽略 if (this.config.getFileService().shouldGeminiIgnoreFile(params.absolute_path)) { return `File path is ignored by .geminiignore pattern(s)`; } return null; } async execute(params: ReadFileToolParams, _signal: AbortSignal): Promise<ToolResult> { const validationError = this.validateToolParams(params); if (validationError) { return { llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`, returnDisplay: validationError, }; } const result = await processSingleFileContent( params.absolute_path, this.config.getTargetDir(), params.offset, params.limit, ); return { llmContent: result.llmContent, returnDisplay: result.returnDisplay, }; } }
这个实现展示了几个重要的安全特性: ● 路径验证:确保只能访问允许的目录 ● 忽略文件检查:尊重 .geminiignore 配置 ● 分页支持:支持大文件的分页读取
2. 内存工具
内存工具的核心作用是让AI助手能够"记住"用户提供的重要信息 ,这些信息会被保存到本地文件中,供后续对话使用。
export class MemoryTool extends BaseTool<SaveMemoryParams, ToolResult> { static readonly Name: string = 'save_memory'; async execute(params: SaveMemoryParams, _signal: AbortSignal): Promise<ToolResult> { const { fact } = params; if (!fact || typeof fact !== 'string' || fact.trim() === '') { const errorMessage = 'Parameter "fact" must be a non-empty string.'; return { llmContent: JSON.stringify({ success: false, error: errorMessage }), returnDisplay: `Error: ${errorMessage}`, }; } try { await MemoryTool.performAddMemoryEntry(fact, getGlobalMemoryFilePath(), { readFile: fs.readFile, writeFile: fs.writeFile, mkdir: fs.mkdir, }); const successMessage = `Okay, I've remembered that: "${fact}"`; return { llmContent: JSON.stringify({ success: true, message: successMessage }), returnDisplay: successMessage, }; } catch (error) { // 错误处理... } } static async performAddMemoryEntry(text: string, memoryFilePath: string, fsAdapter: FsAdapter): Promise<void> { let processedText = text.trim(); // 移除可能被误解为 markdown 列表项的前导连字符 processedText = processedText.replace(/^(-+\s*)+/, '').trim(); const newMemoryItem = `- ${processedText}`; // 读取现有内容 let content = ''; try { content = await fsAdapter.readFile(memoryFilePath, 'utf-8'); } catch (_e) { // 文件不存在,将创建新文件 } const headerIndex = content.indexOf(MEMORY_SECTION_HEADER); if (headerIndex === -1) { // 没有找到记忆区块,添加新的区块 const separator = ensureNewlineSeparation(content); content += `${separator}${MEMORY_SECTION_HEADER}\n${newMemoryItem}\n`; } else { // 找到记忆区块,在其中添加新条目 // ... 复杂的文本处理逻辑 } await fsAdapter.writeFile(memoryFilePath, content, 'utf-8'); } }
内存工具的设计亮点: ● 结构化存储:使用 Markdown 格式组织记忆内容 ● 智能文本处理:自动处理格式化问题 ● 依赖注入:通过 fsAdapter 参数便于测试
3. Shell工具
export class ShellTool extends BaseTool<ShellToolParams, ToolResult> { async shouldConfirmExecute( params: ShellToolParams, abortSignal: AbortSignal, ): Promise<ToolCallConfirmationDetails | false> { // Shell 命令通常需要用户确认 return { type: 'exec', title: 'Execute Shell Command', command: params.command, rootCommand: params.command.split(' ')[0], onConfirm: async (outcome: ToolConfirmationOutcome) => { // 处理用户确认结果 }, }; } async execute( params: ShellToolParams, signal: AbortSignal, updateOutput?: (output: string) => void, ): Promise<ToolResult> { // 在沙箱环境中执行命令 const child = spawn(command, args, { cwd: params.directory || this.config.getTargetDir(), env: { ...process.env, ...sandboxEnv }, }); // 实时输出更新 if (updateOutput) { const updateInterval = setInterval(() => { updateOutput(currentOutput); }, OUTPUT_UPDATE_INTERVAL_MS); } // 处理命令执行结果... } }
Shell工具展示了系统的安全机制: ● 用户确认:危险操作需要明确确认 ● 沙箱执行:在受控环境中运行命令 ● 实时反馈:支持流式输出更新
工具执行流程
整个工具执行流程设计得非常优雅:

这个流程的关键特点:
- 参数验证:在执行前确保参数有效性
- 用户确认:危险操作需要用户明确同意
- 结果分离:AI 和用户看到不同格式的结果
- 错误处理:每个环节都有完善的错误处理机制
扩展性设计
自定义工具开发
开发自定义工具非常简单,只需继承BaseTool:
class MyCustomTool extends BaseTool<MyParams, ToolResult> { constructor() { super( 'my_custom_tool', 'My Custom Tool', 'Description of what this tool does', Icon.Hammer, { properties: { param1: { type: Type.STRING, description: '...' }, param2: { type: Type.NUMBER, description: '...' }, }, required: ['param1'], type: Type.OBJECT, }, ); } async execute(params: MyParams, signal: AbortSignal): Promise<ToolResult> { // 实现具体逻辑 return { llmContent: 'Result for AI', returnDisplay: 'Result for user', }; } }
配置驱动的工具发现
通过配置文件可以轻松集成外部工具:
{ "toolDiscoveryCommand": "./scripts/discover-tools.sh", "toolCallCommand": "./scripts/call-tool.sh", "mcpServers": { "myServer": { "command": "node", "args": ["./my-mcp-server.js"] } } }
安全性考虑
工具系统在设计时充分考虑了安全性:
1. 路径安全
● 所有文件操作都限制在指定的根目录内
● 支持.geminiignore文件排除敏感文件
● 绝对路径验证防止路径遍历攻击
2. 用户确认机制
async shouldConfirmExecute(params: TParams): Promise<ToolCallConfirmationDetails | false> { // 根据操作危险程度决定是否需要确认 if (isDangerousOperation(params)) { return { type: 'exec', title: 'Dangerous Operation', onConfirm: async (outcome) => { // 处理用户决定 }, }; } return false; }
3. 沙箱执行
● Shell 命令在受限环境中执行
● 环境变量过滤
● 资源限制(内存、CPU、时间)
4. 输入验证
● JSON Schema验证确保参数格式正确 ● 自定义验证逻辑处理业务规则 ● 类型安全的TypeScript接口
性能优化
1. 懒加载
工具只在需要时才被实例化和注册,减少启动时间。
2. 流式输出
async execute( params: TParams, signal: AbortSignal, updateOutput?: (output: string) => void, ): Promise<TResult> { // 支持实时输出更新 if (updateOutput) { setInterval(() => { updateOutput(getCurrentOutput()); }, 1000); } }
3. 可中断操作
通过 AbortSignal 支持长时间运行操作的取消。
4. 结果缓存
对于幂等操作,可以实现结果缓存减少重复计算。
总结
Gemini CLI的工具系统通过标准化的接口、灵活的扩展机制、完善的安全措施和优雅的执行流程,为构建实用的AI助手提供了坚实的基础,其中不乏设计亮点:
- 类型安全的接口设计:确保编译时和运行时的正确性
- 灵活的扩展机制:支持内置工具、命令行发现和 MCP 服务器
- 完善的安全机制:多层次的安全验证和用户确认
- 优雅的执行流程:清晰的职责分离和错误处理
对于AI 开发者以及所有对AI工具集成感兴趣的人来说,如何让复杂的系统保持简洁、安全和可扩展,Gemini CLI的工具系统都值得深入学习和借鉴。
推荐阅读

在AI Agent领域,Deep Research应该算是最早的智能体应用,最初是由Google和OpenAI推出商用版本,后来也出现很多开源的项目。

在沉寂了半年之后,智谱推出了新一代开源模型GLM-4.5系列,采用MOE架构并使用混合推理模式。模型统一提升了在推理、代码与智能体等多方面的能力,专为复杂智能体应用打造。
