diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts index 31ead18cf8..170d9d2804 100644 --- a/packages/opencode/src/cli/cmd/tui/worker.ts +++ b/packages/opencode/src/cli/cmd/tui/worker.ts @@ -72,7 +72,7 @@ export const rpc = { }, async server(input: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) { if (server) await server.stop(true) - server = await Server.listen(input) + server = await Server.listen({ type: "tcp", ...input }) return { url: server.url.toString() } }, async checkUpgrade(input: { directory: string }) { diff --git a/packages/opencode/src/cli/network.ts b/packages/opencode/src/cli/network.ts index 41f8184ef5..a4a49640ce 100644 --- a/packages/opencode/src/cli/network.ts +++ b/packages/opencode/src/cli/network.ts @@ -58,5 +58,5 @@ export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: Con const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] - return { hostname, port, mdns, mdnsDomain, cors } + return { type: "tcp" as const, hostname, port, mdns, mdnsDomain, cors } } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 2505c92f19..c6adb432ce 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -42,7 +42,7 @@ type ServerApp = { } export type TcpListenOptions = CorsOptions & { - type?: "tcp" + type: "tcp" port: number hostname: string mdns?: boolean @@ -119,7 +119,17 @@ export async function listen(opts: ListenOptions): Promise { const listenEffect: (opts: ListenOptions) => Effect.Effect = Effect.fn("Server.listen")( function* (opts: ListenOptions) { const state = yield* startWithPortFallback(opts) - if (opts.type === "socket") return yield* makeSocketListener(state, opts.socket) + if (opts.type === "socket") { + const listenerUrl = makeURL("localhost", 0) + url = listenerUrl + + return { + type: "socket" as const, + socket: opts.socket, + url: listenerUrl, + stop: yield* makeStop(state, Effect.void), + } + } const address = yield* tcpAddress(state) const listenerUrl = makeURL(opts.hostname, address.port) @@ -177,20 +187,6 @@ function startListener(opts: ListenOptions) { ) } -function makeSocketListener(state: ListenerState, socket: string) { - return Effect.gen(function* () { - const listenerUrl = makeURL("localhost", 0) - url = listenerUrl - - return { - type: "socket" as const, - socket, - url: listenerUrl, - stop: yield* makeStop(state, Effect.void), - } - }) -} - function tcpAddress(state: ListenerState) { return Effect.gen(function* () { if (state.server.address._tag === "TcpAddress") return state.server.address diff --git a/packages/opencode/test/server/httpapi-cors.test.ts b/packages/opencode/test/server/httpapi-cors.test.ts index 4e9680c7ce..266adb153a 100644 --- a/packages/opencode/test/server/httpapi-cors.test.ts +++ b/packages/opencode/test/server/httpapi-cors.test.ts @@ -85,7 +85,9 @@ describe("HttpApi CORS", () => { it.live("uses custom CORS origins passed to the server", () => Effect.gen(function* () { const listener = yield* Effect.acquireRelease( - Effect.promise(() => Server.listen({ hostname: "127.0.0.1", port: 0, cors: ["https://custom.example"] })), + Effect.promise(() => + Server.listen({ type: "tcp", hostname: "127.0.0.1", port: 0, cors: ["https://custom.example"] }), + ), (listener) => Effect.promise(() => listener.stop(true)), ) diff --git a/packages/opencode/test/server/httpapi-listen.test.ts b/packages/opencode/test/server/httpapi-listen.test.ts index 60cd214fea..f8b4e8baee 100644 --- a/packages/opencode/test/server/httpapi-listen.test.ts +++ b/packages/opencode/test/server/httpapi-listen.test.ts @@ -37,7 +37,7 @@ async function startListener() { Flag.OPENCODE_SERVER_USERNAME = auth.username process.env.OPENCODE_SERVER_PASSWORD = auth.password process.env.OPENCODE_SERVER_USERNAME = auth.username - return Server.listen({ hostname: "127.0.0.1", port: 0 }) + return Server.listen({ type: "tcp", hostname: "127.0.0.1", port: 0 }) } async function startNoAuthListener() { @@ -45,7 +45,7 @@ async function startNoAuthListener() { Flag.OPENCODE_SERVER_USERNAME = auth.username delete process.env.OPENCODE_SERVER_PASSWORD process.env.OPENCODE_SERVER_USERNAME = auth.username - return Server.listen({ hostname: "127.0.0.1", port: 0 }) + return Server.listen({ type: "tcp", hostname: "127.0.0.1", port: 0 }) } function authorization() { diff --git a/packages/opencode/test/server/httpapi-mdns.test.ts b/packages/opencode/test/server/httpapi-mdns.test.ts index 6b88a1f10b..52e24fc8ae 100644 --- a/packages/opencode/test/server/httpapi-mdns.test.ts +++ b/packages/opencode/test/server/httpapi-mdns.test.ts @@ -45,7 +45,7 @@ describe("HttpApi Server.listen mDNS", () => { test("skips publish for loopback hostnames", async () => { Flag.OPENCODE_SERVER_PASSWORD = "mdns-secret" Flag.OPENCODE_SERVER_USERNAME = "opencode" - const listener = await Server.listen({ hostname: "127.0.0.1", port: 0, mdns: true }) + const listener = await Server.listen({ type: "tcp", hostname: "127.0.0.1", port: 0, mdns: true }) try { expect(events.filter((e) => e.kind === "publish")).toEqual([]) } finally { @@ -57,7 +57,7 @@ describe("HttpApi Server.listen mDNS", () => { test("publishes for non-loopback hostnames and unpublishes on stop", async () => { Flag.OPENCODE_SERVER_PASSWORD = "mdns-secret" Flag.OPENCODE_SERVER_USERNAME = "opencode" - const listener = await Server.listen({ hostname: "0.0.0.0", port: 0, mdns: true }) + const listener = await Server.listen({ type: "tcp", hostname: "0.0.0.0", port: 0, mdns: true }) try { const published = events.filter((e) => e.kind === "publish") expect(published.length).toBe(1) @@ -73,7 +73,7 @@ describe("HttpApi Server.listen mDNS", () => { test("scope finalizer unpublishes even if stop() is not called for force-close", async () => { Flag.OPENCODE_SERVER_PASSWORD = "mdns-secret" Flag.OPENCODE_SERVER_USERNAME = "opencode" - const listener = await Server.listen({ hostname: "0.0.0.0", port: 0, mdns: true }) + const listener = await Server.listen({ type: "tcp", hostname: "0.0.0.0", port: 0, mdns: true }) expect(events.filter((e) => e.kind === "publish").length).toBe(1) // Plain (graceful) stop without close=true should still unpublish. await withTimeout(listener.stop(), 10_000, "timed out stopping graceful mdns listener")