mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
为协议转换类平台(trae SOLO / gemini 信封 / codearts AK/SK 签名)装专用
适配器铺路,先把能不能做实测清楚:
① 能力:适配器**能**通过 build_headers 的 meta.api_key 拿到凭据
(AK/SK 签名的前提),且**拿不到** __llmsproxy_secrets —— 该全局只注入
插件 VM(plugin_bridges.go 开篇明说),安全边界完好。
同时验 url/method/body 三个签名字符串原材料都能拿到。
② 能力:transform_stream_chunk **跨 chunk 保持状态**(5 个 chunk 拿到同一个
Lua 状态,表地址恒定,calls 1→5)。这是 trae 跨帧累积 tool_calls、
gemini 跨帧剥信封的前提。此前只是从池化 LIFO 复用**推断**的,现实测钉死。
③ 能力:适配器能用内核的 sha256_hex(用标准向量 sha256("abc") 校验),
AK/SK 签名不需要外部进程。
背景:内核早已实现 __llmsproxy_adapters.install(含归属校验与冲突仲裁),
但 channels 插件对该全局的引用数是 0 —— 平台协议差异此前被当成 adapter
名差异(全塞 openai)。
225 lines
8.0 KiB
Go
225 lines
8.0 KiB
Go
package lua
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// 本文件锁死"插件装的适配器能做到哪些事"——三条能力,缺任何一条,
|
||
// 为协议转换类平台(trae SOLO / gemini 信封 / codearts AK/SK 签名)
|
||
// 装专用适配器这条路就是死的。都是**实测**得出的机制事实,不是推断。
|
||
//
|
||
// 背景:参考插件每个平台一个继承宿主 LlmAdapter 的真类,自己实现推理协议;
|
||
// 我们的内核是 proxy,平台差异此前被当成 adapter 名差异(全塞 openai)。
|
||
// 内核其实早已实现 __llmsproxy_adapters.install,只是 channels 插件没用过。
|
||
|
||
// ── 能力①:适配器能拿到凭据(AK/SK 签名必需),但拿不到保险库 ──
|
||
//
|
||
// 安全边界:__llmsproxy_secrets 只注入插件 VM(plugin_bridges.go 开篇明说
|
||
// "适配器共用 setupGlobals 而不该拿到这些能力")。适配器要凭据只能走
|
||
// build_headers 的 meta.api_key —— 那是**内核按源注入**的,范围受控。
|
||
const credentialProbeAdapter = `
|
||
local adapter = {}
|
||
adapter.name = "credprobe"
|
||
adapter.version = "1"
|
||
adapter.endpoint = "/x"
|
||
function adapter.build_headers(meta)
|
||
local ak = tostring(meta.api_key or "")
|
||
return {
|
||
["X-Probe-Has-Key"] = (ak ~= "") and "yes" or "no",
|
||
["X-Probe-Key-Len"] = tostring(#ak),
|
||
["X-Probe-Secrets-Global"] = (__llmsproxy_secrets ~= nil) and "yes" or "no",
|
||
["X-Probe-Has-Url"] = tostring(meta.url or ""),
|
||
["X-Probe-Method"] = tostring(meta.method or ""),
|
||
["X-Probe-Body-Len"] = tostring(#(meta.body or "")),
|
||
}
|
||
end
|
||
return adapter
|
||
`
|
||
|
||
const credentialProbePlugin = `
|
||
local plugin = { name = "capcred", version = "1.0" }
|
||
plugin.exports = { "install" }
|
||
function plugin.install()
|
||
return { name = (__llmsproxy_adapters.install("credprobe", [==[` + credentialProbeAdapter + `]==])) }
|
||
end
|
||
return plugin
|
||
`
|
||
|
||
func TestAdapterCapabilityCredentialAccess(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
if err := ps.LoadSource("capcred", credentialProbePlugin); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := ps.Call("capcred", "install", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// 完整凭据 JSON(codearts 场景:AK/SK/ST 都在里面)
|
||
cred := `{"access_key_id":"AK","secret_access_key":"SK","security_token":"ST"}`
|
||
hdrs, err := vm.BuildHeaders("credprobe", map[string]interface{}{
|
||
"url": "https://sts.example/v1/oauth2/tokens", "method": "POST",
|
||
"body": "grant_type=x", "api_key": cred,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("BuildHeaders: %v", err)
|
||
}
|
||
if hdrs["X-Probe-Has-Key"] != "yes" {
|
||
t.Error("适配器必须能通过 meta.api_key 拿到凭据(AK/SK 签名的前提)")
|
||
}
|
||
if hdrs["X-Probe-Key-Len"] != fmt.Sprint(len(cred)) {
|
||
t.Errorf("api_key 长度 %s ≠ %d(凭据被截断?)", hdrs["X-Probe-Key-Len"], len(cred))
|
||
}
|
||
// 安全边界:适配器**不得**能读保险库
|
||
if hdrs["X-Probe-Secrets-Global"] != "no" {
|
||
t.Error("安全回归:适配器拿到了 __llmsproxy_secrets —— " +
|
||
"该全局只应注入插件 VM")
|
||
}
|
||
// build_headers 必须能拿到 url/method/body(签名要用的签名字符串原材料)
|
||
if !strings.Contains(hdrs["X-Probe-Has-Url"], "sts.example") {
|
||
t.Error("build_headers 拿不到 url(无法构造签名)")
|
||
}
|
||
if hdrs["X-Probe-Method"] != "POST" {
|
||
t.Errorf("build_headers 拿不到 method:%q", hdrs["X-Probe-Method"])
|
||
}
|
||
if hdrs["X-Probe-Body-Len"] != "12" {
|
||
t.Errorf("build_headers 拿不到 body:len=%s", hdrs["X-Probe-Body-Len"])
|
||
}
|
||
}
|
||
|
||
// ── 能力②:流式转换跨 chunk 保持状态 ──
|
||
//
|
||
// 为什么必须测:Trae 的 SOLO 转换要跨帧累积 tool_calls,gemini 要跨帧剥
|
||
// 信封。机制上 Transform 走池化 acquire(LIFO 复用),单个流是串行调用,
|
||
// 所以**应当**拿回同一个 worker —— 但那是推断,这里实测。
|
||
const streamStateAdapter = `
|
||
local adapter = {}
|
||
adapter.name = "streambuf"
|
||
adapter.version = "1"
|
||
adapter.endpoint = "/x"
|
||
local calls = 0
|
||
local acc = {}
|
||
function adapter.transform_stream_chunk(raw)
|
||
calls = calls + 1
|
||
acc[#acc + 1] = raw
|
||
return '{"calls":' .. calls .. ',"acc":' .. #acc .. ',"vm":"' .. tostring(adapter) .. '"}'
|
||
end
|
||
return adapter
|
||
`
|
||
|
||
const streamStatePlugin = `
|
||
local plugin = { name = "capstream", version = "1.0" }
|
||
plugin.exports = { "install" }
|
||
function plugin.install()
|
||
return { name = (__llmsproxy_adapters.install("streambuf", [==[` + streamStateAdapter + `]==])) }
|
||
end
|
||
return plugin
|
||
`
|
||
|
||
func TestAdapterCapabilityStreamStateAcrossChunks(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
if err := ps.LoadSource("capstream", streamStatePlugin); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := ps.Call("capstream", "install", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
var last, firstVM string
|
||
for i := 1; i <= 5; i++ {
|
||
out, err := vm.Transform("streambuf", "transform_stream_chunk",
|
||
`{"chunk":`+fmt.Sprint(i)+`}`)
|
||
if err != nil {
|
||
t.Fatalf("chunk %d: %v", i, err)
|
||
}
|
||
last = out
|
||
if i == 1 {
|
||
firstVM = out[strings.Index(out, `"vm"`):]
|
||
}
|
||
}
|
||
if !strings.Contains(last, `"calls":5`) {
|
||
t.Fatalf("跨 chunk 状态未保持:末次 %s(期望 calls:5;若为 1 则每个 chunk "+
|
||
"都拿到全新 Lua 状态,协议转换类适配器不可行)", last)
|
||
}
|
||
if !strings.Contains(last, `"acc":5`) {
|
||
t.Fatalf("累积数组未跨 chunk 保持:%s", last)
|
||
}
|
||
// 同一个 Lua 状态(表地址恒定)—— 比计数更直接的证据
|
||
if got := last[strings.Index(last, `"vm"`):]; got != firstVM {
|
||
t.Errorf("chunk 间 Lua 状态被换了:首 %s 末 %s", firstVM, got)
|
||
}
|
||
}
|
||
|
||
// ── 能力③:适配器能自带加密原语(签名/摘要)──
|
||
//
|
||
// 内核在 setupGlobals 注册 sha256_hex / hmac_sha256_hex,适配器与插件共用,
|
||
// 所以 AK/SK 签名(SDK-HMAC-SHA256)不需要外部进程。
|
||
const cryptoProbeAdapter = `
|
||
local adapter = {}
|
||
adapter.name = "cryptoprobe"
|
||
adapter.version = "1"
|
||
adapter.endpoint = "/x"
|
||
function adapter.probe()
|
||
return {
|
||
has_sha256 = (sha256_hex ~= nil) and "yes" or "no",
|
||
has_hmac = (hmac_sha256_hex ~= nil) and "yes" or "no",
|
||
-- 固定向量:sha256("abc")
|
||
sha256_abc = sha256_hex and sha256_hex("abc") or "",
|
||
hmac_ok = hmac_sha256_hex and hmac_sha256_hex("k", "v") or "",
|
||
}
|
||
end
|
||
return adapter
|
||
`
|
||
|
||
const cryptoProbePlugin = `
|
||
local plugin = { name = "capcrypto", version = "1.0" }
|
||
plugin.exports = { "install", "probe" }
|
||
function plugin.install()
|
||
return { name = (__llmsproxy_adapters.install("cryptoprobe", [==[` + cryptoProbeAdapter + `]==])) }
|
||
end
|
||
-- 探针:直接要求内核在该适配器上下文里跑一个函数是不可能的(Transform 只认
|
||
-- 那几个钩子),故用 transform_request 把 probe 结果带出来。
|
||
return plugin
|
||
`
|
||
|
||
func TestAdapterCapabilityCryptoPrimitives(t *testing.T) {
|
||
vm, ps, _ := runtimePlugins(t)
|
||
if err := ps.LoadSource("capcrypto", cryptoProbePlugin); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := ps.Call("capcrypto", "install", nil); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !vm.HasFn("cryptoprobe", "probe") {
|
||
t.Skip("适配器未导出 probe(本断言不依赖它)")
|
||
}
|
||
// 直接用 Transform 调 probe 不行(它只认 transform_* 钩子),
|
||
// 改为验证内核确实注册了两个原语:拿一个用它们的适配器做端到端。
|
||
h, err := vm.BuildHeaders("cryptoprobe", map[string]interface{}{"api_key": "k"})
|
||
if err != nil {
|
||
t.Fatalf("BuildHeaders: %v", err)
|
||
}
|
||
_ = h
|
||
// 真正的断言放在下面:装一个用 sha256_hex 的适配器并跑它的钩子
|
||
if err := vm.LoadAdapterSource("hashproof", `
|
||
local adapter = {}
|
||
adapter.name = "hashproof"
|
||
adapter.version = "1"
|
||
adapter.endpoint = "/x"
|
||
function adapter.transform_request(raw)
|
||
-- sha256("abc") 的标准值(用内核原语算;写死的是期望值)
|
||
return '{"got":"' .. sha256_hex("abc") .. '"}'
|
||
end
|
||
return adapter
|
||
`); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
out, err := vm.Transform("hashproof", "transform_request", "{}")
|
||
if err != nil {
|
||
t.Fatalf("transform_request: %v", err)
|
||
}
|
||
const wantSHA256abc = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
|
||
if !strings.Contains(out, wantSHA256abc) {
|
||
t.Fatalf("内核 sha256_hex 原语在适配器里不可用或不正确:%s", out)
|
||
}
|
||
}
|