# Clavue v2 Phase 4 战报 (2026-05-07)

> 接续 Phase 1+2+3，对 OpenAI Responses Adapter 做更深拆分
> 起点（Phase 3 终点）：adapter 主文件 1865 行，7 个子模块 530 行
> 终点（Phase 4 终点）：adapter 主文件 **1626 行 (-239)**，10 个子模块 **830 行**

---

## 一、Phase 4 量化指标

| 指标 | Phase 3 终点 | Phase 4 终点 | Δ |
|------|-------------|-------------|---|
| `openaiResponsesAdapter.ts` | 1865 行 | **1626 行** | -239 |
| `responsesAdapter/` 子模块数 | 7 | **10** | +3 |
| 子模块总行数 | 530 行 | **830 行** | +300 |
| 全量测试通过 | 616/621 | **616/621** | 持平 |
| 因拆分引入的回归 | 0 | **0** | ✅ |

---

## 二、四阶段累计 adapter 拆分

```
起点 (Phase 0):
  openaiResponsesAdapter.ts  2288 行

Phase 2 (P2-1):
  -> 2034 行 (-254)
  + paths.ts 28
  + systemReminderPromotion.ts 72
  + headers.ts 42
  + jsonHelpers.ts 60
  + sseEncoding.ts 29
  + errorResponses.ts 108

Phase 3 (P3-1):
  -> 1865 行 (-169)
  + toolTranslation.ts 191

Phase 4 (P4-1/2/3):
  -> 1626 行 (-239)
  + toolResultStringify.ts 78
  + usageMapping.ts 88
  + reasoningAndText.ts 134

累计：2288 → 1626 行 (-662 / -28.9%)
子模块：0 → 10 个 (830 行)
```

---

## 三、Phase 4 完成项

### P4-1: Reasoning/Text 辅助函数抽取

**新文件**：`src/services/api/responsesAdapter/reasoningAndText.ts` (134 行)

**抽出内容**（7 个函数）：
- `translateReasoning(thinking, effort)` — Anthropic thinking 配置 → OpenAI Responses reasoning effort
- `mapAnthropicEffortToResponsesEffort(effort)` — low/medium/high/max → low/medium/high/xhigh
- `translateTextConfig(outputConfig)` — 输出配置（verbosity, json_schema format）翻译
- `normalizeGptVerbosity(value)` — verbosity 归一化为 low/medium/high
- `flattenSystemPrompt(rawSystem)` — string 或 blocks[] system → 单一字符串
- `getLastAssistantMessageIndex(messages)` — 找最后一条 assistant 消息位置
- `mergeInstructions(base, promoted)` — base instructions + promoted system reminders 合并去重

**收益**：reasoning 策略和 verbosity 规则独立可审计；测试可专门 lock 这些 mapping 不会无声变化。

### P4-2: Tool Result Stringify 抽取

**新文件**：`src/services/api/responsesAdapter/toolResultStringify.ts` (78 行)

**抽出内容**（3 个函数）：
- `stringifyToolResult(block)` — Anthropic tool_result 块 → 字符串（保留 [tool_error] 标记）
- `stringifyToolResultContent(content)` — 处理 string / array / null / 其他 JSON
- `stringifyFallbackContentBlock(block)` — 未知 type 的 fallback 渲染

**收益**：tool_result 渲染逻辑独立，便于测试 [tool_error] 标记保留、空内容降级到 'null' 等边界。

### P4-3: Usage / Stop Reason 抽取

**新文件**：`src/services/api/responsesAdapter/usageMapping.ts` (88 行)

**抽出内容**（4 个函数）：
- `mapChatCompletionsStopReason(response)` — finish_reason → Anthropic stop_reason
- `getFirstChoice(payload)` — Chat Completions choices[0] 解析
- `mapResponsesStopReason(response, sawFunctionCall)` — Responses output → stop_reason
- `createAnthropicUsage(rawUsage, rawToolUsage)` — usage 字段映射 + 默认值填充

**收益**：stop_reason 映射表独立，未来 OpenAI 加新 finish_reason 时只改这一个文件；usage schema 扩展不影响主路径。

---

## 四、子模块总览

| 文件 | 行数 | 职责 |
|------|------|------|
| `paths.ts` | 28 | URL 解析、路径常量、模型路由判断 |
| `systemReminderPromotion.ts` | 72 | 控制文本提升到 instructions 字段 |
| `headers.ts` | 42 | request-id 回填、auth header 翻译 |
| `jsonHelpers.ts` | 60 | JsonRecord 类型 + asRecord/getString/safeParseJson 等基础工具 |
| `sseEncoding.ts` | 29 | SSE encode/decode |
| `errorResponses.ts` | 108 | 4 种 error 响应构造器（404 fallback、tool 翻译失败等） |
| `toolTranslation.ts` | 191 | 7 个 tool schema 翻译函数 + TranslatedToolSet 类型 |
| `toolResultStringify.ts` | 78 | tool_result 块字符串化 |
| `usageMapping.ts` | 88 | stop_reason 映射 + usage 字段构造 |
| `reasoningAndText.ts` | 134 | reasoning effort、verbosity、system prompt 处理 |
| **总计** | **830** | **10 个子模块** |

---

## 五、剩余在主文件的逻辑

主文件仍 1626 行，未拆分的部分：

| 逻辑簇 | 估算行数 | 拆分难度 | 建议 |
|--------|---------|---------|------|
| 主入口 `maybeFetchAnthropicViaOpenAIResponses` | ~80 | - | 保留主文件 |
| 请求构造 (`buildResponsesRequest`, `buildChatCompletionsRequest`, `translateMessagesInput`, `translateUserContent`, `translateAssistantContent`) | ~600 | 中 | Phase 5 候选（高 ROI） |
| 流式翻译状态机 (`StreamTranslationState`, `handleOutputItem*`, `handleContentPart*`, `handleFunctionCallArguments*`, `finalizeOpenBlocks`) | ~500 | 高 | Phase 6 候选（耦合度高） |
| 响应构造 (`buildAnthropicMessageResponse`, `buildAnthropicChatCompletionsMessageResponse`, `translateResponsesMessageItem`, `stringifyResponsesMessageContent`) | ~200 | 低 | Phase 5 候选 |
| 图片块翻译 (`translateAnthropicImageBlockToResponses`, `translateResponsesImagePartToChat`, `normalizeOpenAIImageDetail`) | ~80 | 低 | Phase 5 候选 |
| 杂项辅助 (`bodyToString`, `parseJsonBody`, `summarizeTranslatedToolSet`, `shouldUseOpenAIResponsesAdapterForRequest`) | ~80 | 低 | 保留主文件 |
| 类型定义 + `buildInternalOpenAIResponsesAdapterRequestPlan` | ~150 | - | 保留主文件 |

**建议路径**：
- Phase 5：抽出图片翻译 + 响应构造（低风险）
- Phase 6：抽出请求构造 messages/user/assistant translators
- Phase 7：流式状态机（最高风险，需要先建立 contract 测试套件）

最终目标：主文件 < 600 行（仅保留主入口、类型、内部 plan builder、杂项 helper）。

---

## 六、测试状态

- **22/22** adapter 测试持续通过（Phase 2/3/4 全程绿）
- **616/621** 全量测试持续通过（仅 release pipeline 因 dist 重建未通过，与源码无关）
- **0** 个因拆分引入的回归

---

## 七、累计四阶段成果

| 维度 | 起点 | Phase 4 终点 |
|------|------|-------------|
| `.js` 文件数 | 51 | **18** (-65%) |
| `.ts` 文件数 | 1439 | **1481** (+42) |
| Adapter 主文件 | 2288 行 | **1626 行** (-29%) |
| Adapter 子模块 | 0 | **10 个 / 830 行** |
| Context Management 默认启用 | 否（ant gate） | 是 |
| Context Management 测试 | 0 | 8 |
| MAO `*.js` | 8 | 0 |
| MAO 文件锁 | glob 假锁 | proper-lockfile |
| `/mao` 公开命令 | 注册 | 撤下 |
| Companion opt-in 文档 | 无 | 有 |
| 测试通过 | 599 | **616** (+17) |
| 因迁移引入的失败 | n/a | **0** |

---

## 八、下一步建议

### 优先（Phase 5）
1. **图片块翻译抽取**（最简单，~80 行，低风险）
2. **响应构造抽取**（中等，~200 行）— `buildAnthropicMessageResponse`, `buildAnthropicChatCompletionsMessageResponse` 等
3. **请求构造抽取**（中等，~600 行最大块）— `buildResponsesRequest`, `buildChatCompletionsRequest`, message/content translators

### 中期（Phase 6）
4. **流式状态机抽取**（最高风险）— 必须先建立 contract 测试，确保事件序列完全一致
5. **commands/*/index.js 迁移**（18 个 thin wrappers，完美主义清理）

### 长期
6. discipline 字段从 route policy 解耦
7. Provider profile 验证流程异步化
8. 动态模型发现替代硬编码 configs.ts

— Phase 4 战报完。
