diff --git a/bun.lock b/bun.lock index 3087110df5..0f044f0791 100644 --- a/bun.lock +++ b/bun.lock @@ -101,6 +101,7 @@ "dependencies": { "@effect/platform-node": "catalog:", "@opencode-ai/client": "workspace:*", + "@opencode-ai/codemode": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/schema": "workspace:*", @@ -813,7 +814,6 @@ "version": "1.17.14", "dependencies": { "@effect/platform-node": "catalog:", - "@opencode-ai/codemode": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", "@opencode-ai/simulation": "workspace:*", diff --git a/packages/cli/package.json b/packages/cli/package.json index e11986e5b2..fdd5f49666 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "dependencies": { "@effect/platform-node": "catalog:", "@opencode-ai/client": "workspace:*", + "@opencode-ai/codemode": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/plugin": "workspace:*", "@opencode-ai/schema": "workspace:*", diff --git a/packages/server/src/code-mode.ts b/packages/cli/src/code-mode.ts similarity index 87% rename from packages/server/src/code-mode.ts rename to packages/cli/src/code-mode.ts index 3976a5bb03..55784971e8 100644 --- a/packages/server/src/code-mode.ts +++ b/packages/cli/src/code-mode.ts @@ -1,18 +1,18 @@ -export * as ServerCodeMode from "./code-mode" +export * as CodeModeHost from "./code-mode" import { NodeHttpClient } from "@effect/platform-node" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { ToolRegistry } from "@opencode-ai/core/tool/registry" import { OpenAPI, Tool } from "@opencode-ai/codemode" +import { Api } from "@opencode-ai/server/api" +import { ServerAuth } from "@opencode-ai/server/auth" import { Effect, Layer } from "effect" import { HttpClient, HttpClientRequest } from "effect/unstable/http" import { OpenApi } from "effect/unstable/httpapi" import type { Server } from "node:http" -import { Api } from "./api" -import { ServerAuth } from "./auth" -export function replacement(server: Server, password: string): LayerNode.Replacement { - return ToolRegistry.codeModeReplacement(makeTools(client(server), password)) +export function replacements(server: Server, password: string): LayerNode.Replacements { + return [ToolRegistry.codeModeReplacement(makeTools(client(server), password))] } export function makeTools(client: Layer.Layer, password: string): ToolRegistry.CodeModeTools { diff --git a/packages/cli/src/server-process.ts b/packages/cli/src/server-process.ts index 48471a573a..2d5f7261e4 100644 --- a/packages/cli/src/server-process.ts +++ b/packages/cli/src/server-process.ts @@ -13,6 +13,7 @@ import { randomBytes, randomUUID } from "node:crypto" import path from "node:path" import { Effect, FileSystem, Logger, Option, Redacted, Schedule, Schema } from "effect" import { HttpServer } from "effect/unstable/http" +import { CodeModeHost } from "./code-mode" import { Env } from "./env" import { ServiceConfig } from "./services/service-config" import { Updater } from "./services/updater" @@ -63,6 +64,7 @@ const processEffect = Effect.fnUntraced(function* (options: Options) { hostname: options.hostname ?? config.hostname ?? "127.0.0.1", port: Option.fromNullishOr(options.port ?? config.port), password, + replacements: (server) => CodeModeHost.replacements(server, password), }).pipe(Effect.provide(Logger.layer([], { mergeWithExisting: false }))) if (options.mode === "service") yield* register(address, password) const url = HttpServer.formatAddress(address) diff --git a/packages/server/test/code-mode.test.ts b/packages/cli/test/code-mode.test.ts similarity index 84% rename from packages/server/test/code-mode.test.ts rename to packages/cli/test/code-mode.test.ts index 671900a6f7..54f7c1cf0e 100644 --- a/packages/server/test/code-mode.test.ts +++ b/packages/cli/test/code-mode.test.ts @@ -2,9 +2,9 @@ import { expect, test } from "bun:test" import { CodeMode } from "@opencode-ai/codemode" import { Effect, Layer } from "effect" import { HttpClient, HttpClientResponse } from "effect/unstable/http" -import { ServerCodeMode } from "../src/code-mode" +import { CodeModeHost } from "../src/code-mode" -test("exposes the authenticated server API through CodeMode", async () => { +test("exposes the authenticated OpenCode API through CodeMode", async () => { const requests: Array<{ readonly url: string; readonly authorization?: string }> = [] const client = Layer.succeed( HttpClient.HttpClient, @@ -21,7 +21,7 @@ test("exposes the authenticated server API through CodeMode", async () => { ) }), ) - const result = await CodeMode.make({ tools: ServerCodeMode.makeTools(client, "secret") }) + const result = await CodeMode.make({ tools: CodeModeHost.makeTools(client, "secret") }) .execute("return await tools.opencode.v2.health.get({})") .pipe(Effect.runPromise) diff --git a/packages/server/package.json b/packages/server/package.json index 5ffcd7d4fa..6a0fc15ad3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -13,7 +13,6 @@ }, "dependencies": { "@effect/platform-node": "catalog:", - "@opencode-ai/codemode": "workspace:*", "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", "@opencode-ai/simulation": "workspace:*", diff --git a/packages/server/src/process.ts b/packages/server/src/process.ts index e62c29f3e2..dea9a3f30d 100644 --- a/packages/server/src/process.ts +++ b/packages/server/src/process.ts @@ -10,15 +10,15 @@ import { HealthGroup } from "@opencode-ai/protocol/groups/health" import { Context, Effect, Layer, Option } from "effect" import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http" import { HttpApi, HttpApiClient } from "effect/unstable/httpapi" -import { createServer } from "node:http" +import { createServer, type Server } from "node:http" import { ServerAuth } from "./auth" -import { ServerCodeMode } from "./code-mode" import { createRoutes } from "./routes" export type Options = { readonly hostname: string readonly port: Option.Option readonly password: string + readonly replacements?: (server: Server) => LayerNode.Replacements } const ReadinessApi = HttpApi.make("readiness").add(HealthGroup) @@ -43,21 +43,21 @@ export const start = Effect.fn("ServerProcess.start")(function* (options: Option }) function listen(options: Options) { - if (Option.isSome(options.port)) return bind(options.hostname, options.port.value, options.password) + if (Option.isSome(options.port)) return bind(options, options.port.value) const next = (port: number): ReturnType => - bind(options.hostname, port, options.password).pipe( + bind(options, port).pipe( Effect.catch((error) => (port === 65_535 ? Effect.fail(error) : next(port + 1))), ) return next(4096) } -function bind(hostname: string, port: number, password: string) { +function bind(options: Options, port: number) { const server = createServer() return Layer.build( - HttpRouter.serve(createRoutes(password, [ServerCodeMode.replacement(server, password)]), { + HttpRouter.serve(createRoutes(options.password, options.replacements?.(server)), { disableListenLog: true, }).pipe( - Layer.provideMerge(NodeHttpServer.layer(() => server, { port, host: hostname })), + Layer.provideMerge(NodeHttpServer.layer(() => server, { port, host: options.hostname })), Layer.provide(AppNodeBuilder.build(LayerNode.group([Credential.node, PermissionSaved.node, Project.node]))), ), ).pipe(