diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 0e13ddbd..1d6a7327 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -11,7 +11,7 @@ export const ServeCommand = cmd({ alias: ["p"], type: "number", describe: "port to listen on", - default: 4096, + default: 0, }) .option("hostname", { alias: ["h"], diff --git a/packages/sdk/js/src/server.ts b/packages/sdk/js/src/server.ts index edaa4cf6..08bf1290 100644 --- a/packages/sdk/js/src/server.ts +++ b/packages/sdk/js/src/server.ts @@ -1,21 +1,68 @@ import { spawn } from "node:child_process" export type ServerConfig = { - host?: string + hostname?: string port?: number + signal?: AbortSignal + timeout?: number } export async function createOpencodeServer(config?: ServerConfig) { config = Object.assign( { - host: "127.0.0.1", + hostname: "127.0.0.1", port: 4096, + timeout: 5000, }, config ?? {}, ) - const proc = spawn(`opencode`, [`serve`, `--host=${config.host}`, `--port=${config.port}`]) - const url = `http://${config.host}:${config.port}` + const proc = spawn(`opencode`, [`servel`, `--hostname=${config.hostname}`, `--port=${config.port}`], { + signal: config.signal, + }) + + const url = await new Promise((resolve, reject) => { + const id = setTimeout(() => { + reject(new Error(`Timeout waiting for server to start after ${config.timeout}ms`)) + }, config.timeout) + let output = "" + proc.stdout?.on("data", (chunk) => { + output += chunk.toString() + const lines = output.split("\n") + for (const line of lines) { + if (line.startsWith("opencode server listening")) { + const match = line.match(/on\s+(https?:\/\/[^\s]+)/) + if (!match) { + throw new Error(`Failed to parse server url from output: ${line}`) + } + clearTimeout(id) + resolve(match[1]) + return + } + } + }) + proc.stderr?.on("data", (chunk) => { + output += chunk.toString() + }) + proc.on("exit", (code) => { + clearTimeout(id) + let msg = `Server exited with code ${code}` + if (output.trim()) { + msg += `\nServer output: ${output}` + } + reject(new Error(msg)) + }) + proc.on("error", (error) => { + clearTimeout(id) + reject(error) + }) + if (config.signal) { + config.signal.addEventListener("abort", () => { + clearTimeout(id) + reject(new Error("Aborted")) + }) + } + }) return { url, diff --git a/tsconfig.json b/tsconfig.json index 65fa6c7f..75ddeff3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,9 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@tsconfig/bun/tsconfig.json", - "compilerOptions": {} + "compilerOptions": { + "customConditions": [ + "development" + ], + } }