1
0
Files
nex/docs/analysis_reference/analysis_cc-switch.md

723 lines
42 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.
# CC-Switch API 转换层详细分析报告
## 1. 项目概述
**CC-Switch** 是一个基于 **Tauri (Rust 后端 + Web 前端)** 的桌面应用用于管理多个大模型编程工具Claude Code、Codex CLI、Gemini CLI 等)的配置切换。其核心功能之一是**本地 HTTP 代理服务器**,在客户端与上游 LLM API 之间充当协议翻译层,使仅支持特定 API 格式的客户端(如 Claude Code 只支持 Anthropic Messages API能通过代理访问不同协议的后端服务。
### 核心能力
-**Anthropic Messages API** 请求/响应转换为 **OpenAI Chat Completions**、**OpenAI Responses API**、**Google Gemini Native** 等格式
- 支持**流式 (SSE)** 和**非流式**两种模式的实时协议翻译
- 内置故障转移、熔断器、Thinking 自修复、Copilot 请求优化等企业级特性
- 支持 GitHub Copilot、ChatGPT Plus/Pro (Codex OAuth) 等动态 Token 认证的供应商
### 与同类项目的核心差异
CC-Switch 是唯一的**桌面端**代理工具(非服务端部署),面向个人开发者场景。其协议转换是**单方向锚定**的——以 Anthropic Messages API 为唯一"客户端协议",向多种上游协议转换。相比 One-API / New-API 的多协议互转、LiteLLM 的 OpenAI-centric 统一格式CC-Switch 的转换矩阵更聚焦但深度更大(如 Gemini Shadow Store、Copilot Optimizer 等属于独有的复杂机制)。
---
## 2. 技术架构总览
```
┌─────────────────────────────────────────────────────────────────┐
│ CC-Switch Tauri 桌面应用 │
├─────────────────────────────────────────────────────────────────┤
│ Web 前端 (React) │ Rust 后端 (Tauri) │
│ │ ┌──────────────────────────────────────┐ │
│ - 供应商管理 │ │ proxy/ 模块 (Axum HTTP Server) │ │
│ - 配置切换 │ │ │ │
│ - 用量展示 │ │ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ │ server.rs│──▶│ handlers.rs │ │ │
│ │ │ │ (路由层) │ │ (请求分发器) │ │ │
│ │ │ └──────────┘ └───────┬────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────────▼─────────┐ │ │
│ │ │ │ forwarder.rs (转发器) │ │ │
│ │ │ │ - 供应商选择 / 故障转移 │ │ │
│ │ │ │ - 请求体预处理 / 参数过滤 │ │ │
│ │ │ │ - Thinking 自修复 / Copilot优化 │ │ │
│ │ │ └──────────────┬─────────────────┘ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────▼─────────────────┐ │ │
│ │ │ │ providers/ (适配器层) │ │ │
│ │ │ │ │ │ │
│ │ │ │ ┌─────────┐ ┌──────────────┐ │ │ │
│ │ │ │ │adapter.rs│ │ claude.rs │ │ │ │
│ │ │ │ │(Trait定义)│ │ (Claude适配器)│ │ │ │
│ │ │ │ └─────────┘ ├──────────────┤ │ │ │
│ │ │ │ │ codex.rs │ │ │ │
│ │ │ │ ├──────────────┤ │ │ │
│ │ │ │ │ gemini.rs │ │ │ │
│ │ │ │ └──────────────┘ │ │ │
│ │ │ └─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │
│ │ │ │ 转换层 (transform/ 模块) │ │ │
│ │ │ │ │ │ │
│ │ │ │ transform.rs (→Chat) │ │ │
│ │ │ │ transform_responses.rs (→Resp) │ │ │
│ │ │ │ transform_gemini.rs (→Gemini)│ │ │
│ │ │ └─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │
│ │ │ │ 流式转换层 (streaming/ 模块) │ │ │
│ │ │ │ │ │ │
│ │ │ │ streaming.rs (Chat) │ │ │
│ │ │ │ streaming_responses.rs (Resp) │ │ │
│ │ │ │ streaming_gemini.rs (Gemini) │ │ │
│ │ │ └─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ ┌─────────────────────────────────┐ │ │
│ │ │ │ 辅助模块 │ │ │
│ │ │ │ gemini_shadow.rs (状态回放) │ │ │
│ │ │ │ gemini_schema.rs (Schema转换) │ │ │
│ │ │ │ copilot_optimizer.rs (Copilot优化)│ │ │
│ │ │ │ thinking_rectifier.rs (Thinking修复)│ │
│ │ │ │ thinking_budget_rectifier.rs │ │ │
│ │ │ │ cache_injector.rs (缓存注入) │ │ │
│ │ │ │ body_filter.rs (私有参数过滤) │ │ │
│ │ │ │ model_mapper.rs (模型名映射) │ │ │
│ │ │ │ gemini_url.rs (Gemini URL构建)│ │ │
│ │ │ └─────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
**源文件规模**`proxy/` 目录共 53 个源文件,其中核心转换层 6 个文件合计约 7,700 行,辅助模块 7 个文件合计约 4,000 行,转发器和处理器合计约 3,700 行。
---
## 3. 路由层 (server.rs)
**文件**: `src-tauri/src/proxy/server.rs` (354 行)
代理服务器基于 **Axum** 框架构建,使用手动 hyper HTTP/1.1 accept loop通过 TCP `peek()` 捕获原始请求头大小写,存入 `OriginalHeaderCases` 扩展供转发器使用)。
### 路由表
| 路由路径 | 处理器 | 协议 |
|---------|--------|------|
| `/v1/messages`, `/claude/v1/messages` | `handle_messages` | Claude (Anthropic Messages API) |
| `/chat/completions`, `/v1/chat/completions`, `/v1/v1/chat/completions`, `/codex/v1/chat/completions` | `handle_chat_completions` | OpenAI Chat Completions |
| `/responses`, `/v1/responses`, `/v1/v1/responses`, `/codex/v1/responses` | `handle_responses` | OpenAI Responses API |
| `/responses/compact`, `/v1/responses/compact`, `/v1/v1/responses/compact`, `/codex/v1/responses/compact` | `handle_responses_compact` | Responses Compact (Codex CLI 远程压缩透传) |
| `/v1beta/*path`, `/gemini/v1beta/*path` | `handle_gemini` | Gemini (Google AI API) |
| `/health` | `health_check` | 健康检查 |
| `/status` | `get_status` | 状态查询 |
**关键设计细节**:
- **多前缀兼容**: 裸路径 (`/v1/...`) 和带供应商前缀 (`/claude/`, `/codex/`, `/gemini/`) 均支持,兼容不同客户端的 `base_url` 配置
- **双前缀容错**: `/v1/v1/...` 路由处理客户端双重前缀的配置错误
- **无前缀路由**: `/chat/completions``/responses` 不带 `/v1/`,适配 Codex CLI 的特定 base_url 配置
- **请求体限制**: `DefaultBodyLimit::max(200MB)`,支撑大体积上下文和图片请求
---
## 4. 适配器层 (Adapter Pattern)
### 4.1 ProviderAdapter Trait
**文件**: `src-tauri/src/proxy/providers/adapter.rs` (50 行)
所有供应商适配器都实现统一的 `ProviderAdapter` trait
```rust
pub trait ProviderAdapter: Send + Sync {
fn name(&self) -> &'static str;
fn extract_base_url(&self, provider: &Provider) -> Result<String, ProxyError>;
fn extract_auth(&self, provider: &Provider) -> Option<AuthInfo>;
fn build_url(&self, base_url: &str, endpoint: &str) -> String;
fn get_auth_headers(&self, auth: &AuthInfo) -> Vec<(HeaderName, HeaderValue)>;
fn needs_transform(&self, _provider: &Provider) -> bool; // 默认 false
fn transform_request(&self, body: Value, provider: &Provider) -> Result<Value, ProxyError>; // 默认透传
fn transform_response(&self, body: Value) -> Result<Value, ProxyError>; // 默认透传
}
```
其中 `needs_transform()``transform_request()``transform_response()` 提供默认实现(透传),供应商适配器仅需覆写需要的方法。
### 4.2 供应商类型枚举 (ProviderType)
**文件**: `src-tauri/src/proxy/providers/mod.rs` (515 行)
系统定义 8 种供应商类型,决定认证和请求处理逻辑:
| ProviderType | 说明 | 认证策略 | 是否需要转换 |
|---|---|---|---|
| `Claude` | Anthropic 官方 API | Anthropic (`x-api-key`) | 视 api_format 决定 |
| `ClaudeAuth` | Claude 中转服务 (仅 Bearer) | ClaudeAuth | 视 api_format 决定 |
| `Codex` | OpenAI Codex | Bearer | 否 |
| `Gemini` | Google Gemini API (API Key) | Google (`x-goog-api-key`) | 否 |
| `GeminiCli` | Gemini CLI (OAuth) | GoogleOAuth | 否 |
| `OpenRouter` | OpenRouter 中转 | Bearer | 否 (已支持原生) |
| `GitHubCopilot` | GitHub Copilot | GitHubCopilot (动态 Token) | **是** → OpenAI Chat/Responses |
| `CodexOAuth` | ChatGPT Plus/Pro 反代 | CodexOAuth (动态 Token) | **是** → OpenAI Responses |
**适配器工厂** `get_adapter_for_provider_type()` 的映射逻辑:
- `Claude`, `ClaudeAuth`, `OpenRouter`, `GitHubCopilot`, `CodexOAuth``ClaudeAdapter`
- `Codex``CodexAdapter`
- `Gemini`, `GeminiCli``GeminiAdapter`
### 4.3 API 格式识别
**文件**: `src-tauri/src/proxy/providers/claude.rs` (1313 行)
Claude 适配器通过 `get_claude_api_format()` 函数识别需要使用的 API 格式,优先级为:
1. **Codex OAuth 强制**: `meta.provider_type == "codex_oauth"``openai_responses`
2. **meta.apiFormat (SSOT)**: `meta.api_format` 字段 → `openai_chat` / `openai_responses` / `gemini_native`
3. **settings_config.api_format**: 旧版兼容
4. **openrouter_compat_mode**: 旧版 OpenRouter 兼容开关 → `openai_chat`
5. **默认**: `anthropic` (直接透传)
### 4.4 认证策略
**文件**: `src-tauri/src/proxy/providers/auth.rs` (259 行)
7 种认证策略覆盖所有上游服务:
| 策略 | Header 注入 |
|------|-----------|
| `Anthropic` | `x-api-key: <key>` + `anthropic-version: 2023-06-01` (版本由转发器注入) |
| `ClaudeAuth` | `Authorization: Bearer <key>` (无 `x-api-key`) |
| `Bearer` | `Authorization: Bearer <key>` |
| `Google` | `x-goog-api-key: <key>` |
| `GoogleOAuth` | `Authorization: Bearer <token>` + `x-goog-client: GeminiCLI/1.0` |
| `GitHubCopilot` | `Authorization: Bearer <token>` + 多个 Copilot 专有 Header (`openai-intent`, `x-initiator`, `x-interaction-type`, `x-request-id`, `x-agent-task-id` 等约 10 个) |
| `CodexOAuth` | `Authorization: Bearer <token>` + `originator: cc-switch` + `chatgpt-account-id` |
**动态认证机制**: GitHub Copilot 和 Codex OAuth 的 Token 是动态获取的,通过 `CopilotAuthManager``CodexOAuthAuth` 分别管理 Token 的刷新和多账户绑定(通过 `meta.authBinding` 实现供应商到账户的映射)。
---
## 5. 转换层详解 — 核心协议转换
### 5.1 Anthropic ↔ OpenAI Chat Completions
**文件**: `src-tauri/src/proxy/providers/transform.rs` (1193 行)
#### 请求转换: `anthropic_to_openai(body) → Value`
将 Anthropic Messages API 请求转换为 OpenAI Chat Completions 请求。
**字段映射**:
| Anthropic 字段 | OpenAI 字段 | 说明 |
|---|---|---|
| `system` (string) | `messages[0].role = "system"` | System prompt 提升为 system role message |
| `system` (array) | `messages[0].role = "system"` (合并) | 多个 system message 合并,冲突的 `cache_control` 被丢弃 |
| `messages` | `messages` | 按角色转换内容格式 |
| `max_tokens` | `max_tokens` / `max_completion_tokens` | o-series 模型使用 `max_completion_tokens` |
| `temperature` | `temperature` | 直接透传 |
| `top_p` | `top_p` | 直接透传 |
| `stop_sequences` | `stop` | 直接透传 |
| `tools[].input_schema` | `tools[].function.parameters` | Schema 格式转换,过滤 `BatchTool`,移除 `format: "uri"` |
| `tool_choice` | `tool_choice` | 直接透传 |
| `thinking` + `output_config.effort` | `reasoning_effort` | 仅对支持推理的模型注入 (o-series, GPT-5+) |
**消息内容转换**:
- `text``{type: "text", text: "..."}`
- `image``{type: "image_url", image_url: {url: "data:...;base64,..."}}` (data URI)
- `tool_use``tool_calls[{id, type: "function", function: {name, arguments}}]`
- `tool_result``{role: "tool", tool_call_id: "...", content: "..."}` (独立 tool role 消息)
- `thinking` → 丢弃
**推理强度 (reasoning_effort) 映射规则**:
| Anthropic thinking 配置 | OpenAI reasoning_effort |
|---|---|
| `type: "adaptive"` | `"xhigh"` |
| `type: "enabled"` + budget < 4000 | `"low"` |
| `type: "enabled"` + budget 4000-15999 | `"medium"` |
| `type: "enabled"` + budget >= 16000 | `"high"` |
| `type: "enabled"` + 无 budget | `"high"` |
| `output_config.effort: "max"` | `"xhigh"` |
**特殊处理**:
- 多个 system message 自动合并,冲突的 `cache_control` 被丢弃
- 支持 `cache_control` 在 system message、text block、tool 上的透传
- `clean_schema()` 清理 JSON Schema 中 OpenAI 不支持的 `format: "uri"` 等字段
#### 响应转换: `openai_to_anthropic(body) → Value`
| OpenAI 字段 | Anthropic 字段 |
|---|---|
| `choices[0].message.content` | `content[{type: "text"}]` |
| `choices[0].message.tool_calls` | `content[{type: "tool_use", id, name, input}]` |
| `choices[0].message.function_call` | `content[{type: "tool_use"}]` (旧格式兼容) |
| `choices[0].message.refusal` | `content[{type: "text", text: refusal}]` |
| `choices[0].finish_reason` | `stop_reason` (stop→end_turn, length→max_tokens, tool_calls→tool_use) |
| `usage.prompt_tokens` | `usage.input_tokens` |
| `usage.completion_tokens` | `usage.output_tokens` |
| `usage.prompt_tokens_details.cached_tokens` | `usage.cache_read_input_tokens` |
---
### 5.2 Anthropic ↔ OpenAI Responses API
**文件**: `src-tauri/src/proxy/providers/transform_responses.rs` (1329 行)
Responses API 是 OpenAI 2025 年推出的新一代 API采用扁平化的 input/output 结构。
#### 请求转换: `anthropic_to_responses(body, cache_key, is_codex_oauth) → Value`
| Anthropic 字段 | Responses API 字段 | 说明 |
|---|---|---|
| `system` | `instructions` | System prompt 转为 instructions 字段 |
| `messages` | `input` | 消息数组转为扁平化的 input items |
| `max_tokens` | `max_output_tokens` | 统一使用 max_output_tokens |
| `tools[].input_schema` | `tools[].parameters` | Schema 格式转换,移除 `cache_control` |
| `tool_choice.type = "any"` | `tool_choice = "required"` | 映射工具选择策略 |
| `tool_choice.type = "tool"` | `tool_choice = {type: "function", name}` | 强制指定工具 |
| `thinking` | `reasoning.effort` | 推理强度映射 |
**消息结构转换核心差异** — 消息从"嵌套在 role message 中"变为"独立 top-level item"
- `tool_use` 从 assistant message 中**提升**为独立的 `function_call` item:
```json
// Anthropic
{"role": "assistant", "content": [{"type": "tool_use", "id": "call_1", ...}]}
// Responses API
{"type": "function_call", "call_id": "call_1", ...}
```
- `tool_result` 从 user message 中**提升**为独立的 `function_call_output` item:
```json
// Anthropic
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "call_1", ...}]}
// Responses API
{"type": "function_call_output", "call_id": "call_1", ...}
```
- 文本类型区分: user 的 text → `input_text`, assistant 的 text → `output_text`
**Codex OAuth (ChatGPT Plus/Pro 反代) 特殊协议约束**:
当 `is_codex_oauth = true` 时,需满足 ChatGPT 后端的协议限制:
- `store: false` — 不允许服务端持久化
- `include: ["reasoning.encrypted_content"]` — 保持多轮 reasoning 上下文ChatGPT 后端通过加密 reasoning blob 维持推理状态)
- 删除 `max_output_tokens`、`temperature`、`top_p` (ChatGPT 后端不支持这些参数)
- 兜底注入 `instructions`("")、`tools`([])、`parallel_tool_calls`(false) 默认值
- 强制 `stream: true` (CC-Switch SSE 解析层只支持流式)
#### 响应转换: `responses_to_anthropic(body) → Value`
| Responses API 字段 | Anthropic 字段 |
|---|---|
| `output[type="message"].content[type="output_text"]` | `content[{type: "text"}]` |
| `output[type="function_call"]` | `content[{type: "tool_use", id: call_id, name, input}]` |
| `output[type="reasoning"].summary` | `content[{type: "thinking", thinking: ...}]` |
| `output[type="message"].content[type="refusal"]` | `content[{type: "text"}]` |
| `status = "completed"` (无 tool_use) | `stop_reason: "end_turn"` |
| `status = "completed"` (有 function_call) | `stop_reason: "tool_use"` |
| `status = "incomplete"` (reason: max_output_tokens) | `stop_reason: "max_tokens"` |
| `usage.input_tokens` | `usage.input_tokens` |
| `usage.input_tokens_details.cached_tokens` | `usage.cache_read_input_tokens` |
---
### 5.3 Anthropic ↔ Google Gemini Native
**文件**: `src-tauri/src/proxy/providers/transform_gemini.rs` (2152 行)
将 Anthropic Messages 请求转换为 Gemini `generateContent` 格式。这是三个转换中最复杂的,涉及有状态管理。
#### 请求转换: `anthropic_to_gemini_with_shadow(body, shadow_store, provider_id, session_id) → Value`
| Anthropic 字段 | Gemini 字段 | 说明 |
|---|---|---|
| `system` | `systemInstruction.parts[{text}]` | System prompt 转为 systemInstruction |
| `messages` | `contents[{role, parts}]` | role: assistant→model, 其他→user |
| `max_tokens` | `generationConfig.maxOutputTokens` | |
| `temperature` | `generationConfig.temperature` | |
| `top_p` | `generationConfig.topP` | |
| `stop_sequences` | `generationConfig.stopSequences` | |
| `tools[].input_schema` | `tools[].functionDeclarations[]` | 转为 Gemini FunctionDeclaration |
| `tool_choice` | `toolConfig.functionCallingConfig` | auto→AUTO, none→NONE, any→ANY, tool→ANY+allowedFunctionNames |
**消息内容转换**:
- `text` → `{text: "..."}`
- `image` (base64) → `{inlineData: {mimeType, data}}`
- `document` (base64) → `{inlineData: {mimeType, data}}`
- `tool_use` → `{functionCall: {name, args, id}}` (仅 assistant)
- `tool_result` → `{functionResponse: {name, response, id}}` (通过 id→name 映射解析)
- `thinking` / `redacted_thinking` → 丢弃
#### Shadow Store (状态回放机制)
**文件**: `src-tauri/src/proxy/providers/gemini_shadow.rs` (389 行)
Gemini 的 `thoughtSignature` 和 `functionCall.id` 是有状态的需要跨轮次保留。CC-Switch 实现了 `GeminiShadowStore` 来解决此问题:
- **存储结构**: 以 `(provider_id, session_id)` 为键,存储 `GeminiAssistantTurn`(包含 assistant_content + tool_calls 及 thoughtSignature
- **容量限制**: 默认 200 个 session每 session 64 轮对话,超出后 LRU 淘汰最旧 session
- **回放机制**: 在后续请求中,将存储的 shadow turn包含 thoughtSignature 和原始 Gemini 格式内容)替换到对应 assistant 消息的位置,确保多轮对话的 `functionResponse` 和 `thoughtSignature` 能正确传递
- **线程安全**: 使用 `std::sync::RwLock`(非 tokio async lock因为 shadow store 操作很快
#### 工具参数修正 (Tool Call Rectification)
Gemini 模型有时会将工具调用的参数结构"展平"或混淆(如将 `{skill: "git-commit"}` 发为 `{name: "git-commit"}`)。系统通过 `AnthropicToolSchemaHints` 机制:
1. 从请求中的 `tools[].input_schema` 提取预期参数结构
2. 在响应中检测并修正参数名映射 (如 `name` → `skill`)
3. 处理 `parameters` 嵌套展平问题
4. 通过 `rectify_tool_call_parts()` 实现类型强制转换(如 string `"123"` → integer `123`
#### 合成 ID 机制
Gemini 2.x 并行调用常省略 `functionCall.id`。系统处理:
- `synthesize_tool_call_id()` 生成 `gemini_synth_<uuid>` 格式的合成 ID在 Anthropic 侧使用
- **不会**转发回 Gemini在构建 Gemini 请求时被过滤)
- 流式场景支持 "synth → real id upgrade":当后续 chunk 携带真实 ID 时替换合成 ID
#### Gemini Schema 构建
**文件**: `src-tauri/src/proxy/providers/gemini_schema.rs` (338 行)
工具定义的 Schema 转换使用双通道策略:
- **`parameters` 通道**: 使用受限的 Gemini Schema 格式(去掉 `$ref`, `$defs`, `additionalProperties`, `oneOf`, `allOf`, `const`, `not`, `if/then/else` 等不支持的关键字)
- **`parametersJsonSchema` 通道**: 使用完整 JSON Schema 格式(当 Schema 包含 Gemini 不支持的关键字时自动升级)
- `build_gemini_function_parameters()` 自动根据 Schema 复杂度选择通道
#### 响应转换: `gemini_to_anthropic_with_shadow_and_hints(body, ...) → Value`
| Gemini 字段 | Anthropic 字段 |
|---|---|
| `candidates[0].content.parts[{text}]` | `content[{type: "text"}]` |
| `candidates[0].content.parts[{functionCall}]` | `content[{type: "tool_use", id, name, input}]` |
| `candidates[0].content.parts[{thought: true}]` | 丢弃 (Gemini thinking) |
| `candidates[0].finishReason = "STOP"` | `stop_reason: "end_turn"` / `"tool_use"` |
| `candidates[0].finishReason = "MAX_TOKENS"` | `stop_reason: "max_tokens"` |
| `candidates[0].finishReason = "SAFETY"` 等 | `stop_reason: "refusal"` |
| `promptFeedback.blockReason` | `stop_reason: "refusal"` + 错误文本 |
| `usageMetadata.promptTokenCount` | `usage.input_tokens` |
| `usageMetadata.cachedContentTokenCount` | `usage.cache_read_input_tokens` |
---
## 6. 流式转换层
### 6.1 OpenAI Chat Completions SSE → Anthropic SSE
**文件**: `src-tauri/src/proxy/providers/streaming.rs` (821 行)
**转换状态机**:
```
OpenAI SSE chunk ──▶ 状态解析 ──▶ Anthropic SSE event
choices[0].delta.content ──────▶ content_block_start (type: "text")
content_block_delta (type: "text_delta")
content_block_stop
choices[0].delta.reasoning ────▶ content_block_start (type: "thinking")
content_block_delta (type: "thinking_delta")
content_block_stop
choices[0].delta.tool_calls ───▶ content_block_start (type: "tool_use")
content_block_delta (type: "input_json_delta")
content_block_stop
choices[0].finish_reason ──────▶ message_delta (stop_reason)
[DONE] ───────────────────────▶ message_stop
```
**特殊处理**:
- **UTF-8 安全**: 处理 TCP 分包导致的 UTF-8 多字节字符截断,使用 `utf8_remainder` 缓冲区确保不会产生 U+FFFD 替换字符
- **Copilot 无限空白 Bug 检测**: 跟踪连续空白字符数,超过 20 个时强制中止 tool call 流Copilot 有时会无限输出空白字符的已知 Bug
- **延迟工具调用**: 处理 `id/name` 在 `arguments` 之后到达的乱序情况
### 6.2 OpenAI Responses API SSE → Anthropic SSE
**文件**: `src-tauri/src/proxy/providers/streaming_responses.rs` (1070 行)
Responses API 使用**命名事件** (named events) 的生命周期模型,与 Chat Completions 的 delta chunk 模型完全不同。
**事件映射**:
| Responses API SSE Event | Anthropic SSE Event |
|---|---|
| `response.created` | `message_start` |
| `response.content_part.added` (output_text) | `content_block_start(type:text)` |
| `response.output_text.delta` | `content_block_delta(text_delta)` |
| `response.refusal.delta` | `content_block_delta(text_delta)` |
| `response.output_text.done` | `content_block_stop` |
| `response.output_item.added` (function_call) | `content_block_start(type:tool_use)` |
| `response.function_call_arguments.delta` | `content_block_delta(input_json_delta)` |
| `response.function_call_arguments.done` | `content_block_stop` |
| `response.reasoning.delta` | `content_block_start(type:thinking)` + `content_block_delta(thinking_delta)` |
| `response.reasoning.done` | `content_block_stop` |
| `response.completed` | `message_delta` (stop_reason + usage) + `message_stop` |
**状态管理**: 维护 `content_part_key` (item_id + content_index 复合键)、`index_by_key` (content part → Anthropic index 映射)、`tool_index_by_item_id` (item_id → index 映射)、`open_indices` (未关闭的 block 索引集合)。
### 6.3 Gemini SSE → Anthropic SSE
**文件**: `src-tauri/src/proxy/providers/streaming_gemini.rs` (1054 行)
Gemini 的 `streamGenerateContent?alt=sse` 每个 chunk 都是完整的 `GenerateContentResponse`,需要增量合并。
**核心机制**:
- **累积快照模型**: 每个 SSE chunk 包含完整的 candidates系统通过比较 `functionCall.name` 和位置匹配来去重和合并跨 chunk 的工具调用
- **工具调用 ID 去重合并**: `merge_tool_call_snapshots()` 处理跨 chunk 的工具调用累积(支持合成 ID → 真实 ID 升级)
- **Shadow Store 记录**: 在流结束时记录 assistant turn 到 shadow store包括 `thoughtSignature`
- **Tool Call Rectification**: 流式场景下同样应用工具参数修正
- **thoughtSignature 提取**: 从 text parts 中提取 `thoughtSignature` 用于后续回放
---
## 7. 请求处理流程
### 7.1 主处理链
以 Claude Code → CC-Switch → 上游 API 为例的完整流程:
```
Claude Code 发送 Anthropic Messages API 请求
[server.rs] 路由匹配 → /v1/messages → handle_messages()
[handlers.rs] handle_messages():
1. 解析请求体
2. 创建 RequestContext (识别 AppType::Claude, 提取 session_id)
3. 调用 forwarder.forward_with_retry()
[forwarder.rs] RequestForwarder.forward_with_retry():
遍历供应商列表(故障转移队列):
1. 检查熔断器状态
2. forward() 内部处理:
a. get_adapter(AppType::Claude) → ClaudeAdapter
b. 提取 base_url应用 model_mapper 映射模型名
c. 规范化 thinking 类型
d. 确定端点路径Copilot 动态端点 / 格式化重写)
e. Copilot Optimizer: 分类请求、清理孤立 tool_result、合并 tool_result、热身降级、确定性 ID
f. 解析 API 格式: get_claude_api_format()
g. 如需转换: transform_claude_request_for_api_format()
- openai_chat → anthropic_to_openai()
- openai_responses → anthropic_to_responses()
- gemini_native → anthropic_to_gemini_with_shadow()
h. body_filter: 过滤 `_` 前缀的私有参数
i. 获取动态认证 Token (Copilot/CodexOAuth)
j. 构建有序 HeaderMap
k. 发送请求 (hyper for HTTP/CONNECT proxy, reqwest for SOCKS5 proxy)
3. 成功: 记录结果、更新状态、触发 UI 切换
4. 失败: 检查 thinking_rectifier / thinking_budget_rectifier各自最多触发一次修复后重试
[handlers.rs] 响应处理:
if needs_transform:
流式: 根据格式选择:
- openai_responses → create_anthropic_sse_stream_from_responses()
- gemini_native → create_anthropic_sse_stream_from_gemini()
- 其他 → create_anthropic_sse_stream()
非流式: 读取完整响应,应用:
- responses_to_anthropic()
- gemini_to_anthropic_with_shadow_and_hints()
- openai_to_anthropic()
else:
透传响应
```
### 7.2 Copilot Optimizer
**文件**: `src-tauri/src/proxy/copilot_optimizer.rs` (1251 行)
专用于 GitHub Copilot 的请求优化系统,在转发器内部对请求进行预处理:
| 优化策略 | 说明 |
|---------|------|
| **请求分类** | 识别 initiator (user/agent)、热身请求、压缩请求、子代理请求 |
| **孤立 tool_result 清理** | 清理无对应 tool_use 的 tool_resultClaude Code 偶尔产生) |
| **tool_result 合并** | 将多个 tool_result 合并为一个,减少 Copilot 高级计费 |
| **热身降级** | 检测到热身请求时降级为小模型,节省成本 |
| **确定性请求 ID** | 基于 session 生成确定性 `x-request-id` 和 `x-interaction-id`,提高 Copilot 稳定性 |
| **子代理头修改** | 子代理请求时修改 `x-initiator`、`x-interaction-type` 为 agent |
### 7.3 Thinking 自修复机制
**文件**: `src-tauri/src/proxy/thinking_rectifier.rs` (609 行), `thinking_budget_rectifier.rs` (307 行)
两个独立的自修复 rectifier在 Anthropic API 返回特定错误时自动修复并重试:
| Rectifier | 触发条件 | 修复动作 | 触发次数限制 |
|-----------|---------|---------|------------|
| **Signature Rectifier** | Anthropic API 报告 thinking signature 无效 | 移除 `thinking`/`redacted_thinking` blocks 和 `signature` 字段 | 每请求 1 次 |
| **Budget Rectifier** | Anthropic API 报告 thinking budget 错误 | 调整 `thinking.budget_tokens` | 每请求 1 次 |
修复后通过 `release_permit_neutral()` 释放熔断器的 HalfOpen 探测槽位,不影响健康统计。
### 7.4 其他辅助机制
| 模块 | 文件 | 行数 | 说明 |
|------|------|------|------|
| **私有参数过滤** | `body_filter.rs` | 261 | 过滤 `_` 前缀的参数,允许客户端传递控制参数而不发送到上游 |
| **缓存注入** | `cache_injector.rs` | 329 | 为 Bedrock 等支持 prompt 缓存的供应商注入缓存控制标记 |
| **Thinking 优化** | `thinking_optimizer.rs` | 230 | Bedrock 场景下的 thinking 参数优化 |
| **模型名映射** | `model_mapper.rs` | 312 | 将客户端请求的模型名映射为上游实际模型名 |
| **Gemini URL** | `gemini_url.rs` | 583 | 处理 Gemini API 的复杂 URL 构建逻辑 |
| **SSE 基础** | `sse.rs` | 294 | SSE 事件解析和生成的基础工具 |
---
## 8. 熔断器与故障转移
### 8.1 熔断器
**文件**: `src-tauri/src/proxy/circuit_breaker.rs` (482 行)
三态熔断器保护上游服务:
| 参数 | 默认值 |
|------|-------|
| `failure_threshold` | 4 (连续失败次数) |
| `success_threshold` | 2 (连续成功次数) |
| `timeout_seconds` | 60 (Open 状态持续时间) |
| `error_rate_threshold` | 0.6 (错误率阈值) |
| `min_requests` | 10 (最小请求数) |
**状态转换**: Closed (正常) → Open (熔断) → HalfOpen (探测,最多 1 个并发探测请求) → Closed/Open
### 8.2 供应商路由
**文件**: `src-tauri/src/proxy/provider_router.rs` (512 行)
- 故障转移关闭时:仅使用当前供应商
- 故障转移开启时:返回按优先级排列的故障转移队列,跳过熔断中的供应商
---
## 9. 支持的协议转换矩阵
```
┌──────────────────────────────────────────────┐
│ CC-Switch 代理 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
Claude Code ──────▶│ │ Claude │ │ Codex │ │ Gemini │ │
(Anthropic │ │ Handler │ │ Handler │ │ Handler │ │
Messages API) │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────▼─────────────▼──────────────▼─────┐ │
│ │ Forwarder (转发器) │ │
│ │ - 供应商选择 / 故障转移 / Copilot优化 │ │
│ │ - Thinking自修复 / 参数过滤 │ │
│ └────────────────┬───────────────────────┘ │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │OpenAI │ │OpenAI │ │ Gemini │ │
│ │Chat │ │Responses│ │ Native │ │
│ │Complet. │ │API │ │ │ │
│ └────┬────┘ └────┬────┘ └────┬─────┘ │
│ │ │ │ │
└───────┼───────────┼───────────┼─────────────┘
▼ ▼ ▼
┌───────────┐ ┌──────────┐ ┌──────────────┐
│OpenAI/ │ │ChatGPT/ │ │ Google AI │
│OpenRouter/│ │OpenAI/ │ │ Gemini API │
│Copilot │ │Codex OAuth│ │ │
└───────────┘ └──────────┘ └──────────────┘
```
### 转换路径汇总
| 转换路径 | 请求转换 | 响应转换 | 流式转换 | 使用场景 |
|---------|---------|---------|---------|---------|
| Anthropic → OpenAI Chat | `anthropic_to_openai()` | `openai_to_anthropic()` | `streaming.rs` | GitHub Copilot, OpenRouter (旧版) |
| Anthropic → OpenAI Responses | `anthropic_to_responses()` | `responses_to_anthropic()` | `streaming_responses.rs` | Codex OAuth (ChatGPT Plus/Pro) |
| Anthropic → Gemini | `anthropic_to_gemini_with_shadow()` | `gemini_to_anthropic_with_shadow_and_hints()` | `streaming_gemini.rs` | Gemini 模型直连 |
| Anthropic → Anthropic (透传) | 直接转发 | 直接转发 | 直接转发 | Anthropic 官方, OpenRouter (新版) |
---
## 10. 文件索引
### 核心转换文件
| 文件路径 | 功能 | 代码行数 |
|---------|------|---------|
| `src-tauri/src/proxy/providers/transform.rs` | Anthropic ↔ OpenAI Chat Completions 转换 | 1193 |
| `src-tauri/src/proxy/providers/transform_responses.rs` | Anthropic ↔ OpenAI Responses API 转换 | 1329 |
| `src-tauri/src/proxy/providers/transform_gemini.rs` | Anthropic ↔ Gemini Native 转换 | 2152 |
| `src-tauri/src/proxy/providers/streaming.rs` | OpenAI Chat SSE → Anthropic SSE 流式转换 | 821 |
| `src-tauri/src/proxy/providers/streaming_responses.rs` | Responses SSE → Anthropic SSE 流式转换 | 1070 |
| `src-tauri/src/proxy/providers/streaming_gemini.rs` | Gemini SSE → Anthropic SSE 流式转换 | 1054 |
### 适配器与认证
| 文件路径 | 功能 | 代码行数 |
|---------|------|---------|
| `src-tauri/src/proxy/providers/adapter.rs` | ProviderAdapter trait 定义 | 50 |
| `src-tauri/src/proxy/providers/auth.rs` | AuthInfo / AuthStrategy 认证类型定义 | 259 |
| `src-tauri/src/proxy/providers/claude.rs` | Claude 适配器 (API 格式检测, URL 构建, 认证) | 1313 |
| `src-tauri/src/proxy/providers/codex.rs` | Codex 适配器 | 305 |
| `src-tauri/src/proxy/providers/gemini.rs` | Gemini 适配器 | 442 |
| `src-tauri/src/proxy/providers/mod.rs` | ProviderType 枚举, get_adapter() 工厂函数 | 515 |
| `src-tauri/src/proxy/providers/copilot_auth.rs` | Copilot 动态 Token 管理 | 1820 |
| `src-tauri/src/proxy/providers/codex_oauth_auth.rs` | Codex OAuth 动态 Token 管理 | 1132 |
| `src-tauri/src/proxy/providers/gemini_shadow.rs` | Gemini Shadow Store (状态回放) | 389 |
| `src-tauri/src/proxy/providers/gemini_schema.rs` | Gemini Schema 构建 (双通道策略) | 338 |
### 请求处理链
| 文件路径 | 功能 | 代码行数 |
|---------|------|---------|
| `src-tauri/src/proxy/server.rs` | Axum HTTP 服务器, 路由注册 | 354 |
| `src-tauri/src/proxy/handlers.rs` | 请求处理器 (分发 + Claude 格式转换入口) | 643 |
| `src-tauri/src/proxy/forwarder.rs` | 请求转发器 (供应商选择, 故障转移, 请求预处理) | 2169 |
| `src-tauri/src/proxy/copilot_optimizer.rs` | Copilot 请求优化 | 1251 |
| `src-tauri/src/proxy/thinking_rectifier.rs` | Thinking Signature 自修复 | 609 |
| `src-tauri/src/proxy/thinking_budget_rectifier.rs` | Thinking Budget 自修复 | 307 |
| `src-tauri/src/proxy/thinking_optimizer.rs` | Bedrock Thinking 优化 | 230 |
| `src-tauri/src/proxy/cache_injector.rs` | Prompt 缓存注入 | 329 |
| `src-tauri/src/proxy/body_filter.rs` | 私有参数过滤 (`_` 前缀) | 261 |
| `src-tauri/src/proxy/model_mapper.rs` | 模型名映射 | 312 |
| `src-tauri/src/proxy/gemini_url.rs` | Gemini URL 构建 | 583 |
| `src-tauri/src/proxy/sse.rs` | SSE 基础工具 | 294 |
| `src-tauri/src/proxy/provider_router.rs` | 供应商路由 (故障转移队列) | 512 |
| `src-tauri/src/proxy/circuit_breaker.rs` | 熔断器实现 | 482 |
| `src-tauri/src/proxy/handler_context.rs` | RequestContext 构建 | 246 |
| `src-tauri/src/proxy/response_processor.rs` | 响应处理 (透传/日志/usage) | 915 |
| `src-tauri/src/proxy/session.rs` | 会话管理 | 565 |
| `src-tauri/src/proxy/hyper_client.rs` | Hyper HTTP 客户端 | 626 |
| `src-tauri/src/proxy/http_client.rs` | Reqwest HTTP 客户端 (SOCKS5) | 392 |
| `src-tauri/src/proxy/error.rs` | 错误类型定义 | 177 |
| `src-tauri/src/proxy/error_mapper.rs` | 错误映射 | 99 |
---
## 11. 关键设计特点
### 11.1 单方向锚定转换
所有转换以 Anthropic Messages API 为锚点,仅实现 Anthropic ↔ X 的双向转换。客户端协议只有一种Anthropic Messages API上游协议有三种OpenAI Chat、OpenAI Responses、Gemini Native加一种透传Anthropic 直连)。
### 11.2 流式 + 非流式双轨支持
每种协议转换都同时实现了非流式 (JSON-to-JSON) 和流式 (SSE 事件逐事件翻译,基于 `async_stream` 异步流)。
### 11.3 有状态转换 (Gemini Shadow Store)
Gemini 的 `thoughtSignature` 是有状态的,系统通过 Shadow Store 在内存中记录每轮 assistant 的原始 Gemini 响应,在后续请求中回放。这是 CC-Switch 独有的复杂机制。
### 11.4 自修复能力 (Thinking Rectifiers)
两个独立 rectifier 在 API 错误时自动修复请求并重试,大幅提高了对 Anthropic API thinking 功能变更的容错能力。
### 11.5 Copilot 深度适配
Copilot Optimizer 针对性地解决了 Copilot 的多个实际问题(无限空白 Bug、请求不稳定、高级计费优化等是与供应商深度绑定的典型案例。
### 11.6 请求头大小写保留
通过手动 hyper accept loop + TCP `peek()` 捕获原始请求头大小写,转发给上游时保持一致(部分上游 API 对 header 大小写敏感)。
### 11.7 代理协议支持
支持 HTTP/CONNECT 和 SOCKS5 两种代理协议,分别使用 hyper 和 reqwest 客户端。
### 11.8 UTF-8 安全
流式转换中处理 TCP 分包导致的 UTF-8 多字节字符截断,使用 `utf8_remainder` 缓冲区确保不会产生 U+FFFD 替换字符。