mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
test+docs(channels): 三私有协议平台的源接线判据 + 自装适配器机制实录
补齐"装了适配器 ≠ 源用了它"这条判据。这是这类改动最可能的漏配,且 **不会报错** —— 只是签名/信封不生效、上游返回流内错误或 401,看起来像 "平台不稳定"。三个平台的失败模式不同、根因同一个(平台协议差异被当成 适配器名差异): codearts → 华为云 AK/SK 签名 trae → SOLO 载荷格式 gemini → 私有 /v1internal: 信封 + 身份五头 判据同时验 adapter 名、base_url 与"源里必须有模型"。 docs/channels-parity.md 补两节: - 三个私有协议平台已改用插件自装适配器(含 credential_shape 的引入原因: codearts 凭据没有 access_token,credentialOf 取不到 → 静默不出源) - 附录:机制(内核早已具备,channels 插件引用数为 0)、三条实测能力、 六个"静默失败"类踩坑、判据清单
This commit is contained in:
@ -117,31 +117,45 @@ chat URL **全部缺路径前缀**,11 个平台里 11 个都不对。已逐个
|
||||
| zcode | `zcode.z.ai/api/v1/zcode-plan/anthropic/v1/messages` | `zcode.z.ai/v1/messages` |
|
||||
| gemini | `/v1internal:generateContent`(私有动词) | `/v1beta/models/{model}:generateContent`(公开,凭据不匹配) |
|
||||
|
||||
### 未闭环:trae 的载荷转换
|
||||
### ✅ 已闭环:三个私有协议平台改用**插件自装适配器**
|
||||
|
||||
trae **不是 OpenAI 兼容**。参考 `trae.ts:30` 注释:
|
||||
「载荷必须转换:OpenAI 的 `{model, messages, tools, tool_choice, stream}` 需要
|
||||
映射为 SOLO 格式(`function`、`config_name` 等字段),不能透传」;
|
||||
`model` 还要映射到 `config_name`(查远端模型列表)。
|
||||
这是"为什么参考正常、我们不正常"的正面修复。参考每个平台是继承宿主
|
||||
`LlmAdapter` 的真类;我们此前把平台协议差异当成适配器名差异(全塞 openai)。
|
||||
内核其实**早已实现** `__llmsproxy_adapters.install`(含归属校验/冲突仲裁/
|
||||
测试),但 channels 插件对该全局的引用数是 **0**。现已第一次真正使用:
|
||||
|
||||
我们有一个 `internal/lua/adapters/trae.lua`,但它描述的是
|
||||
`trae-local-api`(本地 OpenAI 兼容代理,`127.0.0.1:19900`),**不做** SOLO
|
||||
转换(`grep config_name` = 0 命中),且平台声明用的是 `adapter = "openai"`。
|
||||
所以 trae 的**端点现在对了,载荷仍然错**。这是一块独立性工作。
|
||||
| 平台 | 自装适配器 | 解决的协议差异 |
|
||||
|---|---|---|
|
||||
| codearts | `codearts` | 华为云 SDK-HMAC-SHA256 **AK/SK 签名**(canonical/string-to-sign 逐字节) |
|
||||
| trae | `channels-trae` | **SOLO 载荷**:config_name 双字段、function 通道、tool_calls→function_call、parameters→JSON 字符串、tool_choice 归一化 |
|
||||
| gemini | `channels-gemini` | 私有 `/v1internal:` **信封** + 身份五头 + 字母序序列化 + 响应剥一层 |
|
||||
|
||||
### 未闭环:gemini 的私有协议信封
|
||||
命名说明:trae 用 `channels-trae` 而非 `trae`,因为内置 `trae.lua` 服务的是
|
||||
**运维自建的 trae-local-api 反代**(`127.0.0.1:19900`,生产 config.yaml 在用),
|
||||
与本插件的直连通道是两回事。重名会被内核冲突仲裁挂起(正确的护栏)——
|
||||
判据里专门断言内置 `trae` 未被顶掉。
|
||||
|
||||
端点是 `/v1internal:` 了,但参考 `gemini.ts` 还有三件必须保留的细节:
|
||||
1. **身份五头逐字写死、不许随机**(且**不带** `x-goog-api-key` / `x-goog-api-client`)
|
||||
2. **流式请求刻意不带 `Accept` 头**
|
||||
3. **信封逐层字母序序列化**(Go 的 `encoding/json` 对 map 自动字母序,TS 的
|
||||
`JSON.stringify` 按插入序 —— 不排序字节就与上游不同)
|
||||
**新增能力:`credential_shape`**。签名平台需要**完整凭据 JSON**(适配器自己
|
||||
解 AK/SK),其余平台交裸 access token。此前 `syncAccountSource` 一律用
|
||||
`credentialOf` 取 `access_token`,于是 codearts 凭据(只有
|
||||
`access_key_id`/`secret_access_key`/`security_token`)取不到 token →
|
||||
**静默 return、不出源**。现已按 `plat.credential_shape` 区分。
|
||||
|
||||
**另一处静默失败已修**:`syncAccountSource` 拿到内核 `add_source` 的失败
|
||||
(权限未授、形状不符)时不记日志,表现为"账号加了但不出源",排查零线索。
|
||||
现补失败日志(区分内核 err 与 `json.ok==false`)。
|
||||
|
||||
### 仍未闭环:gemini 的 sigstore(明确不做)
|
||||
|
||||
参考 `gemini-sigstore.ts`(268 行)持久化 tool-call 签名,供跨请求回填。
|
||||
我们推理走**无状态适配器**,插件侧缓存会成为死代码。签名缺失时上游会拒,
|
||||
适配器如实透传错误。理由与结论已记在此处,避免反复讨论。
|
||||
|
||||
## 四、明确不做的(附理由)
|
||||
|
||||
| 参考模块 | 行数 | 为什么不做 |
|
||||
|---|---|---|
|
||||
| `gemini-sigstore.ts` | 268 | 我们推理走无状态适配器,插件不参与推理 → 插件侧缓存会成为死代码 ⛔ |
|
||||
| `gemini-sigstore.ts` | 268 | 推理走无状态适配器,插件侧缓存会成为死代码。签名缺失时上游会拒,适配器如实透传 ⛔ |
|
||||
| `qoder-wasm.ts` | 1442 | 需随插件分发 298KB wasm 二进制;B 类,待立项 |
|
||||
| `zcode-captcha.ts` | 2212 | 阿里云滑块,需真实浏览器/验证码链路;B 类 |
|
||||
| `captcha-*.ts` | 1223 | 同上 |
|
||||
@ -246,3 +260,58 @@ channels 插件一个适配器都没装过 —— 它只会注册 `adapter = "op
|
||||
- 流式转换能否跨 chunk 保持状态(trae 的 SSE 需要跨帧累积 tool_calls)
|
||||
|
||||
这两条是下一步落地前必须先探的。
|
||||
|
||||
---
|
||||
|
||||
# 插件自装适配器:机制与判据(2026-10-13)
|
||||
|
||||
## 机制(内核早已具备,本轮才开始使用)
|
||||
|
||||
```
|
||||
internal/lua/plugin_adapter_bridge.go
|
||||
InstallAdapter(plugin, name, code) # 含归属校验、冲突仲裁(影子内置 → 挂起等裁决)
|
||||
registerAdapterBridge → Lua 全局 __llmsproxy_adapters.install / .update / .remove
|
||||
```
|
||||
注册点在 `bootPluginState`,**先于** `DoString(code)` —— 所以插件在脚本执行期
|
||||
就能装适配器。适配器装在**适配器 VM**(与插件 VM 分离,各自独立)。
|
||||
|
||||
## 三条能力(实测,不是推断)
|
||||
|
||||
`internal/lua/plugin_adapter_capability_test.go` 锁死:
|
||||
|
||||
| 能力 | 结论 | 为什么关键 |
|
||||
|---|---|---|
|
||||
| 拿凭据 | 适配器**能**经 `build_headers` 的 `meta.api_key` 拿到(`url`/`method`/`body` 也在) | AK/SK 签名的前提;同时**验不到** `__llmsproxy_secrets`(只注入插件 VM,安全边界完好) |
|
||||
| 流式状态 | `transform_stream_chunk` **跨 chunk 保持同一 Lua 状态**(表地址恒定) | SOLO 跨帧累积 tool_calls、gemini 跨帧剥信封的前提 |
|
||||
| 加密原语 | 适配器可用内核 `sha256_hex` / `hmac_sha256_hex`(用标准向量校验) | 签名不需要外部进程 |
|
||||
|
||||
## 踩过的坑(都是"静默失败"类,值得记)
|
||||
|
||||
1. **`function` 是 Lua 关键字** —— 写 `body.function = x` 是**语法错误**
|
||||
(`'<name>' expected near 'function'`),必须 `body["function"]`。
|
||||
插件脚本本身能编译通过,**只在适配器 `load()` 时才暴露**。
|
||||
2. **Lua 长字符串里不能有字面 NUL 字节** —— 报
|
||||
`unfinished long string near '<eof>'`,且**报在文件末尾**,完全指不到现场。
|
||||
3. **本 Lua 构建不支持按位异或 `~`**(`unexpected symbol near '~'`)。
|
||||
4. **装载失败只回"源码无法编译"、不给行号** —— 三个自装适配器现统一加
|
||||
**本地预编译护栏**(先 `load` 一次拿行号)。上面三个坑都是靠它定位的。
|
||||
5. **抽公共代码要抽最全的实现**。前导重构时我先抽了 `transform_error` 的
|
||||
最小版(只认 `{error:{message}}`),打挂 6 个既有测试 —— 最小版丢掉了
|
||||
HTML 错误页、裸文本、扁平 `{code,message}`、FastAPI `{detail}`。
|
||||
抽最小实现是**静默降级**:不报错,只是少认几种错误体。
|
||||
6. **判据必须能看见真实函数的返回值**。我一度用 `kernelTestCore` 裸加载插件
|
||||
(没授 `add_source` 权限)测源注册,源一直不出;直到把内核原始返回
|
||||
(`kernel action add_source is not granted to plugin channels`)带出来才
|
||||
定位。**黑盒断言只在失败时告诉你"不对",不告诉你"为什么"**。
|
||||
|
||||
## 判据清单
|
||||
|
||||
| 文件 | 锁死什么 |
|
||||
|---|---|
|
||||
| `internal/lua/plugin_adapter_capability_test.go` | 三条能力(凭据/流式状态/加密原语) |
|
||||
| `internal/lua/adapter_prelude_test.go` | 前导注入 + 语义值正确 + 旧适配器行为不变 |
|
||||
| `internal/lua/adapter_prelude_equivalence_test.go` | 重构前后逐字节等价(含反向断言) |
|
||||
| `internal/lua/channels_trae_solo_test.go` | SOLO 7 条规则 + 三处反向断言 |
|
||||
| `internal/lua/channels_gemini_adapter_test.go` | 身份五头逐值 + 不得发 google key/Accept + 字母序 + 响应剥信封 |
|
||||
| `internal/core/channels_codearts_adapter_test.go` | 装机/归属 + **Go stdlib 独立复算 HMAC 验签** |
|
||||
| `internal/core/channels_private_adapter_wiring_test.go` | 三平台的**源真的引用了**自装适配器 |
|
||||
|
||||
Reference in New Issue
Block a user