一、背景
1.1、为什么要学习 Claude Code?
Claude Code 是 Anthropic 推出的命令行 AI 编程助手,它代表了当前 AI Agent 在生产环境中的最佳实践。通过学习它的源码架构,我们可以理解:
- Agent 的核心循环如何设计
- 工具系统如何组织
- 权限控制如何实现
- 会话管理如何持久化
1.2、项目目标
从零开始,构建一个功能完整的 Coding Agent,具备:
- 核心 Agent 循环
- 文件操作能力(读/写/编辑/搜索)
- 命令执行能力
- 任务规划能力(Todo 列表)
- 会话持久化(保存/恢复)
- 权限控制(危险操作需确认)
二、整体架构
2.1、项目结构
src/
├── agent/ # Agent 核心循环
│ ├── loop.ts # 主循环逻辑
│ └── types.ts # 类型定义
├── api/ # LLM API 客户端
│ └── client.ts # DeepSeek API 封装
├── tools/ # 工具实现
│ ├── base.ts # 工具基类
│ ├── registry.ts # 工具注册表
│ ├── bash.ts # 命令执行
│ ├── read.ts # 读文件
│ ├── write.ts # 写文件
│ ├── edit.ts # 编辑文件
│ ├── glob.ts # 搜索文件
│ ├── grep.ts # 搜索内容
│ ├── todo.ts # 任务管理
│ └── web.ts # 网络请求
├── permissions/ # 权限系统
│ ├── types.ts # 权限类型
│ └── checker.ts # 权限检查
├── state/ # 会话管理
│ └── session.ts # 会话存储
└── index.ts # 入口文件
2.2、技术栈
| 技术 | 用途 |
|---|---|
| TypeScript | 类型安全 |
| Node.js | 运行时 |
| DeepSeek API | LLM 服务 |
| readline | 命令行交互 |
三、核心实现
参考 Claude Code 的query.ts,核心循环采用while + needsFollowUp模式:
async processUserInput(userInput: string): Promise<string> {
this.messages.push({ role: 'user', content: userInput });
let iteration = 0;
while (iteration < this.config.maxIterations) {
iteration++;
// 1. 调用 LLM
const response = await this.llm.chat(this.messages, tools);
// 2. 检查是否需要继续
let needsFollowUp = false;
if (response.stopReason === 'tool_use') {
needsFollowUp = true;
}
// 3. 没有工具调用 → 结束
if (!needsFollowUp) {
this.messages.push({ role: 'assistant', content: response.content });
return response.content;
}
// 4. 执行工具
for (const toolUse of response.toolUses) {
const tool = this.toolRegistry.get(toolUse.name);
const result = await tool.execute(toolUse.input);
this.messages.push({ role: 'tool', content: result });
}
// 5. 继续循环
}
}
关键设计:
needsFollowUp标记判断是否需要继续- 工具执行结果必须返回给 LLM
- 最大循环次数防止无限循环
3.2 工具系统
工具接口设计
export interface Tool {
name: string; // 工具名称
description: string; // 给 LLM 的描述
schema: object; // 参数 JSON Schema
execute(input: any): Promise<ToolResult>; // 执行逻辑
isReadOnly(): boolean; // 是否只读
}
工具注册表
export class ToolRegistry {
private static instance: ToolRegistry;
private tools: Map<string, Tool> = new Map();
register(tool: Tool): void {
this.tools.set(tool.name, tool);
}
getLLMToolDefinitions(): any[] {
return this.getAll().map(tool => ({
name: tool.name,
description: tool.description,
input_schema: tool.schema
}));
}
}
已实现的工具
| 工具 | 功能 | 权限 |
|---|---|---|
| bash | 执行系统命令 | 需要确认 |
| read_file | 读取文件 | 自动允许 |
| write_file | 写入文件 | 需要确认 |
| file_edit | 编辑文件 | 需要确认 |
| glob | 搜索文件 | 自动允许 |
| grep | 搜索内容 | 自动允许 |
| todo_write | 任务管理 | 自动允许 |
| web_fetch | 网络请求 | 自动允许 |
| ask_user_question | 询问用户 | 需要确认 |
3.3 权限系统
权限级别
export enum PermissionMode {
ALLOW = 'allow', // 自动允许
ASK = 'ask', // 需要确认
DENY = 'deny' // 自动拒绝
}
危险命令检测
export const DANGEROUS_PATTERNS = [
{ pattern: /rm\s+-rf/, level: DangerLevel.DANGEROUS, message: '递归强制删除' },
{ pattern: /del\s+/f/, level: DangerLevel.DANGEROUS, message: '强制删除文件' },
{ pattern: /format\s+[a-z]:/, level: DangerLevel.DANGEROUS, message: '格式化磁盘' },
{ pattern: /sudo/, level: DangerLevel.CAUTION, message: '管理员权限' }
];
权限检查流程
async checkPermission(request: PermissionRequest): Promise<PermissionResult> {
// 1. 检查危险等级
const dangerLevel = this.checkDangerLevel(toolName, input);
if (dangerLevel === DangerLevel.DANGEROUS) {
return { allowed: true, mode: PermissionMode.ASK, reason: '危险操作' };
}
// 2. 匹配权限规则
for (const rule of this.rules) {
if (this.matchTool(rule.tool, toolName)) {
return { allowed: rule.mode !== PermissionMode.DENY, mode: rule.mode };
}
}
// 3. 默认需要确认
return { allowed: true, mode: PermissionMode.ASK, reason: '未配置规则' };
}
3.4 会话管理
会话存储
export interface Session {
id: string;
name: string;
messages: Message[];
createdAt: Date;
updatedAt: Date;
}
会话命令
| 命令 | 功能 |
|---|---|
| /sessions | 查看所有会话 |
| /save | 保存当前会话 |
| /resume | 恢复会话 |
| /rename | 重命名会话 |
| /delete | 删除会话 |
| /session-info | 查看会话详情 |
四、遇到的挑战与解决
4.1 输入重复问题
问题:在 PowerShell 中输入命令时,每个字符会重复显示(如 test 显示为 tteesstt)。
原因:
- PSReadLine 模块与 readline 冲突
AskUserQuestionTool创建了独立的 readline 实例
解决:
- 切换到 CMD 或在 VS Code 终端中运行
- 修改
AskUserQuestionTool,复用主 readline 而不是创建新实例
4.2 readline 冲突
问题:权限确认时创建新的 readline 实例,导致输入y后退出 Agent。
原因:多个 readline 实例竞争输入。
解决:复用主 readline 实例,不创建新的。
4.3 API 消息格式错误
问题:tool_calls后没有对应的tool消息。
原因:消息历史中 assistant 消息和 tool 消息不匹配。
解决:确保每个tool_use后都有对应的tool_result。
五、收获与总结
学到的设计模式
| 模式 | 应用 |
|---|---|
| 单例模式 | 工具注册表、会话管理器 |
| 工厂模式 | buildTool创建工具 |
| 策略模式 | 权限检查策略 |
| 观察者模式 | 会话状态变化通知 |
对 Agent 架构的理解
- 核心循环是基础 -
while + needsFollowUp模式 - 工具系统是关键 - 统一的接口 + 能力标记
- 权限控制是保障 - 危险操作需要确认
- 会话管理是体验 - 持久化让用户可以继续对话
项目成果
- 代码量:4000+ 行
- 工具数:9 个
- 完成阶段:5 个
- GitHub 仓库:github.com/Tranz-elen/…
六、后续计划
| 方向 | 说明 |
|---|---|
| WebSearchTool | 添加网络搜索能力 |
| MCP 协议 | 对标 Claude Code |
| 多 Agent 协作 | 团队模式 |
| 上下文压缩 | 处理长对话 |
七、参考资源
如果你对这个项目感兴趣,欢迎访问 GitHub 仓库查看完整代码。