本文详细介绍了使用 TypeScript 从零构建生产级 AI Agent CLI(类 Claude Code)的技术实践。文章深入拆解了六层架构设计,重点讲解了 Agent Loop 状态机、系统提示词分段缓存优化、内置工具链实现、安全权限控制、MCP 与 LSP 协议集成,以及多 Agent 协作与持久化记忆系统的核心原理。
前两天突发奇想:一个生产级的 agentic CLI 到底需要哪些组件?每一层的具体怎么实现?SSE 缓冲区怎么管理、system prompt 怎么分段、工具权限怎么拦截、上下文满了怎么压缩。这些问题靠读文档回答不了,靠逆向混淆代码效率极低。
所以选择了另一条路:以 Claude Code 为参照系,从零重建一个功能等价的实现——纯 TypeScript,零框架,唯一的依赖是 fast-glob(因为原生 glob 在跨平台路径处理上有已知缺陷)。
两天之后的结果是 46 个文件,一万行 TypeScript。这篇文章记录的是这个过程中每一层的技术决策和实现细节。
在开始写任何代码之前,先要在脑子里跑通一个最小循环:用户输入一句话,CLI 怎么把它变成一次 API 调用,API 响应怎么变成终端输出或工具执行,工具结果怎么再送回模型。把这个循环画清楚,架构就基本定了。
核心流程如下:
用户输入 → 组装请求 → API 调用(SSE)→ 解析响应事件流 → 根据 stop_reason 决定分支:
目录结构按层划分:
这六层之间的依赖是单向的,core/ 不依赖 ui/,tools/ 不依赖 skills/。
技术选型的核心理由是 Node.js 22 的原生能力。fetch 和 ReadableStream 在 22 版本中已经稳定,不需要 node-fetch 或任何 HTTP 客户端库。TextDecoder 处理 UTF-8 字节流,Buffer 处理二进制,child_process.exec 执行 Shell 命令——所有这些都是标准库。唯一无法绕过的是文件系统 glob,fast-glob 在处理 gitignore 规则和大型目录的性能上比原生实现好一个数量级,这个依赖是值得的。
默认支持御三家和自定义平台,自定义模型(OpenAI 兼容格式)是一个附加需求。部分本地模型(如 Ollama、LM Studio)暴露 OpenAI 兼容接口,事件格式与 其他格式不同。处理方式是在 SSE 客户端初始化时传入 format: 'openai' 参数,在事件解析层做格式适配,将 OpenAI 的 delta 结构翻译成统一的内部事件类型。这样 Agent Loop 层完全不感知 API 格式差异。
最直觉的 system prompt 写法是一个大字符串,把所有指令拼在一起传给 API。这个方式在原型阶段没问题,但在生产环境有两个显著缺陷:
将 system 参数设为 block 数组,每个 block 可以独立设置 cache_control。按可变性将 system prompt 分为两类:
进程生命周期内不变,打上 cache_control 后首次写入缓存,后续命中。
这是官方订阅能节省 API 费用的核心原因,因为缓存命中率极高。
不带 cache_control,每轮重新计算。
完整请求体的 system 字段最终是一个有序 block 数组,顺序固定:身份 → 工具指南 → 编码规范 → 安全规则 → 风格指南 → 环境信息 → Git 上下文 → CLAUDE.md → MCP 指令。Anthropic 的 prompt caching 按照 block 数组的前缀匹配来识别缓存,排在前面的静态内容越稳定,缓存命中率越高。
Agent Loop 的骨架是一个有上限的 while 循环,最大迭代次数 25 次。25 轮足够完成大多数真实任务(读文件、分析、修改、验证通常在 10 轮内完成),同时防止 runaway loop 耗尽 API 额度。
每次迭代的流程:
工具执行管线是 Agent Loop 中最复杂的部分,共六个阶段:
自动 compact 机制处理上下文窗口溢出。在每轮迭代开始时,估算当前 messages 数组的 token 数(总字符数除以 4),如果超过模型上下文限制的 85%,触发压缩——发起一次独立的 API 调用生成摘要,用摘要替换原来的 messages 数组。
工具系统的入口是 TOOL_DEFINITIONS,一个 JSON Schema 数组,描述每个工具的名称、用途和参数结构。模型通过这个数组“知道”有哪些工具可以调用。
核心工具的实现细节:
old_string 必须在文件中唯一出现,否则报错,迫使模型提供精确的定位。Deferred Tools 是一个性能优化。低频工具不放入每次请求的 tools 数组,而是标记为 deferred。模型需要时先通过 ToolSearch 查询获取完整 schema,再在下一轮调用。这降低了约 40% 的固定开销。
权限系统是安全核心,设计目标是在用户体验与安全性之间取得平衡。
MCP(Model Context Protocol)让工具变成可以独立部署的进程。协议层是 JSON-RPC 2.0 over stdio。McpManager 管理多个 server 的生命周期,工具名通过前缀进行命名空间隔离。
LSP 集成则为模型提供实时的代码诊断信息。LspClient 实现 LSP 客户端侧,Write/Edit 执行后自动通知语言服务器,诊断结果作为 lsp_diagnostics 注入下一轮 system prompt。这让模型能立即看到编译器反馈,缩短了修复路径。
插件系统允许在不修改核心代码的情况下扩展能力。每个插件通过 plugin.json 声明扩展点:skills、agents、hooks、commands、mcpServers 和 lspServers。
Skills 是参数化的 prompt 模板。例如输入 /commit 时,系统展开模板、注入上下文并提交给 agent loop。内置技能包括 Git 操作(commit/pr/review)、项目初始化(init)、代码审查(simplify)等。
当任务可以分解时,多 Agent 并行能显著缩短时间。子 Agent 通过 executeSubAgent() 运行,是主循环的简化版。
worktree 隔离,子 Agent 在独立分支操作,由主 Agent 决定是否合并。run_in_background,完成后以 tool_result 形式通知。Auto Memory 用文件系统模拟持久记忆,存储在项目专属目录下,分为四种 Markdown 类型:
MEMORY.md 是索引文件,对话开始时注入 system prompt,模型按需读取。记忆的读写完全复用现有工具链,用户也可以直接编辑文本。
整个项目验证了一个认知:Claude Code 的工程质量确实极高。其分段 prompt 的三层缓存设计展示了极强的工程意识。
构建 Agent 工具的核心难点在于 Harness Engineering(治理工程)。调用 API 只是基础,真正的挑战在于如何正确反馈工具结果、处理流式交互以及在长任务中实现错误恢复。
- 本文转载自: x.com/icebearminer/statu... , 如有侵权请联系管理员删除。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!