model-manager:readCatalog + DB_EXEC_SRC 全改 sys.stdout.buffer.write(防 GBK UnicodeEncodeError)

This commit is contained in:
youpaishidifu
2026-10-19 10:36:53 +08:00
parent c14fa0811d
commit 270bcc1358
+31 -19
View File
@@ -69,22 +69,30 @@ function fileLog(msg: string) {
// 读 catalog(只读 SQLite,OC 自身持锁但只读不冲突;用 Python 子进程读保证一致)
async function readCatalog(): Promise<any | null> {
try {
const r = spawn("python", [
"-c",
`
import sqlite3, json, sys
db = r"${DB_PATH.replace(/\\/g, "\\\\")}"
const py = process.platform === "win32" ? "python" : "python3"
const r = spawn(
py,
[
"-c",
`
import sqlite3, json, sys, os
db = os.path.join(os.path.expanduser("~"), ".local", "share", "opencode", "opencode.db")
con = sqlite3.connect(db)
row = con.execute("SELECT value FROM kv WHERE key='models-dev:catalog'").fetchone()
con.close()
if not row:
print(json.dumps({"error": "catalog row missing"}))
sys.stdout.buffer.write(json.dumps({"error": "catalog row missing"}).encode("utf-8"))
sys.exit(0)
print(row[0])
sys.stdout.buffer.write(row[0].encode("utf-8"))
`,
], { stdio: ["ignore", "pipe", "pipe"] })
],
{
stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" },
},
)
let out = ""
r.stdout.on("data", (d) => (out += d))
r.stdout.on("data", (d) => (out += d.toString("utf-8")))
await new Promise((res) => r.on("close", res))
if (!out.trim()) return null
return JSON.parse(out.trim())
@@ -161,10 +169,15 @@ async function processOutbox(): Promise<void> {
}
// SQLite 写执行器(Python,子进程独立连接)
// 注意:Windows 控制台默认 GBK,provider/modelID 可能含非 GBK 字符 → print 会 UnicodeEncodeError。
// 所有 stdout 输出统一走 sys.stdout.buffer.write(s.encode('utf-8'))。
const DB_EXEC_SRC = `
import sqlite3, json, sys, os, hashlib
from datetime import datetime
def out(s):
sys.stdout.buffer.write(str(s).encode("utf-8"))
DB = os.path.join(os.path.expanduser("~"), ".local", "share", "opencode", "opencode.db")
KEY = "models-dev:catalog"
PRISTINE = os.path.join(os.path.expanduser("~"), ".config", "opencode", "model-manager.pristine.json")
@@ -174,12 +187,11 @@ def load():
row = con.execute("SELECT value FROM kv WHERE key=?", (KEY,)).fetchone()
con.close()
if not row:
sys.exit("catalog row missing")
out("catalog row missing"); sys.exit(0)
return json.loads(row[0])
def save(cat):
cat["updatedAt"] = int(datetime.now().timestamp() * 1000)
# digest = sha256(body)
import hashlib as h
cat["digest"] = h.sha256(cat["body"].encode("utf-8")).hexdigest()
con = sqlite3.connect(DB, timeout=30)
@@ -206,14 +218,14 @@ if op == "disable":
cat = load()
body, p = get_provider_models(cat, provider)
if not p:
print("provider not found: " + provider); sys.exit(0)
out("provider not found: " + provider); sys.exit(0)
if model_id in p["models"]:
p["models"].pop(model_id, None)
cat["body"] = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
save(cat)
print("disabled " + provider + "/" + model_id)
out("disabled " + provider + "/" + model_id)
else:
print("model not present (already disabled?): " + model_id)
out("model not present (already disabled?): " + model_id)
elif op == "enable":
# 从 pristine 恢复该 model
@@ -229,9 +241,9 @@ elif op == "enable":
p["models"][model_id] = pmodels[model_id]
cat["body"] = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
save(cat)
print("enabled " + provider + "/" + model_id)
out("enabled " + provider + "/" + model_id)
else:
print("model not in pristine: " + model_id)
out("model not in pristine: " + model_id)
elif op == "reset":
# 整个 provider 的 models 换回 pristine
@@ -243,12 +255,12 @@ elif op == "reset":
body[provider] = pbody[provider]
cat["body"] = json.dumps(body, ensure_ascii=False, separators=(",", ":"))
save(cat)
print("reset " + provider)
out("reset " + provider)
else:
print("provider not in pristine: " + provider)
out("provider not in pristine: " + provider)
else:
print("unknown op: " + op)
out("unknown op: " + op)
`
// 把 DB 执行器落盘(首次 setup 时写一次,幂等)