fix(cli): unify server resolution · argszero/opencode@cc686ab · GitHub
Skip to content

Commit cc686ab

Browse files
committed
fix(cli): unify server resolution
1 parent 6524dfc commit cc686ab

6 files changed

Lines changed: 59 additions & 28 deletions

File tree

packages/cli/src/commands/commands.ts

Lines changed: 1 addition & 0 deletions

packages/cli/src/commands/handlers/api.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Effect, Option } from "effect"
33
import { Commands } from "../commands"
44
import { Runtime } from "../../framework/runtime"
55
import { Service } from "@opencode-ai/client/effect"
6-
import { ServiceConfig } from "../../services/service-config"
6+
import { Server } from "../../services/server"
77

88
const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"])
99

@@ -18,9 +18,12 @@ type OpenApi = {
1818
export default Runtime.handler(
1919
Commands.commands.api,
2020
Effect.fn("cli.api")(function* (input) {
21-
const options = yield* ServiceConfig.options()
22-
const found = yield* Service.discover(options)
23-
const endpoint = found ?? (yield* Service.start(options))
21+
const server = yield* Server.resolve({
22+
server: Option.getOrUndefined(input.server),
23+
standalone: input.standalone,
24+
mismatch: "ignore",
25+
})
26+
const endpoint = server.endpoint
2427
const params = Option.getOrElse(input.param, () => ({}))
2528
const request = yield* resolveRequest(endpoint, input.request, params)
2629
const headers = new Headers(Service.headers(endpoint))

packages/cli/src/services/server.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import { Standalone } from "./standalone"
1010
export type Args = {
1111
readonly server?: string
1212
readonly standalone?: boolean
13+
readonly mismatch?: "replace" | "ignore" | "error"
1314
}
1415

1516
export type Resolved = {
1617
readonly endpoint: Service.Endpoint
17-
readonly discover?: () => Promise<Service.Endpoint>
18+
readonly reconnect?: (attempt: number) => Promise<Service.Endpoint>
1819
readonly reload?: () => Promise<void>
1920
}
2021

@@ -45,11 +46,19 @@ export const resolve = Effect.fn("cli.server.resolve")(function* (args: Args) {
4546
}
4647

4748
const options = yield* ServiceConfig.options()
48-
const endpoint = yield* Service.start(options)
49+
const endpoint = yield* resolveManaged(options, args.mismatch ?? "replace")
4950
const reconnectOptions = { ...options, version: undefined }
5051
return {
5152
endpoint,
52-
discover: () => Effect.runPromise(Service.start(reconnectOptions).pipe(Effect.provide(NodeFileSystem.layer))),
53+
reconnect: (attempt) =>
54+
Effect.runPromise(
55+
Effect.gen(function* () {
56+
if (attempt > 3) return yield* Service.start(reconnectOptions)
57+
const endpoint = yield* Service.discover(reconnectOptions)
58+
if (endpoint !== undefined) return endpoint
59+
return yield* Effect.fail(new Error("Background server is unavailable"))
60+
}).pipe(Effect.provide(NodeFileSystem.layer)),
61+
),
5362
reload: () =>
5463
Effect.runPromise(
5564
Effect.gen(function* () {
@@ -60,6 +69,20 @@ export const resolve = Effect.fn("cli.server.resolve")(function* (args: Args) {
6069
} satisfies Resolved
6170
})
6271

72+
const resolveManaged = Effect.fnUntraced(function* (
73+
options: Service.Options,
74+
mismatch: NonNullable<Args["mismatch"]>,
75+
) {
76+
if (mismatch === "replace") return yield* Service.start(options)
77+
if (mismatch === "ignore") return yield* Service.start({ ...options, version: undefined })
78+
79+
const compatible = yield* Service.discover(options)
80+
if (compatible !== undefined) return compatible
81+
const existing = yield* Service.discover({ ...options, version: undefined })
82+
if (existing !== undefined) return yield* Effect.fail(new Error("Background server version does not match this client"))
83+
return yield* Service.start(options)
84+
})
85+
6386
function connectError(endpoint: Service.Endpoint, cause: unknown) {
6487
if (isUnauthorizedError(cause)) {
6588
return new Error(

packages/tui/src/app.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const appBindingCommands = [
147147
export type TuiInput = {
148148
server: {
149149
endpoint: Service.Endpoint
150-
discover?: () => Promise<Service.Endpoint>
150+
reconnect?: (attempt: number) => Promise<Service.Endpoint>
151151
reload?: () => Promise<void>
152152
}
153153
args: Args
@@ -200,10 +200,10 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
200200
Effect.tryPromise(() => api.location.get()).pipe(Effect.map((response) => response.directory)),
201201
),
202202
)
203-
const discover = input.server.discover
204-
const reconnect = discover
205-
? async () => {
206-
const endpoint = await discover()
203+
const reconnectEndpoint = input.server.reconnect
204+
const reconnect = reconnectEndpoint
205+
? async (attempt: number) => {
206+
const endpoint = await reconnectEndpoint(attempt)
207207
const next = { baseUrl: endpoint.url, headers: Service.headers(endpoint) }
208208
return {
209209
client: createOpencodeClient({ ...next, directory }),
@@ -339,7 +339,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
339339
<SDKProvider
340340
client={createOpencodeClient({ ...options, directory })}
341341
api={api}
342-
discover={reconnect}
342+
reconnect={reconnect}
343343
reload={input.server.reload}
344344
>
345345
<PermissionProvider>

packages/tui/src/context/sdk.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
2626
init: (props: {
2727
client: OpencodeClient
2828
api: OpenCodeClient
29-
discover?: () => Promise<{ client: OpencodeClient; api: OpenCodeClient }>
29+
reconnect?: (attempt: number) => Promise<{ client: OpencodeClient; api: OpenCodeClient }>
3030
// Stops and starts the managed service; present only in service mode.
3131
reload?: () => Promise<void>
3232
}) => {
@@ -122,8 +122,8 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
122122
// Re-resolve the transport before retrying: the server may have
123123
// moved (service restarted on a new port) or need starting. Static
124124
// transports (--server, standalone) resolve to the same address.
125-
if (props.discover) {
126-
const next = await props.discover().catch(() => undefined)
125+
if (props.reconnect) {
126+
const next = await props.reconnect(attempt).catch(() => undefined)
127127
if (abort.signal.aborted || controller.signal.aborted) return
128128
if (next) {
129129
client = next.client
@@ -135,7 +135,7 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
135135
attempt,
136136
error: message,
137137
})
138-
await wait(250, controller.signal)
138+
await wait(1_000, controller.signal)
139139
}
140140
})()
141141
return ready

packages/tui/test/cli/tui/use-event.test.tsx

Lines changed: 15 additions & 11 deletions

0 commit comments

Comments
 (0)