@ -140,7 +140,7 @@ plugin.kernel = {
-- WebUI 可调用的导出函数( POST /api/plugins/channels/call/{fn})。
-- set_endpoints 让运维把渠道端点指向自建代理/镜像;测试用它打假上游。
plugin.exports = { " set_endpoints " , " account_add " , " account_list " , " account_update " , " account_clear_credentialless " , " account_delete " , " account_test " , " account_refresh " , " account_reorder " , " account_reset " , " account_retest " , " account_adopt " , " 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_pool_reason " , " 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 " , " __trae_solo_probe " , " __adapter_install_probe " , " __store_dir_probe " , " __set_loopback_probe " , " reap_loopbacks " , " reap_orphan_sources " , " __add_source_for_test " , " __forget_account_probe " , " __set_pool_cred_probe " , " __cred_headers_probe " , " __b64_probe " , " __codearts_jwk_pem_probe " , " __platform_def_probe " , " __zcode_diag_probe " , " __trae_entry_probe " , " __list_sources_probe " , " __clear_priorities_probe " , " __resync_sources_probe " , " __utf8_clip_probe " , " __event_log_probe " , " __is_model_gone_probe " , " __rank_accounts_probe " , " __chosen_account_probe " , " __inject_refresh_platform_probe " , " __clear_capabilities_probe " , " __qoder_claim_outcome_probe " , " __is_free_probe " , " __claim_covers_probe " , " __schedule_probe " , " __parse_capability_probe " , " __inject_capability_probe " , " __ttl_reset_probe " , " __ttl_probe " , " __ttl_invalidate_probe " , " __serial_probe " , " __serial_release_probe " , " __set_legacy_cred_probe " , " __read_cred_probe " , " __clear_all_creds_probe " , " credits_tier_probe " , " __set_lock_permanent_probe " , " __set_expiring_window_probe " , " __expiring_window_probe " , " __record_dead_probe " , " __catalog_gate_probe " , " __catalog_gate_fail_probe " , " credits_capability " , " claims_capable " }
plugin.exports = { " set_endpoints " , " account_add " , " account_list " , " account_update " , " account_clear_credentialless " , " account_delete " , " account_test " , " account_refresh " , " account_reorder " , " account_reset " , " account_retest " , " account_adopt " , " 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_pool_reason " , " 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 " , " __trae_solo_probe " , " __adapter_install_probe " , " __store_dir_probe " , " __set_loopback_probe " , " reap_loopbacks " , " reap_orphan_sources " , " __add_source_for_test " , " __forget_account_probe " , " __set_pool_cred_probe " , " __cred_headers_probe " , " __b64_probe " , " __codearts_jwk_pem_probe " , " __platform_def_probe " , " __zcode_diag_probe " , " __trae_entry_probe " , " __trae_catalog_probe " , " __trae_headers_probe " , " __list_sources_probe " , " __clear_priorities_probe " , " __resync_sources_probe " , " __utf8_clip_probe " , " __event_log_probe " , " __is_model_gone_probe " , " __rank_accounts_probe " , " __chosen_account_probe " , " __inject_refresh_platform_probe " , " __clear_capabilities_probe " , " __qoder_claim_outcome_probe " , " __is_free_probe " , " __claim_covers_probe " , " __schedule_probe " , " __parse_capability_probe " , " __inject_capability_probe " , " __ttl_reset_probe " , " __ttl_probe " , " __ttl_invalidate_probe " , " __serial_probe " , " __serial_release_probe " , " __set_legacy_cred_probe " , " __read_cred_probe " , " __clear_all_creds_probe " , " credits_tier_probe " , " __set_lock_permanent_probe " , " __set_expiring_window_probe " , " __expiring_window_probe " , " __record_dead_probe " , " __catalog_gate_probe " , " __catalog_gate_fail_probe " , " credits_capability " , " claims_capable " }
@ -197,7 +197,7 @@ local pickAccountForSource, accountBalanceRaw, balanceTier
-- ⇒ 上游回「not able to authenticate you」; 而同一账号的**签到领取是成功的**
-- ( claim_fn 那条路同样拿到 nil, 但它内部有 Bearer 兜底且 claim 端点接受)。
-- ⇒ 教训: Lua 的 local 只对**其后的**代码可见;闭包不是延迟绑定的魔法。
local traeCheckinHeaders , traeFixNickname
local traeCheckinHeaders , traeFixNickname , traeIdeHeaders
local traeCallback , traeDeriveDeviceId15 , traeDeriveMarketUserId , traeDeriveSessionId
local PLATFORMS = {
@ -273,6 +273,20 @@ local PLATFORMS = {
} } ,
auth = " none " , -- 鉴权头由 headers_fn 负责
parse = " trae_batch " ,
-- ★★ 只收**适配器真正会发的那条通道**里的模型。
--
-- 实测( 2026-10-20, 真实目录 22 个 function / 93 条去重模型):
-- 同一个 config_name 在不同 function 里的**可用性不同**,而本插件的
-- trae 适配器只会发 `solo_work_lite`(见 TRAE_FUNCTION) 。把别的通道
-- 的模型也列出来,用户选中后上游要么回**空内容**( kimi-k2.8-preview /
-- deepseek-V3 / glm-4.7 / code-review-judge 实测皆空),要么**悄悄回落到
-- 默认模型**( custom_model_gpt-5 自称 GPT-4、sagitta 自称 Claude ——
-- 这正是"模型不正确"的真实来源)。
--
-- 收窄到 solo_work_lite 后: 20 个模型,逐个实测都能返回内容且确实是
-- 它自称的那个模型( glm-5.3 / kimi-k3 / minimax-m3.1-flash / qwen3.8-max /
-- step-5-preview / deepseek-v4.1-flash …)。
admit_functions = { " solo_work_lite " } ,
} ,
-- trae 非 OpenAI 兼容:端点是 SOLO 的 /api/agent/v3/llm_utils_chat,
-- 载荷须转 config_name/function 格式(见 trae-adapter.ts:374) 。
@ -291,14 +305,31 @@ local PLATFORMS = {
" Doubao-Seed-2.1-Turbo " , " Doubao-Seed-2.0-Code " , " glm-5.2 " , " glm-5-turbo " ,
" glm-5 " , " DeepSeek-V4-Pro " , " DeepSeek-V4-Flash " , " kimi-k3 " , " kimi-k2.7-code " ,
" kimi-k2.6 " , " minimax-m3 " , " qwen-3.7-plus " , " sagitta " , " aquila " ,
} ,
-- ★ custom_model_* 是 TRAE 的「自定义模型槽位」,**不是可调用的模型**:
-- 上游目录里它们的判据是 `is_custom_model: true`(本插件的 trae_batch
-- 解析器早就按这条**准入**剔除),但兜底表此前把它们当普通模型列出来 ——
-- 于是远端目录取不到时( 企业租户账号实测: 22 个 function 全回
-- `2001 Config detail ... not found` ⇒ 目录为空),用户在模型选择器里
-- 能看到这些条目,选中后上游回空内容(实测 custom_model_kimi /
-- custom_model_claude / custom_model_1M 均返回空 content) ——
-- 表现就是「模型不正确 / 渠道不可用」。
-- 它们与内部子 agent 同性质:保留在目录里(排查可见),但不进可选列表。
-- ★ 远端目录一旦可用,就**不再补兜底表**( 参考语义: remote ?? fallback) 。
-- 兜底表里的名字是历史快照,其中有若干**上游已不存在**的条目
-- (实测 sagitta 自称 Claude、aquila 自称 ChatGPT、glm-5 / DeepSeek-V4-Flash
-- 在 solo_work_lite 通道里根本没有配置)—— 补进来 = 给用户一堆"点了回错模型"
-- 的选项。仅在远端目录为空时才用兜底表(那才是它的用途)。
models_fallback_no_supplement = true ,
models_hidden = {
" browser_use_subagent " , " explore_sub_agent_v13 " , " explore_sub_agent_v2 " , " summary " ,
} ,
models_unavailable = {
" custom_model_gemini " , " custom_model_placeholder " , " custom_model_1M_text " ,
" custom_model_1M " , " custom_model_kimi " , " custom_model_claude " ,
" custom_model_gpt-5 " , " custom_model_no-fc " , " custom_model_deepseek_chat " ,
" custom_model_deepseek_reasoner " , " custom_model_deepseek_v4 " ,
} ,
models_hidden = {
" browser_use_subagent " , " explore_sub_agent_v13 " , " explore_sub_agent_v2 " , " summary " ,
} ,
-- 每日签到: POST /trae/api/v2/ug/checkin_credits/claim body {} 到 UG 主机。
checkin = {
url = " https://api.trae.cn/trae/api/v2/ug/checkin_credits/claim " ,
@ -964,19 +995,27 @@ local PLATFORMS = {
-- * 新增 beam-free / nemotron-3.5-super-vl-free / jev-1.13-free /
-- glm-5.3-flash-free / minimax-m3.1-flash-free
models = {
" big-pickle " , " beam-free " , " longcat-2.5-preview-free" ,
" big-pickle " , " longcat-2.5-preview-free " ,
" mimo-v2.6-flash-free " , " nemotron-3.5-super-vl-free " ,
" nemotron-3.5-lightning-free " , " jev-1.13-free " , " glm-5.3-flash-free " ,
" nemotron-3.5-lightning-free " , " glm-5.3-flash-free " ,
" minimax-m3.1-flash-free " , " deepseek-v4.1-flash-free " ,
" deepseek-v4-flash " , " deepseek-v4.1-flash " ,
" glm-5.2 " , " kimi-k2.5 " , " minimax-m2.7 " , " minimax-m3 " , " qwen3.8-max " ,
} ,
models_free = {
" big-pickle " , " beam-free " , " longcat-2.5-preview-free" ,
" big-pickle " , " longcat-2.5-preview-free " ,
" mimo-v2.6-flash-free " , " nemotron-3.5-super-vl-free " ,
" nemotron-3.5-lightning-free " , " jev-1.13-free " ,
" nemotron-3.5-lightning-free " ,
" glm-5.3-flash-free " , " minimax-m3.1-flash-free " , " deepseek-v4.1-flash-free " ,
} ,
-- ★ 目录里有、但**本协议下不可调用**的免费模型( 2026-10-20 逐个实测):
-- * jev-1.13-free → 400 "Model does not support this protocol."
-- (它不是 OpenAI chat 协议,用 /v1/chat/completions 必失败)
-- * beam-free → 200 但 content 为空、无 finish_reason( 上游未真正产出)
-- 二者若留在可选列表,用户选中后只会看到"空回复/报错" ⇒ 判为不可用,
-- 既不进 UI 列表, 也不进上游源( models_unavailable 与 hidden 的区别见
-- effectiveModels 的注释)。
models_unavailable = { " jev-1.13-free " , " beam-free " } ,
} ,
-- CodeArts( 华为) —— 凭据 + SDK-HMAC-SHA256 签到(照参考 codearts-credits.ts) 。
@ -3735,6 +3774,12 @@ local deadModelIds
local function effectiveModels ( provider )
local plat = platformById ( provider )
if not plat then return { } end
-- models_unavailable: **声明上就不可调用**的条目(不是"暂不显示")。
-- 与 models_hidden 的区别: hidden 只是不进 UI 列表(内部子 agent 仍可被
-- 显式调用) , unavailable 连**上游源**都不进 —— 因为它们调用必然失败/回空,
-- 进源只会让客户端选中后报错( trae 的 custom_model_* 槽位就是这类)。
local unavailable = { }
for _ , m in ipairs ( plat.models_unavailable or { } ) do unavailable [ m ] = true end
local remote = plugin.state . remoteModels and plugin.state . remoteModels [ provider ]
local out , seen = { } , { }
-- 远端在前(权威顺序)
@ -3745,9 +3790,15 @@ local function effectiveModels(provider)
end
end
end
-- 兜底补位(只补远端没有的)
for _ , id in ipairs ( plat.models or { } ) do
if not seen [ id ] then seen [ id ] = true out [ # out + 1 ] = id end
-- 兜底补位(只补远端没有的)。
-- ⚠️ plat.models_fallback_no_supplement=true 的平台**不补**:远端目录可用时
-- 它就是权威,兜底表只是"拉不到时的替代",补进来会把已下架/错映射的名字
-- 重新塞进列表(见该字段注释里的实测)。
local haveRemote = type ( remote ) == " table " and type ( remote.ids ) == " table " and # remote.ids > 0
if not ( plat.models_fallback_no_supplement and haveRemote ) then
for _ , id in ipairs ( plat.models or { } ) do
if not seen [ id ] then seen [ id ] = true out [ # out + 1 ] = id end
end
end
-- 剔除**有阳性证据**已失效的模型(照参考 dead-model-store 的剔除语义)。
-- ⚠️ 只剔这里记录过的(真正 404 过的),**绝不**拿远端目录反推下架 ——
@ -3755,7 +3806,7 @@ local function effectiveModels(provider)
local dead = deadModelIds ( provider )
local kept = { }
for _ , id in ipairs ( out ) do
if not dead [ id ] then kept [ # kept + 1 ] = id end
if not dead [ id ] and not unavailable [ id ] then kept [ # kept + 1 ] = id end
end
return kept
end
@ -4056,6 +4107,18 @@ local function parseRemoteModels(kind, body, provider)
if type ( g ) == " table " then
local chan = rstr ( g , " function " , " Function " )
-- 准入:未声明 function 或不在白名单 → 整组丢弃
-- ★ 追加一道「本适配器可发通道」闸门(声明式,见 models_remote.admit_functions) :
-- 白名单只说明"这个通道在本适配器的推理端点下可调用",不代表
-- **适配器会发它** —— trae 适配器恒发 solo_work_lite, 别的通道的
-- 模型即使可调用,经我们这条路发出去也会空回/回落默认模型。
local admitted = nil
do
local pl = platformById ( provider )
local mr2 = pl and pl.models_remote
local af = mr2 and mr2.admit_functions
if type ( af ) == " table " then admitted = { } for _ , f in ipairs ( af ) do admitted [ f ] = true end end
end
if admitted and chan and not admitted [ chan ] then chan = nil end
if chan and rankOf [ chan ] then
local list = g.config_info_list or g.ConfigInfoList
if type ( list ) == " table " then
@ -4161,6 +4224,9 @@ end
local function refreshRemoteModels ( provider )
local plat = platformById ( provider )
if not plat then return nil , " 未知平台 " end
-- 最近一次目录响应的**原始体**:解析失败时用来区分"网络/HTTP 故障"与
-- "服务端 200 但账号侧没有可下发配置"(后者文案完全不同,见函数尾部)。
local lastBody = nil
local mr = plat.models_remote
if not mr then return nil , " 该平台无远端目录端点 " end
-- 取一个可用账号的凭据(无账号则无凭据,仅当 auth=none 时仍可拉)
@ -4199,10 +4265,10 @@ local function refreshRemoteModels(provider)
local req = { url = url , method = method , headers = headers }
if ep.body then req.body = ep.body end
if ep.sign == " trae_solo " then
-- TRAE 的鉴权是 Cloud-IDE-JWT + 约 20 个客户端头(含按 user_id 派生的
-- 设备身份)—— 不能只发 Bearer。这里复用**平台既有的 ** traeCheckinHeaders
-- 实现(它已经过实测:简化头会被拒),不另写一套头部构造 ,
-- 否则“chat 能通、目录 401”这类差异极难查 。
-- TRAE 的鉴权是 Cloud-IDE-JWT + 一组客户端头 —— 不能只发 Bearer。
-- ⚠️ 必须用 **IDE 头 **( traeIdeHeaders) , 不能用签到头:
-- 签到头是 `api.trae.cn` 的 UG/积分形态( VSCode UA + X-Market-*) ,
-- 发到 IDE 目录端点会被逐 function 判 2001( 见 traeIdeHeaders 的注释) 。
local cred
for _ , a in ipairs ( poolList ( provider ) ) do
if a.enabled ~= false then
@ -4215,11 +4281,13 @@ local function refreshRemoteModels(provider)
end
if not cred then
lastErr = " 无可用 TRAE 凭据 "
elseif type ( traeCheckin Headers ) ~= " function " then
lastErr = " traeCheckin Headers 未就绪(前向引用顺序问题) "
elseif type ( traeIde Headers ) ~= " function " then
lastErr = " traeIde Headers 未就绪(前向引用顺序问题) "
else
local uid = cred.uid or " "
local h , herr = traeCheckinHeaders ( plat , cred , uid )
-- ★ 目录是 **IDE 端点**,必须用 IDE 头(不是签到头)。
-- 2026-10-20 事故:这里曾复用 traeCheckinHeaders ⇒ 22 个 function 全回
-- 2001 "Config detail ... not found" ⇒ 永远拉不到目录。详见 traeIdeHeaders。
local h , herr = traeIdeHeaders ( plat , cred , " application/json " )
if herr then
lastErr = tostring ( herr )
else
@ -4256,12 +4324,30 @@ local function refreshRemoteModels(provider)
if err or not resp or resp.status ~= 200 then
lastErr = ( err or ( " HTTP " .. tostring ( resp and resp.status ) ) )
else
lastBody = resp.body or " "
for _ , id in ipairs ( parseRemoteModels ( ep.parse or mr.parse , resp.body , provider ) ) do
if not seen [ id ] then seen [ id ] = true merged [ # merged + 1 ] = id end
end
end
end
if # merged == 0 then return nil , lastErr or " 远端返回空目录 " end
if # merged == 0 then
-- ★ 把"空目录"再细分一层:远程 200 但一条配置都没有,通常不是网络问题,
-- 而是**账号侧**没有可下发的 function 配置( 企业租户账号实测: 22 个
-- function 全回 error_code 2001 "Config detail for function: X not found")。
-- 这两类原因的处置完全不同(网络重试 vs 找租户管理员),日志不能都说
-- "远端返回空目录"。判据只看响应体里出现 2001 或 function_configs。
if not lastErr then
local seenBody = false
if type ( lastBody ) == " string " and lastBody : find ( " function_configs " , 1 , true ) then
seenBody = true
end
if seenBody then
return nil , " 远端 200 但无模型配置(账号侧未下发 function config, "
.. " 企业/租户账号需管理员开启,实测 error_code 2001) "
end
end
return nil , lastErr or " 远端返回空目录 "
end
if not plugin.state . remoteModels then plugin.state . remoteModels = { } end
plugin.state . remoteModels [ provider ] = {
ids = merged ,
@ -7184,6 +7270,13 @@ local DEAD_GONE_PATTERNS = {
" unknown%s+model " ,
" no%s+such%s+model " ,
" nonexistent%s+model " ,
-- ★ opencode zen 的下架文案( 2026-10-20 实测):
-- 410 `Model space-bunny-free has been deprecated. Use minimax-m3.1-flash-free instead.`
-- 此前不识别 ⇒ 该模型一直留在源里,用户选中必失败("渠道不可用"观感)。
-- 带 "has been deprecated" 整句是为了不误伤「参数 X 已弃用」这类**非模型**文案:
-- "Model ... has been deprecated" 的主语必须是 Model( %s+ 吃掉名字)。
" model%s+%S+%s+has%s+been%s+deprecated " ,
" model%s+has%s+been%s+deprecated " ,
}
-- 反向模式:命中即**不是**下架(照参考 NOT_GONE_PATTERNS) 。
@ -8089,6 +8182,7 @@ function plugin.model_list(req)
-- models_hidden: 内部子 agent( 照参考 isHidden 语义)不进用户可选列表。
local hidden = { }
for _ , m in ipairs ( plat.models_hidden or { } ) do hidden [ m ] = true end
for _ , m in ipairs ( plat.models_unavailable or { } ) do hidden [ m ] = true end
-- models_free: 参考的 isFree 字段(官方语义:匿名/免费可用)。
local free = { }
for _ , m in ipairs ( plat.models_free or { } ) do free [ m ] = true end
@ -9749,6 +9843,52 @@ traeCheckinHeaders = function(plat, cred, uid)
return h , nil
end
-- traeIdeHeaders(plat, cred, accept): **IDE/Agent 端点**的头(对齐参考 traeSOLOHeaders) 。
--
-- ⚠️ 与 traeCheckinHeaders 是**两套不同的头**,不能互相替代:
-- - traeCheckinHeaders 面向 `api.trae.cn/trae/api/v2/ug|pay`(签到/积分):
-- VSCode 形态的 UA + X-Market-* + X-Lscbd-* + Vscode-Sessionid,
-- 设备身份用**派生**的 market device id。
-- - traeIdeHeaders 面向 `trae-api-cn.mchost.guru/api/...`(模型目录、对话):
-- Trae/0.1.52 UA + X-App-Id + X-Ide-Version(*) + Request-Traffic-Type,
-- 设备身份用**凭据里登录时的** device_id / machine_id。
--
-- 2026-10-20 真实事故:目录刷新复用了 traeCheckinHeaders, 上游对 22 个 function
-- **全部**回 `error_code 2001 Config detail for function: X not found`( HTTP 200) ,
-- 于是插件永远拿不到远端目录、只剩兜底表 —— 用 IDE 头直发同一端点,返回
-- 2.6 MB、每个 function 都带 config_info_list。症状( "拉不到上游目录")与真因
-- (头用错了)之间隔着整整一层协议,这就是必须分开写、并在注释里点明的原因。
traeIdeHeaders = function ( plat , cred , accept )
local tok = tostring ( ( cred and cred.access_token ) or " " )
if tok == " " then return nil , " 缺少 access_token " end
local h = {
[ " Content-Type " ] = " application/json " ,
[ " Accept " ] = accept or " application/json " ,
[ " User-Agent " ] = ( plat and plat.user_agent ) or " Trae/0.1.52 " ,
[ " Authorization " ] = " Cloud-IDE-JWT " .. tok ,
[ " X-Cloudide-Token " ] = tok ,
[ " X-Ide-Token " ] = tok ,
[ " X-App-Id " ] = ( plat and plat.app_id ) or " 6eefa01c-1036-4c7e-9ca5-d891f63bfcd8 " ,
[ " X-App-Version " ] = " default " ,
[ " X-Ide-Version " ] = ( plat and plat.ide_version ) or " 0.1.52 " ,
[ " X-Ide-Version-Code " ] = ( plat and plat.ide_version_code ) or " 20260811 " ,
[ " X-App-Version-Code " ] = ( plat and plat.ide_version_code ) or " 20260811 " ,
[ " X-Ide-Version-Type " ] = " stable " ,
[ " X-Device-Type " ] = ( plat and plat.device_type ) or " macos " ,
[ " X-OS-Version " ] = ( plat and plat.os_version ) or " macOS 15.7.4 " ,
[ " X-Device-Brand " ] = ( plat and plat.device_brand ) or " Apple " ,
[ " Request-Traffic-Type " ] = " prod " ,
}
local uid = tostring ( ( cred and cred.uid ) or " " )
if uid ~= " " then h [ " X-Uid " ] = uid end
-- ★ 设备身份只认凭据里登录时落下的那两个值;不派生、不造。
local mid = tostring ( ( cred and cred.machine_id ) or " " )
local did = tostring ( ( cred and cred.device_id ) or " " )
if mid ~= " " then h [ " X-Machine-Id " ] = mid end
if did ~= " " then h [ " X-Device-Id " ] = did end
return h , nil
end
-- 表驱动签到推广到需自定义头的平台:在 PLATFORMS 定义后绑定。
--
-- credentialRef 是「秘密引用」, creds 是插件自解密后的 JSON( 最可靠) 。
@ -12986,6 +13126,94 @@ function plugin.__list_sources_probe()
return { json = { sources = out } }
end
-- plugin.__trae_headers_probe: 只给判据用 —— 返回 trae 两套头各自的键集合。
--
-- 存在理由: 2026-10-20 事故是「目录刷新用了签到头( traeCheckinHeaders) 而不是
-- IDE 头( traeIdeHeaders) 」, 上游因此对每个 function 回 2001、目录恒空。
-- 这个错误**在代码里看不出来**(两套头都"能跑"),只能靠契约钉死:
-- - 签到头面向 api.trae.cn 的 UG/积分:必须有 X-Market-Client-Id / X-Lscbd-*,
-- **不得**有 X-App-Id / X-Machine-Id( 那是 IDE 端的身份);
-- - IDE 头面向 trae-api-cn.mchost.guru: 必须有 X-App-Id / X-Ide-Version /
-- X-Machine-Id, **不得**有 X-Market-Client-Id。
-- 判据比对键集合即可,不需要真实网络。
function plugin . __trae_headers_probe ( args )
local plat = platformById ( " trae " )
local cred = {
access_token = " jwt-test " , uid = " 1000 " ,
device_id = " dev-32 " , machine_id = " mid-32 " ,
}
local ide = traeIdeHeaders ( plat , cred , " application/json " )
local chk = traeCheckinHeaders ( plat , cred , cred.uid )
local function keyset ( h )
local out = { }
for k in pairs ( h or { } ) do out [ # out + 1 ] = k end
table.sort ( out )
return out
end
return { json = { ide = keyset ( ide ) , checkin = keyset ( chk ) } }
end
-- plugin.__trae_catalog_probe: 只给诊断用 —— 原样回传 TRAE 远端模型目录的
-- HTTP 状态与前 1200 字节响应体。
--
-- 存在理由:`refresh_models` 只把「解析出几条」汇总成「远端返回空目录」,
-- 看不到**上游到底回了什么**(空数组?错误信封?形状变了?)。模型目录决定
-- 用户在渠道页能选哪些模型,它是"渠道不可用"类报障的第一现场,不能只有一句
-- 二手结论。探测逻辑与 refreshRemoteModels 的 trae 分支**同源**(同一个
-- traeCheckinHeaders + 同一份请求体),避免"探针通、真路径不通"。
function plugin . __trae_catalog_probe ( args )
args = args or { }
local plat = platformById ( " trae " )
if not plat or not plat.models_remote then
return { json = { error = " trae 平台未声明 models_remote " } }
end
local ep = ( plat.models_remote . endpoints or { } ) [ 1 ] or { }
local cred
for _ , a in ipairs ( poolList ( " trae " ) ) do
if a.enabled ~= false then
local raw = __llmsproxy_secrets.get ( a.credentialRef )
local okj , cj = pcall ( json.decode , raw or " " )
if okj and type ( cj ) == " table " and cj.access_token then cred = cj break end
end
end
if not cred then return { json = { error = " 无可用 TRAE 凭据 " } } end
if type ( traeCheckinHeaders ) ~= " function " then
return { json = { error = " traeCheckinHeaders 未就绪 " } }
end
local h , herr = traeCheckinHeaders ( plat , cred , cred.uid or " " )
if herr then return { json = { error = " 构造头部失败: " .. tostring ( herr ) } } end
-- 诊断用覆盖: Accept 在参考里对目录请求是 application/json, 而 checkin 的
-- 头部构造给的是 text/event-stream —— 用它可以判定"是不是 Accept 导致空目录"。
if type ( args.accept ) == " string " and args.accept ~= " " then
h [ " Accept " ] = args.accept
end
-- 诊断用:额外头部(企业/租户类账号的 IDE 端点通常要求租户身份,
-- 而参考的 traeSOLOHeaders 不发。用它验证"缺租户头导致全 2001")。
if type ( args.headers ) == " table " then
for k , v in pairs ( args.headers ) do
if type ( k ) == " string " and type ( v ) == " string " then h [ k ] = v end
end
end
if type ( args.ide_version ) == " string " and args.ide_version ~= " " then
h [ " X-Ide-Version " ] = args.ide_version
h [ " X-App-Version-Code " ] = args.ide_version
h [ " X-Ide-Version-Code " ] = args.ide_version
end
local resp , err = __llmsproxy_http ( {
url = ( type ( args.url ) == " string " and args.url ~= " " and args.url ) or ep.url ,
method = ep.method or " POST " ,
headers = h , body = ( type ( args.body ) == " string " and args.body ~= " " and args.body ) or ep.body or " {} " ,
} )
if err then return { json = { error = tostring ( err ) } } end
local body = ( resp and resp.body ) or " "
return { json = {
status = resp and resp.status or 0 ,
bytes = # body ,
head = body : sub ( 1 , math.min ( tonumber ( args.clip ) or 1200 , # body ) ) ,
parsed = # parseRemoteModels ( " trae_batch " , body , " trae " ) ,
} }
end
-- plugin.__trae_entry_probe: 只给测试/诊断用 —— 报告某模型最终选中条目的关键字段。
-- “合并规则选错了条目”( 空档位覆盖有档位, issue IKI7WT/IKILR7) 这类问题
-- 没有它只能靠猜。