热重载抄 ai-mcp doReload(SDK client 三级降级+ctx.serverUrl,弃写死4096)

This commit is contained in:
youpaishidifu
2026-10-19 13:00:01 +08:00
parent 270bcc1358
commit ab619d1ee9
+46 -11
View File
@@ -55,8 +55,8 @@ const DB_PATH = join(homedir(), ".local", "share", "opencode", "opencode.db")
// SQLite 写执行器(Python,OC 外部子进程跑,绕 OC 自身 DB 写锁)
const DB_EXEC = join(homedir(), ".config", "opencode", "model-manager-db-exec.py")
// reload 端点(OC 热重载,/models 立即生效)
const RELOAD_URL = "http://127.0.0.1:4096/api/location/reload"
// 热重载通道见 doReload()(抄 ai-mcp:SDK client.location.reload 三级降级 + ctx.serverUrl 兜底,
// 不再写死 4096 —— 用户环境 4096 无监听导致 reload 全挂、/models 不刷新)
function fileLog(msg: string) {
try {
@@ -120,17 +120,52 @@ async function execDbWrite(op: string, payload: any): Promise<string> {
}
// 热重载 OC(让 /models 立即反映 catalog 变更)
async function hotReload(): Promise<void> {
try {
await fetch(RELOAD_URL, { method: "POST" })
fileLog("hot reload 已触发")
} catch (e: any) {
fileLog("hot reload 失败(需手动重启 OC): " + (e && e.message ? e.message : String(e)))
// 通道整段抄自 ai-mcp 的 doReload(MCP 对调已验证此链路可用):
// 进程内 SDK client.location.reload 三种调用形态 → HTTP POST ctx.serverUrl /api/location/reload
// 关键差异:用 ctx.serverUrl(OC 自己告知插件的服务地址),绝不写死端口
// V2 上下文可能有 client 属性:优先 ctx.client(若宿主提供),否则回退 ctx 本身(抄 ai-mcp pickClient)
function pickClient(ctx: any) {
if (!ctx) return ctx
if (ctx.client && (ctx.client.location || ctx.client.config)) return ctx.client
return ctx
}
async function doReload(ctx: any): Promise<boolean> {
const attempts: Array<() => Promise<any>> = []
const client = pickClient(ctx)
if (client && client.location && typeof client.location.reload === "function") {
attempts.push(() => client.location.reload({ location: undefined as any }))
attempts.push(() => client.location.reload({}))
attempts.push(() => client.location.reload())
}
const serverUrl = ctx && (ctx as any).serverUrl
if (serverUrl) {
attempts.push(async () => {
const res = await fetch(new URL("/api/location/reload", serverUrl), {
method: "POST",
headers: { "content-type": "application/json" },
body: "{}",
})
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res
})
}
let lastErr: any = null
for (const fn of attempts) {
try {
await fn()
fileLog("hot reload 已触发(/models 应立即生效)")
return true
} catch (e: any) {
lastErr = e
}
}
const msg = lastErr ? lastErr.message : "没有可用的 reload 通道(ctx 无 location/serverUrl)"
fileLog("hot reload 失败(需重启 OC 生效): " + msg)
return false
}
// 从 outbox 消费一条操作并执行
async function processOutbox(): Promise<void> {
async function processOutbox(ctx: any): Promise<void> {
if (!existsSync(OUTBOX)) return
let lines: string[]
try {
@@ -165,7 +200,7 @@ async function processOutbox(): Promise<void> {
const res = await execDbWrite(op, payload)
fileLog(`DB 写结果 op=${op}: ${res}`)
await hotReload()
await doReload(ctx)
}
// SQLite 写执行器(Python,子进程独立连接)
@@ -289,7 +324,7 @@ export default {
const daemon = setInterval(async () => {
if (stopped) return
try {
await processOutbox()
await processOutbox(ctx)
} catch (e: any) {
fileLog("daemon tick 异常(已吞掉): " + (e && e.message ? e.message : String(e)))
}