feat(channels): CodeArts 用**插件自装的专用适配器**接入(华为云 AK/SK 签名)

这是"为什么参考正常、我们不正常"的正面修复。参考每个平台是继承宿主
LlmAdapter 的真类(CodeArtsAdapter 自己做 SDK-HMAC-SHA256 签名);我们
此前把平台协议差异当成 adaptter 名差异(全塞 openai),签名无从表达。

内核其实早已实现 __llmsproxy_adapters.install(含归属校验/冲突仲裁/
测试),但 channels 插件对该全局的引用数是 **0**。本提交第一次真正用它。

改动:
1. 插件在加载末尾装自己的 codearts 适配器(CODEARTS_ADAPTER_SRC):
   build_headers 里做华为云 SDK-HMAC-SHA256 签名
   (canonical = METHOD\\nURI\\nQUERY\\nCANONICAL_HEADERS\\n\\nSIGNED_HEADERS
   \\nPAYLOAD_HASH;Access= 不是 Credential=;URI 补尾斜杠;
   Agent-Type/X-Language 签名后追加不进 canonical)
2. codearts 平台 adapter=codearts,新增 credential_shape="full"
3. syncAccountSource 支持 credential_shape:签名平台要**完整凭据 JSON**
   (适配器自己解 AK/SK),其余平台仍交裸 access token

★ 途中挖出的两个真实缺陷(都是"静默失败",没有任何报错):

① **凭据形状不匹配 → 不出源**。syncAccountSource 用 credentialOf 取
   access_token,而 codearts 凭据是 {access_key_id, secret_access_key,
   security_token} —— 没有 access_token,于是 tok 为空、函数静默 return。
   加了 credential_shape="full" 解决。

② **add_source 失败是静默的**。内核拒绝(插件无权限、形状不符)时
   syncAccountSource 不记日志,表现为"账号加了但不出源",排查无线索。
   现补上失败日志(区分内核 err 与 json.ok==false)。
   我自己就被 ① 绕了一圈:用 kernelTestCore 裸加载插件(没授 add_source
   权限)时源一直不出,直到把内核原始返回值带出来才看到
   "kernel action add_source is not granted to plugin channels"。
   ——判据里必须能看到**真实函数的返回值**,否则只能在黑盒里猜。

判据(全绿):
- TestCodeArtsAdapterInstalledByPlugin:适配器确实被装上、归属是 channels、
  且**用 Go 标准库独立复算 HMAC** 验签(形态对但算法错时形态断言全绿,
  复算才咬得住);另断言 canonical 不含 authorization/agent-type、
  URI 有尾斜杠
- TestCodeArtsSourceUsesPluginAdapter:源**真的**引用 codearts 适配器
  ("装了但源还指向 openai"是这类改动最可能的漏配,且不报错)

回归:internal/{lua,core,provider,gateway} 全绿。
This commit is contained in:
JianFeeeee
2026-10-13 23:14:04 +08:00
parent a7a0c6adee
commit 1f54143956
2 changed files with 448 additions and 8 deletions

View File

@ -0,0 +1,171 @@
package core
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"strings"
"testing"
)
// CodeArts 专用适配器是通过**插件自装**(__llmsproxy_adapters.install)
// 引入的,不是内置适配器 —— 因为 AK/SK 签名属厂商知识,内置里出现 codearts
// 就破了"内核零厂商名"纪律。
//
// 这组判据要证明三件事:
// 1. 插件加载时确实把适配器装上了
// 2. 装上之后,源能引用它(不是"装了但引用不到")
// 3. 它产出的签名是**真的能验**(而不是拼出个像签名的字符串)
func TestCodeArtsAdapterInstalledByPlugin(t *testing.T) {
c := kernelTestCore(t)
ps := c.Plugins()
src, err := readBundledChannels(t)
if err != nil {
t.Fatal(err)
}
if err := ps.LoadSource("channels", src); err != nil {
t.Fatal(err)
}
// ① 适配器必须在插件加载后就位
adapters := c.ListAdapters()
found := false
for _, a := range adapters {
if a.Name == "codearts" {
found = true
}
}
if !found {
t.Fatalf("插件未装 codearts 适配器;已装: %#v", adapters)
}
// ② 归属必须是 channels 插件(证明是它装的,不是内置)
if owner := ps.AdapterOwnerOf("codearts"); owner != "channels" {
t.Errorf("适配器归属应为 channels,实得 %q", owner)
}
// ③ 签名必须真的可验:用 Go 标准库按 SDK-HMAC-SHA256 独立复算
got, err := ps.Call("channels", "__codearts_adapter_sign_probe", map[string]interface{}{
"url": "https://snap-access.cn-north-4.myhuaweicloud.com/api/v2/chat/completions",
"method": "POST",
"body": `{"model":"CodeArts-Pangu"}`,
"api_key": `{"access_key_id":"AKTEST","secret_access_key":"SKTEST","security_token":"STTEST"}`,
})
if err != nil {
t.Fatalf("签名探针: %v", err)
}
w, _ := got.(map[string]interface{})
m, _ := w["json"].(map[string]interface{})
if m == nil {
t.Fatalf("探针无 json: %#v", got)
}
if m["error"] != nil {
t.Fatalf("探针报错: %v", m["error"])
}
auth, _ := m["authorization"].(string)
if !strings.HasPrefix(auth, "SDK-HMAC-SHA256 Access=AKTEST,SignedHeaders=") {
t.Fatalf("Authorization 形态不符(参考要求 Access= 不是 Credential=): %s", auth)
}
if !strings.Contains(auth, "Signature=") {
t.Fatalf("Authorization 缺 Signature: %s", auth)
}
// 签名后追加的头必须在,且不参与 canonical
for _, k := range []string{"Agent-Type", "X-Language"} {
if m["hdr_"+k] == nil || m["hdr_"+k] == "" {
t.Errorf("缺签名后追加的头 %s", k)
}
}
if m["hdr_Agent-Type"] != "PromptCenter" {
t.Errorf("Agent-Type = %v,应为 PromptCenter", m["hdr_Agent-Type"])
}
// canonical 不得包含签名后追加的头(参考注释点名:它们是签名后加的)
canon, _ := m["canonical"].(string)
if strings.Contains(canon, "agent-type") || strings.Contains(canon, "x-language") {
t.Errorf("canonical 不应含签名后追加的头:\n%s", canon)
}
// Authorization 不得进 canonical
if strings.Contains(canon, "authorization") {
t.Errorf("canonical 不应含 authorization:\n%s", canon)
}
// x-sdk-content-sha256 说明 payload hash 参与了签名
if !strings.Contains(canon, "x-sdk-content-sha256") {
t.Errorf("canonical 应含 x-sdk-content-sha256:\n%s", canon)
}
// URI 必须以 / 结尾(参考点名的易错点)
if !strings.Contains(canon, "/api/v2/chat/completions/\n") {
t.Errorf("canonical 的 URI 未补尾斜杠(参考点名的易错点):\n%s", canon)
}
// ④ **独立验签**:用 Go 标准库按 SDK-HMAC-SHA256 从 canonical 复算签名,
// 必须与适配器给出的 Signature 逐字节相同。
//
// 为什么必须有这一步:前面几条只验证"形态正确"(Access=、SignedHeaders、
// 尾斜杠)。签名算法本身写错(HMAC 输入少一段、canonical 分隔符用错)时
// 形态依然全对,但上游会 401。复算才是真正的判据。
sts, _ := m["string_to_sign"].(string)
if sts == "" {
t.Fatal("探针未带出 string_to_sign(判据必须比对真实函数算出的中间值)")
}
// string_to_sign = "SDK-HMAC-SHA256\n" + date + "\n" + sha256(canonical)
parts := strings.SplitN(sts, "\n", 3)
if len(parts) != 3 || parts[0] != "SDK-HMAC-SHA256" {
t.Fatalf("string_to_sign 头段不符: %q", sts)
}
canonSum := sha256.Sum256([]byte(canon))
if parts[2] != hex.EncodeToString(canonSum[:]) {
t.Errorf("string_to_sign 的 canonical 摘要不符\n算得: %s\n实得: %s",
hex.EncodeToString(canonSum[:]), parts[2])
}
mac := hmac.New(sha256.New, []byte("SKTEST"))
mac.Write([]byte(sts))
wantSig := hex.EncodeToString(mac.Sum(nil))
gotSig := auth[strings.Index(auth, "Signature=")+len("Signature="):]
if gotSig != wantSig {
t.Errorf("签名独立复算不符(算法写错?)\n复算: %s\n实得: %s", wantSig, gotSig)
}
b, _ := json.Marshal(m)
t.Logf("签名探针输出: %s", string(b))
}
// 源注册必须真的引用插件装的 codearts 适配器 —— "装了但源还指向 openai"
// 是这类改动最可能的漏配,且不会报错(只是签名不生效、上游 401)。
func TestCodeArtsSourceUsesPluginAdapter(t *testing.T) {
// ⚠️ 必须用 poolCore:它给 channels 授予了 add_source/list_sources 等
// 内核权限。用 kernelTestCore 裸加载插件时 add_source 会被拒
//("kernel action add_source is not granted to plugin channels"),
// 而这个拒绝**不报错、只是不出源** —— 我就在这里绕了一圈才看出根因。
c, ps := poolCore(t)
// 建一个 codearts 账号 → 触发源注册
m, err := ps.Call("channels", "account_add", map[string]interface{}{
"platform": "codearts",
"credential": `{"access_key_id":"AK","secret_access_key":"SK","security_token":"ST"}`,
})
if err != nil {
t.Fatalf("account_add: %v", err)
}
w, _ := m.(map[string]interface{})
j, _ := w["json"].(map[string]interface{})
if j == nil || j["error"] != nil {
t.Fatalf("account_add 报错: %#v", m)
}
aid, _ := j["account_id"].(string)
srcs := listSourcesViaKernel(t, c)
sm, ok := srcHasAccount(srcs, aid)
if !ok {
names := []string{}
for _, x := range srcs {
if n, _ := x["name"].(string); n != "" {
names = append(names, n)
}
}
t.Fatalf("codearts 账号 %q 应已同步源;现有源: %v", aid, names)
}
if ad, _ := sm["adapter"].(string); ad != "codearts" {
t.Errorf("源应引用插件装的 codearts 适配器,实得 %q(签名不会生效)", ad)
}
bu, _ := sm["base_url"].(string)
if !strings.Contains(bu, "snap-access") {
t.Errorf("base_url 不符: %q", bu)
}
}

View File

@ -52,7 +52,7 @@ plugin.kernel = { "add_source", "list_sources", "probe_all", "reset_health", "st
-- WebUI 可调用的导出函数(POST /api/plugins/channels/call/{fn})。
-- set_endpoints 让运维把渠道端点指向自建代理/镜像;测试用它打假上游。
plugin.exports = { "set_endpoints", "account_add", "account_list", "account_update", "account_delete", "account_test", "account_refresh", "account_reorder", "account_reset", "account_retest", "model_list", "model_refresh", "model_set_disabled", "model_set_disabled_many", "model_set_all_disabled", "model_clear_dead", "provider_status", "provider_set_enabled", "provider_get_order", "provider_set_order", "credits_claim_all", "credits_status", "credits_balances", "credits_permanent_lock", "backup_export", "backup_import", "backup_status", "auto_checkin", "gateway_get_enabled", "gateway_set_enabled", "usage_token_ledger", "usage_badge", "usage_badge_preference", "onboarding_status", "onboarding_claim", "__trae_derive_probe", "__codearts_sign_probe", "__qoder_machine_probe", "__qoder_refresh_probe", "__codearts_dpop_probe", "__codearts_cred_probe" }
plugin.exports = { "set_endpoints", "account_add", "account_list", "account_update", "account_delete", "account_test", "account_refresh", "account_reorder", "account_reset", "account_retest", "model_list", "model_refresh", "model_set_disabled", "model_set_disabled_many", "model_set_all_disabled", "model_clear_dead", "provider_status", "provider_set_enabled", "provider_get_order", "provider_set_order", "credits_claim_all", "credits_status", "credits_balances", "credits_permanent_lock", "backup_export", "backup_import", "backup_status", "auto_checkin", "gateway_get_enabled", "gateway_set_enabled", "usage_token_ledger", "usage_badge", "usage_badge_preference", "onboarding_status", "onboarding_claim", "__trae_derive_probe", "__codearts_sign_probe", "__qoder_machine_probe", "__qoder_refresh_probe", "__codearts_dpop_probe", "__codearts_cred_probe", "__codearts_adapter_sign_probe", "__sync_source_probe" }
@ -635,8 +635,12 @@ local PLATFORMS = {
"snap-access.cn-north-4.myhuaweicloud.com",
"devcloud.cn-north-4.huaweicloud.com", "auth.huaweicloud.com" },
-- codearts 的 chat 端点含 /api/v2 前缀(参考 CHAT_API_BASE = .../api/v2)。
base_url = "https://snap-access.cn-north-4.myhuaweicloud.com/api/v2/chat/completions",
adapter = "openai",
base_url = "https://snap-access.cn-north-4.myhuaweicloud.com",
-- 本插件**自己装的**适配器(见 installCodeartsAdapter):华为云 AK/SK 签名
adapter = "codearts",
-- 签名平台:源注册要交**完整凭据 JSON**(适配器自己解 AK/SK 签名),
-- 不是裸 access token。见 syncAccountSource 的注释。
credential_shape = "full",
models = { "CodeArts-Pangu" },
checkin = {
url = "https://snap-access.cn-north-4.myhuaweicloud.com/snap-manager/v1/statistics/plugin",
@ -2915,9 +2919,23 @@ local function syncAccountSource(provider, accountId)
local plat = platformById(provider)
local acct = poolGet(provider, accountId)
if not plat or not acct then return end
-- 凭据可能是裸 token 或完整凭据 JSON;上游源只吃 access token。
local tok = credentialOf(acct.credentialRef)
if not tok or tok == "" then return end
-- 凭据有两种消费方式,取决于平台的适配器:
-- * 多数平台:适配器发 `Authorization: Bearer <access_token>`
-- → 这里必须交出**裸 access token**(不能把整个 JSON 当 key 发上去)
-- * 签名平台(codearts):适配器在 build_headers 里**自己从完整凭据 JSON
-- 解 AK/SK 并签名** → 这里必须交出完整凭据 JSON,交出裸 token 反而
-- 让适配器签不出来(真实缺陷:codearts 账号加了却不出源,因为
-- credentialOf 找不到 access_token 而静默 return)
-- 用 plat.credential_shape 声明,默认 "token"(保持既有平台行为不变)。
local tok, err
if plat.credential_shape == "full" then
tok = __llmsproxy_secrets.get(acct.credentialRef)
err = (tok == nil or tok == "") and "无凭据" or nil
else
tok = credentialOf(acct.credentialRef)
err = (tok == nil or tok == "") and "无凭据" or nil
end
if err then return end
local name = accountSourceName(provider, accountId)
local models = {}
local disabled = disabledModelsOf(provider)
@ -2944,8 +2962,15 @@ local function syncAccountSource(provider, accountId)
if plat.endpoint_stream then src.endpoint_stream = plat.endpoint_stream end
if plat.headers_extra then src.headers = plat.headers_extra end
local r, aerr = __llmsproxy_kernel.call("add_source", { source = src })
if aerr then logEvent("接入源失败 " .. name .. ": " .. aerr)
else logEvent("已同步源 " .. name .. "(" .. #models .. " 模型)") end
-- 失败要**记日志**:源注册失败此前是静默的(插件没权限、凭据形状不符都会
-- 走到这里),表现为"账号加了但不出源",排查时没有任何线索。
if aerr then
logEvent("接入源失败 " .. name .. ": " .. tostring(aerr))
elseif r and r.json and r.json.ok == false then
logEvent("接入源失败 " .. name .. ": " .. tostring(r.json.error))
else
logEvent("已同步源 " .. name .. "(" .. #models .. " 模型)")
end
end
local function dropAccountSource(provider, accountId)
@ -6503,6 +6528,199 @@ end
-- 返回它给出的设备身份头,便于验证「成对出现」与「拿不到时降级」。
-- plugin.__codearts_dpop_probe:只给测试用 —— 生成一把 DPoP 密钥并签一个
-- 真实 JWS,供判据用标准 ES256 验签(验证 DER→raw 转换与 header/payload 形状)。
-- ============================================================================
-- CodeArts 专用适配器(插件在加载时**自己装**)
-- ============================================================================
-- 为什么需要它:CodeArts 的推理端点要 **华为云 AK/SK 签名**
-- (SDK-HMAC-SHA256),且签名必须覆盖 method/uri/query/canonical-headers/
-- payload-hash —— 这不是"改个 endpoint 或加个静态头"能表达的,通用 openai
-- 适配器做不了。
--
-- 为什么由插件装而不是做成内置适配器:CodeArts 的接入是本插件引入的能力
-- (登录 + 凭据 + 源注册都在这里),且内核纪律是"零厂商名"——内置适配器
-- 里出现 codearts 就破了这条线。插件自装正好把厂商知识留在插件域。
--
-- 与内置适配器的差别(照参考 llm-adapter.ts 的 CodeArtsAdapter):
-- * build_headers 动态签名(凭据从 meta.api_key 解出 AK/SK/ST)
-- * 端点是 /api/v2/chat/completions(含 /api/v2 前缀)
-- * Agent-Type: PromptCenter / X-Language: zh-cn(**签名后**追加,
-- 不参与 canonical —— 参考注释点名)
local CODEARTS_ADAPTER_SRC = [==[
local adapter = {}
adapter.name = "codearts"
adapter.version = "1.0.0"
-- ⚠️ 源注册时会用平台声明的 endpoint 覆盖;这里保持一致以免两处漂移。
adapter.endpoint = "/api/v2/chat/completions"
-- 华为云 SDK-HMAC-SHA256 签名。逐条对齐参考 sign.ts:signRequestHuawei:
-- canonical = METHOD\nURI\nQUERY\nCANONICAL_HEADERS\n\nSIGNED_HEADERS\nPAYLOAD_HASH
-- string_to_sign = "SDK-HMAC-SHA256\n" .. x-sdk-date .. "\n" .. sha256(canonical)
-- signature = HMAC-SHA256(SK, string_to_sign)
-- 三个易错点(参考注释都点名了):
-- 1. URI 末尾**必须补 /**(否则签名不匹配)
-- 2. Authorization 用 `Access=` 不是 `Credential=`
-- 3. Agent-Type / X-Language 是**签名后追加**的,不进 canonical
-- signed_parts:返回 (headers, canonical, string_to_sign)。
local function signed_parts(method, url, cred, body)
local ak = cred.access_key_id or cred.access_key or ""
local sk = cred.secret_access_key or cred.secret_key or ""
local st = cred.security_token or ""
if ak == "" or sk == "" then return nil, "凭据缺少 AK/SK" end
local host = url:match("^https?://([^/]+)")
local uri = url:match("^https?://[^/]+(/[^?]*)") or "/"
if uri:sub(-1) ~= "/" then uri = uri .. "/" end
local query = url:match("%?([^#]*)$") or ""
local payload = body or ""
local payload_hash = sha256_hex(payload)
local date = os.date("!%Y%m%dT%H%M%SZ")
local h = {
["host"] = host,
["x-sdk-date"] = date,
["x-sdk-content-sha256"] = payload_hash,
}
if st ~= "" then h["x-security-token"] = st end
if method:upper() ~= "GET" then h["content-type"] = "application/json" end
local keys = {}
for k in pairs(h) do keys[#keys + 1] = k end
table.sort(keys)
local lines, names = {}, {}
for _, k in ipairs(keys) do
lines[#lines + 1] = k .. ":" .. h[k]
names[#names + 1] = k
end
local signed = table.concat(names, ";")
local canonical = table.concat({
method:upper(), uri, query, table.concat(lines, "\n"), "",
signed, payload_hash,
}, "\n")
local sts = "SDK-HMAC-SHA256\n" .. date .. "\n" .. sha256_hex(canonical)
local sig = hmac_sha256_hex(sk, sts)
local out = {}
for _, k in ipairs(keys) do
if k ~= "host" then out[k] = h[k] end
end
out["Authorization"] = "SDK-HMAC-SHA256 Access=" .. ak
.. ",SignedHeaders=" .. signed .. ",Signature=" .. sig
-- 签名后追加:不参与 canonical(参考注释点名)
out["Agent-Type"] = "PromptCenter"
out["X-Language"] = "zh-cn"
return out, canonical, sts
end
-- 兼容旧调用名(build_headers 只用 headers)
local function signed_headers(method, url, cred, body)
local out = signed_parts(method, url, cred, body)
return out
end
-- sign_parts(method,url,cred,body):把签名**中间值**返回给判据用。
-- 与 build_headers 共用同一个 signed_headers(不重复实现 —— 判据必须比对
-- 真实函数算出的 canonical / string_to_sign,自算版会掩盖真实签名被改坏)。
function adapter.sign_parts(method, url, cred, body)
local out, canonical, sts = signed_parts(method, url, cred, body)
if not out then return nil, canonical end
return out, canonical, sts
end
-- build_headers:内核按源注入 meta{url,method,body,api_key,...}。
-- 凭据是完整 JSON(登录时存的就是它),从中解出 AK/SK/ST。
function adapter.build_headers(meta)
local h = { ["Content-Type"] = "application/json" }
local raw = tostring(meta.api_key or "")
local cred = nil
if raw:sub(1, 1) == "{" then
local ok, rec = pcall(json.decode, raw)
if ok and type(rec) == "table" then cred = rec end
end
if cred == nil then
-- 裸 token 形态(兼容旧数据):无法签名,如实说明
h["X-Channels-Error"] = "codearts 需要完整凭据 JSON(AK/SK),当前是裸 token"
return h
end
local signed, err = signed_headers(meta.method or "POST", meta.url or "", cred, meta.body)
if not signed then
h["X-Channels-Error"] = tostring(err)
return h
end
for k, v in pairs(signed) do h[k] = v end
return h
end
-- 请求体:CodeArts 是标准 OpenAI 兼容,只需剥内核私有字段。
function adapter.transform_request(raw_body)
local req = __prelude.decode_body(raw_body)
if req == nil then return raw_body end
__prelude.strip_control(req)
req.model = req.model or "CodeArts-Pangu"
req.stream = req.stream or false
return json.encode(req)
end
function adapter.transform_response(raw_body)
return __prelude.openai_response(raw_body)
end
function adapter.transform_stream_chunk(raw_chunk)
return __prelude.openai_stream_chunk(raw_chunk)
end
function adapter.transform_error(status, body)
return __prelude.openai_error(status, body)
end
return adapter
]==]
-- installCodeartsAdapter():装/更新本插件自己的 codearts 适配器。
-- 幂等(update 失败时退化为 install);失败只记日志,不阻断插件加载 ——
-- 适配器装不上时源注册会报"未知适配器",比让整个插件加载失败更好定位。
local function installCodeartsAdapter()
if not __llmsproxy_adapters then return false, "无适配器桥(旧内核?)" end
local _, uerr = __llmsproxy_adapters.update("codearts", CODEARTS_ADAPTER_SRC)
if uerr then
local _, ierr = __llmsproxy_adapters.install("codearts", CODEARTS_ADAPTER_SRC)
if ierr then return false, ierr end
end
return true, nil
end
-- plugin.__codearts_adapter_sign_probe:只给测试用 —— 直接调**已装的适配器**
-- 的 build_headers,把签名中间值(canonical / signed_headers)带出来,
-- 让判据能比对真实函数算出的值,而不是自己重算一遍(判据与被测对象脱节
-- 的教训:自算版会掩盖真实签名被改坏)。
function plugin.__codearts_adapter_sign_probe(args)
args = args or {}
-- 从适配器池里取 build_headers 的结果不易(插件 VM 与适配器 VM 分离),
-- 故用**同一份源码**在本 VM 里求值后调用 —— 源码是单一定义(上面那个
-- CODEARTS_ADAPTER_SRC 常量),不存在两处漂移。
local f = load(CODEARTS_ADAPTER_SRC)
if not f then return { json = { error = "适配器源码无法编译" } } end
local adapter = f()
local meta = {
url = args.url, method = args.method or "POST",
body = args.body or "", api_key = args.api_key or "",
}
local h = adapter.build_headers(meta)
local out = { authorization = h["Authorization"] }
for k, v in pairs(h) do out["hdr_" .. k] = v end
-- 中间值必须由**真实函数**带出(判据要比对它们,不自己重算)
local cred = nil
local raw = tostring(meta.api_key or "")
if raw:sub(1, 1) == "{" then
local ok2, rec = pcall(json.decode, raw)
if ok2 and type(rec) == "table" then cred = rec end
end
if cred and adapter.sign_parts then
local _, canon, sts = adapter.sign_parts(meta.method, meta.url, cred, meta.body)
out.canonical = canon
out.string_to_sign = sts
end
return { json = out }
end
-- plugin.__codearts_cred_probe:只给测试用 —— 读回最近一个 codearts 账号的
-- 凭据,用来验证**续期材料真的落盘了**(refresh_token / code_verifier /
-- DPoP 私钥 JWK)。只断言"登录返回成功"无法防止凭据被截断成裸 AK/SK。
@ -6972,4 +7190,55 @@ plugin.ui = {
},
}
-- plugin.__sync_source_probe:只给测试/诊断用 —— 手动触发一次源同步并
-- 返回内核 add_source 的原始结果。把"静默不出源"变成可观测的结果。
function plugin.__sync_source_probe(args)
args = args or {}
local plat = platformById(args.platform)
if not plat then return { json = { error = "未知平台" } } end
local acct = poolGet(args.platform, args.account_id)
if not acct then return { json = { error = "账号不存在" } } end
local raw = __llmsproxy_secrets.get(acct.credentialRef)
local tok = nil
if plat.credential_shape == "full" then tok = raw else tok = credentialOf(acct.credentialRef) end
syncAccountSource(args.platform, args.account_id)
-- 回报同步后内核里该源是否存在
local ls = __llmsproxy_kernel.call("list_sources", {})
local found, ad = false, nil
local want = accountSourceName(args.platform, args.account_id)
local arr = (ls and ls.json and ls.json.sources) or (ls and ls.sources) or {}
for _, x in ipairs(arr) do
if x.name == want then found = true ad = x.adapter end
end
-- 复现 add_source,把内核的原始错误带出来("静默不出源"最难查的原因
-- 就是没人看返回值)
local models = {}
local disabled = disabledModelsOf(args.platform)
for _, m in ipairs(effectiveModels(args.platform)) do
if not disabled[m] then models[#models+1] = { id = m, kind = "chat", priority = 20 } end
end
local test_src = {
name = want, base_url = plat.base_url, adapter = plat.adapter or "openai",
api_key = tok, max_concurrent = 2, models = models,
}
if plat.endpoint then test_src.endpoint = plat.endpoint end
local rr, rerr = __llmsproxy_kernel.call("add_source", { source = test_src })
return { json = { credential_shape = plat.credential_shape or "token",
tok_len = tok and #tok or -1,
source_name = want, found = found, adapter = ad,
adapter_decl = plat.adapter,
add_ok = rr and rr.json and rr.json.ok or false,
add_err = tostring(rerr),
add_json = rr and json.encode(rr.json or {}) or "nil",
models_n = #models } }
end
-- 装本插件自己的专用适配器。必须放在**文件末尾**(installCodeartsAdapter 是
-- local,只能从定义处向后可见 —— 本文件已因 local 前向引用踩过 7 次)。
-- 放在脚本执行末尾即可:适配器装载与源注册都发生在脚本执行完之后。
do
local ok, err = installCodeartsAdapter()
plugin._codearts_adapter = { ok = ok, err = err }
end
return plugin