mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
P1-1 anthropic 基座补 build_headers(x-api-key + anthropic-version) 根因:基座只有静态 anthropic-version、没有 build_headers ⇒ 内核兜底发 Authorization: Bearer,而 Anthropic 协议要 x-api-key。 ★ 证据冲突(计划 P1-1 原文一半有误):zcode 的 /zcode-plan/anthropic/v1/messages 虽然**说 Anthropic 协议**,鉴权却是 Bearer <zcode_jwt>(参考 zcode-transport.ts:121-125;全仓 grep x-api-key = 0 次命中)。故 zcode 必须**显式覆盖** build_headers 回到 Bearer, 否则基座的正向修复会把它从"对"改成"错"。 内核改动:internal/provider/provider.go —— 兜底 Bearer 让位给 x-api-key, 避免同一请求带两套鉴权(部分 Anthropic 兼容上游对歧义鉴权直接 401)。 P1-2 channels-cline 的 build_headers 补 workos: 前缀 根因:参考 cline.ts 发 `Bearer workos:<jwt>`,而池里存的是第二步 register 换回的裸 token ⇒ 之前无 build_headers,内核兜底发裸 Bearer。 幂等:已带前缀(含大小写变体)不重复拼。 P1-3 channels-gemini 只确认不改 参考 gemini.ts:353-366 同样是 OAuth Bearer + 身份五头、刻意不发 x-goog-api-key ⇒ 我们现状本来就对。加判据把这个结论钉住,防下轮照 P1-1 的模子把它改坏。 P2 源名契约:仅 [A-Za-z0-9_-],禁 : 与 / 根因:: 与 / 是限定名分隔符,出现在源名里会让 `源名:模型名` 有两种都 自圆其说的切法,同一请求串在不同部署下可能路由到不同源,且**不报错**。 校验加在写入口 core.normalizeSource(硬拒,API 400);启动时只告警, 避免存量带 : 的源名让升级 brick 掉整个网关。 解析入口 splitPinned 不做校验(解析不该拒绝已有数据)。 P3 夹具形状判据(本轮最重要的一条) 根因:trae 的 Result 信封缺陷在扁平夹具下所有判据全绿,真实登录却报 「缺少 Token」—— 夹具形状错 ⇒ 判据必然虚假通过。 通用形状层:shapeFamilies 登记表,每族给 real/naive/fact/eval/assert, 断言 fact(real) 且 !fact(naive) 且 assert(eval(real))。 刻意**不比较 eval 输出形状**:trae 余额实现合法保留顶层兼容路径, 拿输出比对会把合法兼容判成错误;能区分真假的是 fact(额度在哪一层)。 另有仓库扫描:对特征明确的退化形状静态反查,需退化夹具时写 SHAPE-OK 豁免。 判据:11 个文件 / 30 用例。全部变异已验证会报红(9 处)。
130 lines
4.8 KiB
Go
130 lines
4.8 KiB
Go
package lua
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ★ P1-2:Cline 的推理端点要 `Authorization: Bearer workos:<jwt>`。
|
||
//
|
||
// 真实缺陷:参考实现(cline.ts)发头时直接拼 `workos:${token}`,而我们池里
|
||
// 存的是**第二步 register 换回来的 Cline token**(裸 token,不带前缀)
|
||
// ⇒ 之前 channels-cline 干脆没有 build_headers,内核兜底发
|
||
// `Authorization: Bearer <裸token>` ⇒ 少了协议要求的前缀 ⇒ 上游只回泛化的
|
||
// 认证失败(与 trae 修前同型:头发了,但形式不对)。
|
||
//
|
||
// 判据咬**真实产出**(vm.BuildHeaders 就是内核发请求前走的那条路径),
|
||
// 不造 HTTP 假上游(那需要 URL 覆盖/凭据 ref/路径匹配全部对齐,任一环错
|
||
// 都会让「没发请求」伪装成「头不对」,plan §3 第 4 条)。
|
||
func TestClineBuildHeadersAddsWorkOSPrefix(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
src, err := bundledAdapters.ReadFile("plugins/channels.lua")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := ps.LoadSource("channels", string(src)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !vm.HasFn("channels-cline", "build_headers") {
|
||
t.Fatal("★ channels-cline 没有 build_headers —— 内核会兜底发裸 Bearer," +
|
||
"而 Cline 推理端点要 `Bearer workos:<jwt>`")
|
||
}
|
||
|
||
cases := []struct {
|
||
name string
|
||
tok string
|
||
want string
|
||
}{
|
||
{
|
||
name: "无前缀 → 补上 workos:",
|
||
tok: "eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig",
|
||
want: "Bearer workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig",
|
||
},
|
||
{
|
||
name: "已有 workos: 前缀 → 不重复拼",
|
||
tok: "workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig",
|
||
want: "Bearer workos:eyJhbGciOiJIUzI1NiJ9.jwt-payload.sig",
|
||
},
|
||
{
|
||
name: "前缀大小写不同(WorkOS:)→ 视为已带,不重复拼",
|
||
tok: "WorkOS:abc123",
|
||
want: "Bearer WorkOS:abc123",
|
||
},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{
|
||
"api_key": tc.tok,
|
||
"url": "https://api.cline.bot/api/v1/chat/completions",
|
||
"method": "POST",
|
||
"body": `{"model":"x"}`,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("BuildHeaders: %v", err)
|
||
}
|
||
auth := hdrs["Authorization"]
|
||
if auth != tc.want {
|
||
t.Fatalf("★ Authorization 应为 %q,实得 %q\n"+
|
||
" Cline 要 `Bearer workos:<jwt>`;缺前缀或重复拼都会认证失败",
|
||
tc.want, auth)
|
||
}
|
||
// 不得出现 workos:workos: 这种重复前缀(旧数据可能已带前缀)
|
||
if strings.Count(strings.ToLower(auth), "workos:") != 1 {
|
||
t.Fatalf("★ workos: 前缀出现 %d 次(必须恰好 1 次): %q",
|
||
strings.Count(strings.ToLower(auth), "workos:"), auth)
|
||
}
|
||
t.Logf("✓ %s → %s", tc.name, auth)
|
||
})
|
||
}
|
||
}
|
||
|
||
// 配对判据:没有凭据时不得发出 `Bearer workos:` 这种空凭据头
|
||
// (空凭据头会让上游回认证失败,而真因是「没配置 token」,会把人带偏)。
|
||
func TestClineBuildHeadersWithoutToken(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
src, err := bundledAdapters.ReadFile("plugins/channels.lua")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := ps.LoadSource("channels", string(src)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{})
|
||
if err != nil {
|
||
t.Fatalf("BuildHeaders 不该因缺凭据报错: %v", err)
|
||
}
|
||
if auth, has := hdrs["Authorization"]; has {
|
||
t.Fatalf("★ 无凭据时不得发 Authorization(会变成 `Bearer workos:`),实得 %q", auth)
|
||
}
|
||
}
|
||
|
||
// ★ 单一实现:前缀规则只在 with_workos_prefix 里写一次。
|
||
// 判据直接咬那个函数(经适配器导出),确保 build_headers 用的是同一份逻辑 ——
|
||
// 若哪天有人把规则复制到 build_headers 里内联、而导出版留着旧的,这条会红。
|
||
func TestClineWorkOSPrefixHelperIsSingleSource(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
src, err := bundledAdapters.ReadFile("plugins/channels.lua")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := ps.LoadSource("channels", string(src)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !vm.HasFn("channels-cline", "with_workos_prefix") {
|
||
t.Fatal("★ channels-cline 应导出 with_workos_prefix 供判据咬同一份实现" +
|
||
"(否则判据只能自算前缀规则,真实函数被改坏时会继续绿)")
|
||
}
|
||
// 用适配器池里真实的那份脚本再算一遍,与 build_headers 的产出对照。
|
||
// 这里直接调 Transform 不方便(不是 transform 钩子),故用 build_headers
|
||
// 的两种输入反推:带前缀的 token 必须**原样**出现在 Authorization 里。
|
||
hdrs, err := vm.BuildHeaders("channels-cline", map[string]interface{}{
|
||
"api_key": "workos:XYZ",
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := hdrs["Authorization"]; got != "Bearer workos:XYZ" {
|
||
t.Fatalf("已带前缀的 token 必须原样透传,实得 %q", got)
|
||
}
|
||
}
|