2 Commits

Author SHA1 Message Date
93eeba3762 feat(types): 统一格式落地为内核实际使用的类型 —— 私有字段泄漏归零
Phase 2 收尾(入向)。ChatRequest 从"客户端发什么就是什么"改为
UnifiedRequest(最大集合)的别名,内核真正用上了那个超集类型。

改掉的具体缺陷:

1. 私有字段泄漏归零(v1 的核心问题)
   v1 的 disable_thinking 是内核私有开关,却被序列化进交给适配器的 body,
   迫使 10 个适配器各自写 req.disable_thinking = nil 来清理。
   现在它是中立的 thinking 字段({enabled, budget_tokens}),客户端与
   适配器都认这个词:
   - gateway/chat.go:thinkingFromClient() 把 disable_thinking 翻译成
     thinking.enabled=false;冲突时中立字段优先(它能带预算,布尔不能)
   - deepseek.lua:thinking.enabled=false → extra_body.thinking.type=disabled
   - anthropic.lua:thinking.enabled=true + budget → 原生 {type,budget_tokens}
     (enabled=false 时不传该字段——Anthropic 没有 type=disabled 这个写法)
   - 其余 7 个适配器:把"清理内核私有字段"改成"清理中立字段"

2. Tools/ToolChoice 改 json.RawMessage
   原为 []interface{}(OpenAI 原始形状 = 内核在假设某个协议)。
   内核从不解释工具 schema——它原样交给适配器,所以原始形状不损失任何
   内核侧统一性。ToolDef 结构化类型保留在包内供新适配器选用。

3. 删除 MarshalJSON 后门
   v1 手工 splice ExtraBody 进 OpenAI 形状的 body;现在 ExtraBody 是
   UnifiedRequest 上一个正正经经声明的字段。

渐进迁移:ChatRequest 保留为别名(不是新类型),49 处引用零改动编译,
语义变更不被机械改名淹没。

测试:新增 thinking_test.go(翻译链 4 例 + 泄漏回归 + tools 透传);
vm_test.go 两处迁移到中立语义。golden 字节级未漂移。
2026-10-11 09:35:32 +08:00
3ad46ea23e feat(types): 统一线格式 v2 最大集合 + 对外输出 golden 基线
Phase 1:内核定义市面所有主流协议字段的**并集**作为唯一内部格式,
适配器按需取用;内核不再携带任何协议形状。

- internal/types/maxwire.go:UnifiedRequest(21 字段,v1 的 10)、
  UnifiedResult(11,v1 的 8)、UnifiedDelta。覆盖 openai/anthropic/
  gemini/ollama/deepseek 各自必需字段(system 顶层、top_k、stop_sequences、
  thinking 预算、parallel_tool_calls、response_format、safety/citations…)。
- 只带**语义**不带**容器**:generationConfig/systemInstruction/options/
  num_predict/maxOutputTokens 等协议专属形状一律不进统一格式,由适配器
  自行装填。测试 TestNoProtocolContainerLeak 钉死这条。
- 修掉 v1 的私有字段泄漏:disable_thinking/extra_body 曾是内核私有字段,
  却被序列化进交给适配器的 body,导致 10 个适配器各自写
  `req.disable_thinking = nil` 清理。v2 用 thinking 语义字段替代,
  TestNoKernelPrivateFieldLeak 为回归。

Phase 0:建立对外输出的**字节级** golden 基线(.snapshots/),
因为现有 e2e 只有 strings.Contains 宽松断言,无法证明 Phase 2 把
OpenAI wire 移交给适配器 serializer 后输出字节未变。

- e2e/wire_golden_test.go:非流式/流式/生图三个 golden,
  比对前 scrub 掉 id 与 created(每请求变化),其余逐字节比对。
- 反向验证:注入 `chat.completion.chunk.DRIFTED` 漂移后 golden 确实失败
  (证明它不是永远通过的空壳),随后完整恢复。
- 稳定性:连跑 3 次全绿。

验证:go test -tags luajit ./internal/types/ ./e2e/ -run 'TestMax|TestNo|TestWireGolden' 全绿。
2026-10-11 08:24:58 +08:00