Claude Code泄露源码深度解析:揭秘其硬核通信架构
前阵子,Claude Code 意外泄露源码的“事故”在技术圈闹得沸沸扬扬。虽然对 Anthropic 来说有点尴尬,但对广大开发者而言,这简直是一场送上门的“开卷考试”——我们终于得以绕开黑盒,拿着放大镜去围观这款顶流 AI 编程工具的底层实现。借着这次意外的“开源”,今天我们不聊它的 Prompt 有多花哨,就硬核扒一扒它的网络通信机制。很多人第一次上手 Claude Code,会下意识把它当成一个“会调用模型的命令行工具”。但翻看源码后你会发现,格局小了——它的骨架远比“发一个请求、收一段回复”复杂得多。它更像是一套严密的远程协作系统。本地终端只是一个入口,真正的工作是在一个持续在线的会话里完成的。消息来回流转、权限即时确认、断线自动重连……整套机制用一句话概括就是:它不是在“请求一次 AI”,而是在“维持一个长期的会话现场”。以下是我们从源码中提炼出的核心通信架构,看看这套“顶配”的工程设计到底牛在哪里。
一、中枢神经:真正的总控 RemoteSessionManager
在源码中,整套网络通信的“中枢神经”是一个叫做RemoteSessionManager的模块。它负责把所有通信的线头拴在一起。
它做的事情本质上只有四件:建立连接、分发消息、处理权限请求、发送控制指令。
你可以把它想象成一个现场调度员:
服务端发来消息,它负责接住。用户要发消息,它负责送出去。远端模型需要调用本地工具的权限,它负责拦截并转给上层(用户)确认。如果连接意外断开,它负责通知并尝试恢复。
它未必是业务逻辑最复杂的代码,但绝对是通信链路最核心的枢纽。
二、读写分离:WebSocket 听令,HTTP POST 汇报
Claude Code 的会话通信,最核心的设计思路非常朴实:读写分离。
它并没有把所有通信都塞进一条通道里,而是根据场景拆分了协议:
读(下行):通过 WebSocket 持续接收服务端消息。适合“服务端不断推消息下来”的持续订阅场景。写(上行):通过 HTTP POST 把用户输入和事件发回去。适合“单次明确提交”的场景。
1. WebSocket:实时消息的主干道
接收远端消息走的是 WebSocket 订阅。源码中它的连接形式非常干净:
const url = `wss://…/v1/sessions/ws/
${sessionId}/subscribeorganization_uuid=…`
const headers = {
Authorization: `Bearer ${accessToken}`,
‘anthropic-version’: ‘2023-06-01’,
}
这里藏着几个极具工程素养的细节:
不迷信长连接:每次重新连接前都会重新获取一次 token,默认考虑到“认证可能过期”的现实情况。精细的断线处理:遇到 4003 错误码视为永久拒绝(不再重连),遇到 4001 视为暂时不可用(允许有限重试)。它不是个无脑重试的机器,而是个懂语义的通信系统。心跳保活:定时 ping,防止被中间网络设备掐断(长连接系统里最无聊但也最救命的机制)。
2. HTTP POST:真正的写入通道
用户输入和事件提交并不走 WebSocket,而是构造成带有明确“事件语义”的数据包,通过 HTTP POST 发送到接口:
const requestBody = {
events: [
{
uuid: ‘…’,
session_id: sessionId,
type: ‘user’,
parent_tool_use_id: null,
message: {
role: ‘user’,
content: messageContent,
},
},
],
}
这种设计的好处是发送行为清晰、失败可感知、便于重试。Claude Code 传的不是一句话,而是一整段会话中的一个“事件节点”。
三、秩序之美:内容归内容,控制归控制
服务端发来的消息如果像一锅粥一样混在一起,系统很快就会崩溃。Claude Code 的消息流设计了一套非常成熟的分层协议。
消息大致被分为三类:
普通消息:比如助理回复的内容、系统状态、工具执行进度。这些是用户真正看到的“正文”。control_request(控制请求):服务端向客户端发出的控制信号。比如“我需要确认这个工具能不能用”、“中断当前请求”。control_response(控制响应):客户端对控制请求的答复。
这套机制将“内容流”和“控制流”完美剥离。聊天内容绝不会和权限请求混在一起,UI 界面知道该渲染什么,服务端也清楚自己是在等用户拍板,还是在继续生成代码。
四、最精彩的一环:远端工具权限审批流
Claude Code 作为 AI 编程工具,最大的特点是它能操作本地文件、执行命令。这些动作伴随着高风险,因此权限请求绝对不能含糊。
当服务端发来control_request(且类型为can_use_tool)时,系统会进入一套严密的时序协议:
服务端请求权限。客户端记录该请求,并挂起等待。上层 UI 弹出确认框(允许/拒绝)。客户端将用户的决定封装成 control_response 发回服务端。服务端收到确认后,继续执行后续动作。
这不是多此一举,而是远程代理系统必须死守的安全边界。如果请求被取消,本地会清掉等待状态;如果找不到请求 ID,会直接报错。它对权限的要求是:必须明确可追踪,绝不能悬空。
五、幕后功臣:消息“同声传译”与本地调度
机器的语言,人不一定想看。因此在底层协议和前端 UI 之间,Claude Code 还有两层很关键的设计:
1. 消息翻译器(适配层)
服务端发来的 SDK 消息需要被转换成 UI 能理解的格式。比如:
TypeScript
if (msg.type === ‘system’ && msg.subtype === ‘status’) {
return `Status: ${msg.status}` // 将内部状态转化为友好的前端提示
}
服务端在说“我内部状态变了”,翻译器把它变成人话:“正在压缩对话…”。
2. 本地交通枢纽(Bridge 层)
Bridge 层负责本地的消息调度。它的工作包括:
过滤噪音:把不需要送到远端的内部虚拟消息拦截下来。消息去重:利用 UUID 和时间窗口,防止重连或历史回放导致同一条消息/权限请求出现两次。极速响应:保证对控制请求的快速答复,防止服务端超时。
结语:不追求花哨,追求工程级的“稳”
如果把 Claude Code 的核心时序画出来,其实非常克制:
TypeScript
// 1. 建立长连接监听
connectWebSocket()
// 2. 接收与分发
onMessage(msg) {
if (msg.type === ‘control_request’) handlePermissionRequest(msg)
else renderOrDispatch(msg)
}
// 3. HTTP 提交用户输入
sendMessageToSession({ type: ‘user’, message: { … } })
// 4. 权限审批回传
sendControlResponse({ type: ‘control_response’, response: { … } })
读完这段因“意外泄露”而曝光的代码,最大的感受是:真正成熟的网络系统,往往不追求花哨的炫技,而是追求架构的清晰与可持续。
它告诉我们:一个终端工具是如何通过长连接保持活性,如何用事件接口承担写入,如何把权限请求变成严密的时序协议,最终长成了一个稳健的“轻量级远程协作平台”。
这,才是 Claude Code 藏在惊艳表现下,最硬核的技术底色。
END
夜雨聆风