Gemini CLI 深度源码分析:从零到一理解 AI 命令行代理的设计与实现
本文基于 Gemini CLI v0.21.0 源码进行深度分析,带你从架构设计、核心原理到实现细节,全方位理解这个 Google 开源的 AI 命令行代理工具。
目录
- 什么是 Gemini CLI?
- 整体架构设计
- 核心模块详解
- 记忆系统:让 AI "记住" 你的偏好
- 上下文工程:如何管理有限的 Token 窗口
- 工具系统:AI 如何执行实际操作
- MCP 协议:标准化的 AI 工具扩展
- 通信协议与流式响应
- Hook 系统:可扩展的生命周期钩子
- UI 层:终端中的 React 应用
- 使用指南
- 总结与思考
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/genai | Gemini API 客户端 |
| 扩展协议 | MCP SDK v1.23 | 工具扩展支持 |
1.3 项目结构
gemini-cli/
├── packages/
│ ├── cli/ # 前端 UI 层 - 用户交互界面
│ ├── core/ # 核心业务逻辑 - AI 调用、工具执行
│ ├── a2a-server/ # Agent-to-Agent 服务器
│ └── vscode-ide-companion/ # VS Code 插件
├── bundle/ # 打包后的可执行文件
├── scripts/ # 构建和自动化脚本
└── integration-tests/ # 集成测试
2. 整体架构设计
2.1 分层架构图
graph LR
subgraph "用户层"
A[用户输入] --> B[CLI 入口<br/>gemini.tsx]
end
subgraph "UI 层 - packages/cli"
B --> C[AppContainer<br/>React 应用容器]
C --> D[Composer<br/>输入组件]
C --> E[MessageDisplay<br/>消息展示]
C --> F[ToolCallDisplay<br/>工具调用展示]
end
subgraph "服务层 - packages/core"
D --> G[GeminiClient<br/>对话管理器]
G --> H[GeminiChat<br/>API 交互]
G --> I[ToolScheduler<br/>工具调度器]
G --> J[ContextManager<br/>上下文管理]
end
subgraph "工具层"
I --> K[内置工具<br/>read/write/edit/shell]
I --> L[MCP 工具<br/>外部扩展]
end
subgraph "外部服务"
H --> M[Gemini API<br/>REST + SSE]
L --> N[MCP Server<br/>Stdio/SSE]
end
style A fill:#e1f5fe
style M fill:#fff3e0
style N fill:#fff3e0
2.2 核心设计原则
项目采用了几个关键的设计原则:
1. 前后端分离
packages/cli:负责用户界面、输入处理、显示渲染packages/core:负责业务逻辑、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/cli/src/gemini.tsx - 配置加载:
packages/cli/src/config/config.ts - 应用容器:
packages/cli/src/ui/AppContainer.tsx
3.2 GeminiClient - 对话管理的核心
GeminiClient 是整个系统的"大脑",它协调了对话的方方面面:
// packages/core/src/core/client.ts
export class GeminiClient {
private chat?: GeminiChat; // 实际的 API 交互
private sessionTurnCount = 0; // 会话轮次计数
private loopDetector: LoopDetectionService; // 循环检测
private compressionService: ChatCompressionService; // 上下文压缩
// 最大轮次限制,防止无限循环
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
事件类型定义:
// packages/core/src/core/turn.ts
export enum GeminiEventType {
Content = 'content', // 文本内容
ToolCallRequest = 'tool_call_request', // 工具调用请求
ToolCallResponse = 'tool_call_response', // 工具调用响应
Thought = 'thought', // AI 的思考过程
Finished = 'finished', // 完成
Error = 'error', // 错误
ChatCompressed = 'chat_compressed', // 上下文被压缩
LoopDetected = 'loop_detected', // 检测到循环
// ...
}
4. 记忆系统:让 AI "记住" 你的偏好
4.1 记忆的层次结构
Gemini CLI 实现了一个三层记忆系统,让 AI 能够记住从全局偏好到项目特定约定的各种信息:
graph LR
subgraph "记忆层次(优先级从低到高)"
A["全局记忆<br/>~/.gemini/GEMINI.md"] --> B["项目记忆<br/>project/GEMINI.md"]
B --> C["子目录记忆<br/>project/src/GEMINI.md"]
end
subgraph "记忆内容示例"
D["全局偏好<br/>- 喜欢 TypeScript<br/>- 偏好 4 空格缩进"]
E["项目约定<br/>- 使用 ESLint<br/>- 测试用 Vitest"]
F["模块特定<br/>- 组件用函数式<br/>- 状态用 hooks"]
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/await 而不是 .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: 检查 ~/.gemini/GEMINI.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: 最终的用户记忆字符串
核心代码解析:
// packages/core/src/utils/memoryDiscovery.ts
async function getGeminiMdFilePathsInternal(
currentWorkingDirectory: string,
includeDirectoriesToReadGemini: readonly string[],
userHomePath: string,
// ...
): Promise {
// 1. 查找全局记忆文件
const globalMemoryPath = path.join(homedir(), '.gemini', 'GEMINI.md');
// 2. 从当前目录向上查找项目根目录
const projectRoot = await findProjectRoot(currentWorkingDirectory);
// 3. 收集路径上的所有 GEMINI.md 文件
// 顺序:全局 -> 项目根 -> 子目录(越近优先级越高)
}
4.4 保存记忆
当你让 AI "记住" 某些事情时,会调用 MemoryTool:
// packages/core/src/tools/memoryTool.ts
export class MemoryTool extends BaseDeclarativeTool {
// 保存事实到记忆文件
async performAddMemoryEntry(fact: string): Promise {
// 1. 读取当前 GEMINI.md 内容
const content = await fs.readFile(memoryPath, 'utf-8');
// 2. 找到 "## Gemini Added Memories" 部分
// 3. 添加新的记忆项
const newContent = addMemoryEntry(content, fact);
// 4. 写回文件
await fs.writeFile(memoryPath, newContent);
}
}
5. 上下文工程:如何管理有限的 Token 窗口
5.1 Token 的概念
在与 AI 对话时,每条消息都会消耗 "Token"。Token 大致可以理解为:
- 英文:大约 4 个字符 = 1 个 Token
- 中文:大约 1-2 个字符 = 1 个 Token
每个模型都有 Token 限制:
// packages/core/src/core/tokenLimits.ts
export function tokenLimit(model: Model): TokenCount {
switch (model) {
case 'gemini-1.5-pro':
return 2_097_152; // 200 万 Token
case 'gemini-2.5-pro':
case 'gemini-2.5-flash':
return 1_048_576; // 100 万 Token
default:
return 1_048_576;
}
}
5.2 上下文组成
pie title 上下文 Token 分配(示例)
"System Prompt (系统提示)" : 15
"User Memory (用户记忆)" : 10
"对话历史" : 50
"工具输出" : 20
"环境上下文" : 5
5.3 上下文管理策略
graph LR
subgraph "上下文组成"
A[System Prompt<br/>系统提示] --> E[总上下文]
B[对话历史<br/>Messages] --> E
C[用户记忆<br/>GEMINI.md] --> E
D[环境上下文<br/>日期/OS/目录] --> E
end
E --> F{Token 数量}
F -->|小于50%限制| G[正常继续]
F -->|大于50%限制| H[触发压缩]
H --> I[保留最近 30%]
H --> J[压缩旧历史为状态快照]
I --> K[新的对话历史]
J --> K
5.4 对话压缩的实现
当上下文过长时,系统会自动压缩旧的对话历史:
// packages/core/src/services/chatCompressionService.ts
export const DEFAULT_COMPRESSION_TOKEN_THRESHOLD = 0.5; // 50% 时触发
export const COMPRESSION_PRESERVE_THRESHOLD = 0.3; // 保留最近 30%
export class ChatCompressionService {
async compress(chat: GeminiChat, ...): Promise<{
newHistory: Content[] | null;
info: ChatCompressionInfo;
}> {
// 1. 检查是否需要压缩
if (tokenCount < 0.5 * tokenLimit(model)) {
return { newHistory: null, compressionStatus: 'NOOP' };
}
// 2. 确定分割点(保留最近 30%)
const splitPoint = findCompressSplitPoint(
history,
1 - 0.3 // 压缩前 70%
);
// 3. 使用 AI 生成状态快照
const summary = await this.generateStateSnapshot(historyToCompress);
// 4. 组合新历史
return {
newHistory: [
{ role: 'user', parts: [{ text: summary }] },
{ role: 'model', parts: [{ text: 'Got it!' }] },
...historyToKeep // 保留的最近历史
],
compressionStatus: 'COMPRESSED'
};
}
}
5.5 状态快照格式
压缩后的状态快照包含关键信息:
实现用户认证功能
- Build Command: `npm run build`
- Testing: Tests run with `npm test`
- 使用 JWT 进行认证
- CWD: `/home/user/project/src`
- READ: `package.json`, `auth/login.ts`
- MODIFIED: `auth/jwt.ts`
- CREATED: `auth/__tests__/jwt.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 文件系统/Shell
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:
// packages/core/src/tools/tools.ts
export abstract class BaseDeclarativeTool {
constructor(
readonly name: string, // 工具名称
readonly displayName: string, // 显示名称
readonly description: string, // 描述(给 AI 看)
readonly kind: 'Read' | 'Write' | 'Execute', // 类型
readonly schema: FunctionDeclaration, // 参数 schema
readonly requiresConfirmation: boolean // 是否需要确认
) {}
// 验证参数
protected abstract validateToolParamValues(params: TParams): string | null;
// 创建执行实例
protected abstract createInvocation(params: TParams): ToolInvocation;
}
// 工具调用实例接口
export interface ToolInvocation {
params: TParams;
getDescription(): string;
toolLocations(): ToolLocation[];
shouldConfirmExecute(signal: AbortSignal): Promise;
execute(signal: AbortSignal, updateOutput?: Function): Promise;
}
6.5 工具调用示例
以 read_file 为例,看看一次完整的工具调用:
// 1. AI 请求调用工具
{
name: "read_file",
args: {
file_path: "/path/to/file.ts",
should_read_entire_file: true
}
}
// 2. 验证参数
// - 检查路径是否存在
// - 检查是否有权限读取
// 3. 执行读取
const content = await fs.readFile(filePath, 'utf-8');
// 4. 返回结果
{
llmContent: [{ text: content }], // 给 AI 看的内容
returnDisplay: {
type: 'markdown',
content: '```typescript\n' + content + '\n```'
}
}
7. MCP 协议:标准化的 AI 工具扩展
7.1 什么是 MCP?
MCP(Model Context Protocol)是一个开放协议,用于标准化 AI 模型与外部工具之间的通信。就像 USB 让各种设备能够连接电脑一样,MCP 让各种工具能够接入 AI。
graph TB
subgraph "Gemini CLI"
A[MCP Client] --> B[工具注册表]
end
subgraph "MCP Servers"
C[数据库工具<br/>SQL 查询]
D[API 工具<br/>REST 调用]
E[自定义工具<br/>任何功能]
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 "传输方式"
A[Stdio<br/>本地进程]
B[SSE<br/>Server-Sent Events]
C[HTTP<br/>Streamable]
end
subgraph "适用场景"
D[本地工具<br/>高性能]
E[远程服务<br/>实时推送]
F[Web API<br/>通用兼容]
end
A --> D
B --> E
C --> F
// packages/core/src/tools/mcp-client.ts
// 1. Stdio 传输 - 通过标准输入/输出与本地进程通信
const transport = new StdioClientTransport({
command: 'node',
args: ['./my-mcp-server.js'],
});
// 2. SSE 传输 - 通过 Server-Sent Events
const transport = new SSEClientTransport(
new URL('https://mcp-server.example.com')
);
// 3. HTTP Streamable 传输
const transport = new StreamableHTTPClientTransport(
new URL('https://api.example.com/mcp')
);
7.3 MCP 服务器配置
在 ~/.gemini/settings.json 中配置:
{
"mcpServers": {
"my-database": {
"command": "npx",
"args": ["@my-org/db-mcp-server"],
"env": {
"DATABASE_URL": "postgres://..."
}
},
"github": {
"url": "https://github-mcp.example.com",
"auth": {
"type": "oauth"
}
}
}
}
7.4 MCP 客户端实现
// packages/core/src/tools/mcp-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);
// 创建连接
this.client = await connectToMcpServer(
this.serverName,
this.serverConfig,
this.debugMode
);
// 监听工具更新(动态工具发现)
const capabilities = this.client.getServerCapabilities();
if (capabilities?.tools?.listChanged) {
this.client.setNotificationHandler(
ToolListChangedNotificationSchema,
async () => {
await this.refreshTools(); // 动态更新工具列表
}
);
}
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 /generateContent<br/>(包含历史、工具、配置)
Note over API: 开始生成响应
loop 流式响应 (SSE)
API-->>CLI: data: {"text": "正在"}
API-->>CLI: data: {"text": "思考"}
API-->>CLI: data: {"text": "..."}
end
alt 需要调用工具
API-->>CLI: data: {"functionCall": {...}}
CLI->>CLI: 执行工具
CLI->>API: POST(包含工具结果)
end
API-->>CLI: data: {"finishReason": "STOP"}
8.2 SSE 解析实现
// packages/core/src/code_assist/server.ts
async *requestStreamingPost(
method: string,
req: object,
): Promise> {
const res = await this.client.request({
url: this.getMethodUrl(method),
method: 'POST',
params: { alt: 'sse' }, // 请求 SSE 格式
responseType: 'stream',
body: JSON.stringify(req),
});
// 解析 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 === '') {
// 空行表示一个完整的事件
if (bufferedLines.length > 0) {
yield JSON.parse(bufferedLines.join('\n')) as T;
bufferedLines = [];
}
}
}
}
8.3 请求格式
发送给 Gemini API 的请求结构:
interface GenerateContentRequest {
model: string; // 模型名称
contents: Content[]; // 对话历史
systemInstruction?: Content; // 系统提示
tools?: Tool[]; // 可用工具
toolConfig?: {
functionCallingConfig: {
mode: 'AUTO' | 'ANY' | 'NONE' // 工具调用模式
}
};
generationConfig?: {
temperature?: number; // 温度(创意度)
topP?: number; // Top-P 采样
topK?: number; // Top-K 采样
maxOutputTokens?: number; // 最大输出 Token
thinkingConfig?: {
thinkingBudget: number; // 思考 Token 预算
}
};
}
9. Hook 系统:可扩展的生命周期钩子
9.1 Hook 的概念
Hook 允许你在 AI 执行的关键节点插入自定义逻辑,比如:
- 在工具执行前审批
- 在模型调用前/后记录日志
- 在会话开始/结束时执行清理
9.2 可用的 Hook 事件
graph TB
subgraph "会话生命周期"
A[SessionStart] --> B[BeforeAgent]
B --> C[BeforeModel]
C --> D[AfterModel]
D --> E[BeforeTool]
E --> F[AfterTool]
F --> G[AfterAgent]
G --> H[SessionEnd]
end
subgraph "特殊事件"
I[PreCompress]
J[Notification]
K[BeforeToolSelection]
end
// packages/core/src/hooks/types.ts
export enum HookEventName {
SessionStart = 'SessionStart', // 会话开始
SessionEnd = 'SessionEnd', // 会话结束
BeforeAgent = 'BeforeAgent', // 代理执行前
AfterAgent = 'AfterAgent', // 代理执行后
BeforeTool = 'BeforeTool', // 工具执行前
AfterTool = 'AfterTool', // 工具执行后
BeforeModel = 'BeforeModel', // 模型调用前
AfterModel = 'AfterModel', // 模型调用后
PreCompress = 'PreCompress', // 上下文压缩前
Notification = 'Notification', // 通知事件
BeforeToolSelection = 'BeforeToolSelection' // 工具选择前
}
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/block| I[阻止执行]
H -->|ask| J[询问用户]
J --> K{用户决定}
K -->|同意| D
K -->|拒绝| I
9.4 配置 Hook
在 ~/.gemini/settings.json 中配置:
{
"hooks": {
"BeforeTool": [
{
"matcher": "shell",
"command": "/path/to/approve-shell.sh",
"timeout": 5000
}
],
"SessionStart": [
{
"command": "echo 'Session started' >> ~/gemini.log"
}
]
}
}
9.5 Hook 输入/输出格式
// Hook 接收的输入(通过 stdin)
interface HookInput {
session_id: string; // 会话 ID
transcript_path: string; // 对话记录路径
cwd: string; // 当前工作目录
hook_event_name: string; // 事件名称
timestamp: string; // 时间戳
// ... 事件特定的数据
}
// Hook 返回的输出(通过 stdout)
interface HookOutput {
continue?: boolean; // 是否继续执行
decision?: 'allow' | 'deny' | 'ask' | 'block'; // 决定
reason?: string; // 原因
systemMessage?: string; // 要添加的系统消息
suppressOutput?: boolean; // 是否抑制输出
}
10. UI 层:终端中的 React 应用
10.1 Ink - 终端中的 React
Gemini CLI 使用 Ink 库在终端中渲染 React 组件。这意味着 UI 代码和普通的 React 应用非常相似:
// packages/cli/src/ui/components/Composer.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 "AppContainer"
A[SettingsContext] --> B[MouseProvider]
B --> C[SessionProvider]
C --> D[VimModeProvider]
D --> E[App]
end
subgraph "App 组件"
E --> F[Header<br/>标题栏]
E --> G[MessageList<br/>消息列表]
E --> H[Composer<br/>输入框]
E --> I[Footer<br/>状态栏]
end
subgraph "消息类型"
G --> J[UserMessage<br/>用户消息]
G --> K[GeminiMessage<br/>AI 回复]
G --> L[ToolCallDisplay<br/>工具调用]
G --> M[ThoughtDisplay<br/>思考过程]
end
10.3 关键 Context
| Context | 作用 |
|---|---|
SettingsContext | 应用设置和配置 |
SessionContext | 当前会话状态 |
UIActionsContext | UI 操作(发送消息、执行工具等) |
VimModeContext | Vim 模式支持 |
MouseContext | 鼠标交互 |
StreamingContext | 流式响应状态 |
10.4 主题系统
Gemini CLI 支持 24+ 种终端主题:
// packages/cli/src/ui/themes/
export interface Theme {
name: string;
colors: {
primary: string;
secondary: string;
background: string;
text: string;
error: string;
warning: string;
success: string;
// ...
};
}
11. 使用指南
11.1 安装
# 使用 npm
npm install -g @google/gemini-cli
# 或者直接运行
npx @google/gemini-cli
11.2 首次配置
# 首次运行会引导你完成认证
gemini
# 支持的认证方式:
# 1. Google 账号 OAuth(推荐)
# 2. API Key
11.3 基本使用
# 交互模式
gemini
# 单次查询
gemini "这个函数是做什么的?"
# 指定模型
gemini --model gemini-2.5-pro "解释这段代码"
# 恢复上次会话
gemini --resume latest
11.4 常用命令
在交互模式中,可以使用这些命令:
| 命令 | 功能 |
|---|---|
/help | 显示帮助 |
/clear | 清空当前会话 |
/memory | 编辑记忆文件 |
/model | 切换模型 |
/settings | 打开设置 |
/quit 或 Ctrl+C | 退出 |
11.5 记忆文件配置
创建项目记忆文件 GEMINI.md:
# 项目说明
这是一个 Next.js 14 项目,使用 App Router。
## 技术栈
- Next.js 14
- TypeScript
- Tailwind CSS
- Prisma (PostgreSQL)
## 开发规范
- 组件放在 `src/components/`
- API 路由放在 `src/app/api/`
- 使用 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/cli/src/gemini.tsx |
| 应用容器 | packages/cli/src/ui/AppContainer.tsx |
| 对话客户端 | packages/core/src/core/client.ts |
| API 交互 | packages/core/src/core/geminiChat.ts |
| 工具调度 | packages/core/src/core/coreToolScheduler.ts |
| 工具基类 | packages/core/src/tools/tools.ts |
| 工具注册 | packages/core/src/tools/tool-registry.ts |
| 记忆发现 | packages/core/src/utils/memoryDiscovery.ts |
| 记忆工具 | packages/core/src/tools/memoryTool.ts |
| 上下文压缩 | packages/core/src/services/chatCompressionService.ts |
| System Prompt | packages/core/src/core/prompts.ts |
| MCP 客户端 | packages/core/src/tools/mcp-client.ts |
| Hook 类型 | packages/core/src/hooks/types.ts |
| Token 限制 | packages/core/src/core/tokenLimits.ts |
- 本文基于 Gemini CLI v0.21.0 源码分析,作者通过深入阅读源码编写而成。如有任何疑问或建议,欢迎交流讨论!
- 作者【前端领秀】一个喜欢探索前端领域AI赋能的开发者,喜欢我的可以关注我,私信我邀请进入技术群,我会时刻分享最新前沿AI技术;