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