660 lines
21 KiB
Markdown
660 lines
21 KiB
Markdown
# 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`
|
||
|
||
### 核心类型
|
||
|
||
```typescript
|
||
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>
|
||
}
|
||
```
|
||
|
||
### 核心函数
|
||
|
||
```typescript
|
||
export async function initBridgeCore(
|
||
params: BridgeCoreParams
|
||
): Promise<BridgeCoreHandle | null>
|
||
```
|
||
|
||
**BridgeCoreParams 输入参数**:
|
||
|
||
```typescript
|
||
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. **注册桥接环境**
|
||
```typescript
|
||
const reg = await api.registerBridgeEnvironment(bridgeConfig)
|
||
environmentId = reg.environment_id
|
||
environmentSecret = reg.environment_secret
|
||
```
|
||
|
||
2. **创建会话**
|
||
```typescript
|
||
const createdSessionId = await createSession({...})
|
||
currentSessionId = createdSessionId
|
||
```
|
||
|
||
3. **启动工作轮询循环**
|
||
```typescript
|
||
void startWorkPollLoop(pollOpts)
|
||
```
|
||
|
||
4. **处理入口消息**
|
||
```typescript
|
||
transport.onData(data => {
|
||
handleIngressMessage(data, recentPostedUUIDs, recentInboundUUIDs, ...)
|
||
})
|
||
```
|
||
|
||
5. **清理**
|
||
```typescript
|
||
await doTeardownImpl()
|
||
```
|
||
|
||
---
|
||
|
||
## 2. bridgeMessaging.ts - 消息处理
|
||
|
||
**文件位置**: `src/bridge/bridgeMessaging.ts`
|
||
|
||
### 消息入口处理
|
||
|
||
```typescript
|
||
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
|
||
```
|
||
|
||
**消息类型判断**:
|
||
|
||
```typescript
|
||
export function isSDKMessage(value: unknown): value is SDKMessage
|
||
export function isSDKControlResponse(value: unknown): value is SDKControlResponse
|
||
export function isSDKControlRequest(value: unknown): value is SDKControlRequest
|
||
```
|
||
|
||
### 可桥接消息过滤
|
||
|
||
```typescript
|
||
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')
|
||
)
|
||
}
|
||
```
|
||
|
||
### 服务器控制请求处理
|
||
|
||
```typescript
|
||
export function handleServerControlRequest(
|
||
request: SDKControlRequest,
|
||
handlers: ServerControlRequestHandlers,
|
||
): void
|
||
```
|
||
|
||
**支持的请求类型**:
|
||
- `initialize` - 初始化
|
||
- `set_model` - 设置模型
|
||
- `set_max_thinking_tokens` - 设置最大思考 token
|
||
- `set_permission_mode` - 设置权限模式
|
||
- `interrupt` - 中断
|
||
|
||
### 标题文本提取
|
||
|
||
```typescript
|
||
export function extractTitleText(m: Message): string | undefined
|
||
// 从用户消息中提取标题
|
||
```
|
||
|
||
### BoundedUUIDSet - UUID 环形缓冲区
|
||
|
||
```typescript
|
||
export class BoundedUUIDSet {
|
||
constructor(capacity: number)
|
||
add(uuid: string): void
|
||
has(uuid: string): boolean
|
||
clear(): void
|
||
}
|
||
```
|
||
|
||
**用途**:
|
||
- 消息回音过滤
|
||
- 重复消息去重
|
||
|
||
---
|
||
|
||
## 3. replBridge.ts - REPL 会话桥接
|
||
|
||
**文件位置**: `src/bridge/replBridge.ts`
|
||
|
||
### 核心接口
|
||
|
||
```typescript
|
||
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/*)
|
||
|
||
### 初始化流程
|
||
|
||
```typescript
|
||
export async function initReplBridge(
|
||
params: InitBridgeOptions
|
||
): Promise<ReplBridgeHandle | null>
|
||
```
|
||
|
||
### 消息写入
|
||
|
||
```typescript
|
||
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 解析
|
||
|
||
```typescript
|
||
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 刷新调度器
|
||
|
||
```typescript
|
||
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`
|
||
|
||
### 核心类型
|
||
|
||
```typescript
|
||
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
|
||
```
|
||
|
||
### 会话句柄
|
||
|
||
```typescript
|
||
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
|
||
}
|
||
```
|
||
|
||
### 活动提取
|
||
|
||
```typescript
|
||
function extractActivities(
|
||
line: string,
|
||
sessionId: string,
|
||
onDebug: (msg: string) => void
|
||
): SessionActivity[]
|
||
```
|
||
|
||
**活动类型**:
|
||
```typescript
|
||
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 }
|
||
```
|
||
|
||
### 子进程管理
|
||
|
||
```typescript
|
||
const child: ChildProcess = spawn(deps.execPath, args, {
|
||
cwd: dir,
|
||
stdio: ['pipe', 'pipe', 'pipe'],
|
||
env,
|
||
windowsHide: true,
|
||
})
|
||
```
|
||
|
||
**环境变量设置**:
|
||
```typescript
|
||
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`
|
||
|
||
### 权限响应类型
|
||
|
||
```typescript
|
||
type BridgePermissionResponse = {
|
||
behavior: 'allow' | 'deny'
|
||
updatedInput?: Record<string, unknown>
|
||
updatedPermissions?: PermissionUpdate[]
|
||
message?: string
|
||
}
|
||
```
|
||
|
||
### 回调接口
|
||
|
||
```typescript
|
||
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`
|
||
|
||
### 句柄类型
|
||
|
||
```typescript
|
||
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`
|
||
|
||
### 工厂函数
|
||
|
||
```typescript
|
||
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. 关键配置
|
||
|
||
### 轮询配置
|
||
|
||
```typescript
|
||
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 分钟
|
||
}
|
||
```
|
||
|
||
### 重连策略
|
||
|
||
```typescript
|
||
const MAX_RECONNECT_ATTEMPTS = 5
|
||
const INITIAL_BACKOFF_MS = 1000
|
||
const MAX_BACKOFF_MS = 30000
|
||
```
|
||
|
||
### UUID 缓冲区容量
|
||
|
||
```typescript
|
||
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` | 容量唤醒机制 |
|