Bridge Protocol —— Claude Code ↔ IDE 双向通信协议¶
位置:
src/bridge/25 个文件 角色:让 VSCode / JetBrains 等 IDE 与本地 Claude Code CLI 实时双向同步 传输层:原生 WebSocket / stdio / HTTP
1. 架构¶
┌─────────────┐ ┌─────────────────┐
│ Claude Code│ │ IDE │
│ CLI │ ←── Bridge ──→ │ VSCode/ │
│ │ Protocol │ JetBrains │
└─────────────┘ └─────────────────┘
│ │
│ src/bridge/ │
│ - bridgeMain.ts (协调) │
│ - replBridge.ts (CLI 端) │
│ - flushGate.ts (背压) │
│ - jwtUtils.ts (鉴权) │
2. 消息格式¶
所有消息都是 JSON 字符串(序列化后通过 WebSocket / stdio 传输)。
type BridgeMessage = {
type: string // 消息类型
data?: unknown // 消息数据
timestamp?: number // 消息时间戳
id?: string // 可选:消息 ID(用于追踪)
}
3. 30+ 消息类型¶
3.1 CLI → IDE(出站)¶
| 类型 | 数据 | 触发时机 |
|---|---|---|
message_added |
Message |
任何消息加入会话 |
tool_use_start |
{ id, name, input } |
工具调用开始 |
tool_use_delta |
{ id, partial } |
工具输出增量(流式) |
tool_use_result |
{ id, result, isError } |
工具完成 |
cost_update |
{ cost, tokens, model } |
费用更新 |
status |
{ sessions, uptime, memory } |
状态推送(每秒) |
file_changed |
{ path } |
文件修改 |
file_created |
{ path } |
文件创建 |
file_deleted |
{ path } |
文件删除 |
permission_request |
{ tool, input, reason } |
权限询问 |
permission_response |
{ decision } |
权限决策响应 |
plan_approval_request |
{ plan } |
Plan 审批请求 |
plan_approval_response |
{ approved, reason? } |
Plan 审批响应 |
question_request |
{ question, options } |
提问 |
question_response |
{ answer } |
提问回答 |
selection_change |
{ text, path, range } |
IDE 选中变化 |
ide_state |
{ openFiles, activeFile } |
IDE 状态 |
ping |
- | 保持连接 |
pong |
- | 响应 ping |
error |
{ code, message, stack? } |
错误 |
warning |
{ message } |
警告 |
info |
{ message } |
信息 |
progress |
{ stage, percent } |
进度 |
elicitation |
{ schema } |
MCP Elicitation |
elicitation_response |
{ answer } |
Elicitation 响应 |
attachment |
{ path, type, size } |
附件 |
command_output |
{ stdout, stderr } |
命令输出 |
diagnostic |
{ level, message, source } |
诊断信息 |
telemetry_event |
{ event, properties } |
遥测事件 |
3.2 IDE → CLI(入站)¶
| 类型 | 数据 | 用途 |
|---|---|---|
user_input |
{ text } |
用户输入(IDE 端键入) |
file_selected |
{ path } |
选中文件 |
file_saved |
{ path } |
文件保存(让 Claude 知道) |
selection_changed |
{ text, path, range } |
选中内容变化 |
permission_decision |
{ id, allow, remember? } |
权限决策 |
plan_decision |
{ id, approved } |
Plan 决策 |
question_answer |
{ id, answer } |
提问回答 |
cancel_request |
{ id? } |
取消正在进行的请求 |
reset_session |
- | 重置会话 |
switch_session |
{ sessionId } |
切换会话 |
pause_session |
- | 暂停 |
resume_session |
- | 恢复 |
set_model |
{ model } |
切换模型 |
set_permission_mode |
{ mode } |
切换权限模式 |
apply_edit |
{ filePath, newContent } |
IDE 接受 edit |
reject_edit |
{ filePath, reason } |
IDE 拒绝 edit |
ping |
- | 保活 |
pong |
- | 响应 pong |
get_status |
- | 请求状态 |
set_ide_theme |
{ theme } |
IDE 主题同步 |
open_file |
{ path } |
在 IDE 打开文件 |
show_diff |
{ filePath, oldContent, newContent } |
在 IDE 显示 diff |
4. 序列化 / 反序列化¶
// src/bridge/bridgeMessaging.ts(推测)
export function serializeMessage(msg: BridgeMessage): string {
return JSON.stringify(msg, (key, value) => {
// 处理 Buffer、Date、循环引用
if (Buffer.isBuffer(value)) {
return { __type: 'Buffer', data: value.toString('base64') }
}
return value
})
}
export function deserializeMessage(raw: string): BridgeMessage | null {
try {
return JSON.parse(raw, (key, value) => {
if (value && value.__type === 'Buffer') {
return Buffer.from(value.data, 'base64')
}
return value
})
} catch (err) {
logError(err)
return null
}
}
5. 背压控制¶
src/bridge/flushGate.ts(推测实现):
class FlushGate {
private queue: BridgeMessage[] = []
private maxQueueSize = 100
private batchSize = 50
enqueue(msg: BridgeMessage): boolean {
if (this.queue.length >= this.maxQueueSize) {
// 队列满:丢弃(不阻塞 producer)
return false
}
this.queue.push(msg)
this.scheduleFlush()
return true
}
// 批处理:50 条/批
private flush(): void {
const batch = this.queue.splice(0, this.batchSize)
this.transport.send(batch)
if (this.queue.length > 0) this.scheduleFlush()
}
}
关键设计:
- 批处理(50 条/批)—— 不是每条都发
- 队列上限(100)—— 满了丢弃
- 不阻塞 producer —— enqueue 立即返回
6. 鉴权(JWT)¶
远程 Bridge 模式(src/bridge/jwtUtils.ts):
// 短期 JWT(1h 过期)
export function generateBridgeToken(
sessionId: string,
secret: string,
): string {
return jwt.sign({ sessionId }, secret, { expiresIn: '1h' })
}
export function verifyBridgeToken(token: string, secret: string): {
sessionId: string
} | null {
return jwt.verify(token, secret) as { sessionId: string }
}
本地 stdio 模式:无需鉴权(进程间通信)。 远程 WebSocket 模式:必须用 JWT。
7. 保活(Capacity Wake)¶
src/bridge/capacityWake.ts:
// 每 30s 主动 ping 一次
function startCapacityWake(connection: BridgeConnection): NodeJS.Timeout {
return setInterval(() => {
connection.send({ type: 'ping' })
}, 30_000)
}
为什么需要: - 防火墙 / NAT 经常 idle 30s 后断连 - 主动 ping 保持连接活跃 - 不是持续 ping(节省带宽)
8. 重连策略¶
// src/bridge/bridgeMain.ts(推测)
const backoff: BackoffConfig = {
initialMs: 1000,
maxMs: 30000,
factor: 2,
jitter: true,
}
async function reconnectWithBackoff(): Promise<void> {
let attempt = 0
while (!this.connected) {
try {
await this.connect()
return // 成功
} catch (err) {
if (isConnectionError(err)) {
const delay = Math.min(
backoff.initialMs * Math.pow(backoff.factor, attempt),
backoff.maxMs,
)
await sleep(addJitter(delay))
attempt++
} else {
throw err // 不可重试错误
}
}
}
}
指数退避 + 抖动: - attempt 1: ~1s - attempt 2: ~2s - attempt 3: ~4s - ... - 封顶 30s - + 20% 随机抖动
9. 状态机¶
stateDiagram-v2
[*] --> Disconnected
Disconnected --> Connecting: 启动
Connecting --> Handshaking: TCP/stdio 建立
Connecting --> Failed: 连接失败
Handshaking --> Connected: 协议握手成功
Handshaking --> Failed: 协议错误
Failed --> Connecting: 退避重试
Connected --> Paused: 用户暂停
Paused --> Connected: 恢复
Connected --> Disconnected: 网络断开
Disconnected --> Connecting: 自动重连
Connected --> [*]: 关闭
10. 安全考虑¶
- JWT 短期 token(1h 过期)
- 本地 stdio 模式不暴露网络
- WebSocket 模式必须 TLS
- 权限决策可记忆(用户可"always allow this")
- 附件大小限制(防止 OOM)
11. 性能考虑¶
- 批处理 flush(50 条/批)
- 不阻塞 producer(enqueue 立即返回)
- ping 间隔 30s(不是 1s)
- 断连重连带退避(不雪崩)
12. 调试¶
打开 verbose 模式:
看 ~/.claude/logs/bridge.log。
13. 完整消息列表(详细)¶
13.1 出站(CLI → IDE)¶
| # | type | data 字段 | 触发 |
|---|---|---|---|
| 1 | message_added |
Message |
新消息 |
| 2 | message_updated |
Message |
消息更新(如 streaming) |
| 3 | message_removed |
{ id } |
消息删除 |
| 4 | tool_use_start |
{ id, name, input, agent? } |
工具开始 |
| 5 | tool_use_delta |
{ id, partial } |
工具输出增量 |
| 6 | tool_use_result |
{ id, result, isError, durationMs } |
工具完成 |
| 7 | cost_update |
{ cost, tokens, model, totalCost, totalTokens } |
费用累计 |
| 8 | status |
{ sessions, uptime, memory, model } |
1s 推送 |
| 9 | file_changed |
{ path, kind: 'modified' | 'created' | 'deleted' } |
文件变化 |
| 10 | permission_request |
{ id, tool, input, reason } |
需要权限 |
| 11 | plan_approval_request |
{ id, plan, agent } |
Plan 审批 |
| 12 | question_request |
{ id, question, options, multiSelect } |
提问 |
| 13 | selection_change |
{ text, path, range } |
IDE 选中 |
| 14 | ide_state |
{ openFiles, activeFile, recentFiles } |
IDE 状态 |
| 15 | ping |
- | 保活 |
| 16 | error |
{ code, message, stack? } |
错误 |
| 17 | progress |
{ id, stage, percent, message? } |
进度 |
| 18 | elicitation |
{ id, schema, message } |
MCP Elicitation |
| 19 | attachment |
{ path, type, size, mime? } |
附件 |
| 20 | telemetry_event |
{ event, properties } |
遥测 |
13.2 入站(IDE → CLI)¶
| # | type | data 字段 | 用途 |
|---|---|---|---|
| 1 | user_input |
{ text, mode? } |
用户输入 |
| 2 | file_selected |
{ path } |
选中文件 |
| 3 | file_saved |
{ path, content? } |
文件保存 |
| 4 | selection_changed |
{ text, path, range } |
选中变化 |
| 5 | permission_decision |
{ id, allow, remember? } |
权限决策 |
| 6 | plan_decision |
{ id, approved, reason? } |
Plan 决策 |
| 7 | question_answer |
{ id, answer } |
回答 |
| 8 | cancel_request |
{ id? } |
取消 |
| 9 | reset_session |
{ sessionId? } |
重置 |
| 10 | switch_session |
{ sessionId } |
切换 |
| 11 | pause_session |
- | 暂停 |
| 12 | resume_session |
- | 恢复 |
| 13 | set_model |
{ model } |
切模型 |
| 14 | set_permission_mode |
{ mode } |
切模式 |
| 15 | apply_edit |
{ filePath, newContent } |
接受 edit |
| 16 | reject_edit |
{ filePath, reason } |
拒绝 edit |
| 17 | ping |
- | 保活 |
| 18 | get_status |
- | 状态查询 |
| 19 | set_ide_theme |
{ theme } |
主题 |
| 20 | open_file |
{ path, line?, column? } |
打开文件 |
| 21 | show_diff |
{ filePath, oldContent, newContent } |
显示 diff |
14. 配套文件¶
| 文件 | 角色 |
|---|---|
src/bridge/bridgeMain.ts (2999 行) |
协调者 + 状态机 |
src/bridge/replBridge.ts (2406 行) |
CLI 端实现 |
src/bridge/bridgeApi.ts |
API 类型 |
src/bridge/bridgeConfig.ts |
配置 |
src/bridge/bridgeMessaging.ts |
序列化 |
src/bridge/bridgePermissionCallbacks.ts |
权限回调 |
src/bridge/bridgeStatusUtil.ts |
状态工具 |
src/bridge/bridgeUI.ts |
UI 集成 |
src/bridge/capacityWake.ts |
保活 |
src/bridge/codeSessionApi.ts |
Code session API |
src/bridge/createSession.ts |
创建 session |
src/bridge/flushGate.ts |
背压 |
src/bridge/inboundAttachments.ts |
入站附件 |
src/bridge/inboundMessages.ts |
入站消息 |
src/bridge/initReplBridge.ts |
初始化 |
src/bridge/jwtUtils.ts |
JWT 鉴权 |
src/bridge/pollConfig.ts |
轮询配置 |
src/bridge/remoteBridgeCore.ts |
远程 bridge 核心 |
src/bridge/replBridgeHandle.ts |
handle 抽象 |
src/bridge/envLessBridgeConfig.ts |
无 env 配置 |
src/bridge/debugUtils.ts |
调试 |
src/bridge/bridgeDebug.ts |
调试 |
src/bridge/bridgeEnabled.ts |
启用判断 |
src/bridge/bridgePointer.ts |
指针协议 |
src/bridge/pollConfigDefaults.ts |
轮询默认 |
15. 设计哲学¶
- 消息总线而非 RPC —— 任何一方都可发任何消息
- 背压控制 —— flushGate 防淹没
- JWT 短期 token —— 安全 + 可追溯
- 协议版本化 —— 未来扩展
- 保活但不密集 —— 30s 一次
16. IDE 集成参考实现¶
如想给其他 IDE 写 Bridge 集成:
- WebSocket 连
ws://localhost:<port> - 发送
user_input接收消息 - 监听所有
*_added/*_updated消息 - 用户操作时回
*_decision消息 - 处理
permission_request弹权限窗 - 处理
question_request弹问题窗
最小可工作集成 ~500 行代码。