# 多 Provider 认证与协议转换架构 > 本文档描述 Codev (cc-haha) 的多 Provider 认证架构、OAuth 2.0 流程、API 协议转换机制以及 Provider 配置管理体系。 > 代码库:`/home/yuki/Code/Agent/Codev` --- ## 1. 支持的认证提供者 Codev 支持以下 Provider,按认证方式与协议类型分类: | Provider | 认证方式 | 协议格式 | 实现位置 | |---|---|---|---| | **Anthropic (first-party)** | OAuth 2.0 + PKCE 或 API Key | Anthropic Messages | `src/services/oauth/`, `src/services/api/client.ts` | | **Anthropic Bedrock** | AWS STS / IAM 凭证 | Anthropic Messages (SDK) | `src/services/api/client.ts` (条件导入) | | **Anthropic Vertex AI** | GCP google-auth-library | Anthropic Messages (SDK) | `src/services/api/client.ts` (条件导入) | | **Anthropic Foundry (Azure)** | API Key 或 Azure AD | Anthropic Messages (SDK) | `src/services/api/client.ts` (条件导入) | | **OpenAI / Codex** | API Key 或 Codex OAuth | `openai_chat` 或 `openai_responses` | `src/server/services/openaiOfficialProvider.ts` | | **OpenRouter** | API Key | `openai_responses` (Proxy) | `src/utils/model/providers.ts` | | **OpenCode Zen** | API Key 或免费 (public) | `openai_chat` (Fetch Override) | `src/services/api/opencodeClient.ts` | | **NVIDIA NIM** | API Key (build.nvidia.com) | `openai_chat` (Fetch Override) | `src/services/api/nvidiaClient.ts` | | **Local (Ollama/LM Studio/vLLM)** | 无 (Dummy Key) | `anthropic` 或 `openai_chat` | `src/server/config/providerPresets.json` | | **DeepSeek, Zhipu GLM, Kimi, MiniMax, 接口AI, 胜算云** | API Key (auth_token) | `anthropic` (Native) | `src/server/config/providerPresets.json` | --- ## 2. 认证架构模式 ### 2.1 两级架构总览 Codev 拥有两套独立的 Provider 系统,设计目标不同: ``` Tier 1: TUI 内置 Provider (原始 Claude Code) 用途: 终端 /login 快速切换 存储: ~/.claude.json (单字段 authProvider) 实现: src/services/api/client.ts + src/utils/model/providers.ts Tier 2: cc-haha Provider 预设系统 (Codev 扩展) 用途: 桌面端多 Provider 管理、预设配置、Proxy 转换 存储: ~/.claude/cc-haha/providers.json (结构化索引) 实现: src/server/services/providerService.ts src/server/config/providerPresets.ts + .json ``` 两个层级通过 `ProviderService.autoImportTuiProvider()` 自动同步:当桌面端检测到 TUI 已配置 Provider 但自身尚无活跃 Provider 时,自动导入 TUI 的 Provider 配置。 ### 2.2 API Provider 类型定义 Provider 类型定义在 `src/utils/model/providers.ts`: ```typescript export type APIProvider = | 'firstParty' | 'openrouter' | 'openai' | 'local' | 'opencode' | 'nvidia' | 'bedrock' | 'vertex' | 'foundry' ``` Provider 检测优先级: 1. **显式环境变量覆盖** (`CLAUDE_CODE_API_PROVIDER` 或 `BETTER_CLAWD_API_PROVIDER`) 2. **SDK 标志变量** (`CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`) 3. **配置检测** (`isOpencodeConfigured()`, `isNvidiaConfigured()`, `isOpenAIConfigured()`, `isOpenRouterConfigured()`) 4. **文件缓存** (`~/.claude.json` 中的 `authProvider` 字段) ```mermaid flowchart TD A[getAPIProvider] --> B{显式 env 覆盖?} B -->|是| C[返回覆盖值] B -->|否| D{SDK 标志变量?} D -->|Bedrock/Vertex/Foundry| E[返回 SDK 值] D -->|否| F{配置检测} F --> Opencode --> G["opencode"] F --> NVIDIA --> H["nvidia"] F --> OpenAI --> I["openai"] F --> OpenRouter --> J["openrouter"] F --> 缓存文件 --> K["firstParty / 缓存值"] ``` ### 2.3 Anthropic OAuth 2.0 with PKCE Anthropic first-party 认证使用标准的 OAuth 2.0 Authorization Code Flow + PKCE,完整流程: ``` ┌─────────┐ ┌──────────────┐ ┌───────────────┐ ┌────────────┐ │ CLI │ │ localhost │ │ Anthropic │ │ Browser │ │ │ │ OAuth Server │ │ OAuth Provider│ │ (User) │ └────┬────┘ └──────┬───────┘ └───────┬───────┘ └─────┬──────┘ │ 1. start() │ │ │ │────────────────>│ │ │ │ 2. port │ │ │ │<────────────────│ │ │ │ │ │ │ │ 3. generateCodeVerifier(), challenge() │ │ │ 4. buildAuthUrl() │ │ │ │ │ │ │ 5. openBrowser(automaticUrl) │ │ │───────────────────────────────────────────────────────────> │ │ │ │ │ │ 6. HTTP GET /callback?code=...&state= │ │ │<─────────────────────────────────────── │ │ │ │ │ │ 7. validate state, extract code │ │ │ 8. exchangeCodeForTokens(code) │ │ │───────────────────────────────────────>│ │ │ │ 9. access_token + refresh_token │ │<───────────────────────────────────────│ │ │ │ │ │ │10. store tokens, set API key to null │ │ │11. fetchProfileInfo() - 获取订阅类型 │ │ ``` **核心文件:** | 职责 | 文件 | |---|---| | OAuth 服务入口 | `src/services/oauth/index.ts` - `OAuthService` 类 | | PKCE 加密工具 | `src/services/oauth/crypto.ts` - SHA-256 code challenge | | 授权码监听 | `src/services/oauth/auth-code-listener.ts` - 本地 HTTP 服务器 | | Token 交换与刷新 | `src/services/oauth/client.ts` - axios POST 到 token endpoint | | Profile 获取 | `src/services/oauth/getOauthProfile.ts` | | OAuth 类型定义 | `src/services/oauth/types.ts` | | OAuth 端点配置 | `src/constants/oauth.ts` - prod/staging/local 三级 | **OAuth 端点配置** (`src/constants/oauth.ts`) 支持三层环境: - **Production**: `api.anthropic.com`, `platform.claude.com` - **Staging**: `api-staging.anthropic.com`, `platform.staging.ant.dev` (仅 ant 内部) - **Local**: 可配置的 localhost 端口 (用于本地开发) - **Custom (FedStart)**: 受限的白名单 Base URL 覆盖 --- ## 3. Protocol Translation (Fetch Override 模式) ### 3.1 核心问题 Claude Code 使用的 `@anthropic-ai/sdk` 只认识 Anthropic Messages API 格式。对于使用 OpenAI 协议的非 Anthropic Provider,必须进行协议转换。 ``` Anthropic Messages API ←→ OpenAI Chat Completions / Responses API ────────────────────── ───────────────────────────────────── POST /v1/messages POST /v1/chat/completions POST /v1/messages?stream POST /v1/chat/completions?stream=true GET /v1/models GET /v1/models POST /v1/count_tokens (无对应端点,需要 Stub) ``` ### 3.2 Fetch Override 机制 每个非 Anthropic Provider 实现一个 `createXxxFetchOverride()` 函数,通过 Anthropic SDK 的 `ClientOptions['fetch']` 钩子注入: ```typescript export function createNvidiaFetchOverride(): (input: RequestInfo | URL, init?: RequestInit) => Promise ``` 该重写在 `getAnthropicClient()` 中按 Provider 选择性注入: ```typescript // src/services/api/client.ts 第 150-163 行 const provider = getAPIProvider() if (provider === 'opencode') { opencodeFetchOverride = createOpenCodeFetchOverride(resolvedModel) } if (provider === 'nvidia') { nvidiaFetchOverride = createNvidiaFetchOverride() } const resolvedFetch = buildFetch(fetchOverride || opencodeFetchOverride || nvidiaFetchOverride, source) ``` ### 3.3 Fetch Override 通用模式 所有 Fetch Override 实现共享相同的拦截模式: ```mermaid flowchart LR A[SDK 发出请求] --> B{URL 匹配?} B -->|/messages 或 /v1/| C{端点类型} B -->|其他| D[透传 fetch] C -->|/count_tokens| E[Stub: 返回 0] C -->|/models| F[Stub: 返回空列表] C -->|/messages| G[解析 Anthropic Body] G --> H[转换消息格式] H --> I[附加 Provider Auth Header] I --> J[调用上游 API] J --> K{流式?} K -->|是| L[转换 SSE 流] K -->|否| M[转换响应体] L --> N[返回 Anthropic 格式 Response] M --> N ``` ### 3.4 消息格式转换 核心转换函数族位于 `src/services/api/copilotClient.ts`: ``` Anthropic → OpenAI: convertAnthropicMessagesToOpenAI(messages, systemPrompt) - system → role: 'system' 消息 - image block → image_url (data: URI) - tool_result → role: 'tool' 消息 - tool_use → tool_calls 数组 - thinking → reasoning_content (提供商扩展) convertAnthropicToolsToOpenAI(tools) - { name, description, input_schema } → { type: 'function', function: { ... } } OpenAI → Anthropic (流式): convertOpenAIStreamToAnthropic(openaiStream, model) - SSE data: {"choices":[{ "delta":{ "content":"..." } }]} → event: content_block_delta\ndata: {"delta":{"type":"text_delta","text":"..."}} OpenAI → Anthropic (非流式): - choices[0].message.content → content: [{ type: 'text', text: ... }] - tool_calls → tool_use content blocks - finish_reason 'stop' → 'end_turn' - finish_reason 'tool_calls' → 'tool_use' ``` **Streaming SSE 事件对照表:** | Anthropic (输出) | OpenAI (输入) | |---|---| | `message_start` | 合成,包含 message ID | | `content_block_start` | 首次 `delta.content` 或 `delta.tool_calls.id` | | `content_block_delta` text_delta | `delta.content` | | `content_block_delta` thinking_delta | `delta.reasoning_content` | | `content_block_start` tool_use | `delta.tool_calls[].id` | | `content_block_delta` tool_use_delta | `delta.tool_calls[].function.arguments` | | `content_block_stop` | delta 结束 / tool_call 完成 | | `message_delta` | `choices[0].finish_reason` | | `message_stop` | `[DONE]` | ### 3.5 服务端 Proxy 模式 除了客户端的 Fetch Override 模式,Codev 还实现了一套**服务端 Proxy**,位于 `src/server/proxy/handler.ts`。 设计目标:将协议转换逻辑从客户端分离到独立 HTTP 服务,支持更灵活的 Provider 管理。 ``` CLI/SDK Proxy Server Upstream │ │ │ │ POST /proxy/v1/messages │ │ │ (Anthropic 格式请求) │ │ │ ───────────────────────────────────────> │ │ │ │ 获取 Provider 配置 │ │ │ 读取 baseUrl, apiKey, apiFormat │ │ │ │ │ │ anthropicToOpenaiChat(body) │ │ │ ─ 或 ─ │ │ │ anthropicToOpenaiResponses(body) │ │ │ │ │ │ POST /v1/chat/completions │ │ │ 或 POST /v1/responses │ │ │ ──────────────────────────────────> │ │ │ │ │ │ openaiChatToAnthropic(res) │ │ │ 或 openaiResponsesToAnthropic(res) │ │ │ (流式: XxxStreamToAnthropic) │ │ │ │ │ ← Anthropic 格式响应 │ │ │ <─────────────────────────────────────── │ │ ``` **服务端 Proxy 转换文件结构:** ``` src/server/proxy/ ├── handler.ts # 主入口: POST 分发 + 错误处理 ├── transform/ │ ├── types.ts # Anthropic & OpenAI 类型定义 │ ├── anthropicToOpenaiChat.ts # 请求转换: Messages → Chat Completions │ ├── anthropicToOpenaiResponses.ts # 请求转换: Messages → Responses API │ ├── openaiChatToAnthropic.ts # 响应转换: Chat Completions → Messages │ ├── openaiResponsesToAnthropic.ts # 响应转换: Responses API → Messages │ └── toolArguments.ts # Tool 参数格式修正工具 └── streaming/ ├── openaiChatStreamToAnthropic.ts # 流式转换: Chat Completions SSE ├── openaiResponsesStreamToAnthropic.ts # 流式转换: Responses API SSE └── openaiResponsesStreamToAnthropicResponse.ts ``` ### 3.6 DeepSeek 推理兼容性 Proxy 还处理 DeepSeek 的特殊格式: ```typescript function shouldUseDeepSeekReasoningCompat(baseUrl: string): boolean { return /(^|[./-])deepseek([./-]|$)/i.test(baseUrl) || /(^|[./-])opencode\.ai([:/]|$)/i.test(baseUrl) } ``` 启用后,**thinking blocks** 会通过 `reasoning_content` 字段回传,并且 Anthropic 的 `thinking.type` 会被转换为 DeepSeek 兼容格式。 --- ## 4. 各 Provider 集成详解 ### 4.1 NVIDIA NIM **文件**: `src/services/api/nvidiaClient.ts` - **认证**: `getNvidiaApiKey()` → 写入 `Authorization: Bearer ` 请求头 - **特殊头**: `HTTP-Referer: https://claude.ai/`, `X-BILLING-INVOKE-ORIGIN: Better-Clawd` - **端点**: `{baseUrl}/v1/chat/completions` (默认 `https://integrate.api.nvidia.com/v1`) - **Model 列表**: 从 `/v1/models` 动态拉取,缓存于 `cachedNvidiaModels` 模块变量 - **默认 Model**: `nvidia/llama-3.1-nemotron-70b-instruct` (可通过 `NVIDIA_MODEL` 环境变量覆盖) - **网络架构**: 通过 Sidecar 代理转发(`/api/proxy/nvidia`),适用于需要特殊头或 CORS 处理的 Provider。与之对比,OpenCode/OpenRouter 使用直接 Fetch Override 模式。 ### 4.2 OpenCode Zen **文件**: `src/services/api/opencodeClient.ts` - **认证**: 支持 API Key 和匿名免费使用 - **免费模式**: 当 `apiKey` 为 `undefined` 或 `'public'` 时,注入 billing 特征码 (`x-anthropic-billing-header: cc_version=2.1.0-dev...`),标记请求来源用于服务端路由 - **端点**: `https://opencode.ai/zen/v1/chat/completions` - **Model 发现**: - 从 `https://models.dev/api.json` 动态获取模型元数据(云端成本策略) - 从 `https://api.github.com/repos/anomalyco/opencode/releases/latest` 获取版本信息 - 缓存于 `cachedModels` 模块变量 - 支持免费模型列表过滤(life-free models) - **动态 UA**: 根据版本和运行时自动构建 `User-Agent` - **推理内容**: 支持 `reasoning_content` 到 `thinking` block 的转换 - **网络架构**: 直连模式(Direct Fetch Override),无需 Sidecar 代理 ### 4.3 OpenAI / Codex Official **文件**: `src/server/services/openaiOfficialProvider.ts` - **认证**: Codex OAuth (通过 `hahaOpenAIOAuthService`) - **协议**: `openai_responses` (Responses API) - **运行时种类**: `openai_oauth` (需要 Token 刷新) - **Base URL**: `/backend-api/codex` (从 `OPENAI_CODEX_API_ENDPOINT` 派生) - **Model 列表**: 来自 `src/services/openaiAuth/models.ts` 的 `OPENAI_CODEX_MODEL_CATALOG` ### 4.4 自定义 OpenAI 兼容 Provider **文件**: `src/services/api/customOpenAIClient.ts` - **认证**: `ConnectedProviderInfo.apiKey` → `Bearer ` - **协议**: `openai_chat` (Chat Completions) - **Model**: 使用 `custom-openai:` 前缀选择 - **Model 拉取**: `fetchOpenAICompatibleModelIds()` 从 `/v1/models` 获取 - **适用场景**: vLLM, Together AI, Groq 等任何 OpenAI 兼容端点 ### 4.5 GitHub Copilot **文件**: `src/services/api/copilotClient.ts` (原 copilotClient,现已演化为通用转换库) - **认证**: OAuth Token (通过 `connectedProviders['github-copilot']`) - **协议**: `openai_chat` - **端点**: `https://api.githubcopilot.com/chat/completions` - **特殊头**: `Openai-Intent: conversation-edits`, `x-initiator: user` - **Model 发现**: 双源策略 — 优先从 Copilot API 获取,fallback 到 `models.dev/api.json` - **兼容性缓存**: `copilotCompatibilityCache` 持久化模型兼容性信息到 `~/.claude.json` - **Token 参数自动修复**: 当 API 返回 `"Use 'max_completion_tokens' instead"` 时自动切换参数 --- ## 5. Provider 预设系统 (cc-haha) ### 5.1 预设配置 Provider 预设定义在 `src/server/config/providerPresets.json`,每个预设包含: ```json { "id": "deepseek", "name": "DeepSeek", "baseUrl": "https://api.deepseek.com/anthropic", "apiFormat": "anthropic", "defaultModels": { "main": "deepseek-v4-pro", "haiku": "deepseek-v4-flash", "sonnet": "deepseek-v4-pro", "opus": "deepseek-v4-pro" }, "needsApiKey": true, "authStrategy": "auth_token", "defaultEnv": { "ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES": "thinking,effort,..." }, "modelContextWindows": { "deepseek-v4-pro": 1000000 } } ``` **预设支持的 Provider (截至当前):** | Preset | API Format | Auth Strategy | |---|---|---| | official | anthropic | — | | deepseek | anthropic | auth_token | | zhipuglm | anthropic | auth_token | | kimi | anthropic | auth_token | | minimax | anthropic | auth_token | | jiekouai | anthropic | auth_token | | shengsuanyun | anthropic | auth_token | | lmstudio | anthropic | auth_token_empty_api_key | | ollama | anthropic | auth_token_empty_api_key | | nvidia | openai_chat | api_key | | custom | anthropic | auth_token | ### 5.2 认证策略 (`ProviderAuthStrategy`) ```typescript type ProviderAuthStrategy = | 'api_key' // x-api-key: (NVIDIA) | 'auth_token' // Authorization: Bearer (DeepSeek, Zhipu, Kimi...) | 'auth_token_empty_api_key' // Bearer + dummy x-api-key (LM Studio, Ollama) | 'dual_same_token' // x-api-key + Bearer 同一值 | 'dual_dummy' // x-api-key: dummy + Bearer: dummy (OpenAI OAuth) ``` ### 5.3 API Format ```typescript type ApiFormat = | 'anthropic' // 原生 Anthropic Messages API (直连,无需 Proxy) | 'openai_chat' // OpenAI Chat Completions /v1/chat/completions | 'openai_responses' // OpenAI Responses API /v1/responses ``` ### 5.4 存储结构 cc-haha 的 Provider 数据存储在: ``` ~/.claude/cc-haha/ ├── providers.json # Provider 索引 (活跃 ID + Provider 列表) └── settings.json # 同步到 SDK 的环境变量 ``` 迁移历史通过 `persistentStorageMigrations.ts` 管理,使用 `CURRENT_PROVIDER_INDEX_SCHEMA_VERSION` 追踪 schema 版本。 --- ## 6. Model 管理与切换 ### 6.1 Model 解析链 ``` src/utils/model/ ├── providers.ts → getAPIProvider() - 确定当前 Provider ├── modelStrings.ts → getModelStrings() - 解析 Provider 对应的 Model ID ├── model.ts → getMainLoopModel() - 最终选择的 Model └── configs.ts → ALL_MODEL_CONFIGS - 每个 Model 在各 Provider 的映射 ``` **Model 字符串解析流程:** ```mermaid flowchart TD A[getModelStrings] --> B{STATE.modelStrings 已缓存?} B -->|是| C[返回缓存值] B -->|否| D{Provider 类型} D -->|firstParty| E[ALL_MODEL_CONFIGS 默认值] D -->|openai| F[firstParty 默认 + openai 覆盖] D -->|opencode| G[firstParty 默认 + opencode 覆盖] D -->|openrouter| H[firstParty 默认 + openrouter 覆盖] D -->|nvidia| I[所有模型 = NVIDIA_MODEL env / 默认] D -->|bedrock| J[拉取 Bedrock 推理配置 → 匹配] E --> K[applyModelOverrides] F --> K G --> K H --> K I --> K J --> K K --> L[返回 ModelStrings] ``` ### 6.2 Provider 切换时的缓存清除 **重要**:切换 Provider 后必须调用 `clearModelStrings()`,否则旧的 Model ID 会持续生效: ```typescript // src/utils/model/modelStrings.ts export function clearModelStrings(): void { setModelStringsState(null as unknown as ModelStrings) } ``` ### 6.3 Model Context Windows 两种方式配置模型上下文窗口: 1. **Preset 预设** (`providerPresets.json` 中的 `modelContextWindows` 字段) 2. **用户覆盖** (`~/.claude/cc-haha/providers.json` 中的 `modelContextWindows` 字段) --- ## 7. Provider 运行时环境 ### 7.1 运行环境构建 `ProviderService.syncToSettings()` 将活跃 Provider 的配置写入 `~/.claude/cc-haha/settings.json`: ``` ANTHROPIC_BASE_URL=http://localhost:port/proxy/providers/ ANTHROPIC_AUTH_TOKEN=dummy # 用于 Anthropic SDK 认证 ANTHROPIC_API_KEY=dummy # 同上 API_TIMEOUT_MS=300000 ANTHROPIC_MODEL= ANTHROPIC_DEFAULT_HAIKU_MODEL=... ANTHROPIC_DEFAULT_SONNET_MODEL=... ANTHROPIC_DEFAULT_OPUS_MODEL=... MODEL_CONTEXT_WINDOWS={"model-id": 1000000} ``` ### 7.2 OpenAI OAuth 运行时 对于 OpenAI Official Provider,特殊的环境变量: ``` CC_HAHA_OPENAI_OAUTH_PROVIDER=1 OPENAI_CODEX_OAUTH_FILE= ``` --- ## 8. 架构图汇总 ```mermaid graph TB subgraph "Tier 1: TUI 内置 Provider" A1[~/.claude.json] A2[src/utils/model/providers.ts] A3[src/services/api/client.ts] A4[src/services/api/nvidiaClient.ts] A5[src/services/api/opencodeClient.ts] A2 -->|getAPIProvider| A3 A3 -->|createNvidiaFetchOverride| A4 A3 -->|createOpenCodeFetchOverride| A5 end subgraph "Tier 2: cc-haha Provider 系统" B1[~/.claude/cc-haha/providers.json] B2[~/.claude/cc-haha/settings.json] B3[src/server/services/providerService.ts] B4[src/server/config/providerPresets.json] B5[src/server/proxy/handler.ts] B3 -->|读写| B1 B3 -->|syncToSettings| B2 B3 -->|activateProvider| B4 B3 -->|getProviderForProxy| B5 end subgraph "Anthropic SDK" C1[@anthropic-ai/sdk] C2[AnthropicBedrock] C3[AnthropicVertex] C4[AnthropicFoundry] end subgraph "协议转换层" D1[anthropicToOpenaiChat] D2[anthropicToOpenaiResponses] D3[openaiChatToAnthropic] D4[openaiResponsesToAnthropic] D5[openaiChatStreamToAnthropic] D6[openaiResponsesStreamToAnthropic] D7[copilotClient.ts - 通用转换] end subgraph "OAuth 2.0 流程" E1[src/services/oauth/index.ts] E2[src/services/oauth/client.ts] E3[src/services/oauth/crypto.ts] E4[src/services/oauth/auth-code-listener.ts] E5[src/constants/oauth.ts] end A3 -->|第一方| C1 A3 -->|Bedrock| C2 A3 -->|Vertex| C3 A3 -->|Foundry| C4 A3 -->|Fetch Override| D7 B5 --> D1 B5 --> D2 B5 --> D3 B5 --> D4 B5 --> D5 B5 --> D6 C1 --> E1 ``` --- ## 9. 添加新 Provider 的标准流程 当需要支持一个新的 OpenAI 兼容 Provider 时,按以下步骤操作: 1. **Provider 类型**: 在 `src/utils/model/providers.ts` 的 `APIProvider` 联合类型中添加 2. **检测函数**: 实现 `isXxxConfigured()` 并在 `getAPIProvider()` 调用链中加入 3. **Model 字符串**: 在 `src/utils/model/modelStrings.ts` 的 `getBuiltinModelStrings()` 中添加映射 4. **Fetch Override**: 如果协议需要转换,实现 `createXxxFetchOverride()` (参考 `nvidiaClient.ts`) 5. **客户端集成**: 在 `src/services/api/client.ts` 的 `getAnthropicClient()` 中注入 Override 6. **预设配置**: 在 `src/server/config/providerPresets.json` 中添加预设项 7. **环境变量**: 在 `providerRuntimeEnv.ts` 的 `getManagedEnvKeys()` 中添加变量清理 8. **认证策略**: 如果使用非标准认证,在 `buildAnthropicAuthHeaders()` 中添加策略 --- ## 10. 关键陷阱与注意事项 ### 10.1 Model Strings 缓存 **问题**: `getModelStrings()` 缓存 Provider 特定的 Model ID 到 `STATE.modelStrings` 中。切换 Provider 后,如果不调用 `clearModelStrings()`,旧的 Model ID 会持续生效,导致模型解析错误。 **解决**: 所有 Provider 切换路径 (`/login`, Provider 激活) 都必须调用 `clearModelStrings()`。 ### 10.2 DeepSeek 的 max_tokens 限制 Claude Code 默认发送非常大的 `max_tokens` (如 128K),但 DeepSeek 限制为 8192。服务端 Proxy 的 `anthropicToOpenaiChat()` 已省略 `max_tokens` 透传,让上游使用自己的默认值。 ### 10.3 OAuth Token 刷新竞态 `refreshOAuthToken()` 中有一个关键优化:当全局配置和 Secure Storage 中都有 profile 数据时,跳过 `/api/oauth/profile` 的额外网络请求。但在 `installOAuthTokens` → `performLogout` 的 re-login 路径中,需要穿透缓存以确保订阅类型正确。 ### 10.4 Streaming SSE 转义 `convertOpenAIStreamToAnthropic()` 中使用 `JSON.stringify(reasoning_content).slice(1, -1)` 来转义内容中的特殊字符。如果 reasoning_content 包含换行符或 Unicode 字符,直接拼接字符串会导致 SSE 格式损坏。 ### 10.5 Copilot 的 max_tokens → max_completion_tokens 自动修复 Copilot API 对部分模型使用 `max_completion_tokens` 而非 `max_tokens`。`sendCopilotChatCompletion()` 在收到特定错误信息时会自动切换参数并重试,并将兼容性信息缓存 24 小时。 --- ## 11. 参考资料 - **cc-switch** (原始 Proxy 参考实现): https://github.com/farion1231/cc-switch - **Anthropic Messages API**: https://docs.anthropic.com/en/api/messages - **OpenAI Chat Completions API**: https://platform.openai.com/docs/api-reference/chat - **OpenAI Responses API**: https://platform.openai.com/docs/api-reference/responses - **OAuth 2.0 with PKCE**: https://oauth.net/2/pkce/ - **NVIDIA NIM**: https://build.nvidia.com/docs - **OpenCode Zen**: https://opencode.ai