| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- 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()
- },
- }
- }
|