add createOpencodeServer to js sdk and wait for readiness. always use random port for opencode serve. add /client and /server imports for js sdk

This commit is contained in:
Dax Raad 2025-08-21 17:12:31 -04:00
parent aa4dba1541
commit d9233872b9
3 changed files with 57 additions and 6 deletions

View file

@ -11,7 +11,7 @@ export const ServeCommand = cmd({
alias: ["p"], alias: ["p"],
type: "number", type: "number",
describe: "port to listen on", describe: "port to listen on",
default: 4096, default: 0,
}) })
.option("hostname", { .option("hostname", {
alias: ["h"], alias: ["h"],

View file

@ -1,21 +1,68 @@
import { spawn } from "node:child_process" import { spawn } from "node:child_process"
export type ServerConfig = { export type ServerConfig = {
host?: string hostname?: string
port?: number port?: number
signal?: AbortSignal
timeout?: number
} }
export async function createOpencodeServer(config?: ServerConfig) { export async function createOpencodeServer(config?: ServerConfig) {
config = Object.assign( config = Object.assign(
{ {
host: "127.0.0.1", hostname: "127.0.0.1",
port: 4096, port: 4096,
timeout: 5000,
}, },
config ?? {}, config ?? {},
) )
const proc = spawn(`opencode`, [`serve`, `--host=${config.host}`, `--port=${config.port}`]) const proc = spawn(`opencode`, [`servel`, `--hostname=${config.hostname}`, `--port=${config.port}`], {
const url = `http://${config.host}:${config.port}` signal: config.signal,
})
const url = await new Promise<string>((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 { return {
url, url,

View file

@ -1,5 +1,9 @@
{ {
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json", "extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {} "compilerOptions": {
"customConditions": [
"development"
],
}
} }