21 KiB
21 KiB
Claude Code 桥接系统详解
概述
桥接系统(Bridge)是 Claude Code CLI 与 IDE 扩展(VS Code, JetBrains)之间的双向通信层。它使 IDE 能够:
- 在远程设备上启动 Claude Code 会话
- 将用户输入转发到 Claude Code
- 显示 Claude Code 的输出和工具执行结果
- 处理权限请求和响应
核心架构
┌──────────────────────────────────────────────────────────────┐
│ 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
}
桥接生命周期
-
注册桥接环境
const reg = await api.registerBridgeEnvironment(bridgeConfig) environmentId = reg.environment_id environmentSecret = reg.environment_secret -
创建会话
const createdSessionId = await createSession({...}) currentSessionId = createdSessionId -
启动工作轮询循环
void startWorkPollLoop(pollOpts) -
处理入口消息
transport.onData(data => { handleIngressMessage(data, recentPostedUUIDs, recentInboundUUIDs, ...) }) -
清理
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- 设置最大思考 tokenset_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 |
容量唤醒机制 |