跳转至

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_CODE_BRIDGE_DEBUG=true claude

~/.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. 设计哲学

  1. 消息总线而非 RPC —— 任何一方都可发任何消息
  2. 背压控制 —— flushGate 防淹没
  3. JWT 短期 token —— 安全 + 可追溯
  4. 协议版本化 —— 未来扩展
  5. 保活但不密集 —— 30s 一次

16. IDE 集成参考实现

如想给其他 IDE 写 Bridge 集成:

  1. WebSocket 连 ws://localhost:<port>
  2. 发送 user_input 接收消息
  3. 监听所有 *_added / *_updated 消息
  4. 用户操作时回 *_decision 消息
  5. 处理 permission_request 弹权限窗
  6. 处理 question_request 弹问题窗

最小可工作集成 ~500 行代码。