diff options
| author | Dax <[email protected]> | 2025-12-07 19:04:14 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-07 19:04:14 -0500 |
| commit | ea7ec60f51f9fe3c6382f644c328188a43545b7b (patch) | |
| tree | 5204cbc059fe10b87c54ffd25782ad83710bdcdb /packages/sdk/js/src/v2/server.ts | |
| parent | 6667856ba5dac3e5dd77c7008cee2d09be894472 (diff) | |
| download | opencode-ea7ec60f51f9fe3c6382f644c328188a43545b7b.tar.gz opencode-ea7ec60f51f9fe3c6382f644c328188a43545b7b.zip | |
v2 SDK (#5216)
Co-authored-by: GitHub Action <[email protected]>
Diffstat (limited to 'packages/sdk/js/src/v2/server.ts')
| -rw-r--r-- | packages/sdk/js/src/v2/server.ts | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/packages/sdk/js/src/v2/server.ts b/packages/sdk/js/src/v2/server.ts new file mode 100644 index 000000000..a09e14ab2 --- /dev/null +++ b/packages/sdk/js/src/v2/server.ts @@ -0,0 +1,120 @@ +import { spawn } from "node:child_process" +import { type Config } from "./gen/types.gen.js" + +export type ServerOptions = { + hostname?: string + port?: number + signal?: AbortSignal + timeout?: number + config?: Config +} + +export type TuiOptions = { + project?: string + model?: string + session?: string + agent?: string + signal?: AbortSignal + config?: Config +} + +export async function createOpencodeServer(options?: ServerOptions) { + options = Object.assign( + { + hostname: "127.0.0.1", + port: 4096, + timeout: 5000, + }, + options ?? {}, + ) + + const proc = spawn(`opencode`, [`serve`, `--hostname=${options.hostname}`, `--port=${options.port}`], { + signal: options.signal, + env: { + ...process.env, + OPENCODE_CONFIG_CONTENT: JSON.stringify(options.config ?? {}), + }, + }) + + const url = await new Promise<string>((resolve, reject) => { + const id = setTimeout(() => { + reject(new Error(`Timeout waiting for server to start after ${options.timeout}ms`)) + }, options.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 (options.signal) { + options.signal.addEventListener("abort", () => { + clearTimeout(id) + reject(new Error("Aborted")) + }) + } + }) + + return { + url, + close() { + proc.kill() + }, + } +} + +export function createOpencodeTui(options?: TuiOptions) { + const args = [] + + if (options?.project) { + args.push(`--project=${options.project}`) + } + if (options?.model) { + args.push(`--model=${options.model}`) + } + if (options?.session) { + args.push(`--session=${options.session}`) + } + if (options?.agent) { + args.push(`--agent=${options.agent}`) + } + + const proc = spawn(`opencode`, args, { + signal: options?.signal, + stdio: "inherit", + env: { + ...process.env, + OPENCODE_CONFIG_CONTENT: JSON.stringify(options?.config ?? {}), + }, + }) + + return { + close() { + proc.kill() + }, + } +} |
