Read OSS

Gemini CLI 架构:Monorepo 全局导览

中级

前置知识

  • 具备基础 TypeScript 知识
  • 熟悉 Node.js 项目结构
  • 了解 monorepo 的基本概念

Gemini CLI 架构:Monorepo 全局导览

Google 的 Gemini CLI 是一款开源的 AI 编程助手,将 Gemini 模型直接带入终端。它能够编辑文件、执行 shell 命令、搜索网络,并与 MCP 服务器集成——所有这些功能都由一套精密的 TypeScript 代码库统一调度。要为 Gemini CLI 贡献代码或从中学习,首先需要建立清晰的整体认知。本文正是为此而生:带你逐一走过 monorepo 的目录结构、启动流程、核心配置对象,以及将一切串联起来的两套事件系统。

7 个包的 Monorepo 布局

Gemini CLI 使用 npm workspaces 将代码组织为七个包,每个包都有明确的职责边界。

包名 职责
packages/core 后端逻辑:API 客户端、工具、策略引擎、调度器、hooks、MCP、安全
packages/cli 基于 React/Ink 的终端 UI,支持交互与非交互两种模式
packages/sdk 用于在其他应用中嵌入 Gemini CLI 的编程 API
packages/a2a-server 实验性 Agent-to-Agent 协议服务器
packages/devtools 基于浏览器的调试检查器
packages/vscode-ide-companion 用于 IDE 集成的 VS Code 扩展
packages/test-utils 共享测试基础设施

packages/core/src/index.ts 中的 barrel export 清晰展示了 core 包的体量——超过 270 行的重导出,涵盖 config、policy、tools、scheduling、MCP、hooks、agents、telemetry 等方方面面。它是整个代码库的引力中心。

graph TD
    subgraph Consumers
        CLI[packages/cli<br/>Terminal UI]
        SDK[packages/sdk<br/>Programmatic API]
        A2A[packages/a2a-server<br/>Agent-to-Agent]
        DEV[packages/devtools<br/>Debug Inspector]
        VSC[packages/vscode-ide-companion<br/>VS Code Extension]
    end
    
    CORE[packages/core<br/>Backend Logic]
    TEST[packages/test-utils<br/>Test Infrastructure]
    
    CLI --> CORE
    SDK --> CORE
    A2A --> SDK
    DEV --> CORE
    VSC --> CORE
    CLI -.-> TEST
    CORE -.-> TEST

提示: 初次探索 Gemini CLI 时,建议从 packages/core/src/index.ts 入手。其中的 import 分组——config、核心逻辑、tools、services、hooks——恰好映射了实际的架构分层。

Core 与 CLI 的职责分离

Gemini CLI 最重要的架构决策,是将 core(无 UI 的后端)与 cli(终端 UI)彻底解耦。正是这一分离,使三种不同的交互方式得以共享同一套后端:

  1. 交互式 CLI ——在终端中渲染的 React/Ink 应用
  2. 非交互式 CLI ——用于管道输入和 CI/CD 场景
  3. SDK ——通过 GeminiCliAgent 实现编程式嵌入

packages/sdk/src/index.ts 出人意料地简洁——仅有五个重导出。所有繁重的工作都在 core 的 agent session、tool registry 和 client 层中完成。SDK 将这些封装为更友好的 GeminiCliAgent / GeminiCliSession API。

这一分离还带来了实际的性能收益:CLI 仅在进入交互模式时才懒加载 React/Ink,从而保持非交互路径的高效运行。

启动流程:从 Shebang 到交互模式

理解启动流程是读懂 Gemini CLI 的关键。入口文件 packages/cli/index.ts 看似简单:注册全局未捕获异常处理器(值得注意的是,它会抑制 Windows 上已知的 node-pty 竞争条件),然后调用 main()

真正的复杂性藏在 packages/cli/src/gemini.tsx 中。main() 函数负责编排一个多阶段的启动流程:

flowchart TD
    A[Entry: index.ts] --> B[main&#40;&#41;]
    B --> C[Setup handlers & patch stdio]
    C --> D[Load settings & worktree]
    D --> E[Parse arguments]
    E --> F[Configure DNS & auth type]
    F --> G[Load CLI config &#40;partial&#41;]
    G --> H[Refresh auth / admin settings]
    H --> I{Sandbox needed?}
    I -- Yes --> J[Relaunch in sandbox]
    I -- No --> K[Relaunch in child process]
    J --> L[Exit parent]
    K --> M[Full config load]
    M --> N{Interactive?}
    N -- Yes --> O[startInteractiveUI&#40;&#41;]
    N -- No --> P[runNonInteractive&#40;&#41;]

其中有几处值得关注的设计决策:

两阶段 config 加载。 config 会被加载两次——在沙箱决策之前进行一次局部加载(第 330 行),进入沙箱之后再进行一次完整加载(第 454 行)。这是因为认证必须在沙箱化之前完成(沙箱会拦截 OAuth 重定向),而扩展则不应在沙箱化之前加载。

沙箱重启机制。 如果启用了沙箱且当前不在沙箱环境中(!process.env['SANDBOX']),进程会在容器或 macOS seatbelt 中重新启动自身。这对用户完全透明,但对安全性至关重要。

懒加载 UI。 第 167–185 行startInteractiveUI 函数会动态导入体积较大的 interactiveCli.js 模块,使非交互模式的运行路径完全不包含 React/Ink 的依赖。

Config 核心对象与 AgentLoopContext

每个 Gemini CLI 会话的核心都是 Config 类——一个约 3700 行的对象,同时实现了 McpContextAgentLoopContext 接口。它定义于 packages/core/src/config/config.ts#L736,持有以下内容:

  • Tool、prompt 和 resource 注册表
  • MCP 和 A2A 客户端管理器
  • 沙箱管理器和策略引擎
  • 模型路由服务和内容生成器
  • Hook 系统、skill 管理器和文件发现服务
  • 会话状态、遥测设置、IDE 模式等更多配置
classDiagram
    class Config {
        +toolRegistry: ToolRegistry
        +mcpClientManager: McpClientManager
        +sandboxManager: SandboxManager
        +modelRouterService: ModelRouterService
        +policyEngine: PolicyEngine
        +hookSystem: HookSystem
        +skillManager: SkillManager
        +contentGenerator: ContentGenerator
        +getMessageBus(): MessageBus
        +getGeminiClient(): GeminiClient
        +initialize(): Promise
    }
    
    class AgentLoopContext {
        <<interface>>
        +config: Config
        +promptId: string
        +parentSessionId?: string
        +toolRegistry: ToolRegistry
        +promptRegistry: PromptRegistry
        +resourceRegistry: ResourceRegistry
        +messageBus: MessageBus
        +geminiClient: GeminiClient
        +sandboxManager: SandboxManager
    }
    
    Config ..|> AgentLoopContext
    Config ..|> McpContext

AgentLoopContext 接口为单次 agent 执行提供了一个范围受限的视图。与其将整个 Config 传递给 GeminiClientScheduler 等组件,不如传入一个仅包含当前执行范围所需的注册表、message bus 和沙箱管理器的 AgentLoopContext。这在 sub-agent 场景中尤为重要——每个 sub-agent 都会获得自己派生出的 context。

提示: 当你阅读接受 AgentLoopContext 参数的代码时,请记住 context.config 始终可以访问完整的 Config 对象。这个接口只是一种约束惯例,并非硬性隔离边界。

双事件系统:coreEvents 与 MessageBus

Gemini CLI 针对不同的通信场景使用了两套独立的事件系统。

CoreEventEmitter——全局横切关注点

coreEvents 单例 是一个带类型的 EventEmitter,负责处理全局通知。其 CoreEvent 枚举定义了 UserFeedbackModelChangedConsoleLogRetryAttemptMcpProgressQuotaChanged 等事件。

它有一个巧妙的设计:CoreEventEmitter 实现了事件积压机制。如果在任何监听器注册之前就触发了事件(这在启动阶段很常见),事件会被放入积压队列(最多 10,000 条),待监听器出现后统一消费。这确保了启动早期的警告信息不会丢失。

MessageBus——工具确认的发布/订阅

MessageBus 处理另一种模式:调度器与 UI 之间用于工具确认的请求-响应通信。当某个工具调用需要用户批准时,调度器会发布一条 TOOL_CONFIRMATION_REQUEST。MessageBus 检查策略(允许/拒绝/询问用户),然后自动决议或转发给 UI 处理。

flowchart LR
    S[Scheduler] -->|TOOL_CONFIRMATION_REQUEST| MB[MessageBus]
    MB -->|PolicyEngine.check| PE{Policy Decision}
    PE -- ALLOW --> AR[Auto-resolve confirmed]
    PE -- DENY --> AD[Auto-resolve denied]
    PE -- ASK_USER --> UI[Forward to UI]
    UI -->|TOOL_CONFIRMATION_RESPONSE| MB
    MB --> S

第 46–72 行derive() 方法为 sub-agent 创建带命名空间的子 bus。派生出的 bus 会在确认请求中为 sub-agent 名称添加前缀,确保父 agent 与子 agent 的流程互不干扰,同时共享同一套底层事件基础设施。

代码库导读:从哪里开始读

以下是为新贡献者推荐的阅读顺序:

步骤 文件 原因
1 packages/cli/src/gemini.tsx 理解启动流程
2 packages/core/src/config/config.ts 了解 Config 持有的内容
3 packages/core/src/core/client.ts Agentic loop 的编排器
4 packages/core/src/core/turn.ts 流式事件的工作方式
5 packages/core/src/scheduler/scheduler.ts 工具的执行方式
6 packages/core/src/policy/policy-engine.ts 安全模型
7 packages/core/src/tools/definitions/coreTools.ts Tool schema 定义

需要了解的关键命名约定:

  • core/ ——agentic loop 层(client、turn、chat、prompts)
  • tools/definitions/ ——按模型系列拆分的 tool schema
  • scheduler/ ——工具执行编排
  • confirmation-bus/ ——MessageBus 系统
  • hooks/ ——五组件 hook 系统
  • mcp/ ——MCP 客户端与 OAuth
  • policy/ ——基于规则的策略引擎

Tool 定义与调用之间的关系遵循 builder 模式:coreTools.ts 通过 BaseDeclarativeTool 定义声明式 schema,调度器则实例化 ToolInvocation 对象来完成实际执行。我们将在第三篇文章中深入探讨这一机制。

在下一篇文章中,我们将深入解析 agentic loop——GeminiClientTurnGeminiChat 构成的三层架构,以及它是如何将用户输入转化为流式工具调用与响应的。