| 20 | } |
| 21 | |
| 22 | export async function createOpencodeServer(options?: ServerOptions) { |
| 23 | options = Object.assign( |
| 24 | { |
| 25 | hostname: "127.0.0.1", |
| 26 | port: 4096, |
| 27 | timeout: 5000, |
| 28 | }, |
| 29 | options ?? {}, |
| 30 | ) |
| 31 | |
| 32 | const args = [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`] |
| 33 | if (options.config?.logLevel) args.push(`--log-level=${options.config.logLevel}`) |
| 34 | |
| 35 | const proc = launch(`opencode`, args, { |
| 36 | env: { |
| 37 | ...process.env, |
| 38 | OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}), |
| 39 | }, |
| 40 | }) |
| 41 | let clear = () => {} |
| 42 | |
| 43 | const url = await new Promise<string>((resolve, reject) => { |
| 44 | const id = setTimeout(() => { |
| 45 | clear() |
| 46 | stop(proc) |
| 47 | reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`)) |
| 48 | }, options.timeout) |
| 49 | let output = "" |
| 50 | let resolved = false |
| 51 | proc.stdout?.on("data", (chunk) => { |
| 52 | if (resolved) return |
| 53 | output += chunk.toString() |
| 54 | const lines = output.split("\n") |
| 55 | for (const line of lines) { |
| 56 | if (line.startsWith("opencode server listening")) { |
| 57 | const match = line.match(/on\s+(https?:\/\/[^\s]+)/) |
| 58 | if (!match) { |
| 59 | clear() |
| 60 | stop(proc) |
| 61 | clearTimeout(id) |
| 62 | reject(new Error(`Failed to parse server url from output: ${line}`)) |
| 63 | return |
| 64 | } |
| 65 | clearTimeout(id) |
| 66 | resolved = true |
| 67 | resolve(match[1]!) |
| 68 | return |
| 69 | } |
| 70 | } |
| 71 | }) |
| 72 | proc.stderr?.on("data", (chunk) => { |
| 73 | output += chunk.toString() |
| 74 | }) |
| 75 | proc.on("exit", (code) => { |
| 76 | clearTimeout(id) |
| 77 | let msg = `Server exited with code ${code}` |
| 78 | if (output.trim()) { |
| 79 | msg += `\nServer output: ${output}` |