File size: 27,752 Bytes
f468102 96f34e3 f468102 96f34e3 f468102 96f34e3 f468102 96f34e3 f468102 96f34e3 f468102 762b1c8 f468102 762b1c8 f468102 762b1c8 f468102 762b1c8 f468102 762b1c8 f468102 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 | # 多 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
|