summaryrefslogtreecommitdiffhomepage
path: root/packages/sdk/js/src/v2/server.ts
diff options
context:
space:
mode:
authorDax <[email protected]>2025-12-07 19:04:14 -0500
committerGitHub <[email protected]>2025-12-07 19:04:14 -0500
commitea7ec60f51f9fe3c6382f644c328188a43545b7b (patch)
tree5204cbc059fe10b87c54ffd25782ad83710bdcdb /packages/sdk/js/src/v2/server.ts
parent6667856ba5dac3e5dd77c7008cee2d09be894472 (diff)
downloadopencode-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.ts120
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()
+ },
+ }
+}