From fe9d5c6054bf692c66af9a281cb5c9acf150bf91 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 13 Oct 2026 18:40:00 +0800 Subject: [PATCH] =?UTF-8?q?docs(channels):=20=E8=A1=A5=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E5=B7=AE=E5=BC=82=E5=88=86=E6=9E=90=20=E2=80=94=E2=80=94=20?= =?UTF-8?q?=E4=B8=BA=E4=BB=80=E4=B9=88=E5=8F=82=E8=80=83=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E3=80=81=E6=88=91=E4=BB=AC=E9=9D=A0=20adapter=20=E5=90=8D?= =?UTF-8?q?=E7=A1=AC=E6=92=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 查证参考仓架构后的结论: - 参考:一个平台一个继承宿主 LlmAdapter 的真类(trae 做 SOLO 转换、 codearts 做 AK/SK 签名、zcode 走 Anthropic),公共逻辑抽在 openai-compat.ts(1196 行,8/12 adapter 复用) - 我们:内核是 proxy,插件只注册源;平台差异被当成 adapter 名差异 - 关键:内核**早已实现** __llmsproxy_adapters.install(含归属校验、冲突 仲裁、测试 TestPluginInstallsAdapterFromLua 通过),但 channels.lua 对这个全局的引用数是 **0** —— 一个适配器都没装过 - 我们还缺参考的共享前导:openai.lua 与 anthropic.lua 各复制一份 safe_tool_id - 记录了下一步落地前必须先探的两条风险(适配器 VM 能否读 secrets、 流式转换能否跨 chunk 保持状态) --- docs/channels-parity.md | 86 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/docs/channels-parity.md b/docs/channels-parity.md index a20f501..c717c94 100644 --- a/docs/channels-parity.md +++ b/docs/channels-parity.md @@ -160,3 +160,89 @@ trae **不是 OpenAI 兼容**。参考 `trae.ts:30` 注释: 参数才是 minPort。 5. **Lua 双引号串里的 `\1`** 是转义坑(`sed 's/…/…/\1/'` 会报 invalid escape sequence)→ 改用 `awk -F': ' '{print $NF}'`。 + +--- + +# 为什么参考插件是"正常"的:架构差异(2026-10-13 查证) + +用户问「为什么他们就正常」。查完参考仓的架构后,答案是**三层结构差异**, +不是某一个端点的笔误。 + +## 参考的架构:一个平台 = 一个适配器类 + +参考依赖宿主 SDK 的 seam:`import { LlmAdapter } from '@deepseek-ai/dsh-llm'`。 +每个平台有一个**继承基类的真类**,同时拥有登录、模型目录、推理三件事: + +| 文件 | 类 | 干什么 | +|---|---|---| +| `trae-adapter.ts` | `TraeAdapter` | SOLO 协议转换(`config_name`/`function`,非 OpenAI 兼容) | +| `llm-adapter.ts` | `CodeArtsAdapter` | 华为云 AK/SK 签名 + `maas_type: benefit` 判定 | +| `zcode-adapter.ts` | `ZcodeAdapter` | Anthropic Messages 协议(非 OpenAI) | +| `qoder-adapter.ts` | `QoderAdapter` | WASM 加密端点 + 信封剥离 | +| `gemini-adapter.ts` | `GeminiAdapter` | `/v1internal:` 私有动词 + 字母序信封 | + +注册方式是 `registerAdapterIdempotent(ctx.llm, [PROVIDER], adapter)` +(`llm-adapter.ts:2080`)。 + +**但它不重复**:共享层是 `src/openai-compat.ts`(1196 行)—— +`serializeMessages`(消息序列化)、`consumeOpenAiSse`(SSE 消费)、 +`httpErrorCode` / `isTransportError`(错误归类)、`userContentParts`(多模态)。 +12 个 adapter 里有 8 个 `import from './openai-compat'`。 + +即:**公共协议逻辑一份,平台差异各自一个类**。 + +## 我们的架构:内核是 proxy,插件只注册源 + +- 适配器是 Lua 文件(12 个),通过 4 个钩子参与: + `transform_request` / `transform_response` / `transform_stream_chunk` / + `transform_error`,外加 `build_headers`(动态签名,见下)。 +- 声明面:`endpoint` / `endpoint_stream` / `ingress` / `headers`。 +- 插件通过 `__llmsproxy_kernel.call("add_source", {source={...}})` 把平台 + 注册为上游源,`source.adapter` 引用适配器名。 + +## 关键发现:机制早就有,但 channels 插件从没用过 + +内核**完整实现了**插件自装适配器: + +``` +internal/lua/plugin_adapter_bridge.go + func (ps *Plugins) InstallAdapter(plugin, name, code string) (string, error) + registerAdapterBridge → Lua 全局表 __llmsproxy_adapters.install / .update / .remove +``` + +- 有归属校验(`adapterOwnership`:只能改自己装的) +- 有冲突仲裁(影子内置/他人所有 → 挂起等运维裁决,不是硬拒) +- 有测试证明可用:`TestPluginInstallsAdapterFromLua`(PASS) + +**但 `grep -c '__llmsproxy_adapters' internal/lua/plugins/channels.lua` = 0。** +channels 插件一个适配器都没装过 —— 它只会注册 `adapter = "openai"` 的源。 +这就是"为什么我们不正常"的直接原因:**平台协议差异被当成适配器名差异**。 + +## 我们还缺的一层:共享协议模块 + +参考有 `openai-compat.ts`;我们没有对应物。`openai.lua` 自己写明 +「Lua adapters have no shared prelude」,`safe_tool_id` 在 `openai.lua` 与 +`anthropic.lua` 里**各复制了一份**(各 6 处引用)。 + +所以正确做法是两层: +1. 抽一个 Lua 共享前导(消息序列化 / SSE 消费 / 错误归类), + 或内核在加载时注入 —— 消掉复制粘贴 +2. channels 插件为需要真协议转换的平台(trae SOLO、gemini 信封、 + codearts AK/SK 签名)**装自己的适配器**,而不是硬塞给 `openai` + +### 所需的适配器能力(crypto + 出站 + 流式) + +已核实内核提供: +- `sha256_hex` / `hmac_sha256_hex`(`vm.go` setupGlobals) +- `build_headers(meta)` 收 `{url, method, body, api_key, timestamp, + client_session, source{name,meta}}` —— CodeArts 的 AK/SK 签名用它 + (与参考 `signRequestHuawei` 同构,但 AK/SK 要从 `api_key` 里解出) +- `transform_stream_chunk` 逐 chunk 调用(`provider.go:1295`) +- 插件有完整 OS 能力(`io.popen`/`os.execute`)→ 可用 openssl 做 EC 签名 + +### 未验证的风险 + +- 适配器 VM 能否访问 `__llmsproxy_secrets`(AK/SK 要解出 SK 才能签名) +- 流式转换能否跨 chunk 保持状态(trae 的 SSE 需要跨帧累积 tool_calls) + +这两条是下一步落地前必须先探的。