# 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; fn extract_auth(&self, provider: &Provider) -> Option; 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; // 默认透传 fn transform_response(&self, body: Value) -> Result; // 默认透传 } ``` 其中 `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: ` + `anthropic-version: 2023-06-01` (版本由转发器注入) | | `ClaudeAuth` | `Authorization: Bearer ` (无 `x-api-key`) | | `Bearer` | `Authorization: Bearer ` | | `Google` | `x-goog-api-key: ` | | `GoogleOAuth` | `Authorization: Bearer ` + `x-goog-client: GeminiCLI/1.0` | | `GitHubCopilot` | `Authorization: Bearer ` + 多个 Copilot 专有 Header (`openai-intent`, `x-initiator`, `x-interaction-type`, `x-request-id`, `x-agent-task-id` 等约 10 个) | | `CodexOAuth` | `Authorization: Bearer ` + `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_` 格式的合成 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_result(Claude 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 替换字符。