Files
2026-04-03 13:01:19 +08:00

21 KiB
Raw Permalink Blame History

Claude Code 桥接系统详解

概述

桥接系统Bridge是 Claude Code CLI 与 IDE 扩展VS Code, JetBrains之间的双向通信层。它使 IDE 能够:

  1. 在远程设备上启动 Claude Code 会话
  2. 将用户输入转发到 Claude Code
  3. 显示 Claude Code 的输出和工具执行结果
  4. 处理权限请求和响应

核心架构

┌──────────────────────────────────────────────────────────────┐
│                     IDE Extension (VS Code / JetBrains)     │
│  - 用户界面                                                  │
│  - 输入处理                                                  │
│  - 输出展示                                                  │
└──────────────────────────────────────────────────────────────┘
                              │
                              │ WebSocket / HTTP
                              ▼
┌──────────────────────────────────────────────────────────────┐
│                     Bridge Layer                             │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                   bridgeMain.ts                        │  │
│  │  入口点,管理桥接生命周期                              │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                bridgeMessaging.ts                      │  │
│  │  消息处理、入口路由、流量控制                          │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                replBridge.ts                            │  │
│  │  REPL 会话桥接                                         │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                sessionRunner.ts                         │  │
│  │  会话执行管理                                          │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │                jwtUtils.ts                              │  │
│  │  JWT 认证工具                                         │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐  │
│  │           bridgePermissionCallbacks.ts                   │  │
│  │  权限回调处理                                          │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌──────────────────────────────────────────────────────────────┐
│                   Claude Code CLI (子进程)                  │
│  - 命令处理                                                │
│  - 工具执行                                                │
│  - API 调用                                                │
└──────────────────────────────────────────────────────────────┘

1. bridgeMain.ts - 桥接主入口

文件位置: src/bridge/bridgeMain.ts

核心类型

export type BridgeState = 'ready' | 'connected' | 'reconnecting' | 'failed'

export type ReplBridgeHandle = {
  bridgeSessionId: string
  environmentId: string
  sessionIngressUrl: string
  writeMessages(messages: Message[]): void
  writeSdkMessages(messages: SDKMessage[]): void
  sendControlRequest(request: SDKControlRequest): void
  sendControlResponse(response: SDKControlResponse): void
  sendControlCancelRequest(requestId: string): void
  sendResult(): void
  teardown(): Promise<void>
}

核心函数

export async function initBridgeCore(
  params: BridgeCoreParams
): Promise<BridgeCoreHandle | null>

BridgeCoreParams 输入参数:

type BridgeCoreParams = {
  dir: string                    // 工作目录
  machineName: string            // 机器名称
  branch: string                 // Git 分支
  gitRepoUrl: string | null      // Git 仓库 URL
  title: string                  // 会话标题
  baseUrl: string                // API 基础 URL
  sessionIngressUrl: string      // 会话入口 URL
  workerType: string            // 工作器类型
  getAccessToken: () => string | undefined
  createSession: (opts: {...}) => Promise<string | null>
  archiveSession: (sessionId: string) => Promise<void>
  toSDKMessages?: (messages: Message[]) => SDKMessage[]
  onAuth401?: (staleAccessToken: string) => Promise<boolean>
  getPollIntervalConfig?: () => PollIntervalConfig
  initialMessages?: Message[]
  previouslyFlushedUUIDs?: Set<string>
  onInboundMessage?: (msg: SDKMessage) => void
  onPermissionResponse?: (response: SDKControlResponse) => void
  onInterrupt?: () => void
  onSetModel?: (model: string | undefined) => void
  onSetMaxThinkingTokens?: (maxTokens: number | null) => void
  onSetPermissionMode?: (mode: PermissionMode) => {...}
  onStateChange?: (state: BridgeState, detail?: string) => void
  onUserMessage?: (text: string, sessionId: string) => boolean
  perpetual?: boolean
  initialSSESequenceNum?: number
}

桥接生命周期

  1. 注册桥接环境

    const reg = await api.registerBridgeEnvironment(bridgeConfig)
    environmentId = reg.environment_id
    environmentSecret = reg.environment_secret
    
  2. 创建会话

    const createdSessionId = await createSession({...})
    currentSessionId = createdSessionId
    
  3. 启动工作轮询循环

    void startWorkPollLoop(pollOpts)
    
  4. 处理入口消息

    transport.onData(data => {
      handleIngressMessage(data, recentPostedUUIDs, recentInboundUUIDs, ...)
    })
    
  5. 清理

    await doTeardownImpl()
    

2. bridgeMessaging.ts - 消息处理

文件位置: src/bridge/bridgeMessaging.ts

消息入口处理

export function handleIngressMessage(
  data: string,
  recentPostedUUIDs: BoundedUUIDSet,
  recentInboundUUIDs: BoundedUUIDSet,
  onInboundMessage: ((msg: SDKMessage) => void | Promise<void>) | undefined,
  onPermissionResponse?: ((response: SDKControlResponse) => void) | undefined,
  onControlRequest?: ((request: SDKControlRequest) => void) | undefined,
): void

消息类型判断:

export function isSDKMessage(value: unknown): value is SDKMessage
export function isSDKControlResponse(value: unknown): value is SDKControlResponse
export function isSDKControlRequest(value: unknown): value is SDKControlRequest

可桥接消息过滤

export function isEligibleBridgeMessage(m: Message): boolean {
  // 排除虚拟消息
  if ((m.type === 'user' || m.type === 'assistant') && m.isVirtual) {
    return false
  }
  return (
    m.type === 'user' ||
    m.type === 'assistant' ||
    (m.type === 'system' && m.subtype === 'local_command')
  )
}

服务器控制请求处理

export function handleServerControlRequest(
  request: SDKControlRequest,
  handlers: ServerControlRequestHandlers,
): void

支持的请求类型:

  • initialize - 初始化
  • set_model - 设置模型
  • set_max_thinking_tokens - 设置最大思考 token
  • set_permission_mode - 设置权限模式
  • interrupt - 中断

标题文本提取

export function extractTitleText(m: Message): string | undefined
// 从用户消息中提取标题

BoundedUUIDSet - UUID 环形缓冲区

export class BoundedUUIDSet {
  constructor(capacity: number)
  add(uuid: string): void
  has(uuid: string): boolean
  clear(): void
}

用途:

  • 消息回音过滤
  • 重复消息去重

3. replBridge.ts - REPL 会话桥接

文件位置: src/bridge/replBridge.ts

核心接口

export type ReplBridgeTransport = {
  setOnConnect(callback: () => void): void
  setOnData(callback: (data: string) => void): void
  setOnClose(callback: (code: number | undefined) => void): void
  connect(): void
  write(message: object): Promise<void>
  writeBatch(messages: object[]): Promise<void>
  close(): void
  getStateLabel(): string
  getLastSequenceNum(): number
  isConnectedStatus(): boolean
}

传输类型

1. HybridTransport (v1)

  • WebSocket 读取
  • HTTP POST 写入
  • 目标: Session-Ingress

2. SSETransport (v2)

  • Server-Sent Events 读取
  • HTTP POST 写入
  • 目标: CCR (/worker/*)

初始化流程

export async function initReplBridge(
  params: InitBridgeOptions
): Promise<ReplBridgeHandle | null>

消息写入

writeMessages(messages: Message[]) {
  // 过滤可桥接消息
  const filtered = messages.filter(m =>
    isEligibleBridgeMessage(m) &&
    !initialMessageUUIDs.has(m.uuid) &&
    !recentPostedUUIDs.has(m.uuid)
  )

  // 转换并发送
  const sdkMessages = toSDKMessages(filtered)
  void transport.writeBatch(events)
}

4. jwtUtils.ts - JWT 认证

文件位置: src/bridge/jwtUtils.ts

JWT 解析

export function decodeJwtPayload(token: string): unknown | null {
  // 剥离 sk-ant-si- 前缀
  const jwt = token.startsWith('sk-ant-si-')
    ? token.slice('sk-ant-si-'.length)
    : token

  // 解析 payload (不验证签名)
  const parts = jwt.split('.')
  if (parts.length !== 3 || !parts[1]) return null
  return jsonParse(Buffer.from(parts[1], 'base64url').toString('utf8'))
}

Token 刷新调度器

export function createTokenRefreshScheduler({
  getAccessToken,
  onRefresh,
  label,
  refreshBufferMs = 5 * 60 * 1000,  // 5 分钟
}): {
  schedule: (sessionId: string, token: string) => void
  scheduleFromExpiresIn: (sessionId: string, expiresInSeconds: number) => void
  cancel: (sessionId: string) => void
  cancelAll: () => void
}

刷新策略:

  • 过期前 5 分钟刷新(默认)
  • 指数退避重试
  • 最多 3 次连续失败

5. sessionRunner.ts - 会话执行管理

文件位置: src/bridge/sessionRunner.ts

核心类型

type SessionSpawnerDeps = {
  execPath: string
  scriptArgs: string[]
  env: NodeJS.ProcessEnv
  verbose: boolean
  sandbox: boolean
  debugFile?: string
  permissionMode?: string
  onDebug: (msg: string) => void
  onActivity?: (sessionId: string, activity: SessionActivity) => void
  onPermissionRequest?: (sessionId: string, request: PermissionRequest, accessToken: string) => void
}

export function createSessionSpawner(deps: SessionSpawnerDeps): SessionSpawner

会话句柄

export type SessionHandle = {
  sessionId: string
  done: Promise<SessionDoneStatus>
  activities: SessionActivity[]
  accessToken: string
  lastStderr: string[]
  currentActivity: SessionActivity | null
  kill(): void
  forceKill(): void
  writeStdin(data: string): void
  updateAccessToken(token: string): void
}

活动提取

function extractActivities(
  line: string,
  sessionId: string,
  onDebug: (msg: string) => void
): SessionActivity[]

活动类型:

type SessionActivity =
  | { type: 'tool_start'; summary: string; timestamp: number }
  | { type: 'text'; summary: string; timestamp: number }
  | { type: 'result'; summary: string; timestamp: number }
  | { type: 'error'; summary: string; timestamp: number }

子进程管理

const child: ChildProcess = spawn(deps.execPath, args, {
  cwd: dir,
  stdio: ['pipe', 'pipe', 'pipe'],
  env,
  windowsHide: true,
})

环境变量设置:

const env: NodeJS.ProcessEnv = {
  ...deps.env,
  CLAUDE_CODE_OAUTH_TOKEN: undefined,
  CLAUDE_CODE_ENVIRONMENT_KIND: 'bridge',
  CLAUDE_CODE_SESSION_ACCESS_TOKEN: opts.accessToken,
  CLAUDE_CODE_USE_CCR_V2: opts.useCcrV2 ? '1' : undefined,
  CLAUDE_CODE_WORKER_EPOCH: String(opts.workerEpoch),
}

6. bridgePermissionCallbacks.ts - 权限回调

文件位置: src/bridge/bridgePermissionCallbacks.ts

权限响应类型

type BridgePermissionResponse = {
  behavior: 'allow' | 'deny'
  updatedInput?: Record<string, unknown>
  updatedPermissions?: PermissionUpdate[]
  message?: string
}

回调接口

type BridgePermissionCallbacks = {
  sendRequest(
    requestId: string,
    toolName: string,
    input: Record<string, unknown>,
    toolUseId: string,
    description: string,
    permissionSuggestions?: PermissionUpdate[],
    blockedPath?: string
  ): void

  sendResponse(requestId: string, response: BridgePermissionResponse): void

  cancelRequest(requestId: string): void

  onResponse(
    requestId: string,
    handler: (response: BridgePermissionResponse) => void
  ): () => void  // 返回取消订阅函数
}

7. replBridgeHandle.ts - REPL 桥接句柄

文件位置: src/bridge/replBridgeHandle.ts

句柄类型

export type ReplBridgeHandle = {
  bridgeSessionId: string
  environmentId: string
  sessionIngressUrl: string
  writeMessages(messages: Message[]): void
  writeSdkMessages(messages: SDKMessage[]): void
  sendControlRequest(request: SDKControlRequest): void
  sendControlResponse(response: SDKControlResponse): void
  sendControlCancelRequest(requestId: string): void
  sendResult(): void
  teardown(): Promise<void>
}

8. replBridgeTransport.ts - 传输层实现

文件位置: src/bridge/replBridgeTransport.ts

工厂函数

export function createV1ReplTransport(
  transport: HybridTransport
): ReplBridgeTransport

export function createV2ReplTransport(opts: {
  sessionUrl: string
  ingressToken: string
  sessionId: string
  initialSequenceNum: number
}): Promise<ReplBridgeTransport>

9. 入口消息处理流程

IDE Extension
     │
     │ 1. 用户输入
     ▼
┌─────────────────┐
│  WS 连接        │
└─────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ handleIngressMessage()                   │
│                                         │
│ 1. 解析 JSON                            │
│ 2. 检查 UUID (回音/重复过滤)            │
│ 3. 分发消息类型                         │
│    - user → onInboundMessage           │
│    - control_response → onPermission   │
│    - control_request → onControlRequest │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ Claude Code CLI (子进程)                │
│                                         │
│ - 执行工具                              │
│ - AI 推理                              │
│ - 返回结果                              │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ writeMessages() / writeSdkMessages()  │
│                                         │
│ 1. 过滤消息                            │
│ 2. 转换为 SDK 格式                     │
│ 3. 通过 transport.writeBatch() 发送    │
└─────────────────────────────────────────┘
     │
     ▼
IDE Extension (WebSocket 订阅)
     │
     ▼
显示输出

10. 权限请求流程

Claude Code CLI
     │
     ▼ (can_use_tool 请求)
┌─────────────────────────────────────────┐
│ BridgePermissionCallbacks               │
│                                         │
│ sendRequest(requestId, toolName, input) │
└─────────────────────────────────────────┘
     │
     │ WebSocket / HTTP
     ▼
┌─────────────────────────────────────────┐
│ IDE Extension                          │
│                                         │
│ 显示权限对话框                          │
│ 用户允许/拒绝                           │
└─────────────────────────────────────────┘
     │
     ▼
┌─────────────────────────────────────────┐
│ sendResponse(requestId, response)      │
│                                         │
│ response: {                            │
│   behavior: 'allow' | 'deny',         │
│   updatedInput?,                       │
│   updatedPermissions?                   │
│ }                                       │
└─────────────────────────────────────────┘
     │
     ▼
onResponse() 回调
     │
     ▼
CLI 继续执行或中止

11. 关键配置

轮询配置

const DEFAULT_POLL_CONFIG = {
  poll_interval_ms_not_at_capacity: 5000,      // 5 秒
  poll_interval_ms_at_capacity: 120000,         // 2 分钟
  reclaim_older_than_ms: 5 * 60 * 1000,       // 5 分钟
  non_exclusive_heartbeat_interval_ms: 30000,  // 30 秒
  session_keepalive_interval_v2_ms: 120000,    // 2 分钟
}

重连策略

const MAX_RECONNECT_ATTEMPTS = 5
const INITIAL_BACKOFF_MS = 1000
const MAX_BACKOFF_MS = 30000

UUID 缓冲区容量

const recentPostedUUIDs = new BoundedUUIDSet(2000)
const recentInboundUUIDs = new BoundedUUIDSet(2000)

相关源码文件

文件 功能
bridgeMain.ts 桥接主入口和生命周期管理
bridgeMessaging.ts 消息处理、路由、流量控制
replBridge.ts REPL 会话桥接
replBridgeTransport.ts 传输层实现
sessionRunner.ts 子进程会话管理
jwtUtils.ts JWT 解析和刷新调度
bridgePermissionCallbacks.ts 权限回调接口
replBridgeHandle.ts 句柄类型定义
bridgeApi.ts 桥接 API 客户端
pollConfig.ts 轮询配置
flushGate.ts 刷新门控
capacityWake.ts 容量唤醒机制