| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(lua): 插件 Lua 侧适配器桥接(R4 Lua 绑定)+ 真二进制适配器接手 e2e 补上两个此前报告为"缺口"的东西。其中第一个的旧结论是**错的**,这里更正。 ## R4 Lua 绑定:__llmsproxy_adapters 用户问的"为什么 Lua 绑不上"—— 结论是**能绑,之前的归因错了**。 旧结论是「NewTable + PushGoClosure + SetField 是 golua 雷区,会 SIGSEGV」。 反证:内核自己的 buildJSONTable 用的就是这同一个模式,长期稳定。真正的变量 是当时 Phase 4 的**双 VM 半成品**(钩子一个 VM、定时任务一个私有 VM), bootPluginState 签名与调用点不一致,我改到一半编译不过就仓促回滚,留下的 中间态让 billing 一跑就崩。把双 VM 改回单 VM 之后,同一模式一次就通。 顺带回答一个备选方案(用户提出:Lua 只投递请求、Go 串行执行):不需要。 p.mu 已经天然串行化同一插件的所有入口;再加队列只会引入"谁排空/超时后积压 怎么算"的新问题。而且桥接调用本身是微秒级的(交给 Go 重建一个小池),真正 长耗时的是插件自己去取 token 的 HTTP 请求,那跟桥接无关。 能力(Go 侧强制,非约定): - install(name, source) → (生效名, nil) | (nil, err) - update(name, source) → (true, nil) | (nil, err) 只限自己装的 - remove(name) → (true, nil) | (nil, err) 只限自己装的 - 调用身份是插件**启动时的固定名**,不从 Lua 状态现读 ⇒ 无法冒充别的插件 - 不能影子内置(Lua 侧同样被拒,不是绕过 Go 校验的后门) - 出错返回 (nil, err) 而非抛异常,与钩子/HTTP 处理器同一套约定 **分片 VM 故意不给**:分片是文档化的「只读、可丢弃」工作单元,而适配器 install 是**全局**副作用(改共享注册表)。给分片等于让"只读"的按需调用悄悄 改写所有请求共用的适配器 —— 正是分片契约要防的不可见变更。 测试 6 个:install/rotate/remove 从 Lua 调用、影子内置被拒、**定时任务里 轮换 token 且请求钩子立即可见**(R4 闭环)、分片无桥接。 ## 真二进制 e2e:适配器接手对外服务 用户问到的第二个点。此前 /v1/messages 的 Anthropic 接管只在**进程内** gateway 层测过(internal/gateway/messages_test.go),绕过了只会在真实进程里 发生的事:适配器磁盘 seed、配置加载、真 HTTP 栈。 新增 e2e/adapter_takeover_test.go,打**真编译的二进制** + 只说 Anthropic 的 假上游(该上游对不符形状的请求主动报错,不做宽容接受): - 非流式:出向必须是 Anthropic message 形状,不能有 OpenAI choices 外壳 - 流式:必须是 message_start…message_stop 序列,**不得**出现 [DONE] - 压力:96 并发(48 流式)混合打真二进制,全部 200,无 5xx,无形状串味 压力测试初版把并发上限设成 4 于是大量 429,我把断言改正为:429 是内核**正确** 的容量背压,不算失败;失败的定义是 5xx 或 200 但内容/形状错。现在 96/96 通过。 | 11 天前 | |
test(e2e): channels device-poll 真二进制端到端 + 修正插件 HTTP 前缀 TestChannelsDevicePollE2E 用真二进制跑通完整链路(对齐参考插件形态的终局验收): 假平台上游(设备码/token/模型/对话端点) → 真网关进程 + plugin_dir(SeedBundled 落地 channels) → PUT /api/plugins/channels/kernel 授权(运维批准动作面) → call/set_endpoints 把 minimax 端点指到假上游(自建代理能力复用) → POST /api/plugins/http/channels/login 发起 device-poll → GET .../login/status 轮询:先 pending(authorization_pending), 后 completed(token 下发)且 account 取 nickname → 终局证据:/v1/models 出现 channels-minimax 的模型 MiniMax-M3 调试中抓到并修复:channels.lua 三处旧挂载前缀 /api/plugins/channels/http/ (正确为 /api/plugins/http/,spawnBrowser 回调路径、callback redirect_uri、 UI fetch 基址)—— UI 上点登录/回调一直会 404。 顺带:e2e 就绪探针复用 startGateway(无源时 /v1/models 空列表也 200)。 | 10 天前 | |
chore(main): 回流 release/v2.0.x(v2.0.12)—— 内容对齐,main 无独有内容 按发布纪律回流。**注意这是内容对齐而非 merge**,理由与依据: 1. 纪律禁止 merge 回主干,因为 merge 会把发布分支的**版本号变更**带进 main。 本项目版本号**只由 git describe --tags 推导**(Makefile VERSION), 仓库里没有任何文件存放版本号 ⇒ 回流不存在版本污染风险。 2. 逐项核对确认 **main 是 release 的内容严格子集**: - main 独有的**文件**:0 个; - internal/lua/plugins.go / internal/core/kernel_actions.go 等内核文件 main 独有行数:0; - deploy.sh 那 14 行只是 emoji 清理(release 刻意去掉了 emoji); - channels.lua 那 333 行是**旧版内容**(旧 exports 表、旧模型名单)。 - main 独有的 9 个提交按 patch-id **全部已在 release 中等效存在** (git cherry 逐条列出均为 -,即已合入)。 3. 故回流 = 把 release 的内容带到 main,等价于"只捡拾修复、不带版本号", 且不会丢任何 main 独有的修复。 本次带回 v2.0.12 的全部内容(37 个提交): - 源串行 + 按模型节流(照 model-gate) - 已失效模型存储 + 远端目录闸门 - 余额分档选号 - 模型能力元数据(models.dev,isFree 权威判定) - **凭据续期修复**(此前恒报 ok:true 却一个请求都不发) - 通用件 TTL 缓存 + 串行锁;account_test/account_adopt/account_delete 修正 - 内核级:出站授权持久化重启后不生效、__llmsproxy_plugin_dir ground truth 校验:git diff release/v2.0.x = 0 行;go build -tags luajit ./... 通过。 | 8 天前 | |
feat(plugins): 服务路径/适配器名冲突进裁决而非崩溃或硬拒绝(用户要求 A+B) 此前两类冲突各有问题: A) 两个插件声明同一条 /api/plugins/http/ 服务路径 → 分发器按加载顺序静默 选一个(装个新插件就可能改变既有路径归属)。 B) 插件装与内置/他插件同名的适配器 → 硬错误 ErrAdapterNameReserved / owned by。 用户要求:都改成「报冲突、列出候选、运维选一个保留、选择持久化」,绝不崩溃 或拒绝启动。 ## A 类:插件 HTTP 服务路径冲突 - dispatch(plugin_http_routes.go)同路径多认领时不再 load-order 选边:未裁决 返回 409 并点名候选;已裁决按赢家分发。 - route_conflicts.go:RouteConflicts/RouteWinner/ResolveRouteConflict,只统计 loaded 且未禁用的插件;赢家插件若不再声明该路径,裁决作废回未裁决态。 ## B 类:适配器名冲突 - InstallAdapter 撞名(内置或他插件所有)不再硬拒,改挂起:现任继续服务,候选 入 AdapterConflicts,返回 typed ErrAdapterConflictPending(区分「代码错」)。 - ResolveAdapterConflict:incumbent 保留现任 / candidate 真正装上插件版(绕过 reserved 门禁——这是运维显式授权覆盖内置的唯一入口)并转移归属。 - 现任内置永不被静默覆盖,守住「第三方插件不得篡改内核发行物」信任边界。 ## 持久化 + WebAPI + WebUI - 两张裁决表落 .route-conflicts.json(routes + adapters),LoadDir 时读回; 重启后插件再装同名适配器自动套用上次裁决。 - WebAPI:GET /api/plugins/conflicts 列冲突;POST {kind,path|name,winner} 裁决。 - WebUI:插件页顶部「冲突裁决」卡片,逐条列路径/适配器冲突 + 候选 + 裁决按钮。 ## 测试 - 单测 conflict_arbitration_test.go 6 项全过:路径冲突检测/裁决/持久化/重启、 赢家消失回退、适配器撞名挂起/incumbent/candidate/归属转移、非法 winner 拒绝、 自身更新不受影响(token 轮换不走冲突路径)。 - e2e conflict_arbitration_e2e_test.go 2 项全过(真二进制):两插件抢路径→409 点名→裁决→200 赢家应答→/v1/models 无回归;插件装 shadow openai→挂起→现任 保留→裁决 candidate→插件版生效→冲突清空。 - 全量 build + vet 通过,既有 lua/gateway/core 回归无破坏。 修正过程中发现并修了一个真 bug:LoadAdapterSource 对 key≠declared 的适配器 重装会建第二个池、留旧池继续服务(v2 装了 v1 还在跑)——改成统一按注册键 归档,裁决 candidate 后新版本才真正生效。 | 10 天前 | |
fix(startup): 密钥警告改读真实生效的 key 集合 启动时那条「gateway_keys is EMPTY — without a key every request is rejected」 读的是 legacy 的 cfg.GatewayKeys 段,而鉴权实际用 cfg.Keys(core.ListKeys)。 seedKeys 首次启动把 gateway_keys 搬进 keys[] 之后,YAML 里那个列表就不再 被鉴权使用。于是在它被清空(例如轮换掉 starter key 之后)而 keys[] 仍有 7 把可用 key(含 admin)时,进程每次启动都谎报「所有请求都会被拒绝」。 实测:生产日志出现该警告,而同一个 key 请求 /v1/models 返回 200。 - main.go 改为检查 c.ListKeys(),文案改成不绑定字段名。 - 顺带删掉 gateway.New 的 gatewayKeys 参数:函数体从未使用它, 只读 ListKeys(),留着会继续诱导人以为鉴权来自那个列表。 判据:e2e/TestStartupWarningReflectsRealKeysNotLegacyList —— 构造 「gateway_keys 空 + keys[] 有 key」的真实形态,先断言该 key 确实能鉴权, 再断言日志里不再出现那句谎报。变异验证:回退成 GatewayKeys() 即变红。 | 23 天前 | |
feat(types): 统一线格式 v2 最大集合 + 对外输出 golden 基线 Phase 1:内核定义市面所有主流协议字段的**并集**作为唯一内部格式, 适配器按需取用;内核不再携带任何协议形状。 - internal/types/maxwire.go:UnifiedRequest(21 字段,v1 的 10)、 UnifiedResult(11,v1 的 8)、UnifiedDelta。覆盖 openai/anthropic/ gemini/ollama/deepseek 各自必需字段(system 顶层、top_k、stop_sequences、 thinking 预算、parallel_tool_calls、response_format、safety/citations…)。 - 只带**语义**不带**容器**:generationConfig/systemInstruction/options/ num_predict/maxOutputTokens 等协议专属形状一律不进统一格式,由适配器 自行装填。测试 TestNoProtocolContainerLeak 钉死这条。 - 修掉 v1 的私有字段泄漏:disable_thinking/extra_body 曾是内核私有字段, 却被序列化进交给适配器的 body,导致 10 个适配器各自写 req.disable_thinking = nil 清理。v2 用 thinking 语义字段替代, TestNoKernelPrivateFieldLeak 为回归。 Phase 0:建立对外输出的**字节级** golden 基线(.snapshots/), 因为现有 e2e 只有 strings.Contains 宽松断言,无法证明 Phase 2 把 OpenAI wire 移交给适配器 serializer 后输出字节未变。 - e2e/wire_golden_test.go:非流式/流式/生图三个 golden, 比对前 scrub 掉 id 与 created(每请求变化),其余逐字节比对。 - 反向验证:注入 chat.completion.chunk.DRIFTED 漂移后 golden 确实失败 (证明它不是永远通过的空壳),随后完整恢复。 - 稳定性:连跑 3 次全绿。 验证:go test -tags luajit ./internal/types/ ./e2e/ -run 'TestMax|TestNo|TestWireGolden' 全绿。 | 11 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 11 天前 | ||
| 10 天前 | ||
| 8 天前 | ||
| 10 天前 | ||
| 23 天前 | ||
| 11 天前 |