/** * 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 { 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 | null = null /** Arranca OmniRoute si no está levantado y espera a que responda. */ async function ensureServer(reason: string): Promise { 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") } }, } }