refactor(client): namespace service exports and share the registration contract

This commit is contained in:
Dax Raad 2026-07-03 12:36:46 -04:00
commit dd768e30e2
8 changed files with 47 additions and 50 deletions

View file

@ -4,7 +4,6 @@ import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../services/service-config"
import type { Transport } from "@opencode-ai/client/effect"
const methods = new Set(["delete", "get", "head", "options", "patch", "post", "put"])
@ -61,7 +60,7 @@ export function rawRequest(input: readonly string[]) {
}
function resolveRequest(
transport: Transport,
transport: Service.Transport,
input: readonly string[],
params: Record<string, string>,
) {

View file

@ -3,7 +3,6 @@ import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Effect, Option } from "effect"
import { Service } from "@opencode-ai/client/effect"
import type { Transport } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../services/service-config"
import { Standalone } from "../../services/standalone"
import { Updater } from "../../services/updater"
@ -23,7 +22,7 @@ export default Runtime.handler(Commands, (input) =>
return {
url: server,
headers: password ? { authorization: "Basic " + btoa("opencode:" + password) } : undefined,
} satisfies Transport
} satisfies Service.Transport
}
if (input.standalone) return yield* Standalone.transport()
const options = yield* ServiceConfig.options()

View file

@ -14,6 +14,7 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import { Commands } from "../commands"
import { Runtime } from "../../framework/runtime"
import { Service } from "@opencode-ai/client/effect"
import { ServiceConfig } from "../../services/service-config"
import { Updater } from "../../services/updater"
import { randomBytes, randomUUID } from "crypto"
@ -63,8 +64,11 @@ export default Runtime.handler(
// not a startup lock: the atomic rename elects the latest writer, the watcher
// self-evicts losers, and the finalizer id-guard keeps an exiting server from
// deleting its successor's registration.
const RegistrationId = Schema.Struct({ id: Schema.optional(Schema.String) })
const decodeRegistrationId = Schema.decodeUnknownEffect(Schema.fromJsonString(RegistrationId))
// Written and read through Service.Info so the file the server registers is
// provably the contract clients discover with.
const infoJson = Schema.fromJsonString(Service.Info)
const encodeInfo = Schema.encodeEffect(infoJson)
const decodeInfo = Schema.decodeUnknownEffect(infoJson)
const register = Effect.fnUntraced(function* (address: HttpServer.Address) {
const fs = yield* FileSystem.FileSystem
@ -73,20 +77,17 @@ const register = Effect.fnUntraced(function* (address: HttpServer.Address) {
const secret = yield* ServiceConfig.password()
const temp = file + "." + id + ".tmp"
yield* fs.makeDirectory(path.dirname(file), { recursive: true })
yield* fs.writeFileString(
temp,
JSON.stringify({
id,
version: InstallationVersion,
url: HttpServer.formatAddress(address),
pid: process.pid,
password: secret,
}),
{ mode: 0o600 },
)
const encoded = yield* encodeInfo({
id,
version: InstallationVersion,
url: HttpServer.formatAddress(address),
pid: process.pid,
password: secret,
})
yield* fs.writeFileString(temp, encoded, { mode: 0o600 })
yield* fs.rename(temp, file)
const currentID = fs.readFileString(file).pipe(
Effect.flatMap(decodeRegistrationId),
Effect.flatMap(decodeInfo),
Effect.map((info) => info.id),
Effect.orElseSucceed(() => undefined),
)

View file

@ -5,7 +5,7 @@ import { Effect, FileSystem, Schema } from "effect"
import { randomBytes } from "crypto"
import path from "path"
// The CLI's service configuration file, plus the ServiceOptions binding that
// The CLI's service configuration file, plus the Service.Options binding that
// points the client package's service operations at this CLI: which
// registration file (by channel), which version, and how to spawn opencode.

View file

@ -5,11 +5,11 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Global } from "@opencode-ai/core/global"
import { loadBuiltinPlugins } from "@opencode-ai/tui/builtins"
import { OpenCode } from "@opencode-ai/client/promise"
import type { Transport } from "@opencode-ai/client/effect"
import type { Service } from "@opencode-ai/client/effect"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
import type { Args } from "@opencode-ai/tui/context/args"
export function runTui(transport: Transport, args: Args, discover?: () => Promise<Transport>) {
export function runTui(transport: Service.Transport, args: Args, discover?: () => Promise<Service.Transport>) {
const config = TuiConfig.resolve({}, { terminalSuspend: false })
let disposeSlots: (() => void) | undefined
return Effect.gen(function* () {