All posts
Synced from Juejin2025-12-1010 min read

Gemini CLI 深度源码分析:从零到一理解 AI 命令行代理的设计与实现

本文基于 Gemini CLI v0.21.0 源码进行深度分析,带你从架构设计、核心原理到实现细节,全方位理解这个 Google 开源的 AI 命令行代理工具。 目录 1. 什么是 Gemini CLI? 2. 整体架构设计 3. 核心模块详解 4. 记忆系统:让 AI "记住" 你的偏好 5. 上下文工程:如何管理有限的 Token 窗口...

SynopsisThis post is preserved inside the rebuilt blog shell while keeping the original Chinese content intact.Original source

Gemini CLI 深度源码分析:从零到一理解 AI 命令行代理的设计与实现

本文基于 Gemini CLI v0.21.0 源码进行深度分析,带你从架构设计、核心原理到实现细节,全方位理解这个 Google 开源的 AI 命令行代理工具。

目录

  1. 什么是 Gemini CLI?
  2. 整体架构设计
  3. 核心模块详解
  4. 记忆系统:让 AI "记住" 你的偏好
  5. 上下文工程:如何管理有限的 Token 窗口
  6. 工具系统:AI 如何执行实际操作
  7. MCP 协议:标准化的 AI 工具扩展
  8. 通信协议与流式响应
  9. Hook 系统:可扩展的生命周期钩子
  10. UI 层:终端中的 React 应用
  11. 使用指南
  12. 总结与思考

1. 什么是 Gemini CLI?

1.1 简单来说

Gemini CLI 是 Google 开源的一个 AI 命令行代理(AI Agent)。你可以把它想象成一个住在你终端里的智能助手,它能:

  • 理解你的自然语言指令:比如"帮我修复这个 bug"、"给这个函数加上单元测试"
  • 自动执行操作:读写文件、运行命令、搜索代码
  • 记住你的偏好:你喜欢用 TypeScript、偏好 4 空格缩进等
  • 持续对话:可以恢复之前的会话继续工作

1.2 技术栈一览

类别 技术 说明
语言 TypeScript 5.3+ 核心开发语言
运行时 Node.js 20+ 最低版本要求
UI 框架 React 19 + Ink 终端中的 React
包管理 npm workspaces Monorepo 结构
构建工具 esbuild 快速打包
测试框架 Vitest 单元测试
AI SDK @google\u002Fgenai Gemini API 客户端
扩展协议 MCP SDK v1.23 工具扩展支持

1.3 项目结构

gemini-cli\u002F
├── packages\u002F
│   ├── cli\u002F          # 前端 UI 层 - 用户交互界面
│   ├── core\u002F         # 核心业务逻辑 - AI 调用、工具执行
│   ├── a2a-server\u002F   # Agent-to-Agent 服务器
│   └── vscode-ide-companion\u002F  # VS Code 插件
├── bundle\u002F           # 打包后的可执行文件
├── scripts\u002F          # 构建和自动化脚本
└── integration-tests\u002F # 集成测试

2. 整体架构设计

2.1 分层架构图

graph LR
    subgraph "用户层"
        A[用户输入] --> B[CLI 入口<br\u002F>gemini.tsx]
    end

    subgraph &#34;UI 层 - packages\u002Fcli&#34;
        B --> C[AppContainer<br\u002F>React 应用容器]
        C --> D[Composer<br\u002F>输入组件]
        C --> E[MessageDisplay<br\u002F>消息展示]
        C --> F[ToolCallDisplay<br\u002F>工具调用展示]
    end

    subgraph &#34;服务层 - packages\u002Fcore&#34;
        D --> G[GeminiClient<br\u002F>对话管理器]
        G --> H[GeminiChat<br\u002F>API 交互]
        G --> I[ToolScheduler<br\u002F>工具调度器]
        G --> J[ContextManager<br\u002F>上下文管理]
    end

    subgraph &#34;工具层&#34;
        I --> K[内置工具<br\u002F>read\u002Fwrite\u002Fedit\u002Fshell]
        I --> L[MCP 工具<br\u002F>外部扩展]
    end

    subgraph &#34;外部服务&#34;
        H --> M[Gemini API<br\u002F>REST + SSE]
        L --> N[MCP Server<br\u002F>Stdio\u002FSSE]
    end

    style A fill:#e1f5fe
    style M fill:#fff3e0
    style N fill:#fff3e0

2.2 核心设计原则

项目采用了几个关键的设计原则:

1. 前后端分离

  • packages\u002Fcli:负责用户界面、输入处理、显示渲染
  • packages\u002Fcore:负责业务逻辑、API 调用、工具执行

2. 流式处理

  • 使用 JavaScript 的 AsyncGenerator 实现流式响应
  • 用户可以实时看到 AI 的输出,而不是等待完整响应

3. 插件化工具系统

  • 内置工具通过统一的 Tool 接口注册
  • 外部工具通过 MCP 协议动态发现和调用

4. 分层记忆

  • 全局记忆:用户级别的偏好设置
  • 项目记忆:项目级别的约定
  • 会话记忆:当前对话的上下文

3. 核心模块详解

3.1 启动流程

让我们跟随代码,看看当你在终端输入 gemini 时发生了什么:

sequenceDiagram
    participant User as 用户
    participant CLI as gemini.tsx
    participant Config as 配置加载
    participant Auth as 认证模块
    participant App as AppContainer
    participant Client as GeminiClient

    User->>CLI: 执行 gemini 命令
    CLI->>Config: 加载配置 loadCliConfig()
    Config-->>CLI: 配置对象
    CLI->>Auth: 检查认证状态
    Auth-->>CLI: 认证结果

    alt 需要认证
        CLI->>User: 显示认证界面
        User->>CLI: 完成认证
    end

    CLI->>App: 渲染 React 应用
    App->>Client: 初始化 GeminiClient
    Client->>Client: 加载工具、记忆
    Client-->>App: 准备就绪
    App-->>User: 显示输入框

关键代码位置:

  • 入口文件:packages\u002Fcli\u002Fsrc\u002Fgemini.tsx
  • 配置加载:packages\u002Fcli\u002Fsrc\u002Fconfig\u002Fconfig.ts
  • 应用容器:packages\u002Fcli\u002Fsrc\u002Fui\u002FAppContainer.tsx

3.2 GeminiClient - 对话管理的核心

GeminiClient 是整个系统的"大脑",它协调了对话的方方面面:

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fcore\u002Fclient.ts
export class GeminiClient {
  private chat?: GeminiChat;           \u002F\u002F 实际的 API 交互
  private sessionTurnCount = 0;        \u002F\u002F 会话轮次计数
  private loopDetector: LoopDetectionService;      \u002F\u002F 循环检测
  private compressionService: ChatCompressionService; \u002F\u002F 上下文压缩

  \u002F\u002F 最大轮次限制,防止无限循环
  private static MAX_TURNS = 100;
}

主要职责:

职责 说明
对话初始化 创建 GeminiChat 实例,设置系统提示
消息发送 处理用户输入,调用 API
工具调用协调 当 AI 请求工具时,协调执行
上下文压缩 当 token 超限时,压缩历史
循环检测 防止 AI 陷入无限工具调用循环
会话恢复 支持从之前的会话继续

3.3 Turn - 一次对话轮次

每次用户发送消息到收到完整回复,被称为一个 "Turn":

graph LR
    A[用户消息] --> B[发送到 API]
    B --> C{响应类型}
    C -->|文本| D[显示内容]
    C -->|工具调用| E[执行工具]
    C -->|思考| F[显示思考过程]
    E --> G[工具结果]
    G --> B
    D --> H[Turn 结束]
    F --> H

事件类型定义:

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fcore\u002Fturn.ts
export enum GeminiEventType {
  Content = 'content',           \u002F\u002F 文本内容
  ToolCallRequest = 'tool_call_request',    \u002F\u002F 工具调用请求
  ToolCallResponse = 'tool_call_response',  \u002F\u002F 工具调用响应
  Thought = 'thought',           \u002F\u002F AI 的思考过程
  Finished = 'finished',         \u002F\u002F 完成
  Error = 'error',               \u002F\u002F 错误
  ChatCompressed = 'chat_compressed',       \u002F\u002F 上下文被压缩
  LoopDetected = 'loop_detected',           \u002F\u002F 检测到循环
  \u002F\u002F ...
}

4. 记忆系统:让 AI "记住" 你的偏好

4.1 记忆的层次结构

Gemini CLI 实现了一个三层记忆系统,让 AI 能够记住从全局偏好到项目特定约定的各种信息:

graph LR
    subgraph &#34;记忆层次(优先级从低到高)&#34;
        A[&#34;全局记忆<br\u002F>~\u002F.gemini\u002FGEMINI.md&#34;] --> B[&#34;项目记忆<br\u002F>project\u002FGEMINI.md&#34;]
        B --> C[&#34;子目录记忆<br\u002F>project\u002Fsrc\u002FGEMINI.md&#34;]
    end

    subgraph &#34;记忆内容示例&#34;
        D[&#34;全局偏好<br\u002F>- 喜欢 TypeScript<br\u002F>- 偏好 4 空格缩进&#34;]
        E[&#34;项目约定<br\u002F>- 使用 ESLint<br\u002F>- 测试用 Vitest&#34;]
        F[&#34;模块特定<br\u002F>- 组件用函数式<br\u002F>- 状态用 hooks&#34;]
    end

    A --- D
    B --- E
    C --- F

    style A fill:#e8f5e9
    style B fill:#e3f2fd
    style C fill:#fce4ec

4.2 记忆文件格式

记忆存储在 Markdown 文件中,便于人类阅读和编辑:

# GEMINI.md - 项目记忆文件

## 项目概述
这是一个 React + TypeScript 前端项目。

## 编码约定
- 使用函数式组件,不用类组件
- 状态管理使用 React hooks
- 样式使用 Tailwind CSS

## 构建和测试
- 构建命令:`npm run build`
- 测试命令:`npm test`
- Lint 命令:`npm run lint`

## Gemini Added Memories
- 用户偏好使用 async\u002Fawait 而不是 .then()
- 日志使用 console.log 而不是 alert

4.3 记忆发现机制

sequenceDiagram
    participant App as 应用
    participant Discovery as MemoryDiscovery
    participant FS as 文件系统
    participant Processor as ImportProcessor

    App->>Discovery: loadServerHierarchicalMemory()

    Note over Discovery: 第一步:查找全局记忆
    Discovery->>FS: 检查 ~\u002F.gemini\u002FGEMINI.md
    FS-->>Discovery: 文件内容(如果存在)

    Note over Discovery: 第二步:向上查找项目根
    Discovery->>FS: findProjectRoot(查找 .git)
    FS-->>Discovery: 项目根目录路径

    Note over Discovery: 第三步:收集所有 GEMINI.md
    loop 从当前目录到项目根
        Discovery->>FS: 检查目录下的 GEMINI.md
        FS-->>Discovery: 文件路径
    end

    Note over Discovery: 第四步:处理 @import
    Discovery->>Processor: processImports()
    loop 递归处理导入
        Processor->>FS: 读取被导入的文件
        FS-->>Processor: 文件内容
    end

    Processor-->>Discovery: 合并后的记忆内容
    Discovery-->>App: 最终的用户记忆字符串

核心代码解析:

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Futils\u002FmemoryDiscovery.ts
async function getGeminiMdFilePathsInternal(
  currentWorkingDirectory: string,
  includeDirectoriesToReadGemini: readonly string[],
  userHomePath: string,
  \u002F\u002F ...
): Promise {
  \u002F\u002F 1. 查找全局记忆文件
  const globalMemoryPath = path.join(homedir(), '.gemini', 'GEMINI.md');

  \u002F\u002F 2. 从当前目录向上查找项目根目录
  const projectRoot = await findProjectRoot(currentWorkingDirectory);

  \u002F\u002F 3. 收集路径上的所有 GEMINI.md 文件
  \u002F\u002F 顺序:全局 -> 项目根 -> 子目录(越近优先级越高)
}

4.4 保存记忆

当你让 AI "记住" 某些事情时,会调用 MemoryTool

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Ftools\u002FmemoryTool.ts
export class MemoryTool extends BaseDeclarativeTool {
  \u002F\u002F 保存事实到记忆文件
  async performAddMemoryEntry(fact: string): Promise {
    \u002F\u002F 1. 读取当前 GEMINI.md 内容
    const content = await fs.readFile(memoryPath, 'utf-8');

    \u002F\u002F 2. 找到 &#34;## Gemini Added Memories&#34; 部分
    \u002F\u002F 3. 添加新的记忆项
    const newContent = addMemoryEntry(content, fact);

    \u002F\u002F 4. 写回文件
    await fs.writeFile(memoryPath, newContent);
  }
}

5. 上下文工程:如何管理有限的 Token 窗口

5.1 Token 的概念

在与 AI 对话时,每条消息都会消耗 "Token"。Token 大致可以理解为:

  • 英文:大约 4 个字符 = 1 个 Token
  • 中文:大约 1-2 个字符 = 1 个 Token

每个模型都有 Token 限制:

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fcore\u002FtokenLimits.ts
export function tokenLimit(model: Model): TokenCount {
  switch (model) {
    case 'gemini-1.5-pro':
      return 2_097_152;   \u002F\u002F 200 万 Token
    case 'gemini-2.5-pro':
    case 'gemini-2.5-flash':
      return 1_048_576;   \u002F\u002F 100 万 Token
    default:
      return 1_048_576;
  }
}

5.2 上下文组成

pie title 上下文 Token 分配(示例)
    &#34;System Prompt (系统提示)&#34; : 15
    &#34;User Memory (用户记忆)&#34; : 10
    &#34;对话历史&#34; : 50
    &#34;工具输出&#34; : 20
    &#34;环境上下文&#34; : 5

5.3 上下文管理策略

graph LR
    subgraph &#34;上下文组成&#34;
        A[System Prompt<br\u002F>系统提示] --> E[总上下文]
        B[对话历史<br\u002F>Messages] --> E
        C[用户记忆<br\u002F>GEMINI.md] --> E
        D[环境上下文<br\u002F>日期\u002FOS\u002F目录] --> E
    end

    E --> F{Token 数量}
    F -->|小于50%限制| G[正常继续]
    F -->|大于50%限制| H[触发压缩]

    H --> I[保留最近 30%]
    H --> J[压缩旧历史为状态快照]
    I --> K[新的对话历史]
    J --> K

5.4 对话压缩的实现

当上下文过长时,系统会自动压缩旧的对话历史:

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fservices\u002FchatCompressionService.ts
export const DEFAULT_COMPRESSION_TOKEN_THRESHOLD = 0.5;  \u002F\u002F 50% 时触发
export const COMPRESSION_PRESERVE_THRESHOLD = 0.3;       \u002F\u002F 保留最近 30%

export class ChatCompressionService {
  async compress(chat: GeminiChat, ...): Promise<{
    newHistory: Content[] | null;
    info: ChatCompressionInfo;
  }> {
    \u002F\u002F 1. 检查是否需要压缩
    if (tokenCount < 0.5 * tokenLimit(model)) {
      return { newHistory: null, compressionStatus: 'NOOP' };
    }

    \u002F\u002F 2. 确定分割点(保留最近 30%
    const splitPoint = findCompressSplitPoint(
      history,
      1 - 0.3  \u002F\u002F 压缩前 70%
    );

    \u002F\u002F 3. 使用 AI 生成状态快照
    const summary = await this.generateStateSnapshot(historyToCompress);

    \u002F\u002F 4. 组合新历史
    return {
      newHistory: [
        { role: 'user', parts: [{ text: summary }] },
        { role: 'model', parts: [{ text: 'Got it!' }] },
        ...historyToKeep  \u002F\u002F 保留的最近历史
      ],
      compressionStatus: 'COMPRESSED'
    };
  }
}

5.5 状态快照格式

压缩后的状态快照包含关键信息:


    
        实现用户认证功能
    

    
        - Build Command: `npm run build`
        - Testing: Tests run with `npm test`
        - 使用 JWT 进行认证
    

    
        - CWD: `\u002Fhome\u002Fuser\u002Fproject\u002Fsrc`
        - READ: `package.json`, `auth\u002Flogin.ts`
        - MODIFIED: `auth\u002Fjwt.ts`
        - CREATED: `auth\u002F__tests__\u002Fjwt.test.ts`
    

    
        - 创建了 JWT 验证函数
        - 运行测试,3 个通过,1 个失败
        - 修复了 token 过期验证的 bug
    

    
        1. [DONE] 实现 JWT 生成
        2. [DONE] 实现 JWT 验证
        3. [IN PROGRESS] 修复测试失败
        4. [TODO] 集成到登录流程
    

6. 工具系统:AI 如何执行实际操作

6.1 工具的概念

工具(Tool)是 AI 与外部世界交互的方式。当 AI 需要读文件、执行命令时,它会"调用工具":

sequenceDiagram
    participant AI as Gemini AI
    participant Scheduler as ToolScheduler
    participant Tool as 具体工具
    participant FS as 文件系统\u002FShell

    AI->>Scheduler: 请求调用 read_file
    Scheduler->>Scheduler: 验证参数
    Scheduler->>Tool: 创建 ReadFileTool 实例
    Tool->>Tool: shouldConfirmExecute()

    alt 需要确认
        Tool-->>User: 显示确认对话框
        User-->>Tool: 确认执行
    end

    Tool->>FS: 读取文件
    FS-->>Tool: 文件内容
    Tool-->>Scheduler: 返回结果
    Scheduler-->>AI: 工具调用结果

6.2 内置工具列表

工具名 功能 类型 需确认
read_file 读取文件内容(支持文本、图片、PDF) Read
write_file 创建或覆写文件 Write
edit 编辑文件的特定部分 Write
shell 执行 Shell 命令 Execute
grep 在文件中搜索文本 Read
glob 匹配文件路径模式 Read
ls 列出目录内容 Read
web_fetch 获取网页内容 Read
web_search 执行网络搜索 Read
memory 保存信息到记忆 Write

6.3 工具的生命周期

stateDiagram-v2
    [*] --> Pending: AI 请求工具
    Pending --> Validating: 开始验证
    Validating --> Scheduled: 验证通过
    Validating --> Error: 验证失败

    Scheduled --> Confirming: 需要确认
    Scheduled --> Executing: 无需确认

    Confirming --> Executing: 用户同意
    Confirming --> Cancelled: 用户拒绝

    Executing --> Success: 执行成功
    Executing --> Error: 执行失败

    Success --> [*]
    Error --> [*]
    Cancelled --> [*]

6.4 工具基类设计

所有工具都继承自 BaseDeclarativeTool

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Ftools\u002Ftools.ts
export abstract class BaseDeclarativeTool {
  constructor(
    readonly name: string,           \u002F\u002F 工具名称
    readonly displayName: string,    \u002F\u002F 显示名称
    readonly description: string,    \u002F\u002F 描述(给 AI 看)
    readonly kind: 'Read' | 'Write' | 'Execute',  \u002F\u002F 类型
    readonly schema: FunctionDeclaration,  \u002F\u002F 参数 schema
    readonly requiresConfirmation: boolean \u002F\u002F 是否需要确认
  ) {}

  \u002F\u002F 验证参数
  protected abstract validateToolParamValues(params: TParams): string | null;

  \u002F\u002F 创建执行实例
  protected abstract createInvocation(params: TParams): ToolInvocation;
}

\u002F\u002F 工具调用实例接口
export interface ToolInvocation {
  params: TParams;
  getDescription(): string;
  toolLocations(): ToolLocation[];
  shouldConfirmExecute(signal: AbortSignal): Promise;
  execute(signal: AbortSignal, updateOutput?: Function): Promise;
}

6.5 工具调用示例

read_file 为例,看看一次完整的工具调用:

\u002F\u002F 1. AI 请求调用工具
{
  name: &#34;read_file&#34;,
  args: {
    file_path: &#34;\u002Fpath\u002Fto\u002Ffile.ts&#34;,
    should_read_entire_file: true
  }
}

\u002F\u002F 2. 验证参数
\u002F\u002F - 检查路径是否存在
\u002F\u002F - 检查是否有权限读取

\u002F\u002F 3. 执行读取
const content = await fs.readFile(filePath, 'utf-8');

\u002F\u002F 4. 返回结果
{
  llmContent: [{ text: content }],   \u002F\u002F 给 AI 看的内容
  returnDisplay: {
    type: 'markdown',
    content: '```typescript\
' + content + '\
```'
  }
}

7. MCP 协议:标准化的 AI 工具扩展

7.1 什么是 MCP?

MCP(Model Context Protocol)是一个开放协议,用于标准化 AI 模型与外部工具之间的通信。就像 USB 让各种设备能够连接电脑一样,MCP 让各种工具能够接入 AI。

graph TB
    subgraph &#34;Gemini CLI&#34;
        A[MCP Client] --> B[工具注册表]
    end

    subgraph &#34;MCP Servers&#34;
        C[数据库工具<br\u002F>SQL 查询]
        D[API 工具<br\u002F>REST 调用]
        E[自定义工具<br\u002F>任何功能]
    end

    A |Stdio| C
    A |SSE| D
    A |HTTP| E

    style A fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#fff3e0
    style E fill:#fff3e0

7.2 MCP 支持的传输方式

graph LR
    subgraph &#34;传输方式&#34;
        A[Stdio<br\u002F>本地进程]
        B[SSE<br\u002F>Server-Sent Events]
        C[HTTP<br\u002F>Streamable]
    end

    subgraph &#34;适用场景&#34;
        D[本地工具<br\u002F>高性能]
        E[远程服务<br\u002F>实时推送]
        F[Web API<br\u002F>通用兼容]
    end

    A --> D
    B --> E
    C --> F
\u002F\u002F packages\u002Fcore\u002Fsrc\u002Ftools\u002Fmcp-client.ts

\u002F\u002F 1. Stdio 传输 - 通过标准输入\u002F输出与本地进程通信
const transport = new StdioClientTransport({
  command: 'node',
  args: ['.\u002Fmy-mcp-server.js'],
});

\u002F\u002F 2. SSE 传输 - 通过 Server-Sent Events
const transport = new SSEClientTransport(
  new URL('https:\u002F\u002Fmcp-server.example.com')
);

\u002F\u002F 3. HTTP Streamable 传输
const transport = new StreamableHTTPClientTransport(
  new URL('https:\u002F\u002Fapi.example.com\u002Fmcp')
);

7.3 MCP 服务器配置

~\u002F.gemini\u002Fsettings.json 中配置:

{
  &#34;mcpServers&#34;: {
    &#34;my-database&#34;: {
      &#34;command&#34;: &#34;npx&#34;,
      &#34;args&#34;: [&#34;@my-org\u002Fdb-mcp-server&#34;],
      &#34;env&#34;: {
        &#34;DATABASE_URL&#34;: &#34;postgres:\u002F\u002F...&#34;
      }
    },
    &#34;github&#34;: {
      &#34;url&#34;: &#34;https:\u002F\u002Fgithub-mcp.example.com&#34;,
      &#34;auth&#34;: {
        &#34;type&#34;: &#34;oauth&#34;
      }
    }
  }
}

7.4 MCP 客户端实现

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Ftools\u002Fmcp-client.ts
export enum MCPServerStatus {
  DISCONNECTED = 'disconnected',
  CONNECTING = 'connecting',
  CONNECTED = 'connected',
  DISCONNECTING = 'disconnecting'
}

export class McpClient {
  private client: Client | undefined;
  private status: MCPServerStatus = MCPServerStatus.DISCONNECTED;

  async connect(): Promise {
    this.updateStatus(MCPServerStatus.CONNECTING);

    \u002F\u002F 创建连接
    this.client = await connectToMcpServer(
      this.serverName,
      this.serverConfig,
      this.debugMode
    );

    \u002F\u002F 监听工具更新(动态工具发现)
    const capabilities = this.client.getServerCapabilities();
    if (capabilities?.tools?.listChanged) {
      this.client.setNotificationHandler(
        ToolListChangedNotificationSchema,
        async () => {
          await this.refreshTools();  \u002F\u002F 动态更新工具列表
        }
      );
    }

    this.updateStatus(MCPServerStatus.CONNECTED);
  }

  async discoverTools(): Promise {
    const response = await this.client.listTools({});
    return response.tools.map(tool => new DiscoveredMCPTool(tool));
  }
}

8. 通信协议与流式响应

8.1 与 Gemini API 的通信

Gemini CLI 使用 REST + SSE 的混合通信方式:

sequenceDiagram
    participant CLI as Gemini CLI
    participant API as Gemini API

    CLI->>API: POST \u002FgenerateContent<br\u002F>(包含历史、工具、配置)

    Note over API: 开始生成响应

    loop 流式响应 (SSE)
        API-->>CLI: data: {&#34;text&#34;: &#34;正在&#34;}
        API-->>CLI: data: {&#34;text&#34;: &#34;思考&#34;}
        API-->>CLI: data: {&#34;text&#34;: &#34;...&#34;}
    end

    alt 需要调用工具
        API-->>CLI: data: {&#34;functionCall&#34;: {...}}
        CLI->>CLI: 执行工具
        CLI->>API: POST(包含工具结果)
    end

    API-->>CLI: data: {&#34;finishReason&#34;: &#34;STOP&#34;}

8.2 SSE 解析实现

\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fcode_assist\u002Fserver.ts
async *requestStreamingPost(
  method: string,
  req: object,
): Promise> {
  const res = await this.client.request({
    url: this.getMethodUrl(method),
    method: 'POST',
    params: { alt: 'sse' },  \u002F\u002F 请求 SSE 格式
    responseType: 'stream',
    body: JSON.stringify(req),
  });

  \u002F\u002F 解析 SSE 数据流
  const rl = readline.createInterface({
    input: res.data as NodeJS.ReadableStream,
  });

  let bufferedLines: string[] = [];
  for await (const line of rl) {
    if (line.startsWith('data: ')) {
      bufferedLines.push(line.slice(6).trim());
    } else if (line === '') {
      \u002F\u002F 空行表示一个完整的事件
      if (bufferedLines.length > 0) {
        yield JSON.parse(bufferedLines.join('\
')) as T;
        bufferedLines = [];
      }
    }
  }
}

8.3 请求格式

发送给 Gemini API 的请求结构:

interface GenerateContentRequest {
  model: string;                    \u002F\u002F 模型名称
  contents: Content[];              \u002F\u002F 对话历史
  systemInstruction?: Content;      \u002F\u002F 系统提示
  tools?: Tool[];                   \u002F\u002F 可用工具
  toolConfig?: {
    functionCallingConfig: {
      mode: 'AUTO' | 'ANY' | 'NONE' \u002F\u002F 工具调用模式
    }
  };
  generationConfig?: {
    temperature?: number;           \u002F\u002F 温度(创意度)
    topP?: number;                  \u002F\u002F Top-P 采样
    topK?: number;                  \u002F\u002F Top-K 采样
    maxOutputTokens?: number;       \u002F\u002F 最大输出 Token
    thinkingConfig?: {
      thinkingBudget: number;       \u002F\u002F 思考 Token 预算
    }
  };
}

9. Hook 系统:可扩展的生命周期钩子

9.1 Hook 的概念

Hook 允许你在 AI 执行的关键节点插入自定义逻辑,比如:

  • 在工具执行前审批
  • 在模型调用前\u002F后记录日志
  • 在会话开始\u002F结束时执行清理

9.2 可用的 Hook 事件

graph TB
    subgraph &#34;会话生命周期&#34;
        A[SessionStart] --> B[BeforeAgent]
        B --> C[BeforeModel]
        C --> D[AfterModel]
        D --> E[BeforeTool]
        E --> F[AfterTool]
        F --> G[AfterAgent]
        G --> H[SessionEnd]
    end

    subgraph &#34;特殊事件&#34;
        I[PreCompress]
        J[Notification]
        K[BeforeToolSelection]
    end
\u002F\u002F packages\u002Fcore\u002Fsrc\u002Fhooks\u002Ftypes.ts
export enum HookEventName {
  SessionStart = 'SessionStart',     \u002F\u002F 会话开始
  SessionEnd = 'SessionEnd',         \u002F\u002F 会话结束
  BeforeAgent = 'BeforeAgent',       \u002F\u002F 代理执行前
  AfterAgent = 'AfterAgent',         \u002F\u002F 代理执行后
  BeforeTool = 'BeforeTool',         \u002F\u002F 工具执行前
  AfterTool = 'AfterTool',           \u002F\u002F 工具执行后
  BeforeModel = 'BeforeModel',       \u002F\u002F 模型调用前
  AfterModel = 'AfterModel',         \u002F\u002F 模型调用后
  PreCompress = 'PreCompress',       \u002F\u002F 上下文压缩前
  Notification = 'Notification',     \u002F\u002F 通知事件
  BeforeToolSelection = 'BeforeToolSelection' \u002F\u002F 工具选择前
}

9.3 Hook 执行流程

graph LR
    A[事件触发] --> B[查找匹配的 Hooks]
    B --> C{有匹配?}
    C -->|否| D[继续执行]
    C -->|是| E[准备 Hook 输入]
    E --> F[执行 Hook 命令]
    F --> G[解析 Hook 输出]
    G --> H{决定类型}
    H -->|allow| D
    H -->|deny\u002Fblock| I[阻止执行]
    H -->|ask| J[询问用户]
    J --> K{用户决定}
    K -->|同意| D
    K -->|拒绝| I

9.4 配置 Hook

~\u002F.gemini\u002Fsettings.json 中配置:

{
  &#34;hooks&#34;: {
    &#34;BeforeTool&#34;: [
      {
        &#34;matcher&#34;: &#34;shell&#34;,
        &#34;command&#34;: &#34;\u002Fpath\u002Fto\u002Fapprove-shell.sh&#34;,
        &#34;timeout&#34;: 5000
      }
    ],
    &#34;SessionStart&#34;: [
      {
        &#34;command&#34;: &#34;echo 'Session started' >> ~\u002Fgemini.log&#34;
      }
    ]
  }
}

9.5 Hook 输入\u002F输出格式

\u002F\u002F Hook 接收的输入(通过 stdin)
interface HookInput {
  session_id: string;        \u002F\u002F 会话 ID
  transcript_path: string;   \u002F\u002F 对话记录路径
  cwd: string;               \u002F\u002F 当前工作目录
  hook_event_name: string;   \u002F\u002F 事件名称
  timestamp: string;         \u002F\u002F 时间戳
  \u002F\u002F ... 事件特定的数据
}

\u002F\u002F Hook 返回的输出(通过 stdout)
interface HookOutput {
  continue?: boolean;        \u002F\u002F 是否继续执行
  decision?: 'allow' | 'deny' | 'ask' | 'block';  \u002F\u002F 决定
  reason?: string;           \u002F\u002F 原因
  systemMessage?: string;    \u002F\u002F 要添加的系统消息
  suppressOutput?: boolean;  \u002F\u002F 是否抑制输出
}

10. UI 层:终端中的 React 应用

10.1 Ink - 终端中的 React

Gemini CLI 使用 Ink 库在终端中渲染 React 组件。这意味着 UI 代码和普通的 React 应用非常相似:

\u002F\u002F packages\u002Fcli\u002Fsrc\u002Fui\u002Fcomponents\u002FComposer.tsx
export function Composer(): JSX.Element {
  const [input, setInput] = useState('');
  const { sendMessage } = useUIActions();

  const handleSubmit = useCallback(() => {
    sendMessage(input);
    setInput('');
  }, [input, sendMessage]);

  return (
    
      › 
      
    
  );
}

10.2 UI 组件结构

graph TB
    subgraph &#34;AppContainer&#34;
        A[SettingsContext] --> B[MouseProvider]
        B --> C[SessionProvider]
        C --> D[VimModeProvider]
        D --> E[App]
    end

    subgraph &#34;App 组件&#34;
        E --> F[Header<br\u002F>标题栏]
        E --> G[MessageList<br\u002F>消息列表]
        E --> H[Composer<br\u002F>输入框]
        E --> I[Footer<br\u002F>状态栏]
    end

    subgraph &#34;消息类型&#34;
        G --> J[UserMessage<br\u002F>用户消息]
        G --> K[GeminiMessage<br\u002F>AI 回复]
        G --> L[ToolCallDisplay<br\u002F>工具调用]
        G --> M[ThoughtDisplay<br\u002F>思考过程]
    end

10.3 关键 Context

Context 作用
SettingsContext 应用设置和配置
SessionContext 当前会话状态
UIActionsContext UI 操作(发送消息、执行工具等)
VimModeContext Vim 模式支持
MouseContext 鼠标交互
StreamingContext 流式响应状态

10.4 主题系统

Gemini CLI 支持 24+ 种终端主题:

\u002F\u002F packages\u002Fcli\u002Fsrc\u002Fui\u002Fthemes\u002F
export interface Theme {
  name: string;
  colors: {
    primary: string;
    secondary: string;
    background: string;
    text: string;
    error: string;
    warning: string;
    success: string;
    \u002F\u002F ...
  };
}

11. 使用指南

11.1 安装

# 使用 npm
npm install -g @google\u002Fgemini-cli

# 或者直接运行
npx @google\u002Fgemini-cli

11.2 首次配置

# 首次运行会引导你完成认证
gemini

# 支持的认证方式:
# 1. Google 账号 OAuth(推荐)
# 2. API Key

11.3 基本使用

# 交互模式
gemini

# 单次查询
gemini &#34;这个函数是做什么的?&#34;

# 指定模型
gemini --model gemini-2.5-pro &#34;解释这段代码&#34;

# 恢复上次会话
gemini --resume latest

11.4 常用命令

在交互模式中,可以使用这些命令:

命令 功能
\u002Fhelp 显示帮助
\u002Fclear 清空当前会话
\u002Fmemory 编辑记忆文件
\u002Fmodel 切换模型
\u002Fsettings 打开设置
\u002FquitCtrl+C 退出

11.5 记忆文件配置

创建项目记忆文件 GEMINI.md

# 项目说明

这是一个 Next.js 14 项目,使用 App Router。

## 技术栈
- Next.js 14
- TypeScript
- Tailwind CSS
- Prisma (PostgreSQL)

## 开发规范
- 组件放在 `src\u002Fcomponents\u002F`
- API 路由放在 `src\u002Fapp\u002Fapi\u002F`
- 使用 Zod 进行数据验证

## 常用命令
- `npm run dev` - 启动开发服务器
- `npm run build` - 构建生产版本
- `npm run test` - 运行测试

12. 总结与思考

12.1 架构亮点

亮点 说明
清晰的分层设计 UI 层和业务逻辑层完全分离,便于测试和维护
优雅的流式处理 使用 AsyncGenerator 实现流式响应,体验流畅
强大的扩展性 MCP 协议支持任意工具扩展,Hook 系统允许自定义逻辑
智能的上下文管理 三层记忆系统 + 自动压缩,充分利用 Token 窗口
完善的安全机制 敏感操作需确认,策略引擎控制工具权限

12.2 可以借鉴的设计模式

模式 在项目中的应用
工厂模式 工具的创建和注册
策略模式 模型路由、压缩策略
观察者模式 事件系统、Hook 触发
装饰器模式 日志包装、录制包装
适配器模式 MCP 工具适配

12.3 对 AI 应用开发的启示

mindmap
  root((AI 应用开发启示))
    上下文工程
      精心设计 System Prompt
      管理好 Token 窗口
      利用压缩保留关键信息
    工具设计
      清晰的接口定义
      完善的错误处理
      考虑安全和权限
    记忆系统
      持久化重要信息
      分层管理记忆
      让用户可编辑
    用户体验
      流式响应
      实时显示进度
      优雅的错误提示

附录:核心文件速查表

功能 文件路径
应用入口 packages\u002Fcli\u002Fsrc\u002Fgemini.tsx
应用容器 packages\u002Fcli\u002Fsrc\u002Fui\u002FAppContainer.tsx
对话客户端 packages\u002Fcore\u002Fsrc\u002Fcore\u002Fclient.ts
API 交互 packages\u002Fcore\u002Fsrc\u002Fcore\u002FgeminiChat.ts
工具调度 packages\u002Fcore\u002Fsrc\u002Fcore\u002FcoreToolScheduler.ts
工具基类 packages\u002Fcore\u002Fsrc\u002Ftools\u002Ftools.ts
工具注册 packages\u002Fcore\u002Fsrc\u002Ftools\u002Ftool-registry.ts
记忆发现 packages\u002Fcore\u002Fsrc\u002Futils\u002FmemoryDiscovery.ts
记忆工具 packages\u002Fcore\u002Fsrc\u002Ftools\u002FmemoryTool.ts
上下文压缩 packages\u002Fcore\u002Fsrc\u002Fservices\u002FchatCompressionService.ts
System Prompt packages\u002Fcore\u002Fsrc\u002Fcore\u002Fprompts.ts
MCP 客户端 packages\u002Fcore\u002Fsrc\u002Ftools\u002Fmcp-client.ts
Hook 类型 packages\u002Fcore\u002Fsrc\u002Fhooks\u002Ftypes.ts
Token 限制 packages\u002Fcore\u002Fsrc\u002Fcore\u002FtokenLimits.ts

  • 本文基于 Gemini CLI v0.21.0 源码分析,作者通过深入阅读源码编写而成。如有任何疑问或建议,欢迎交流讨论!
  • 作者【前端领秀】一个喜欢探索前端领域AI赋能的开发者,喜欢我的可以关注我,私信我邀请进入技术群,我会时刻分享最新前沿AI技术;

扫码\_搜索联合传播样式-白色版.png