refactor(cli): unify server endpoint handling
This commit is contained in:
parent
984cab7938
commit
be7a684791
18 changed files with 212 additions and 223 deletions
|
|
@ -1,9 +1,11 @@
|
|||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Service } from "@opencode-ai/client/effect"
|
||||
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Effect } from "effect"
|
||||
import path from "node:path"
|
||||
import { Daemon } from "../daemon"
|
||||
import { ServiceConfig } from "../services/service-config"
|
||||
import { waitForCatalogReady } from "./catalog.shared"
|
||||
import { INTERACTIVE_INPUT_ERROR, resolveInteractiveStdin } from "./runtime.stdin"
|
||||
import type { RunInput, RunTuiConfig } from "./types"
|
||||
|
|
@ -27,28 +29,26 @@ export type MiniCommandInput = {
|
|||
}
|
||||
|
||||
type Session = Awaited<ReturnType<OpenCodeClient["session"]["get"]>>
|
||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
||||
|
||||
export async function runMini(input: MiniCommandInput) {
|
||||
validate(input)
|
||||
const initialInput = mergeInput(process.stdin.isTTY ? undefined : await Bun.stdin.text(), input.prompt)
|
||||
const runtimeTask = import("./runtime")
|
||||
const directory = input.attach ? input.directory : localDirectory(input.directory)
|
||||
const transportTask = startTransport(input)
|
||||
void transportTask.catch(() => {})
|
||||
const endpointTask = startEndpoint(input)
|
||||
void endpointTask.catch(() => {})
|
||||
|
||||
try {
|
||||
if (input.attach) await transportTask
|
||||
if (input.attach) await endpointTask
|
||||
const sdk = OpenCode.make({
|
||||
baseUrl: "http://opencode.pending",
|
||||
fetch: deferredFetch(transportTask),
|
||||
fetch: deferredFetch(endpointTask),
|
||||
})
|
||||
const attachedSession =
|
||||
input.attach && input.session && !input.directory
|
||||
? await sdk.session.get({ sessionID: input.session }).catch(() => fail("Session not found"))
|
||||
: undefined
|
||||
const resolvedDirectory =
|
||||
directory ?? attachedSession?.location.directory ?? (await remoteDirectory(await transportTask, sdk))
|
||||
directory ?? attachedSession?.location.directory ?? (await remoteDirectory(await endpointTask, sdk))
|
||||
const model = parseModel(input.model)
|
||||
let agentTask: Promise<string | undefined> | undefined
|
||||
const resolveAgent = () => {
|
||||
|
|
@ -120,11 +120,10 @@ function localDirectory(directory?: string): string {
|
|||
}
|
||||
}
|
||||
|
||||
function startTransport(input: MiniCommandInput): Promise<Transport> {
|
||||
function startEndpoint(input: MiniCommandInput): Promise<Service.Endpoint> {
|
||||
if (input.attach) {
|
||||
return Effect.runPromise(
|
||||
Daemon.transport({
|
||||
mode: "attach",
|
||||
Daemon.connect({
|
||||
url: input.attach,
|
||||
password: input.password ?? process.env.OPENCODE_SERVER_PASSWORD,
|
||||
username: input.username ?? process.env.OPENCODE_SERVER_USERNAME,
|
||||
|
|
@ -132,31 +131,36 @@ function startTransport(input: MiniCommandInput): Promise<Transport> {
|
|||
)
|
||||
}
|
||||
return Effect.runPromise(
|
||||
Daemon.transport({ mode: "shared", command: input.serverCommand }).pipe(
|
||||
Effect.gen(function* () {
|
||||
const options = yield* ServiceConfig.options()
|
||||
return yield* Service.start(
|
||||
input.serverCommand === undefined ? options : { ...options, command: input.serverCommand },
|
||||
)
|
||||
}).pipe(
|
||||
Effect.provide(NodeFileSystem.layer),
|
||||
Effect.provide(Global.layerWith({})),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function deferredFetch(transportTask: Promise<{ url: string; headers?: HeadersInit }>): typeof globalThis.fetch {
|
||||
function deferredFetch(endpointTask: Promise<Service.Endpoint>): typeof globalThis.fetch {
|
||||
const fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const transport = await transportTask
|
||||
const endpoint = await endpointTask
|
||||
const request = new Request(input, init)
|
||||
const source = new URL(request.url)
|
||||
const headers = new Headers(request.headers)
|
||||
for (const [key, value] of new Headers(transport.headers)) headers.set(key, value)
|
||||
return globalThis.fetch(new Request(new URL(source.pathname + source.search, transport.url), request), { headers })
|
||||
for (const [key, value] of new Headers(Service.headers(endpoint))) headers.set(key, value)
|
||||
return globalThis.fetch(new Request(new URL(source.pathname + source.search, endpoint.url), request), { headers })
|
||||
}
|
||||
return fetch as typeof globalThis.fetch
|
||||
}
|
||||
|
||||
async function remoteDirectory(
|
||||
transport: { url: string; headers?: HeadersInit },
|
||||
endpoint: Service.Endpoint,
|
||||
sdk: OpenCodeClient,
|
||||
): Promise<string> {
|
||||
const location = await sdk.location.get()
|
||||
if (!location.directory) throw new Error(`Failed to resolve remote directory from ${transport.url}`)
|
||||
if (!location.directory) throw new Error(`Failed to resolve remote directory from ${endpoint.url}`)
|
||||
return location.directory
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { Service } from "@opencode-ai/client/effect"
|
||||
import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/promise"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
|
|
@ -7,6 +8,7 @@ import { Effect } from "effect"
|
|||
import { open } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { Daemon } from "../daemon"
|
||||
import { ServiceConfig } from "../services/service-config"
|
||||
import { Standalone } from "../services/standalone"
|
||||
import { loadRunAgents, waitForCatalogReady } from "./catalog.shared"
|
||||
import { runNonInteractivePrompt } from "./noninteractive"
|
||||
|
|
@ -39,8 +41,6 @@ type FilePart = {
|
|||
mime: string
|
||||
}
|
||||
|
||||
type Transport = { readonly url: string; readonly headers?: HeadersInit }
|
||||
|
||||
type Prepared = {
|
||||
directory?: string
|
||||
message: string
|
||||
|
|
@ -67,17 +67,17 @@ async function run(input: RunCommandInput) {
|
|||
return Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const transport = yield* Standalone.transport({ command: input.standaloneCommand })
|
||||
yield* Effect.promise(() => execute(input, prepared, transport))
|
||||
const endpoint = yield* Standalone.start({ command: input.standaloneCommand })
|
||||
yield* Effect.promise(() => execute(input, prepared, endpoint))
|
||||
}),
|
||||
),
|
||||
)
|
||||
const transport = await startTransport(input)
|
||||
return execute(input, prepared, transport)
|
||||
const endpoint = await startEndpoint(input)
|
||||
return execute(input, prepared, endpoint)
|
||||
}
|
||||
|
||||
async function execute(input: RunCommandInput, prepared: Prepared, transport: Transport) {
|
||||
const client = OpenCode.make({ baseUrl: transport.url, headers: transport.headers })
|
||||
async function execute(input: RunCommandInput, prepared: Prepared, endpoint: Service.Endpoint) {
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const requestedDirectory = prepared.directory ?? (await client.location.get()).directory
|
||||
if (!requestedDirectory) fail("Failed to resolve server directory")
|
||||
const session = await selectSession(client, requestedDirectory, input)
|
||||
|
|
@ -163,11 +163,10 @@ function localDirectory(directory: string | undefined, root: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function startTransport(input: RunCommandInput) {
|
||||
function startEndpoint(input: RunCommandInput) {
|
||||
if (input.server) {
|
||||
return Effect.runPromise(
|
||||
Daemon.transport({
|
||||
mode: "attach",
|
||||
Daemon.connect({
|
||||
url: input.server,
|
||||
password: input.password,
|
||||
username: input.username,
|
||||
|
|
@ -175,7 +174,9 @@ function startTransport(input: RunCommandInput) {
|
|||
)
|
||||
}
|
||||
return Effect.runPromise(
|
||||
Daemon.transport({ mode: "shared" }).pipe(
|
||||
Effect.gen(function* () {
|
||||
return yield* Service.start(yield* ServiceConfig.options())
|
||||
}).pipe(
|
||||
Effect.provide(NodeFileSystem.layer),
|
||||
Effect.provide(Global.layerWith({})),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue