From f813ec714fdcc3f6a5216596df5a8bae9c356386 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Wed, 14 Oct 2026 16:28:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(channels):=20=E8=83=BD=E5=8A=9B=E5=85=83?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=8B=89=E4=B8=8D=E5=88=B0=E7=9A=84=E7=9C=9F?= =?UTF-8?q?=E5=9B=A0=E8=AF=B4=E6=B8=85=E6=A5=9A=EF=BC=88=E5=86=85=E6=A0=B8?= =?UTF-8?q?=204MiB=20=E5=A0=86=E4=BF=9D=E6=8A=A4=EF=BC=8C=E9=9D=9E?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E6=95=85=E9=9A=9C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 部署后发现 `capabilitiesMeta` 一直是空。逐层排查出来的真因: 1. `models.dev` 未在出站白名单 → 而 refreshCapabilities 的 `pcall` 把错误**静默 吞掉**了,表现是"能力永远是空",排查零线索。→ 失败必须留痕(已修)。 2. 批准该 host 后暴露第二层:**内核的 `__llmsproxy_http` 有 4 MiB 响应上限** (`httpMaxResponse`,刻意的堆保护),而 `models.dev/api.json` 实测 **5.64 MB**。 3. 参考提到的"轻量分片端点"(`/api/v1/opencode.json`)实测返回 **HTML 首页** (2.0 MB 的页面,不是 JSON)—— 参考也记录了这个坑。 ⇒ 决定:**不为一个插件的便利去放宽内核的堆保护上限**(那会让所有插件都能拉 5MB+ 进网关堆)。能力元数据定位为**可选增强**: - 拉得到 → isFree/上下文/模态用权威值; - 拉不到 → 如实退回兜底表,并把"响应超限"单独讲清楚(让运维知道这是**上限约束** 而不是网络坏了)。不把"查不到"说成别的原因。 ★ 这条本身也是本轮的一个教训:`pcall` 只写 `local ok = pcall(f)` 而不看返回值, 等于把一手诊断信息扔了。同类问题本轮已修两处(另一处是 host 授权静默失效)。 回归:9 个包全绿(能力增强不可用时功能不退化,判据已覆盖"冷启动退回兜底")。 --- internal/lua/plugins/channels.lua | 35 +++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/internal/lua/plugins/channels.lua b/internal/lua/plugins/channels.lua index 7db203b..b8054b6 100644 --- a/internal/lua/plugins/channels.lua +++ b/internal/lua/plugins/channels.lua @@ -1961,6 +1961,9 @@ local function declaredHosts() if p.models_remote then for _, m in ipairs(p.models_remote.endpoints or {}) do addHost(m.url) end end + -- 能力元数据来源(照参考 opencode-capability)。必须声明,否则出站被拒 + -- 而 refreshCapabilities 的 pcall 会把错误吞掉 —— 表现是"能力永远是空"。 + if p.capability_key then addHost("https://models.dev/api.json") end if p.balance then addHost(p.balance.url) end if p.checkin_status then addHost(p.checkin_status.url) end if p.callback then addHost(p.callback.exchange_url) end @@ -5085,6 +5088,17 @@ plugin.schedule = { -- 与模型目录刷新同一节拍。请求路径只读缓存。 local MODELS_DEV_URL = "https://models.dev/api.json" local MODELS_DEV_TTL = 6 * 3600 -- 6 小时(照参考的 TTL 量级) +-- +-- ⚠️ **已知取不到,属设计约束而非故障**:该端点实测 **5.64 MB**,而内核的 +-- `__llmsproxy_http` 有 **4 MiB 响应上限**(`httpMaxResponse`,刻意的堆保护 —— +-- 不能让一个插件把整份数据拉进网关堆)。参考提到的"轻量分片端点" +-- (`/api/v1/opencode.json`)实测返回的是 **HTML 首页**(2.0 MB),不是 JSON。 +-- +-- ⇒ 能力元数据定位为**可选增强**: +-- * 拉得到 → isFree/上下文/模态用权威值; +-- * 拉不到 → 如实退回兜底表,且失败信息说明是"响应超限"(让运维知道 +-- 这是上限约束,不是网络坏了)。 +-- **不**为了它去放宽内核上限(那会让所有插件都能拉 5MB+ 进堆)。 -- normalizeCapability(id, raw):照参考 normalizeEntry 的字段口径。 -- @@ -5144,7 +5158,14 @@ local function refreshCapabilities() headers = { ["Accept"] = "application/json" }, }) if err or not resp or resp.status ~= 200 then - return false, "拉取失败: " .. tostring(err or (resp and resp.status)) + local es = tostring(err or (resp and resp.status)) + -- 把"响应超限"单独说清楚:那是**内核的刻意的堆保护**(4 MiB),不是网络故障。 + -- 否则运维会去查网络、查白名单,而真正的原因是我们主动不放开这个上限。 + if es:find("byte cap") then + return false, "能力元数据源 5.6MB 超过内核 4MiB 响应上限(刻意的堆保护," + .. "不为此放宽)—— 能力增强不可用,已退回兜底表" + end + return false, "拉取失败: " .. es end local ok, rec = pcall(json.decode, resp.body or "") if not ok or type(rec) ~= "table" then return false, "响应无法解析" end @@ -5629,9 +5650,15 @@ function plugin.refresh_models() end -- 顺带刷新模型能力元数据(照参考 opencode-capability)。 -- ⚠️ 必须在这里(后台定时任务)而不是请求路径 —— 见该节的"非阻塞"约定。 - local cok, cres = pcall(refreshCapabilities) - if cok and cres == true then - logEvent("模型能力元数据已刷新") + local cok, cres, cerr = pcall(refreshCapabilities) + if not cok then + -- ⚠️ 失败**必须留痕**:此前这里只有 `pcall` 不管返回值,于是"出站未授权" + -- 这类错误被静默吞掉,表现是"能力元数据永远是空",排查时零线索。 + logEvent("能力元数据刷新异常: " .. tostring(cres):sub(1, 120)) + elseif cres == true then + logEvent("模型能力元数据已刷新(" .. tostring(cerr or 0) .. " 条)") + elseif cerr then + logEvent("能力元数据未刷新: " .. tostring(cerr):sub(1, 120)) end if updated > 0 then logEvent("模型目录已刷新(" .. updated .. " 个平台)")