请求整流与多协议转换
请求整流与多协议转换
学习目标
- 理解 CC Switch 代理层的请求处理流水线
- 掌握 Claude 接管时的模型别名整流策略
- 了解 thinking 预算整流器的实现
- 识别 SSE 流式转换中的边界情况
前置知识
- 请求整流与多协议格式转换:三大 AI API 协议的差异与通用转换策略
下文聚焦 CC Switch 的具体实现方式。
项目实践
请求处理流水线
CC Switch 代理层的请求处理经过以下阶段:
客户端请求 → 请求头整流 → 路径路由 → 模型名映射 → Token 注入 → 上游转发 ↓客户端响应 ← 流式转换 ← 响应体整流 ← 错误处理 ← 上游响应关键模块(src-tauri/src/proxy/):
| 模块 | 职责 |
|---|---|
handlers.rs | 请求入口、路径解析、应用类型检测 |
forwarder.rs | 上游请求转发、响应流代理 |
response_processor.rs | 响应体格式转换 |
model_mapper.rs | 模型名整流(客户端别名 → 上游真实名) |
providers/ | 各供应商特定的认证和配置适配 |
thinking_rectifier.rs | thinking 预算整流 |
Claude 模型别名整流
这是 CC Switch 代理接管中最关键的整流工作之一。
问题:Claude Code 的模型菜单显示的是环境变量中的模型名。如果上游供应商使用不同的模型名,Claude Code 的模型菜单会显示错误的名称。
解决方案:代理在接管 Claude Live 配置时,写入一组稳定的角色别名:
写入 Live: ANTHROPIC_DEFAULT_HAIKU_MODEL = "claude-haiku-4-5" ANTHROPIC_DEFAULT_SONNET_MODEL = "claude-sonnet-4-6" ANTHROPIC_DEFAULT_OPUS_MODEL = "claude-opus-4-7"
代理内部映射: "claude-haiku-4-5" → 上游真实模型名(如 gemini-2.5-flash) "claude-sonnet-4-6" → 上游真实模型名(如 claude-sonnet-4-6-20250514) "claude-opus-4-7" → 上游真实模型名这样 Claude Code 看到的始终是熟悉的模型别名,而代理负责将这些别名映射到当前供应商的真实模型。
1M 上下文标记:如果上游模型支持 1M 上下文窗口,CC Switch 在写入客户端别名时附加 [1M] 标记(如 claude-sonnet-4-6 [1M]),代理在解析时识别并剥离该标记。
认证策略分流
不同类型的供应商使用不同的认证方式,CC Switch 在接管时采用不同的策略:
传统 API Key 供应商(PreserveExistingOrAuthToken):
- 保留 Live 中已存在的 Token 字段名
- 写入占位符
PROXY_MANAGED - 代理从数据库读取真实 Token 后,根据原始字段名注入
托管 OAuth 供应商(ManagedAccount,如 GitHub Copilot):
- 清除所有 Token 字段(不保留原始字段名)
- 统一写入
ANTHROPIC_API_KEY = "PROXY_MANAGED" - 代理使用内部的
CopilotAuthManager获取有效 Token 并注入
为什么区分这两种策略:OAuth 供应商(如 Copilot)的 Token 是通过设备码流程动态获取的,不存储在 Live 配置中。如果按传统方式保留原始字段名,代理可能写入错误的认证格式。
Thinking 预算整流
Claude 的 thinking 模式需要向供应商发送 thinking 参数。CC Switch 实现了 thinking 预算整流器:
thinking_budget_rectifier { // 客户端可能请求的 thinking 预算超过了供应商支持的最大值 if client_thinking_budget > provider_max_budget { // 整流为供应商最大值 outgoing_thinking_budget = provider_max_budget } // 或者客户端不支持 thinking,但供应商返回了 thinking 内容 if !client_supports_thinking && response_has_thinking { // 过滤掉 thinking 内容块 filter_thinking_blocks() }}设计要点:整流器在请求发送前和响应返回后都需要运行。只整流请求而不整流响应会导致客户端收到不理解的响应格式。
Codex wire_api 强制整流
Codex 支持两种 wire API:chat(Chat Completions)和 responses(Responses API)。
CC Switch 代理在接管 Codex 时强制设置 wire_api = "responses":
# 写入 Codex config.tomlbase_url = "http://127.0.0.1:15721/v1"wire_api = "responses"model = "<上游真实模型名>"原因:代理层统一以 Responses API 格式接收来自 Codex 客户端的请求,然后在内部根据上游供应商的能力转换为适当的格式。这简化了代理的路由逻辑——只需要处理一种入站协议。
问题与规避
陷阱 1:模型名整流遗漏导致上游拒绝请求
问题:代理将请求转发到上游时,如果忘记将客户端模型别名转换为上游真实模型名,上游可能返回 “model not found” 错误。
CC Switch 的解决方案:model_mapper 模块在每次请求发送前强制执行模型名转换。如果映射表中不存在该模型,回退到供应商的默认模型。
陷阱 2:OAuth 供应商的 Token 过期
问题:托管 OAuth 供应商(如 Copilot)的 Token 有有效期,过期后代理继续使用该 Token 会导致 401 错误。
CC Switch 的解决方案:CopilotAuthManager 在每次请求前检查 Token 有效性,如果过期则自动刷新。刷新逻辑独立于请求处理流水线,通过 RwLock 保护共享状态。
陷阱 3:响应整流遗漏特定字段
问题:上游返回的响应可能包含客户端不期望的字段(如 thinking 内容块),如果不整流直接转发,客户端可能崩溃。
CC Switch 的解决方案:
response_processor.rs中维护一个”上游特性 → 客户端兼容性”矩阵- 根据客户端能力决定哪些字段需要过滤、转换或透传
- 对于未知字段,记录警告日志但不中断响应流
设计取舍
为什么强制 Codex 使用 Responses API?
| 入站协议 | 优势 | 劣势 |
|---|---|---|
| Responses API | 功能更丰富(支持工具、模板等),与 Claude Messages API 更接近,整流逻辑更统一 | Codex 旧版本可能不支持 |
| Chat Completions | 更简单,广泛支持 | 功能有限,与 Claude 差异大 |
CC Switch 选择 Responses API 作为统一入站协议,因为它与 Claude 的 Messages API 在结构上更接近(都有 content 数组、工具调用等),整流逻辑可以复用更多代码。
替代方案:客户端协议适配层
另一种方案是在客户端侧做协议适配——让前端直接发送不同协议的请求,代理只做转发。
局限性:
- 需要前端理解所有协议格式,前端复杂度增加
- 代理无法做统一的模型名映射和 Token 注入
- 热切换时前端需要主动切换请求格式
因此,CC Switch 选择在代理层做所有整流工作,前端保持简单。
参考来源
- Anthropic Messages API. https://docs.anthropic.com/en/api/messages
- OpenAI Responses API. https://platform.openai.com/docs/api-reference/responses
- OpenAI Chat Completions API. https://platform.openai.com/docs/api-reference/chat