恢复出厂设置=真删除厂商(remove_provider),停用列表命中改真删除压制,pristine 提前到 setup 拍

This commit is contained in:
youpaishidifu
2026-10-19 22:29:27 +08:00
parent 125d4d099c
commit fb7015ff6c
+80 -5
View File
@@ -78,6 +78,8 @@ const CFG_TEMPLATE = `{
}`
// modelID 级镜像清单(单模型停用的持久化;整厂级走 cfg 的停用列表)
const DISABLED_MIRROR = join(homedir(), ".config", "opencode", "model-manager.disabled.json")
// 已彻底删除的厂商列表(remove_provider 标记;面板据此显示「已删除·可找回」)
const REMOVED_LIST = join(homedir(), ".config", "opencode", "model-manager.removed.json")
// 数据库路径
const DB_PATH = join(homedir(), ".local", "share", "opencode", "opencode.db")
// SQLite 写执行器(Python,OC 外部子进程跑,绕 OC 自身 DB 写锁)
@@ -243,6 +245,24 @@ function mirrorUnmark(provider: string, modelID: string): void {
fileLog(`停用镜像已移除 ${provider}/${modelID}`)
}
// 已删除厂商列表(面板显示「已删除·可找回」用)
function loadRemoved(): string[] {
try {
if (!existsSync(REMOVED_LIST)) return []
const j = JSON.parse(readFileSync(REMOVED_LIST, "utf-8"))
return Array.isArray(j) ? j.filter((x: any) => typeof x === "string") : []
} catch {
return []
}
}
function saveRemoved(list: string[]): void {
try {
writeFileSync(REMOVED_LIST, JSON.stringify(list, null, 2), "utf-8")
} catch (e: any) {
fileLog("已删除厂商列表写入失败: " + (e && e.message ? e.message : String(e)))
}
}
// 读 catalog(只读 SQLite,OC 自身持锁但只读不冲突;用 Python 子进程读保证一致)
async function readCatalog(): Promise<any | null> {
try {
@@ -376,8 +396,8 @@ async function processOutbox(ctx: any): Promise<void> {
}
fileLog(`outbox 命中 op=${op} payload=${JSON.stringify(payload).slice(0, 120)}`)
// reset 前确保有 pristine 备份
if (op === "reset") {
// reset 前确保有 pristine 备份(恢复厂商要靠它把出厂模型拉回来)
if (op === "reset" || op === "remove_provider") {
if (!existsSync(PRISTINE)) {
const cat = await readCatalog()
if (cat) writeFileSync(PRISTINE, JSON.stringify(cat))
@@ -408,8 +428,22 @@ async function processOutbox(ctx: any): Promise<void> {
mirrorUnmark(prov, payload.modelID)
} else if (op === "enable" && s.startsWith("model not in pristine")) {
mirrorUnmark(prov, payload.modelID)
} else if (op === "remove_provider" && (s.startsWith("removed_provider ") || s.startsWith("provider not found"))) {
// 恢复出厂设置(格式化)= 彻底删除该厂 → 记进 cfg 停用列表,OC 刷新 catalog 回潮时自动再压
const list = readStopList()
if (!list.some((p) => patMatch(p, prov))) {
list.push(prov)
writeStopList(list)
fileLog(`已删除厂商 ${prov},并记入 cfg 停用列表(cfg 共 ${list.length} 项,持久化)`)
}
const rm = loadRemoved()
if (!rm.includes(prov)) {
rm.push(prov)
saveRemoved(rm)
fileLog(`已删除厂商列表已记录 ${prov}(面板可显示「已删除·可找回」)`)
}
} else if (op === "reset" && (s.startsWith("reset ") || s.startsWith("provider not in pristine"))) {
// 恢复出厂/格式化:整厂从停用列表摘除(含命中它的通配项),单模型镜像清空
// 恢复该厂商(撤销删除/停用):从停用列表摘除,单模型镜像清空
const list = readStopList()
const kept = list.filter((p) => !patMatch(p, prov))
const removed = list.length - kept.length
@@ -423,6 +457,11 @@ async function processOutbox(ctx: any): Promise<void> {
saveMirror(map)
fileLog(`恢复出厂 ${prov}:单模型停用镜像已清空`)
}
const rm = loadRemoved()
if (rm.includes(prov)) {
saveRemoved(rm.filter((x) => x !== prov))
fileLog(`恢复出厂 ${prov}:已从「已删除厂商」列表移除(可再被 /manage 看到)`)
}
}
} catch (e: any) {
fileLog("停用状态维护异常(已吞掉): " + (e && e.message ? e.message : String(e)))
@@ -553,6 +592,19 @@ elif op == "reset":
else:
out("provider not in pristine: " + provider)
elif op == "remove_provider":
# 真删除:把整个 provider 节点从 catalog body 里摘掉(/models 里该厂彻底消失)
# 恢复靠 reset(pristine 里有完整节点)
cat = load()
body = json.loads(cat["body"])
if provider not in body:
out("provider not found: " + provider); sys.exit(0)
n = len(body[provider].get("models", {}))
body.pop(provider, None)
cat["body"] = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
save(cat)
out("removed_provider " + provider + " models=" + str(n))
else:
out("unknown op: " + op)
`
@@ -569,6 +621,24 @@ function ensureDbExec(): void {
}
}
// 出厂备份:setup 时就拍一次(幂等)。
// 为什么提前拍:若等到第一次 reset/remove_provider 才拍,那时的 catalog 可能已被
// 改过(模型被删过),拍到的就不是「出厂态」,恢复出来的会缺模型。
async function ensurePristine(): Promise<void> {
try {
if (existsSync(PRISTINE)) return
const cat = await readCatalog()
if (!cat || !cat.body) {
fileLog("pristine 暂未建立(catalog 读不到,稍后重试)")
return
}
writeFileSync(PRISTINE, JSON.stringify(cat))
fileLog("pristine 出厂备份已建立(setup 阶段)")
} catch (e: any) {
fileLog("pristine 建立失败(已吞掉): " + (e && e.message ? e.message : String(e)))
}
}
// 读 catalog 的 time_updated(单值,便宜;用于判断 OC 是否刷新覆盖过 catalog)
async function readCatalogTime(): Promise<number> {
return new Promise((resolve) => {
@@ -626,9 +696,13 @@ async function reconcile(ctx: any): Promise<void> {
const p = body[id] || {}
const name = p.name || ""
const models = p.models || {}
if (stopList.length && stopListHits(stopList, id, name) && Object.keys(models).length) {
plan.push({ op: "disable_all", payload: { provider: id } })
// cfg 停用列表命中 → 真删除厂商节点(不能只清空 models:OC 刷新 catalog 会把
// provider 节点连同模型一起加回来,只有摘掉节点才算真删除)
if (stopList.length && stopListHits(stopList, id, name)) {
plan.push({ op: "remove_provider", payload: { provider: id } })
continue
}
// modelID 级镜像命中 → 只摘掉那一个模型
const ids = mirror[id]
if (ids && ids.length) {
for (const mid of ids) {
@@ -671,6 +745,7 @@ export default {
}
ensureDbExec()
ensureCfg() // 防御:OC 更新删掉 cfg 时立即释放新配置(含停用列表镜像恢复)
await ensurePristine() // 出厂备份提前拍,避免首次操作时 catalog 已被改
// 后台 daemon:每 5s 轮询 outbox(普通文件读写,不碰 DB 锁)+ 对账压制回潮
let stopped = false