掘金 人工智能 07月29日 12:21
Gemini CLI 代码解析
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

Gemini CLI开源代码解析,介绍其核心组件、架构、方法及功能。主要包含cli和core两部分,重点分析cli部分的核心通信桥梁acpPeer.ts,涵盖入口函数、核心代理类GeminiAgent及工具方法。详细解析了GeminiAgent类的结构、生命周期方法、通信协议处理、认证管理、消息处理引擎、工具调用系统及文件内容解析器等关键模块,并对runAcpPeer、sendUserMessage、#runTool和#resolveUserMessage等核心方法进行深入分析,展示了其如何处理用户消息、执行工具调用、构建LLM提示词等过程,最后通过业务流程图和使用示例,帮助理解Gemini CLI的工作原理。

🔹Gemini CLI开源代码解析,介绍其核心组件、架构、方法及功能。主要包含cli和core两部分,重点分析cli部分的核心通信桥梁acpPeer.ts,涵盖入口函数、核心代理类GeminiAgent及工具方法。

🔹详细解析了GeminiAgent类的结构、生命周期方法、通信协议处理、认证管理、消息处理引擎、工具调用系统及文件内容解析器等关键模块,展示了Gemini CLI如何处理用户消息、执行工具调用、构建LLM提示词等过程。

🔹分析了runAcpPeer、sendUserMessage、#runTool和#resolveUserMessage等核心方法,包括入口函数建立ACP连接、消息处理引擎处理用户消息并生成响应、工具执行器安全执行工具调用、提示词构建器将用户消息转换为LLM可理解的格式等。

🔹通过业务流程图和使用示例,帮助理解Gemini CLI的工作原理,包括用户输入消息的处理流程、文件内容解析器的智能路径解析、提示词构建、文件内容读取及格式化输出等环节。

🔹总结指出Gemini CLI大体并不复杂,主要依赖基座模型本身的能力,agent主要作用是为LLM提供合适的工具以及上下文。

Gemini CLI 代码解析


前言

Gemini CLI 已经开源发布了一段时间了,使用了下感觉功能还是很强大的。代码开源也给了我们一个很好的机会来观摩这样一个功能强大的 agent 到底是怎么工作的。简单阅读了一下代码,和大家分享一下所得。水平有限,如有错漏还请各位大佬不吝指出。

1. 概述

将 Gemini CLI 代码下载到本地之后我们能发现主要代码分为了 cli 和 core 两部分,cli 中主要是与用户交互和主要业务逻辑调度的部分,core 中则主要是一些底层功能的实现,我们今天主要关注的就是 cli 部分处理核心逻辑的 packages/cli/src/acp/acpPeer.ts

acpPeer.ts 是 Gemini CLI 中客户端与 Gemini API 之间的核心通信桥梁。该文件包含三个主要组件:


2. 整体架构

2.1 核心类结构

class GeminiAgent implements Agent {  // 状态管理  chat?: GeminiChat;  pendingSend?: AbortController;  // 生命周期方法  initialize(_: InitializeParams): Promise<InitializeResponse>  authenticate(): Promise<void>  cancelSendMessage(): Promise<void>  sendUserMessage(params: SendUserMessageParams): Promise<void>  // 私有工具方法  #runTool(abortSignal: AbortSignal, promptId: string, fc: FunctionCall): Promise<PartListUnion>  #resolveUserMessage(message: SendUserMessageParams, abortSignal: AbortSignal): Promise<Part[]>  #debug(msg: string): void}

2.2 分析

整体架构

主要包含两个核心部分:

核心功能模块
    通信协议处理层
    认证管理模块
    消息处理引擎
    工具调用系统
    文件内容解析器
业务流程图(其中的path即为用户在cli中通过@来引用的文件或目录)
graph TD    A[用户输入消息] --> B[解析 path 引用]    B --> C[读取文件内容]    C --> D[构建LLM提示词]    D --> E[发送给Gemini]    E --> F[流式接收响应]    F --> G[是否包含工具调用?]    G -->|是| H[执行工具调用]    G -->|否| I[直接返回文本]    H --> J[需要确认?]    J -->|是| K[用户确认]    J -->|否| L[直接执行]    K --> M[根据结果继续]    L --> M[根据结果继续]    M --> N[返回结果给客户端]

3. 核心方法详细分析

3.1 runAcpPeer - 入口函数

功能:建立 ACP 客户端连接
输入:Config 配置对象,LoadedSettings 设置对象
输出:无(建立连接)

export async function runAcpPeer(config: Config, settings: LoadedSettings) {  // 1. 设置标准输入输出流  const stdout = Writable.toWeb(process.stdout) as WritableStream;  const stdin = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;  // 2. 重定向控制台输出到 stderr  console.log = console.error;  console.info = console.error;  console.debug = console.error;  // 3. 建立 ACP 连接  new acp.ClientConnection(    (client: acp.Client) => new GeminiAgent(config, settings, client),    stdout,    stdin,  );}

3.2 sendUserMessage - 消息处理引擎

功能:处理用户消息并生成响应
输入SendUserMessageParams 包含消息块
输出Promise<void>

执行流程图
flowchart TD    Start([用户消息]) --> Cancel[取消之前请求]    Cancel --> CheckChat{Chat存在?}    CheckChat -->|否| CreateChat[创建GeminiChat]    CheckChat -->|是| SkipCreate[跳过创建]    CreateChat --> Resolve[解析用户消息]    SkipCreate --> Resolve        Resolve --> Build[构建LLM消息] --> Stream[发送流式请求]    Stream --> Process{处理响应}    Process -->|文本| SendText[发送给用户]    Process -->|工具调用| RunTools[执行工具]        RunTools --> BuildNext[构建下条消息]    BuildNext --> Stream        Process -->|完成| End([结束])
关键代码
async sendUserMessage(params: acp.SendUserMessageParams): Promise<void> {  // 1. 初始化  this.pendingSend?.abort();  const pendingSend = new AbortController();  this.pendingSend = pendingSend;  // 2. 解析消息  const parts = await this.#resolveUserMessage(params, pendingSend.signal);  let nextMessage: Content | null = { role: 'user', parts };  // 3. 主循环  while (nextMessage !== null) {    const responseStream = await chat.sendMessageStream({      message: nextMessage.parts,      tools: toolRegistry.getFunctionDeclarations()    });    // 处理响应...    nextMessage = toolCalls.length > 0 ? { role: 'user', parts: toolResponseParts } : null;  }}

3.3 #runTool - 工具执行器

功能:安全执行工具调用
输入AbortSignal, promptId, FunctionCall 输出Promise<PartListUnion>

执行流程图
flowchart TD    Start([工具调用]) --> Validate{验证输入}    Validate -->|无效| Error1[错误: Missing name]    Validate -->|有效| FindTool[查找工具]        FindTool --> Found{工具存在?}    Found -->|否| Error2[错误: Tool not found]    Found -->|是| CheckConfirm{需要确认?}        CheckConfirm -->|是| ShowDialog[显示确认]    ShowDialog --> UserChoice{用户选择}    UserChoice -->|拒绝| Error3[错误: 用户拒绝]    UserChoice -->|允许| Exec[执行工具]        CheckConfirm -->|否| Exec        Exec --> Success{执行成功?}    Success -->|是| ReturnSuccess[返回成功结果]    Success -->|否| Error4[返回错误结果]        Error1 --> ReturnError[返回错误响应]    Error2 --> ReturnError    Error3 --> ReturnError    Error4 --> ReturnError
关键代码
async #runTool(abortSignal: AbortSignal, promptId: string, fc: FunctionCall) {  // 1. 验证和工具查找  if (!fc.name) return errorResponse(new Error('Missing function name'));  const tool = toolRegistry.getTool(fc.name);  if (!tool) return errorResponse(new Error('Tool not found'));  // 2. 用户确认  const confirmationDetails = await tool.shouldConfirmExecute(args, abortSignal);  if (confirmationDetails) {    const result = await this.client.requestToolCallConfirmation({...});    // 处理用户响应...  }  // 3. 执行工具  try {    const toolResult = await tool.execute(args, abortSignal);    // 更新UI状态...    return convertToFunctionResponse(fc.name, callId, toolResult.llmContent);  } catch (e) {    return errorResponse(e);  }}

3.4 #resolveUserMessage - 提示词构建器

功能:将用户消息转换为LLM可理解的格式
输入:SendUserMessageParams, AbortSignal
输出:Promise<Part[]>

执行流程图(其中的path即为用户在cli中通过@来引用的文件或目录)
flowchart TD    Start([开始解析用户消息]) --> ParseChunks{解析消息块}    ParseChunks --> ExtractPaths[提取 path 引用]    ExtractPaths --> CheckPaths{ path 引用存在?}        CheckPaths -->|不存在| ReturnText[直接返回文本]    CheckPaths -->|存在| InitProcessing[初始化处理环境]        InitProcessing --> SetupServices[设置文件发现服务]    SetupServices --> ProcessEachPath[遍历每个 path]        ProcessEachPath --> GitIgnoreCheck{Git忽略检查}    GitIgnoreCheck -->|被忽略| LogIgnored[记录忽略日志]    GitIgnoreCheck -->|允许| PathTypeCheck{路径类型检查}        PathTypeCheck -->|绝对路径| SecurityCheck{安全路径检查}    PathTypeCheck -->|相对路径| ResolvePath[解析相对路径]        SecurityCheck -->|安全| StatCheck{文件/目录检查}    SecurityCheck -->|不安全| SkipPath[跳过路径]        StatCheck -->|文件| AddFileSpec[添加文件规格]    StatCheck -->|目录| ConvertGlob[转换为glob模式]        ResolvePath --> TryStat[尝试文件状态]    TryStat -->|存在| AddResolvedPath[添加解析路径]    TryStat -->|不存在| TryGlob{启用glob搜索?}        TryGlob -->|启用| SearchGlob[执行glob搜索]    TryGlob -->|禁用| SkipPath2[跳过路径]        SearchGlob --> FoundMatch{找到匹配?}    FoundMatch -->|找到| UseFirstMatch[使用第一个匹配]    FoundMatch -->|未找到| LogNotFound[记录未找到日志]        LogIgnored --> NextPath[处理下一个路径]    SkipPath --> NextPath    AddFileSpec --> NextPath    ConvertGlob --> NextPath    AddResolvedPath --> NextPath    UseFirstMatch --> NextPath    SkipPath2 --> NextPath    LogNotFound --> NextPath        NextPath --> MorePaths{还有路径?}    MorePaths -->|是| ProcessEachPath    MorePaths -->|否| BuildQuery[构建查询文本]        BuildQuery --> CheckEmpty{有效路径为空?}    CheckEmpty -->|为空| ReturnDirect[直接返回文本]    CheckEmpty -->|不为空| ReadFiles[读取文件内容]        ReadFiles --> ShowProgress[显示读取进度]    ShowProgress --> ExecuteRead[执行 read_many_files]    ExecuteRead --> FormatContent[格式化内容]        FormatContent --> RegexParse{使用正则解析}    RegexParse -->|匹配| ExtractFileContent[提取文件内容]    RegexParse -->|不匹配| KeepRaw[保持原始格式]        ExtractFileContent --> BuildParts[构建Part数组]    KeepRaw --> BuildParts        BuildParts --> AddHeaderFooter[添加头部尾部标记]    AddHeaderFooter --> ReturnParts[返回完整Part数组]        ReturnText --> Done([完成])    ReturnDirect --> Done    ReturnParts --> Done        ErrorRead --> HandleError[处理错误]    HandleError --> ReturnError[返回错误信息]    ReturnError --> Done
关键代码
async #resolveUserMessage(message: SendUserMessageParams, abortSignal: AbortSignal) {  // 1. 提取路径引用  const atPathCommandParts = message.chunks.filter((part) => 'path' in part);  if (atPathCommandParts.length === 0) {    return simpleTextConversion();  }  // 2. 路径解析  for (const atPathPart of atPathCommandParts) {    if (fileDiscovery.shouldGitIgnoreFile(pathName)) continue;        const absolutePath = path.resolve(this.config.getTargetDir(), pathName);    if (isWithinRoot(absolutePath, this.config.getTargetDir())) {      // 处理文件或目录...    }  }  // 3. 构建提示词  let initialQueryText = buildQueryText(message.chunks, pathMappings);    // 4. 读取文件内容  const result = await readManyFilesTool.execute({    paths: pathSpecsToRead,    respectGitIgnore  });  // 5. 格式化输出  return formatContentForLLM(initialQueryText, result.llmContent);}
详细执行步骤
阶段1:输入分析与路径提取(行333-347)
// 1. 提取所有 @path 引用const atPathCommandParts = message.chunks.filter((part) => 'path' in part);// 2. 快速路径:没有文件引用if (atPathCommandParts.length === 0) {  return message.chunks.map((chunk) => {    if ('text' in chunk) {      return { text: chunk.text };    } else {      throw new Error('Unexpected chunk type');    }  });}
阶段2:环境初始化(行349-365)
// 获取文件发现服务const fileDiscovery = this.config.getFileService();const respectGitIgnore = this.config.getFileFilteringRespectGitIgnore();// 初始化收集容器const pathSpecsToRead: string[] = [];           // 要读取的文件规格const atPathToResolvedSpecMap = new Map<string, string>();  // 路径映射const contentLabelsForDisplay: string[] = [];   // 显示标签const ignoredPaths: string[] = [];             // 被忽略的路径// 获取工具const toolRegistry = await this.config.getToolRegistry();const readManyFilesTool = toolRegistry.getTool('read_many_files');const globTool = toolRegistry.getTool('glob');
阶段3:智能路径解析(行366-467)

每个路径的处理流程:

3.1 Git忽略检查
if (fileDiscovery.shouldGitIgnoreFile(pathName)) {  ignoredPaths.push(pathName);  // 根据配置决定是否跳过  const reason = respectGitIgnore     ? 'git-ignored and will be skipped'     : 'ignored by custom patterns';  console.warn(`Path ${pathName} is ${reason}.`);  continue;}
3.2 路径安全验证
const absolutePath = path.resolve(this.config.getTargetDir(), pathName);if (isWithinRoot(absolutePath, this.config.getTargetDir())) {  // 安全检查通过  const stats = await fs.stat(absolutePath);  if (stats.isDirectory()) {    // 目录自动展开为glob模式    currentPathSpec = pathName.endsWith('/')       ? `${pathName}**`       : `${pathName}/**`;  }}
3.3 容错机制 - Glob搜索
if (isNodeError(error) && error.code === 'ENOENT') {  if (this.config.getEnableRecursiveFileSearch() && globTool) {    const globResult = await globTool.execute({      pattern: `**/*${pathName}*`,      path: this.config.getTargetDir(),    }, abortSignal);    // 使用第一个匹配的文件  }}
阶段4:提示词构建(行469-510)
智能文本拼接算法:
let initialQueryText = '';for (let i = 0; i < message.chunks.length; i++) {  const chunk = message.chunks[i];  if ('text' in chunk) {    initialQueryText += chunk.text;  } else {    // 处理 @path 引用    const resolvedSpec = atPathToResolvedSpecMap.get(chunk.path);    if (resolvedSpec) {      initialQueryText += `@${resolvedSpec}`;    } else {      // 保留原始引用用于错误提示      initialQueryText += `@${chunk.path}`;    }  }}
阶段5:文件内容读取(行520-590)
5.1 执行读取
const toolArgs = {  paths: pathSpecsToRead,  respectGitIgnore,};const toolCall = await this.client.pushToolCall({  icon: readManyFilesTool.icon,  label: readManyFilesTool.getDescription(toolArgs),});const result = await readManyFilesTool.execute(toolArgs, abortSignal);
5.2 内容格式化

使用正则表达式解析文件内容:

const fileContentRegex = /^--- (.*?) ---\n\n([\s\S]*?)\n\n$/;processedQueryParts.push({  text: '\n--- Content from referenced files ---',});for (const part of result.llmContent) {  if (typeof part === 'string') {    const match = fileContentRegex.exec(part);    if (match) {      const filePathSpecInContent = match[1];      const fileActualContent = match[2].trim();      processedQueryParts.push({        text: `\nContent from @${filePathSpecInContent}:\n`,      });      processedQueryParts.push({ text: fileActualContent });    }  }}processedQueryParts.push({  text: '\n--- End of content ---',});
关键数据流
输入到输出的转换
用户消息块├── 文本块 → 直接保留├── @path块 → 路径解析 → 文件读取 → 内容注入└── 无效路径 → 保留原始引用用于错误提示
路径解析映射
用户输入 @src/main.ts    ↓解析为绝对路径 /project/src/main.ts    ↓安全检查 ✓    ↓读取文件内容    ↓格式化为 LLM 提示词
错误处理策略
多层容错机制
用户反馈机制
性能优化
并行处理
内存管理
输出格式示例

最终构建的提示词结构:

[  { text: "用户原始问题" },  { text: "\n--- Content from referenced files ---" },  { text: "\nContent from @src/main.ts:\n" },  { text: "import express from 'express'..." },  { text: "\nContent from @package.json:\n" },  { text: "{\n  \"name\": \"my-app\"\n}" },  { text: "\n--- End of content ---" }]

这个方法是整个系统的智能提示词引擎,通过多层解析 + 智能容错 + 格式化注入实现了从用户输入到LLM可理解格式的转换。


7. 使用示例

7.1 基本消息处理

// 用户输入:"请解释这段代码 @src/main.ts"const result = await agent.sendUserMessage({  chunks: [    { text: "请解释这段代码" },    { path: "src/main.ts" }  ]});

7.2 复杂场景处理

// 用户输入:"优化这个目录下的所有文件 @src/utils/"const result = await agent.sendUserMessage({  chunks: [    { text: "优化这个目录下的所有文件" },    { path: "src/utils/" }  ]});// 自动展开为 src/utils/** 并读取所有文件

8. 总结

以上就是 Gemini cli 的主要运行逻辑,可以看到其实大体并不复杂,google 也并没有在其中设置什么特殊的提示词,主要还是依赖基座模型本身的能力。 agent 主要的作用还是为 LLM 来提供合适的工具以及上下文。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Gemini CLI 代码解析 Agent接口 工具调用 消息处理
相关文章