42 KiB
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:
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→ClaudeAdapterCodex→CodexAdapterGemini,GeminiCli→GeminiAdapter
4.3 API 格式识别
文件: src-tauri/src/proxy/providers/claude.rs (1313 行)
Claude 适配器通过 get_claude_api_format() 函数识别需要使用的 API 格式,优先级为:
- Codex OAuth 强制:
meta.provider_type == "codex_oauth"→openai_responses - meta.apiFormat (SSOT):
meta.api_format字段 →openai_chat/openai_responses/gemini_native - settings_config.api_format: 旧版兼容
- openrouter_compat_mode: 旧版 OpenRouter 兼容开关 →
openai_chat - 默认:
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_callitem:// 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_outputitem:// 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 机制:
- 从请求中的
tools[].input_schema提取预期参数结构 - 在响应中检测并修正参数名映射 (如
name→skill) - 处理
parameters嵌套展平问题 - 通过
rectify_tool_call_parts()实现类型强制转换(如 string"123"→ integer123)
合成 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_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 替换字符。