Files
claude-code-mirror/claude-code-中文Wiki/06-桥接系统.md
2026-04-03 13:01:19 +08:00

660 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | 容量唤醒机制 |