从零构建一个 Coding Agent:学习 Claude Code 的实践

0 阅读4分钟

一、背景

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 APILLM 服务
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)。

原因

  1. PSReadLine 模块与 readline 冲突
  2. AskUserQuestionTool 创建了独立的 readline 实例

解决

  1. 切换到 CMD 或在 VS Code 终端中运行
  2. 修改 AskUserQuestionTool,复用主 readline 而不是创建新实例

4.2 readline 冲突

问题:权限确认时创建新的 readline 实例,导致输入y后退出 Agent。

原因:多个 readline 实例竞争输入。

解决:复用主 readline 实例,不创建新的。

4.3 API 消息格式错误

问题tool_calls后没有对应的tool消息。

原因:消息历史中 assistant 消息和 tool 消息不匹配。

解决:确保每个tool_use后都有对应的tool_result

五、收获与总结

学到的设计模式

模式应用
单例模式工具注册表、会话管理器
工厂模式buildTool创建工具
策略模式权限检查策略
观察者模式会话状态变化通知

对 Agent 架构的理解

  1. 核心循环是基础 - while + needsFollowUp 模式
  2. 工具系统是关键 - 统一的接口 + 能力标记
  3. 权限控制是保障 - 危险操作需要确认
  4. 会话管理是体验 - 持久化让用户可以继续对话

项目成果

六、后续计划

方向说明
WebSearchTool添加网络搜索能力
MCP 协议对标 Claude Code
多 Agent 协作团队模式
上下文压缩处理长对话

七、参考资源

如果你对这个项目感兴趣,欢迎访问 GitHub 仓库查看完整代码。