1
0

docs: 添加 API 参考文档和技术分析文档

This commit is contained in:
2026-04-19 01:43:02 +08:00
parent 2b1c5e96c3
commit b92974716f
14 changed files with 32227 additions and 0 deletions

View File

@@ -0,0 +1,722 @@
# 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 替换字符。