feat(lua)!: 插件只通过 modelrouter SDK 与内核交互,且不再随内核安装

- 新增唯一公开接口表 `modelrouter`(sdk_version=1.0):http / secrets /
  kernel / adapters / plugin_dir。成员与内部桥是**同一对象**而非副本 ——
  复制会让凭据保险库分叉(插件写 A、内核读 B)。
- `llmsproxy` 与 `__llmsproxy_*` 降为同对象兼容别名,只为已装插件继续可用。
  仓库内 4 个插件(channels / billing / quota / usage)全部改用
  `modelrouter.*`:此前它们直接依赖内核私有全局,内核无法在不炸插件的前提下
  改实现(重命名、加守卫、换实现都算破坏性变更)。
- 插件不再由网关安装:`SeedBundled()` 变彻底 no-op,`core.New` 不再调用;
  `ReadBundledPlugin` 改为按源文件定位(脱离 cwd),只服务测试与开发工具。
- 桌面端删除 `BUILTIN_PLUGINS` 与 `syncBundledPluginsOnUpgrade`:升级不再
  删除、改名或覆盖用户插件文件。插件是运维资产,不是内核资源。
- `Builtin` 不再靠"源码与仓库示例逐字节相同"推断,插件一律视为外部文件。
- 判据:SDK 存在/版本/形状、别名与内部桥同一张表、适配器 VM 拿不到 SDK、
  经 SDK 真实写读凭据并驱动内核动作、GUI 升级不动插件文件、release 包不含插件。

说明:channels.lua / quota.lua 同时带有此前的渠道与额度改动(含 OpenCode 免费池
监控),与 SDK 改名同处一个文件,无法文件级拆分,故一并纳入。
This commit is contained in:
JianFeeeee
2026-10-26 22:27:12 +08:00
parent 79bc55cfbe
commit de7553eaa2
19 changed files with 653 additions and 465 deletions

View File

@ -21,10 +21,11 @@ func pluginLoaded(ps *Plugins, name string) bool {
return false
}
// The billing plugin ships with the gateway, so its arithmetic is a contract:
// a wrong price silently produces wrong money. These tests drive it through the
// real hook path and check the NUMBERS, not merely that it loads.
// billing lives in the repository as an optional plugin, NOT inside the binary.
// The gateway never installs it; an operator (or a test) places the file in
// plugin_dir. Its arithmetic is still a contract — a wrong price silently
// produces wrong money — so these tests drive it through the real hook path and
// check the NUMBERS, not merely that it loads.
func billingVM(t *testing.T) (*Plugins, string) {
t.Helper()
dir := filepath.Join(t.TempDir(), "adapters")
@ -35,15 +36,30 @@ func billingVM(t *testing.T) (*Plugins, string) {
t.Cleanup(vm.Stop)
pdir := filepath.Join(t.TempDir(), "plugins")
ps := NewPlugins(vm, pdir)
if err := ps.SeedBundled(); err != nil {
t.Fatalf("seed: %v", err)
}
installRepoPlugin(t, pdir, "billing")
if err := ps.LoadDir(); err != nil {
t.Fatalf("load: %v", err)
}
return ps, pdir
}
// installRepoPlugin copies a repo-example plugin into an operator plugin dir.
// It stands in for the operator action that provisioning normally performs;
// using it keeps "the gateway does not install plugins" true even in tests.
func installRepoPlugin(t *testing.T, dir, name string) {
t.Helper()
src, err := ReadBundledPlugin(name)
if err != nil {
t.Fatalf("read repo plugin %s: %v", name, err)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("mkdir %s: %v", dir, err)
}
if err := os.WriteFile(filepath.Join(dir, name+".lua"), []byte(src), 0o644); err != nil {
t.Fatalf("install %s: %v", name, err)
}
}
// stateOf reads the plugin's published state as a generic map.
func stateOf(t *testing.T, ps *Plugins) map[string]interface{} {
t.Helper()