From 26f64f29fbf60798f1b49b3b068564345ae1dda9 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Fri, 15 May 2026 20:28:53 -0400 Subject: [PATCH] refactor(cli): scope server discovery to CLI --- packages/opencode/src/cli/cmd/serve.ts | 44 +++++++++---------- packages/opencode/src/cli/cmd/tui/thread.ts | 2 +- .../discovery.ts => cli/server-discovery.ts} | 18 ++++---- packages/opencode/src/effect/app-runtime.ts | 2 - 4 files changed, 32 insertions(+), 34 deletions(-) rename packages/opencode/src/{server/discovery.ts => cli/server-discovery.ts} (87%) diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 732aebcf4c..18b16c7281 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -1,6 +1,6 @@ import { Effect } from "effect" import { Server } from "../../server/server" -import { ServerDiscovery } from "@/server/discovery" +import { ServerDiscovery } from "@/cli/server-discovery" import { effectCmd } from "../effect-cmd" import { withNetworkOptions, resolveNetworkOptions } from "../network" import { Flag } from "@opencode-ai/core/flag/flag" @@ -17,26 +17,26 @@ export const ServeCommand = effectCmd({ // Server loads instances per-request via x-opencode-directory header — no // need for an ambient project InstanceContext at startup. instance: false, - handler: Effect.fn("Cli.serve")(function* (args) { - if (!Flag.OPENCODE_SERVER_PASSWORD) { - console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") - } - const opts = yield* resolveNetworkOptions(args) - const server = yield* Effect.promise(() => Server.listen(opts)) - if (args.discoverable) { - yield* ServerDiscovery.Service.use((discovery) => discovery.write(server.url)) - process.on("exit", ServerDiscovery.removeSync) - } - console.log(`opencode server listening on http://${server.hostname}:${server.port}`) + handler: (args) => + Effect.gen(function* () { + if (!Flag.OPENCODE_SERVER_PASSWORD) { + console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.") + } + const opts = yield* resolveNetworkOptions(args) + const server = yield* Effect.promise(() => Server.listen(opts)) + const discovery = args.discoverable ? yield* ServerDiscovery.Service : undefined + if (discovery) { + yield* discovery.write(server.url) + process.on("exit", ServerDiscovery.removeSync) + } + console.log(`opencode server listening on http://${server.hostname}:${server.port}`) - yield* Effect.never.pipe( - Effect.ensuring( - args.discoverable - ? ServerDiscovery.Service.use((discovery) => discovery.remove()).pipe( - Effect.ensuring(Effect.sync(() => process.off("exit", ServerDiscovery.removeSync))), - ) - : Effect.void, - ), - ) - }), + yield* Effect.never.pipe( + Effect.ensuring( + discovery + ? discovery.remove().pipe(Effect.ensuring(Effect.sync(() => process.off("exit", ServerDiscovery.removeSync)))) + : Effect.void, + ), + ) + }).pipe(Effect.provide(ServerDiscovery.defaultLayer)), }) diff --git a/packages/opencode/src/cli/cmd/tui/thread.ts b/packages/opencode/src/cli/cmd/tui/thread.ts index 3c6fb280c7..5914fd7ada 100644 --- a/packages/opencode/src/cli/cmd/tui/thread.ts +++ b/packages/opencode/src/cli/cmd/tui/thread.ts @@ -10,7 +10,7 @@ import { withTimeout } from "@/util/timeout" import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network" import { Filesystem } from "@/util/filesystem" import { ServerAuth } from "@/server/auth" -import { ServerDiscovery } from "@/server/discovery" +import { ServerDiscovery } from "@/cli/server-discovery" import type { GlobalEvent } from "@opencode-ai/sdk/v2" import type { EventSource } from "./context/sdk" import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32" diff --git a/packages/opencode/src/server/discovery.ts b/packages/opencode/src/cli/server-discovery.ts similarity index 87% rename from packages/opencode/src/server/discovery.ts rename to packages/opencode/src/cli/server-discovery.ts index 6d739c3983..8ba7289391 100644 --- a/packages/opencode/src/server/discovery.ts +++ b/packages/opencode/src/cli/server-discovery.ts @@ -1,12 +1,12 @@ -export * as ServerDiscovery from "./discovery" +export * as ServerDiscovery from "./server-discovery" -import { ServerAuth } from "@/server/auth" import { makeRuntime } from "@/effect/run-service" +import { ServerAuth } from "@/server/auth" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { Global } from "@opencode-ai/core/global" import { Context, Effect, Layer, Option, Schema } from "effect" -import path from "path" import { readFileSync, unlinkSync } from "fs" +import path from "path" export const file = path.join(Global.Path.state, "server.json") @@ -23,36 +23,36 @@ export interface Interface { readonly find: () => Effect.Effect } -export class Service extends Context.Service()("@opencode/ServerDiscovery") {} +export class Service extends Context.Service()("@opencode/CliServerDiscovery") {} export const layer = Layer.effect( Service, Effect.gen(function* () { const fs = yield* AppFileSystem.Service - const read = Effect.fn("ServerDiscovery.read")(function* () { + const read = Effect.fn("CliServerDiscovery.read")(function* () { const entry = yield* fs.readJson(file).pipe(Effect.catch(() => Effect.succeed(undefined))) return Option.getOrUndefined(decodeEntry(entry)) }) - const remove = Effect.fn("ServerDiscovery.remove")(function* () { + const remove = Effect.fn("CliServerDiscovery.remove")(function* () { const entry = yield* read() if (entry?.pid !== process.pid) return yield* fs.remove(file).pipe(Effect.ignore) }) - const removeStale = Effect.fn("ServerDiscovery.removeStale")(function* (entry: Entry) { + const removeStale = Effect.fn("CliServerDiscovery.removeStale")(function* (entry: Entry) { const current = yield* read() if (current?.pid !== entry.pid || current.url !== entry.url) return yield* fs.remove(file).pipe(Effect.ignore) }) return Service.of({ - write: Effect.fn("ServerDiscovery.write")(function* (url) { + write: Effect.fn("CliServerDiscovery.write")(function* (url) { yield* fs.writeJson(file, { url: localURL(url).toString(), pid: process.pid }, 0o600).pipe(Effect.orDie) }), remove, - find: Effect.fn("ServerDiscovery.find")(function* () { + find: Effect.fn("CliServerDiscovery.find")(function* () { const entry = yield* read() if (!entry) return undefined const url = yield* healthy(entry.url) diff --git a/packages/opencode/src/effect/app-runtime.ts b/packages/opencode/src/effect/app-runtime.ts index fe631853cb..0ce876ddc6 100644 --- a/packages/opencode/src/effect/app-runtime.ts +++ b/packages/opencode/src/effect/app-runtime.ts @@ -58,7 +58,6 @@ import { DataMigration } from "@/data-migration" import { BackgroundJob } from "@/background/job" import { EventV2Bridge } from "@/event-v2-bridge" import { RuntimeFlags } from "@/effect/runtime-flags" -import { ServerDiscovery } from "@/server/discovery" export const AppLayer = Layer.mergeAll( Npm.defaultLayer, @@ -115,7 +114,6 @@ export const AppLayer = Layer.mergeAll( SyncEvent.defaultLayer, EventV2Bridge.defaultLayer, DataMigration.defaultLayer, - ServerDiscovery.defaultLayer, ).pipe(Layer.provideMerge(InstanceLayer.layer), Layer.provideMerge(Observability.layer)) const rt = ManagedRuntime.make(AppLayer, { memoMap })