diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 2955715f9f..f662fa1ff0 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -25,10 +25,12 @@ export const ServeCommand = effectCmd({ if (conflicts.length) yield* fail(`--socket cannot be used with ${conflicts.join(", ")}`) } const opts = yield* resolveNetworkOptions(args) - const server = yield* Effect.promise(() => - Server.listen(args.socket ? { ...opts, socket: resolveSocketPath(args.socket) } : opts), - ) - if (server.socket) { + const server = args.socket + ? yield* Effect.promise(() => + Server.listen({ type: "socket", socket: resolveSocketPath(args.socket), cors: opts.cors }), + ) + : yield* Effect.promise(() => Server.listen(opts)) + if (server.type === "socket") { console.log(`opencode server listening on socket ${server.socket}`) yield* Effect.never } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 5e32a33e93..f7b1a68fe0 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -19,35 +19,55 @@ globalThis.AI_SDK_LOG_WARNINGS = false const log = Log.create({ service: "server" }) -export type Listener = { +export type TcpListener = { + type: "tcp" hostname: string port: number url: URL - socket?: string stop: (close?: boolean) => Promise } +export type SocketListener = { + type: "socket" + socket: string + url: URL + stop: (close?: boolean) => Promise +} + +export type Listener = TcpListener | SocketListener + type ServerApp = { fetch(request: Request): Response | Promise request(input: string | URL | Request, init?: RequestInit): Response | Promise } -type ListenOptions = CorsOptions & { +export type TcpListenOptions = CorsOptions & { + type?: "tcp" port: number hostname: string - socket?: string mdns?: boolean mdnsDomain?: string } + +export type SocketListenOptions = CorsOptions & { + type: "socket" + socket: string +} + +export type ListenOptions = TcpListenOptions | SocketListenOptions type ListenerState = { scope: Scope.Scope server: Context.Service.Shape http: ListenerServer websockets: WebSocketTracker.Interface } -type EffectListener = Omit & { +type EffectTcpListener = Omit & { stop: (close?: boolean) => Effect.Effect } +type EffectSocketListener = Omit & { + stop: (close?: boolean) => Effect.Effect +} +type EffectListener = EffectTcpListener | EffectSocketListener interface ListenerServer { readonly closeAll: Effect.Effect @@ -74,21 +94,32 @@ export async function openapi() { export let url: URL +export function listen(opts: TcpListenOptions): Promise +export function listen(opts: SocketListenOptions): Promise export async function listen(opts: ListenOptions): Promise { const listener = await Effect.runPromise(listenEffect(opts)) + const stop = (close?: boolean) => Effect.runPromiseExit(listener.stop(close)).then(() => undefined) + if (listener.type === "socket") { + return { + type: "socket" as const, + socket: listener.socket, + url: listener.url, + stop, + } + } return { + type: "tcp" as const, hostname: listener.hostname, port: listener.port, url: listener.url, - socket: listener.socket, - stop: (close?: boolean) => Effect.runPromiseExit(listener.stop(close)).then(() => undefined), + stop, } } const listenEffect: (opts: ListenOptions) => Effect.Effect = Effect.fn("Server.listen")( function* (opts: ListenOptions) { const state = yield* startWithPortFallback(opts) - if (opts.socket) return yield* makeSocketListener(state, opts.socket) + if (opts.type === "socket") return yield* makeSocketListener(state, opts.socket) const address = yield* tcpAddress(state) const listenerUrl = makeURL(opts.hostname, address.port) @@ -97,6 +128,7 @@ const listenEffect: (opts: ListenOptions) => Effect.Effect>, label: string) { +function stop(listener: { stop(close?: boolean): Promise }, label: string) { return withTimeout(listener.stop(true), 10_000, label) } @@ -293,9 +293,9 @@ describe("HttpApi Server.listen", () => { process.platform === "win32" ? `\\\\.\\pipe\\opencode-test-${process.pid}-${Date.now()}` : path.join(tmp.path, "opencode.sock") - const listener = await Server.listen({ hostname: "127.0.0.1", port: 0, socket }) + const listener = await Server.listen({ type: "socket", socket }) try { - expect(listener.port).toBe(0) + expect(listener.type).toBe("socket") expect(listener.socket).toBe(socket) const response = await requestSocketRoot(socket)