先说结论:这份泄露出来的 Claude Code 相关源码,我第一时间看了,但没有停留在“哪个版本、哪个文件泄露”的吃瓜层面,而是把它当成一份难得的“AI 编程工具架构教材”来拆。看完之后最强烈的感受是——这类工具框架本质上就是一条三层装配线:CLI 交互层负责接单,Agent 编排层负责排产,内核执行层负责落地。三层各司其职,模型、工具、权限、上下文才能被拧成一股绳。
这篇博文不追热点,只讲架构。我会结合这次泄露源码中暴露出来的实现细节,把“三层装配线”每一层负责什么、为什么这么拆、实际落地时怎么设计,掰开揉碎讲清楚。同时把安装配置、模型接入、权限报错、上下文丢失这些常见问题也一并串进去。适合三类人看:想把 Claude Code 这类工具用明白的人,想自己开发 Agent 工具框架的人,以及单纯想通过源码学习工程架构的人。
1. 一次源码泄露,为什么值得从架构角度拆解
1.1 这不是一次普通的“吃瓜”
网上流传的 Clauaude Code 相关源码片段里,最值钱的信息其实不是某个具体功能,而是整套代码的组织方式。你可以看到命令行入口怎么处理参数,Agent 循环怎么维护状态,工具调用怎么注册和分发,模型提供商怎么适配,安全策略怎么收敛。这些内容平时散落在文档里,被各种营销号包装成“黑科技”,这次相当于把底牌直接翻给你看。
我当时对照自己实现的几个 Agent 项目逐一比对,发现很多设计决策都似曾相识:工具要声明 JSON Schema,权限要分模式,会话要落盘存档,模型名要映射到具体的 provider。差别在于,这个项目把这些做得非常干净,每一层边界清晰,你几乎可以沿着代码路径一路追下去,从用户敲下命令到模型返回结果,中间没有任何“坨”在一起的地方。
1.2 三层装配线到底指什么
用工厂打比方最直观:
- 第一层是 CLI 交互层。用户输入指令、配置参数、选择模式,这一层全部接住。它负责把“人话”翻译成结构化指令,再把结果渲染回终端。
- 第二层是 Agent 编排层。这是整个框架的中枢。它维护对话历史、决定下一步调哪个工具、根据模型输出分发执行、收集结果再喂回模型,形成一个循环。
- 第三层是内核执行层。文件读写、命令执行、网络请求、上下文裁剪、模型网关,全部在这里完成。它是真正“动手”的地方,也是最需要安全管控的地方。
这三层之间通过明确定义的接口通信。CLI 层不关心模型怎么选,编排层不关心终端怎么渲染,内核层不关心用户说了什么话。每一层都可以被替换、被测试、被单独优化。这就是“装配线”的本质:流水线每个工位只干一件事,但组合起来能完成复杂总装。
1.3 我为什么建议你跟着走一遍
很多人在用 Claude Code 这类工具时,遇到“模型不识别”“工具没生效”“权限一直弹窗”就卡住了,根本原因就是脑子里没有这张三层地图。你以为是一个整体,其实是三个独立模块在协作。一旦你知道报错发生在哪一层,排查范围直接缩小 80%。
这篇文章后面的内容,我会按照三条装配线逐层拆解,每一层都会结合源码片段里的实现逻辑来讲,同时给出实操层面的建议。你不需要提前读源码,跟着我的思路走,就能把整个工具框架的骨架搭出来。
2. 第一层装配线:CLI 交互层,把所有入口拧成一股绳
2.1 CLI 层到底管什么
CLI 层是用户接触最频繁的部分,很多人以为它就是“解析一下命令参数”,但实际上它承担的工作量相当大。从泄露源码中可以看到,入口启动时会做几件固定的事:加载配置文件、初始化日志、检查环境变量、读取 claude_code 目录下的状态文件、然后才进入交互循环。
交互循环本身也是一个微型状态机:等待用户输入、解析指令、判断是否包含工具调用、执行工具、渲染结果、回到等待状态。这个循环跑得顺不顺,直接影响用户体验。我见过不少自建工具把输入解析和工具执行写在同一个函数里,结果改一个参数格式就要动到核心逻辑,维护成本非常高。而这里 CLI 层只做“翻译”和“展示”,执行逻辑全部交给下层,这样才能保证入口稳定。
还有一点值得注意:CLI 层负责权限确认的交互。当编排层判定某个工具需要用户授权时,CLI 层要在终端渲染出提示,等用户输入 y/n,再把结果回传给编排层。这个交互如果做得不好,用户会疯狂按回车,最终误授权。源码里的做法是把权限决策做成一个独立的“确认器”,跑在交互循环里,而不是分散在各个工具调用中。这算是一个很典型的分层设计案例。
2.2 从泄露片段里看到的实现细节
从泄露的源码片段可以确认几个细节:
- 配置管理是集中式的:所有配置项(API Key、模型名、代理地址、权限模式)都被读入一个全局配置对象,各层通过依赖注入拿配置,而不是到处读环境变量。这看起来起眼,实则非常重要。
- 错误处理是分类的:CLI 层捕获的错误会先判断类型,再决定怎么展示。模型不存在的报错和网络超时报错,展示策略完全不同。比如网上那张“deepseek-v4-pro is not a model this version of Claude Code recognizes”的截图,本质上就是模型名映射失败,CLI 层把底层错误包装成了人类可读的提示。
- 输出是流式的:模型生成 token 时,CLI 层通过回调实时渲染,而不是等全部生成完再统一打印。这背后是流式接口和渲染层彻底解耦。
这些细节看起来小,但决定了工具的“手感”。我自己的项目早期是全部生成完再打印,后来发现用户体验天差地别,改成流式之后才有“对话感”。
2.3 CLI 层设计的三条实操建议
第一,入口要薄,逻辑要下沉。入口文件只做启动、装配、监听信号这三件事,其余全部委托给服务对象。不要因为入口文件好改,就把所有逻辑堆进去。
第二,配置要分级。全局配置、项目配置、会话配置必须分开。Claude Code 允许你在项目里放配置文件覆盖全局设置,这个设计非常实用。我建议自建框架时至少做两级:全局级和项目级,项目级配置的优先级要高于全局。
第三,把终端渲染做成独立的渲染器。命令输出、流式 token、工具执行日志、错误信息,各自用独立的渲染方法。这样后续如果想接 TUI、WebSocket 或桌面版,只需要替换渲染器,核心逻辑一行都不用改。这正好回应当前很多人在折腾“Claude Code 桌面版”的需求——桌面版本质上只是换了一层皮,核心还是那条装配线。
3. 第二层装配线:Agent 编排层,工具框架的真正大脑
3.1 Agent 循环和三步状态机
如果说 CLI 层是门面,编排层就是大脑。这里的核心是一个“Agent 循环”:模型输出 → 解析结果 → 判断是否有工具调用 → 执行工具 → 把结果拼进上下文 → 再次请求模型 → 直到模型给出最终答复。
这个循环本质上是三步状态机:
- 观察态:读取当前对话历史和最近一次工具执行结果。
- 决策态:把上下文交给模型,让模型决定是继续调工具还是给出答案。
- 执行态:按模型输出调用具体工具,并把返回值加入上下文。
我在自己项目里实现过这个循环,踩过最大的坑是“死循环”。模型可能反复调用同一个工具,永远不给出结论。源码里的做法是限制了单轮会话的最大工具调用次数,达到上限后强制让模型基于已有信息作答。这个兜底机制非常重要,不加的话,一个错误指令能让框架空转很久。
3.2 工具注册表:模型和工具之间的“翻译官”
编排层最关键的机制是工具注册表。每个工具在被调用之前,都必须先在注册表里声明自己的名称、描述、参数 JSON Schema。这个声明会被拼进系统提示词,让模型“知道”有哪些工具可用以及怎么调用。
在设计层面,这里最容易出现的误区是“注册表只存函数指针”。如果只是把函数存进 Map,模型根本不知道这个函数是干什么的、参数怎么填。正确做法是每个注册项至少包含三部分:
- 元信息:工具名称、一句话描述、详细说明;
- 参数 Schema:结构化的参数定义,包括必填项、类型、枚举值、默认值;
- 执行函数:真正被调用的逻辑,以及超时和重试策略。
从泄露源码看,这个项目的工具注册表是带描述缓存的。同一份工具声明会被反复拼进系统提示词,每次都重新生成会浪费大量 token,所以它做了缓存。这一点很值得学,我在自建框架时就吃过亏,一开始没缓存,每次对话都多花不少 token。
工具调用返回后,返回值要经过“精简”再塞回上下文。因为工具输出可能非常长(比如读取整个文件),直接把原始内容塞回上下文,很快就把上下文窗口撑爆。源码里的做法是截断过长输出,并附带截断提示。这个细节就是工程上的“取舍艺术”——宁可牺牲一点信息,也要保住对话的连续性。
3.3 权限模型:三层里最容易翻车的地方
权限模型是编排层里最容易让人迷惑的模块。Claude Code 提供多种权限模式,比如默认模式(每个敏感操作都要确认)、自动接受编辑模式(文件编辑不再询问)、计划模式(只读不写)。这些模式本质上是同一个权限决策器的不同配置。
从源码里可以看到,权限决策器有一套完整的判断优先级:
- 命中拒绝列表的操作直接拒绝;
- 命中允许列表的操作直接放行;
- 命中“需要确认”列表的操作,弹窗询问用户;
- 未匹配任何规则的高风险操作,按当前模式决定是询问还是默认拒绝。
我见过很多人在自建工具时忽略权限设计,让模型可以直接执行任意命令,结果一朝被注入攻击就翻车。实际上权限模型应该成为编排层的“质检工位”,每一件产品出厂前都得过检。这也是为什么网上很多人遇到“your organization has disabled Claude subscription access for Claude Code”这类报错时一头雾水——他们不知道组织级策略和本地权限策略是两套独立系统,报错来自上层策略,本地配置文件改再多也没用。
3.4 会话快照与上下文管理
Agent 循环跑起来之后,另一个关键问题是:中间状态存在哪里?断线了怎么办?换终端了怎么恢复?
源码实现里有一个清晰的会话快照机制:每次对话轮次结束后,当前会话的完整状态——包括消息历史、上下文文件引用、工具调用记录——会被序列化保存到本地目录。下次启动时,CLI 层会读取这个快照,恢复会话上下文。这就是为什么你关掉终端再打开,还能接上之前的对话。
上下文管理还有一个很核心的操作:系统提示词与 tool definition 的组装。模型请求之前,编排层会把系统提示词、工具声明、会话历史、最近的工具输出拼在一起,拼完后还要做 token 预算检查。如果超出模型上下文上限,会优先压缩历史消息而不是直接报错。这个“压缩优先于报错”的策略,是考虑长期使用的关键设计。
4. 第三层装配线:内核执行层,把能力关进笼子里
4.1 模型网关:为什么换模型会报错
第三层是第一线和“外部世界”打交道的地方,最重要的一项是模型网关。
Claude Code 的核心模型来自 Anthropic,但它并不只支持官方模型。源码里可以看到 Provider 适配层的存在:不同模型提供商有独立的客户端实现,通过统一接口暴露给上层。配置模型时,你需要指定 provider、模型名、API 地址、密钥。这个设计让“接入 DeepSeek”这类需求变得可行——本质上就是新增一个 Provider 适配器。
之前很多人在问“Claude Code 接入 DeepSeek 怎么配”,对照源码就很好理解了:你只需要在配置里填写 DeepSeek 的 provider 信息、模型名和密钥。但这里有个大坑:模型名必须严格对应该 provider 支持的名称。如果填了一个不存在的模型名,比如把 DeepSeek 的模型名填错,或者 Claude Code 版本太老不认识新模型,就会报出“deepseek-v4-pro is not a model this version of Claude Code recognizes”这种错误。
这种报错的本质是模型网关的“模型解析器”无法在注册表里找到对应模型。排查思路也很简单:先确认你用的版本是否支持该模型,再看 provider 是否配对,最后看模型名是否和官方文档完全一致(大小写、连字符都要一致)。我建议配置模型时先手动在终端请求一次该模型的接口,确认能通再填进配置,这样可以避免大部分“配置了但用不了”的问题。
模型网关还有一个容易被忽略的功能:模型名解析和重定向。源码里存在模型别名机制,比如某些缩写会被映射到完整模型名。这个机制的目的很简单——用户不需要记住一串长 ID。我在实际使用中感觉到,这个设计非常有价值。
4.2 系统提示词与工具定义的动态拼装
模型网关准备好之后,还有一个关键环节:把工具定义和系统提示词拼进请求体。这一步发生在内核执行层,但决策信息来自编排层。
从源码看,系统提示词不是写死的字符串,而是通过模板引擎动态生成的。模板里会注入当前日期、可用工具列表、工作目录信息、用户偏好等。工具定义部分则会遍历注册表,把每个工具的 name、description、parameters 序列化成 JSON,再嵌入系统提示词。
这个设计的直接效果是:模型每次收到请求时,都能看到一份“恰到好处”的工具清单和上下文说明。而不是一股脑把所有工具都塞进去——那样既浪费 token,又容易让模型混淆工具职责。很多自建 Agent 框架做不好工具调用,就是因为没有做这个动态筛选和拼装。
拼装完成后还有一道校验:token 预估。如果请求体超过模型上下文限制,内核层会采取“分段塞入”策略:先保证系统提示词和工具定义完整,再尽量多塞历史消息,实在放不下就压缩旧消息。这比直接报错友好太多。
4.3 执行沙箱与遥测:保障“最后一百米”
内核层还包括具体工具的执行,比如文件读写、命令执行、网络请求。这里最核心的是安全边界。
源码里对命令执行做了很严格的控制——不是直接把命令丢给系统 shell,而是走了独立的命令运行器,设置超时、捕获输出、限制工作目录。文件读写也有路径约束,防止模型通过工具读写超出项目目录的敏感文件。这些约束配合编排层的权限决策,形成了纵深防御。
遥测与日志系统也是内核层的一部分。每次工具调用、每次模型请求、每次错误,都会记录结构化日志。这个设计对线上排查极其重要。我之前遇到过一个诡异问题:某个工具偶尔失败,但复现不出来,后来通过日志发现是偶发超时。如果没有遥测,这种问题根本无从查起。
5. 从事件反推工程实践:自己搭工具框架时该抄哪些作业
5.1 三层分离带来的四个直接收益
看完这套三层装配线,最大的收获不是“原来 Claude Code 是这么写的”,而是理解了为什么这种拆分方式能长期演进。
第一,可替换性。模型提供商可以换,终端渲染方式可以换,工具实现可以换,但架构骨架不变。Claude Code 能兼容 DeepSeek、能出桌面版、能加新工具,靠的就是这个松耦合。
第二,可测试性。三层之间接口清晰,每一层都可以独立 mock 测试。你可以不启动真实模型,纯靠假数据测编排层的循环逻辑;也可以不经过 CLI,直接测试内核层的工具执行。这种可测试性让回归变得可控。
第三,权限治理的可审计性。权限决策集中在编排层,所有敏感操作都有明确记录。一旦出安全问题,可以顺着日志追溯整个决策链。
第四,上下文管理的可控性。因为状态管理集中在会话快照里,所以断点续聊、多终端切换才会变成可能,而不是每次都要从头开始。
5.2 这次泄露给我们的安全教训
既然题目是“从泄露源码看工程架构”,工程实践这一节就必须说透泄密事件本身的教训。
- 密钥管理是红线。从项目结构看,密钥信息完全来自环境变量和本地配置文件,源码库里没有任何硬编码密钥。这是正确的做法。但我见过很多个人项目为了省事,把 API Key 直接写进源码,一旦代码泄露,损失远不止是架构暴露。
- 依赖锁定要严肃对待。工具框架依赖大量第三方库,如果依赖版本不做锁定,供应链攻击的风险会成倍放大。泄露代码中多次出现第三方工具的调用,说明对这个项目来说,依赖审计不能松懈。
- 最小化曝光原则。一条装配线不需要把每个零件的图纸都公开。对个人开发者来说,至少要做到:核心架构文档内部沉淀,敏感配置绝不入仓库,第三方服务凭据独立管理。这次事件最值得抄的作业,不是哪段代码,而是“分层之后,每一层都可以单独做防护”的思路。
5.3 从零开始搭建一个小型工具框架的落地步骤
如果你看了前面的拆解,想自己动手搭一个最小可用的“三层装配线”,这里给一条我实际验证过的落地路径。
第一步:定义接口边界。先画出三层之间的接口规范:CLI 层调用编排层时传入什么结构,编排层调用内核层时返回什么结构。建议先用 TypeScript 或 Python 的 dataclass 定义清楚,不要急着写逻辑。
第二步:实现内核层。从最底层的模型网关开始,先接一个 provider,打通“发请求 → 拿响应 → 解析内容”的最小链路。然后实现两个最基础的工具:read_file 和 run_command,配上最简单的安全校验。
第三步:实现工具注册表和 Agent 循环。让模型能够看到工具声明,并基于声明输出调用请求。这个阶段你会体会到 JSON Schema 的意义——模型能不能正确填参数,全靠 Schema 写得好不好。
第四步:实现 CLI 交互层。把编排层暴露成可调用的服务,CLI 只负责读终端输入、调服务、渲染输出。到这个阶段,你已经有一个能跑通的全链路框架了。
第五步:逐步加权限、会话、遥测。权限先做成“所有工具都询问”的保守模式,会话快照做成 JSON 落盘,日志先打印到标准输出。等主流程稳定,再迭代优化。
每一步都要跑通再做下一步,否则调试成本会直线上升。我见过太多人一上来就同时写三层,结果出现问题根本不知道是哪一层出的错。
5.4 高频问题排查速查表
结合这次源码分析和大量实际操作经验,我把最常见的问题整理成一张表,方便你对照排查:
| 现象 | 可能所在层 | 排查思路 |
|---|---|---|
| 命令能启动但无响应 | CLI 层 | 检查配置文件是否加载成功,日志是否有启动报错 |
| 提示模型不存在 | 内核层 | 确认模型名与 provider 支持完全一致,确认版本支持 |
| 工具调用后无结果 | 编排层 | 检查工具注册表是否声明了该工具,参数 Schema 是否匹配 |
| 权限弹窗反复出现 | 编排层 | 检查权限模式配置,确认允许列表是否覆盖目标操作 |
| 对话无法恢复上次内容 | 编排层 | 检查会话快照是否落盘成功,目录是否可写 |
| 上下文很快就满了 | 编排层/内核层 | 检查历史压缩策略是否生效,工具输出是否做了精简 |
| 修改配置不生效 | CLI 层 | 检查配置优先级,确认是否被项目级配置覆盖 |
| 流式输出卡顿 | 内核层/CLI 层 | 检查网络连接,确认流式接口是否有超时保护 |
这张表的价值在于:它告诉你解决问题的顺序,而不是一上来就怀疑“是不是工具坏了”。框架大了以后,绝大多数问题都出在层与层的交界处,而不是某一层的内部。
结尾
我个人在实际操作中的体会是:看完这份源码后,最值钱的不是某个具体函数,而是那种“每一层都知道自己该干什么”的克制感。很多项目写着写着就失控,根因是层与层的边界模糊,工具、权限、上下文、模型全绞在一起。三层装配线的思路,本质上就是逼你自己先想清楚“每条生产线上到底有哪些工位”,再开机生产。
如果你正在用 Claude Code、正在尝试接入其他模型,或者正在自己搭 Agent 工具,不妨按这套三层框架重新审视手头的东西,把每个问题定位到具体层,你会发现排查思路突然就明了了。最后再分享一个小心得:搭建这类框架时,先让最薄的第一层跑通,再去丰富第三层,最后回头打磨第二层,这个顺序能让你的返工率低很多。