117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
/**
|
|
* OmniRoute autostart — OpenCode plugin
|
|
*
|
|
* Arranca el servidor local OmniRoute (http://127.0.0.1:20128) cada vez que
|
|
* OpenCode se inicia, si no está ya escuchando. También lo relanza si se cae
|
|
* durante la sesión. Esto garantiza que los modelos del proveedor `omniroute`
|
|
* estén siempre disponibles (modelos gratuitos de la suscripción Claude Code).
|
|
*
|
|
* Se carga automáticamente desde ~/.config/opencode/plugins/*.ts
|
|
*/
|
|
|
|
import type { Plugin } from "@opencode-ai/plugin"
|
|
import { spawn } from "node:child_process"
|
|
import { appendFileSync, mkdirSync } from "node:fs"
|
|
import { homedir } from "node:os"
|
|
import { join } from "node:path"
|
|
|
|
// ─── Configuración ───────────────────────────────────────────────────────────
|
|
|
|
const OR_URL = "http://127.0.0.1:20128"
|
|
const OR_PORT = 20128
|
|
|
|
const NODE_BIN = process.env.OMNIROUTE_NODE ?? "C:\\Program Files\\nodejs\\node.exe"
|
|
const OR_ENTRY =
|
|
process.env.OMNIROUTE_ENTRY ??
|
|
join(
|
|
process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"),
|
|
"npm",
|
|
"node_modules",
|
|
"omniroute",
|
|
"bin",
|
|
"omniroute.mjs",
|
|
)
|
|
|
|
const LOG_FILE = join(homedir(), ".omniroute", "autostart-plugin.log")
|
|
|
|
// ─── Utilidades ──────────────────────────────────────────────────────────────
|
|
|
|
function log(msg: string): void {
|
|
try {
|
|
mkdirSync(join(homedir(), ".omniroute"), { recursive: true })
|
|
appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`)
|
|
} catch {
|
|
// el log nunca debe romper el arranque de OpenCode
|
|
}
|
|
}
|
|
|
|
/** ¿Responde OmniRoute? El dashboard en "/" devuelve 200 sin autenticación. */
|
|
async function isUp(): Promise<boolean> {
|
|
try {
|
|
const ctrl = new AbortController()
|
|
const timer = setTimeout(() => ctrl.abort(), 3000)
|
|
const res = await fetch(`${OR_URL}/`, { signal: ctrl.signal })
|
|
clearTimeout(timer)
|
|
return res.status > 0 && res.status < 500
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/** Lanza OmniRoute desacoplado para que sobreviva al cierre de OpenCode. */
|
|
function startServer(): void {
|
|
const child = spawn(NODE_BIN, [OR_ENTRY, "serve"], {
|
|
detached: true,
|
|
stdio: "ignore",
|
|
windowsHide: true,
|
|
env: process.env,
|
|
})
|
|
child.unref()
|
|
log(`lanzado OmniRoute (pid ${child.pid})`)
|
|
}
|
|
|
|
// Evita arranques concurrentes si coinciden el inicio y varios session.created
|
|
let starting: Promise<void> | null = null
|
|
|
|
/** Arranca OmniRoute si no está levantado y espera a que responda. */
|
|
async function ensureServer(reason: string): Promise<void> {
|
|
if (await isUp()) return
|
|
if (starting) return starting
|
|
|
|
starting = (async () => {
|
|
log(`OmniRoute caído (${reason}) — arrancando...`)
|
|
startServer()
|
|
// El arranque puede tardar ~60s; sondeamos hasta 2 minutos
|
|
for (let i = 0; i < 40; i++) {
|
|
await new Promise((r) => setTimeout(r, 3000))
|
|
if (await isUp()) {
|
|
log(`OmniRoute listo en puerto ${OR_PORT}`)
|
|
return
|
|
}
|
|
}
|
|
log("OmniRoute no respondió dentro del tiempo de espera")
|
|
})()
|
|
|
|
try {
|
|
await starting
|
|
} finally {
|
|
starting = null
|
|
}
|
|
}
|
|
|
|
// ─── Plugin ──────────────────────────────────────────────────────────────────
|
|
|
|
export const OmniRouteAutostart: Plugin = async () => {
|
|
// Al cargar el plugin (= arranque de OpenCode) nos aseguramos de que esté vivo
|
|
void ensureServer("opencode-start")
|
|
|
|
return {
|
|
event: async ({ event }) => {
|
|
// Re-verificación barata al crear sesión (cubre una caída a mitad de sesión)
|
|
if (event.type === "session.created") {
|
|
void ensureServer("session-created")
|
|
}
|
|
},
|
|
}
|
|
}
|