fix(config): 供应商改名同步重键模型别名 + 删除时清理关联配置(v0.8.3-rc1)
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
修复「改名后模型别名保留旧前缀,僵尸条目删了又出现」这一类配置不一致。 - 改名时同步重键模型别名、全局默认模型、供应商记住的默认模型、子代理池条目 - 改名冲突(撞已存在供应商名 / 遗留裸别名)加 -2/-3 后缀,不再静默吞模型 - 修复复制供应商复用移动语义 rekey 导致源供应商模型被清空 - 加载时不再合并「供应商已不存在」的 SQLite 僵尸模型 - 删除供应商/模型时清理子代理池条目、池默认回落、default_model 置空、 SQLite 用量查询配置(不误删另一 agent,读取失败即跳过) - 防呆:复制供应商改为 dirty-only,放弃修改不再落盘 - 测试基建:统一 crate 级 test_state 锁,消除环境变量并发竞争 测试:前端 146(新增 16)、Rust 197(新增 4)
This commit is contained in:
1 parent
317562781c
commit
35788c2783
20 files changed
+4217
-3893
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { purgeSubagentPoolAliases, rekeySubagentPoolAliases } from "./referenceCleanup";
|
||||
|
||||
const rawWithPool = {
|
||||
thinking: { enabled: true },
|
||||
secondary_model: {
|
||||
default_model: "opencode-go/longcat-2.5-preview-free",
|
||||
model: "opencode-go/longcat-2.5-preview-free",
|
||||
models: {
|
||||
"TokenHub/deepseek-v4.1-flash": "",
|
||||
"opencode-go/longcat-2.5-preview-free": "",
|
||||
"opencode-go/mimo-v2.6-flash": "",
|
||||
"opencode-go-copy/deepseek-v4-flash": "route to deepseek",
|
||||
},
|
||||
force: false,
|
||||
},
|
||||
services: { moonshot_fetch: { api_key: "" } },
|
||||
};
|
||||
|
||||
describe("rekeySubagentPoolAliases", () => {
|
||||
it("re-keys pool keys and defaults when the provider is renamed", () => {
|
||||
const next = rekeySubagentPoolAliases(rawWithPool, {
|
||||
"opencode-go-copy/deepseek-v4-flash": "opencode-go/deepseek-v4-flash",
|
||||
}) as typeof rawWithPool;
|
||||
|
||||
const section = next.secondary_model as Record<string, any>;
|
||||
expect(Object.keys(section.models).sort()).toEqual([
|
||||
"TokenHub/deepseek-v4.1-flash",
|
||||
"opencode-go/deepseek-v4-flash",
|
||||
"opencode-go/longcat-2.5-preview-free",
|
||||
"opencode-go/mimo-v2.6-flash",
|
||||
]);
|
||||
// Descriptions survive the re-key.
|
||||
expect(section.models["opencode-go/deepseek-v4-flash"]).toBe("route to deepseek");
|
||||
// Untouched defaults stay put.
|
||||
expect(section.default_model).toBe("opencode-go/longcat-2.5-preview-free");
|
||||
// Unrelated sections are preserved.
|
||||
expect(next.thinking).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("follows the default when it referenced a renamed alias", () => {
|
||||
const raw = {
|
||||
secondary_model: {
|
||||
default_model: "old/m1",
|
||||
model: "old/m1",
|
||||
models: { "old/m1": "" },
|
||||
},
|
||||
};
|
||||
const next = rekeySubagentPoolAliases(raw, { "old/m1": "new/m1" }) as any;
|
||||
expect(next.secondary_model.default_model).toBe("new/m1");
|
||||
expect(next.secondary_model.model).toBe("new/m1");
|
||||
expect(Object.keys(next.secondary_model.models)).toEqual(["new/m1"]);
|
||||
});
|
||||
|
||||
it("returns the same reference when nothing matches", () => {
|
||||
expect(rekeySubagentPoolAliases(rawWithPool, { "a/b": "c/d" })).toBe(rawWithPool);
|
||||
});
|
||||
|
||||
it("never injects an empty models table into the implicit single-model form", () => {
|
||||
// `{ model: "old/m1" }` with no pool table: re-keying the default must
|
||||
// not add `[secondary_model.models]` — an empty table is invalid for the
|
||||
// engine (its default has to be a key of the table).
|
||||
const raw = { secondary_model: { model: "old/m1" } };
|
||||
const next = rekeySubagentPoolAliases(raw, { "old/m1": "new/m1" }) as any;
|
||||
expect(next.secondary_model).toEqual({ model: "new/m1" });
|
||||
expect(next.secondary_model.models).toBeUndefined();
|
||||
});
|
||||
|
||||
it("never injects a models table into the force form", () => {
|
||||
const raw = { secondary_model: { force: true, default_model: "old/m1", model: "old/m1" } };
|
||||
const next = rekeySubagentPoolAliases(raw, { "old/m1": "new/m1" }) as any;
|
||||
expect(next.secondary_model).toEqual({
|
||||
force: true,
|
||||
default_model: "new/m1",
|
||||
model: "new/m1",
|
||||
});
|
||||
expect(next.secondary_model.models).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("purgeSubagentPoolAliases", () => {
|
||||
it("removes deleted aliases and keeps the rest of the section intact", () => {
|
||||
const next = purgeSubagentPoolAliases(rawWithPool, [
|
||||
"opencode-go-copy/deepseek-v4-flash",
|
||||
]) as typeof rawWithPool;
|
||||
const section = next.secondary_model as Record<string, any>;
|
||||
expect(Object.keys(section.models).sort()).toEqual([
|
||||
"TokenHub/deepseek-v4.1-flash",
|
||||
"opencode-go/longcat-2.5-preview-free",
|
||||
"opencode-go/mimo-v2.6-flash",
|
||||
]);
|
||||
expect(section.default_model).toBe("opencode-go/longcat-2.5-preview-free");
|
||||
expect(section.force).toBe(false);
|
||||
expect(next.thinking).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("falls back to the first remaining key when the default was purged", () => {
|
||||
const next = purgeSubagentPoolAliases(rawWithPool, [
|
||||
"opencode-go/longcat-2.5-preview-free",
|
||||
]) as any;
|
||||
const section = next.secondary_model;
|
||||
expect(section.models).not.toHaveProperty("opencode-go/longcat-2.5-preview-free");
|
||||
expect(section.default_model).toBe("TokenHub/deepseek-v4.1-flash");
|
||||
expect(section.model).toBe("TokenHub/deepseek-v4.1-flash");
|
||||
});
|
||||
|
||||
it("drops the whole section when the pool becomes empty", () => {
|
||||
const raw = {
|
||||
thinking: { enabled: true },
|
||||
secondary_model: {
|
||||
default_model: "p/m",
|
||||
model: "p/m",
|
||||
models: { "p/m": "" },
|
||||
},
|
||||
};
|
||||
const next = purgeSubagentPoolAliases(raw, ["p/m"]) as any;
|
||||
expect(next).not.toHaveProperty("secondary_model");
|
||||
expect(next.thinking).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
it("returns the same reference when no pool entry is affected", () => {
|
||||
expect(purgeSubagentPoolAliases(rawWithPool, ["other/m9"])).toBe(rawWithPool);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user