907b6c06a0fcdef682085a6fe9fd3cbddc058803
model-manager
模型目录管理插件(OpenCode TUI):停用/恢复/恢复出厂。
背景
OpenCode 的 /models 命令读取模型目录,新版目录存在 SQLite 数据库里:
- DB 文件:
~/.local/share/opencode/opencode.db(WAL 模式) - 表:
kv - key:
models-dev:catalog - value:JSON 字符串
{updatedAt, digest, body},其中body本身又是嵌套 JSON 字符串:{ providerID: { id, npm, api, models: { modelID: entry, ... }, ... }, ... }
/models 渲染的就是这份 catalog。要"停用某模型"(不在 /models 显示)或"恢复出厂"(还原所有被停用的模型),就得改这份 DB。
难点:OC 进程持有 SQLite 写锁
OC 的 main 进程常驻打开这个 DB(WAL 模式)。插件跑在 OC 内部(Bun 运行时),如果在插件进程里直接 better-sqlite3 写同一个文件,会撞上 OC 主进程的写锁 → SQLITE_BUSY / 死锁。
解法:插件不直接写 DB,而是走 outbox 队列 + fork 外部 Python 子进程 执行 DB 写:
- 插件收到操作请求(CLI / 外部脚本触发)→ 写一行 JSON 到
~/.config/opencode/model-manager.outbox.jsonl(outbox 队列) - 插件后台 daemon(
setup里 spawn,每 5s 轮询)发现新行 → forkpython子进程执行model-manager-db-exec.py(OCsetup时自动落盘到~/.config/opencode/) - Python 子进程用
sqlite3标准库(Python 对 WAL 模式无独占锁问题)读写 DB - 写完后调
POST http://127.0.0.1:4096/api/location/reload让 OC 热重载目录 - 结果回写到 outbox 的响应字段,daemon 记录日志
三种操作
| 操作 | 含义 | 实现 |
|---|---|---|
disable |
停用某模型(不在 /models 显示) |
在 body 的对应 provider.models 里把该 modelID 条目标记 disabled:true(或移出 models 字典,视 catalog schema) |
enable |
恢复某被停用的模型 | 清除 disabled 标记 / 恢复出厂 entry |
reset |
恢复出厂(全部模型还原) | 用首次启用时自动拍的 ~/.config/opencode/model-manager.pristine.json 备份覆盖 DB catalog |
触发方式
方式一:TUI 命令 /manage(推荐)
OC TUI 里输入 /manage(别名 /managent)打开模型管理面板,操作不直接改 DB,而是写 outbox 队列,由本插件 daemon 消费:
- 第一层:选 provider(顶置「恢复出厂」全局项)
- 第二层:对该 provider 选 停用 / 启用 / 恢复出厂
- 选中模型 → 选 停用 / 启用
命令注册在 tui-plugins/oc-session-all.js(model.manage 命令),写的是同一个 outbox 文件,daemon 统一消费。
方式二:外部脚本写 outbox
直接写一行 JSON 到 ~/.config/opencode/model-manager.outbox.jsonl:
{"op":"disable","payload":{"provider":"anthropic","modelID":"claude-opus-4.6"},"ts":1700000000}
{"op":"enable","payload":{"provider":"anthropic","modelID":"claude-opus-4.6"},"ts":1700000000}
{"op":"reset","payload":{"provider":"anthropic"},"ts":1700000000}
(daemon 5s 轮询,操作有最多 5s 延迟。)
日志
~/.config/opencode/model-manager.log
关键行:
OUTBOX 收到 op=disable provider=anthropic model=claude-opus-4.6DB 写成功,digest 更新reload 200 热重载完成reset 已还原 pristine 备份pristine 备份已建立(首次启用时)
部署
| 端 | 路径 |
|---|---|
| Windows 本机 | ~/.config/opencode/plugins/model-manager.ts |
| WSL | /root/.config/opencode/plugins/model-manager.ts |
依赖:Python 3(用 sqlite3 标准库,无需 pip install)。
由 tools/sync_wsl.py 的 ITEMS 清单管理。
已知取舍
- 不直接写 DB:OC 主进程持写锁,插件进程内直接
better-sqlite3会SQLITE_BUSY。outbox + fork Python 是绕开锁的可靠路径。 - reset 依赖 pristine 备份:首次启用时自动拍
model-manager.pristine.json;若该文件被手动删了,reset会失败(日志报pristine not found),需重新拍一份。 - 5s 轮询:daemon 每 5s 扫一次 outbox,操作有最多 5s 延迟。实为 fire-and-forget,不影响 OC 主流程。
- 目录 reload 用 4096 端口:假设 OC serve 跑在默认端口;若用了
--port自定义端口,reload 调用需带对端口(插件默认 4096,改OPENCODE_PORT环境变量可覆盖)。
文件
model-manager.ts— 插件主文件(TypeScript)model-manager-db-exec.py— DB 写执行器(Python,OCsetup时自动落盘到~/.config/opencode/,本仓库也保留一份供 sync_wsl 同步)
Languages
JavaScript
57.9%
Python
35.9%
TypeScript
6.2%