codev / docs /architecture /provider-auth.md
chenbhao's picture
chore: rename VersperClaw to Codev, update org to chenbhao
96f34e3
|
Raw
History Blame Contribute Delete
27.8 kB
# 多 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<Response>
```
该重写在 `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 <key>` 请求头
- **特殊头**: `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 <key>`
- **协议**: `openai_chat` (Chat Completions)
- **Model**: 使用 `custom-openai:<modelId>` 前缀选择
- **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: <key> (NVIDIA)
| 'auth_token' // Authorization: Bearer <key> (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/<id>
ANTHROPIC_AUTH_TOKEN=dummy # 用于 Anthropic SDK 认证
ANTHROPIC_API_KEY=dummy # 同上
API_TIMEOUT_MS=300000
ANTHROPIC_MODEL=<model-id>
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=<path-to-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