From 7739cc53b4c4ad78621103032f0d94a9d76a7252 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Tue, 28 Apr 2026 11:02:35 -0400 Subject: [PATCH] refactor(httpapi): fork server startup by flag (#24799) --- packages/opencode/.gitignore | 1 + packages/opencode/src/cli/cmd/generate.ts | 19 ++- packages/opencode/src/plugin/index.ts | 2 +- packages/opencode/src/server/adapter.bun.ts | 62 +++++---- packages/opencode/src/server/adapter.node.ts | 115 ++++++++-------- packages/opencode/src/server/adapter.ts | 5 + .../server/routes/instance/httpapi/server.ts | 4 +- .../src/server/routes/instance/index.ts | 126 +----------------- packages/opencode/src/server/server.ts | 42 ++++-- .../test/server/httpapi-bridge.test.ts | 91 +------------ .../test/server/httpapi-config.test.ts | 6 +- .../test/server/httpapi-event.test.ts | 6 +- .../test/server/httpapi-experimental.test.ts | 6 +- .../test/server/httpapi-instance.test.ts | 6 +- .../test/server/httpapi-json-parity.test.ts | 15 +-- .../opencode/test/server/httpapi-mcp.test.ts | 9 +- .../test/server/httpapi-provider.test.ts | 8 +- .../opencode/test/server/httpapi-pty.test.ts | 6 +- .../test/server/httpapi-session.test.ts | 6 +- .../opencode/test/server/httpapi-sync.test.ts | 6 +- .../opencode/test/server/httpapi-tui.test.ts | 5 +- .../test/server/httpapi-workspace.test.ts | 6 +- packages/sdk/js/script/build.ts | 9 +- 23 files changed, 190 insertions(+), 371 deletions(-) diff --git a/packages/opencode/.gitignore b/packages/opencode/.gitignore index 2b20d9c312..6600814a8c 100644 --- a/packages/opencode/.gitignore +++ b/packages/opencode/.gitignore @@ -7,3 +7,4 @@ src/provider/models-snapshot.js src/provider/models-snapshot.d.ts script/build-*.ts temporary-*.md +.artifacts diff --git a/packages/opencode/src/cli/cmd/generate.ts b/packages/opencode/src/cli/cmd/generate.ts index 0531d537c2..768002957d 100644 --- a/packages/opencode/src/cli/cmd/generate.ts +++ b/packages/opencode/src/cli/cmd/generate.ts @@ -1,15 +1,26 @@ import { Server } from "../../server/server" +import { PublicApi } from "../../server/routes/instance/httpapi/public" import type { CommandModule } from "yargs" +import { OpenApi } from "effect/unstable/httpapi" + +type Args = { + httpapi: boolean +} export const GenerateCommand = { command: "generate", - handler: async () => { - const specs = await Server.openapi() + builder: (yargs) => + yargs.option("httpapi", { + type: "boolean", + default: false, + description: "Generate OpenAPI from the experimental Effect HttpApi contract", + }), + handler: async (args) => { + const specs = args.httpapi ? OpenApi.fromApi(PublicApi) : await Server.openapi() for (const item of Object.values(specs.paths)) { for (const method of ["get", "post", "put", "delete", "patch"] as const) { const operation = item[method] if (!operation?.operationId) continue - // @ts-expect-error operation["x-codeSamples"] = [ { lang: "js", @@ -47,4 +58,4 @@ export const GenerateCommand = { }) }) }, -} satisfies CommandModule +} satisfies CommandModule diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index 465d053108..0313022c36 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -127,7 +127,7 @@ export const layer = Layer.effect( Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`, } : undefined, - fetch: async (...args) => (await Server.Default()).app.fetch(...args), + fetch: async (...args) => Server.Default().app.fetch(...args), }) const cfg = yield* config.get() const input: PluginInput = { diff --git a/packages/opencode/src/server/adapter.bun.ts b/packages/opencode/src/server/adapter.bun.ts index 3e70b97e8a..b1f3bae27a 100644 --- a/packages/opencode/src/server/adapter.bun.ts +++ b/packages/opencode/src/server/adapter.bun.ts @@ -1,40 +1,44 @@ import type { Hono } from "hono" import { createBunWebSocket } from "hono/bun" -import type { Adapter } from "./adapter" +import type { Adapter, FetchApp, Opts } from "./adapter" + +function listen(app: FetchApp, opts: Opts, websocket?: ReturnType["websocket"]) { + const start = (port: number) => { + try { + if (websocket) { + return Bun.serve({ fetch: app.fetch, hostname: opts.hostname, idleTimeout: 0, websocket, port }) + } + return Bun.serve({ fetch: app.fetch, hostname: opts.hostname, idleTimeout: 0, port }) + } catch { + return + } + } + const server = opts.port === 0 ? (start(4096) ?? start(0)) : start(opts.port) + if (!server) { + throw new Error(`Failed to start server on port ${opts.port}`) + } + if (!server.port) { + throw new Error(`Failed to resolve server address for port ${opts.port}`) + } + return { + port: server.port, + stop(close?: boolean) { + return Promise.resolve(server.stop(close)) + }, + } +} export const adapter: Adapter = { create(app: Hono) { const ws = createBunWebSocket() return { upgradeWebSocket: ws.upgradeWebSocket, - async listen(opts) { - const args = { - fetch: app.fetch, - hostname: opts.hostname, - idleTimeout: 0, - websocket: ws.websocket, - } as const - const start = (port: number) => { - try { - return Bun.serve({ ...args, port }) - } catch { - return - } - } - const server = opts.port === 0 ? (start(4096) ?? start(0)) : start(opts.port) - if (!server) { - throw new Error(`Failed to start server on port ${opts.port}`) - } - if (!server.port) { - throw new Error(`Failed to resolve server address for port ${opts.port}`) - } - return { - port: server.port, - stop(close?: boolean) { - return Promise.resolve(server.stop(close)) - }, - } - }, + listen: (opts) => Promise.resolve(listen(app, opts, ws.websocket)), + } + }, + createFetch(app) { + return { + listen: (opts) => Promise.resolve(listen(app, opts)), } }, } diff --git a/packages/opencode/src/server/adapter.node.ts b/packages/opencode/src/server/adapter.node.ts index 9c2a41cce2..2f6b2787f5 100644 --- a/packages/opencode/src/server/adapter.node.ts +++ b/packages/opencode/src/server/adapter.node.ts @@ -1,66 +1,73 @@ import { createAdaptorServer, type ServerType } from "@hono/node-server" import { createNodeWebSocket } from "@hono/node-ws" import type { Hono } from "hono" -import type { Adapter } from "./adapter" +import type { Adapter, FetchApp, Opts } from "./adapter" + +async function listen(app: FetchApp, opts: Opts, inject?: (server: ServerType) => void) { + const start = (port: number) => + new Promise((resolve, reject) => { + const server = createAdaptorServer({ fetch: app.fetch }) + inject?.(server) + const fail = (err: Error) => { + cleanup() + reject(err) + } + const ready = () => { + cleanup() + resolve(server) + } + const cleanup = () => { + server.off("error", fail) + server.off("listening", ready) + } + server.once("error", fail) + server.once("listening", ready) + server.listen(port, opts.hostname) + }) + + const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port) + const addr = server.address() + if (!addr || typeof addr === "string") { + throw new Error(`Failed to resolve server address for port ${opts.port}`) + } + + let closing: Promise | undefined + return { + port: addr.port, + stop(close?: boolean) { + closing ??= new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err) + return + } + resolve() + }) + if (close) { + if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") { + server.closeAllConnections() + } + if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") { + server.closeIdleConnections() + } + } + }) + return closing + }, + } +} export const adapter: Adapter = { create(app: Hono) { const ws = createNodeWebSocket({ app }) return { upgradeWebSocket: ws.upgradeWebSocket, - async listen(opts) { - const start = (port: number) => - new Promise((resolve, reject) => { - const server = createAdaptorServer({ fetch: app.fetch }) - ws.injectWebSocket(server) - const fail = (err: Error) => { - cleanup() - reject(err) - } - const ready = () => { - cleanup() - resolve(server) - } - const cleanup = () => { - server.off("error", fail) - server.off("listening", ready) - } - server.once("error", fail) - server.once("listening", ready) - server.listen(port, opts.hostname) - }) - - const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port) - const addr = server.address() - if (!addr || typeof addr === "string") { - throw new Error(`Failed to resolve server address for port ${opts.port}`) - } - - let closing: Promise | undefined - return { - port: addr.port, - stop(close?: boolean) { - closing ??= new Promise((resolve, reject) => { - server.close((err) => { - if (err) { - reject(err) - return - } - resolve() - }) - if (close) { - if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") { - server.closeAllConnections() - } - if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") { - server.closeIdleConnections() - } - } - }) - return closing - }, - } - }, + listen: (opts) => listen(app, opts, ws.injectWebSocket), + } + }, + createFetch(app) { + return { + listen: (opts) => listen(app, opts), } }, } diff --git a/packages/opencode/src/server/adapter.ts b/packages/opencode/src/server/adapter.ts index 272521d7d3..7f4edd2c17 100644 --- a/packages/opencode/src/server/adapter.ts +++ b/packages/opencode/src/server/adapter.ts @@ -1,6 +1,10 @@ import type { Hono } from "hono" import type { UpgradeWebSocket } from "hono/ws" +export type FetchApp = { + fetch(request: Request): Response | Promise +} + export type Opts = { port: number hostname: string @@ -18,4 +22,5 @@ export interface Runtime { export interface Adapter { create(app: Hono): Runtime + createFetch(app: FetchApp): Omit } diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 6a719d94be..5ab00d6a00 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -1,4 +1,4 @@ -import { Effect, Layer, Schema } from "effect" +import { Context, Effect, Layer, Schema } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" import { HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http" import { Bus } from "@/bus" @@ -41,6 +41,8 @@ const Headers = Schema.Struct({ "x-opencode-directory": Schema.optional(Schema.String), }) +export const context = Context.empty() as Context.Context + function decode(input: string) { try { return decodeURIComponent(input) diff --git a/packages/opencode/src/server/routes/instance/index.ts b/packages/opencode/src/server/routes/instance/index.ts index 68b508a9a7..fa11e3e90d 100644 --- a/packages/opencode/src/server/routes/instance/index.ts +++ b/packages/opencode/src/server/routes/instance/index.ts @@ -1,7 +1,7 @@ import { describeRoute, resolver, validator } from "hono-openapi" import { Hono } from "hono" import type { UpgradeWebSocket } from "hono/ws" -import { Context, Effect } from "effect" +import { Effect } from "effect" import z from "zod" import { Format } from "@/format" import { TuiRoutes } from "./tui" @@ -14,18 +14,6 @@ import { LSP } from "@/lsp/lsp" import { Command } from "@/command" import { QuestionRoutes } from "./question" import { PermissionRoutes } from "./permission" -import { Flag } from "@opencode-ai/core/flag/flag" -import { ExperimentalHttpApiServer } from "./httpapi/server" -import { PtyPaths } from "./httpapi/pty" -import { EventPaths } from "./httpapi/event" -import { ExperimentalPaths } from "./httpapi/experimental" -import { FilePaths } from "./httpapi/file" -import { InstancePaths } from "./httpapi/instance" -import { McpPaths } from "./httpapi/mcp" -import { SessionPaths } from "./httpapi/session" -import { SyncPaths } from "./httpapi/sync" -import { TuiPaths } from "./httpapi/tui" -import { WorkspacePaths } from "./httpapi/workspace" import { ProjectRoutes } from "./project" import { SessionRoutes } from "./session" import { PtyRoutes } from "./pty" @@ -42,118 +30,6 @@ import { jsonRequest } from "./trace" export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => { const app = new Hono() - if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { - const handler = ExperimentalHttpApiServer.webHandler().handler - const context = Context.empty() as Context.Context - app.get(EventPaths.event, (c) => handler(c.req.raw, context)) - app.get("/question", (c) => handler(c.req.raw, context)) - app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context)) - app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context)) - app.get("/permission", (c) => handler(c.req.raw, context)) - app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context)) - app.get("/config", (c) => handler(c.req.raw, context)) - app.patch("/config", (c) => handler(c.req.raw, context)) - app.get("/config/providers", (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.console, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.consoleOrgs, (c) => handler(c.req.raw, context)) - app.post(ExperimentalPaths.consoleSwitch, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.tool, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.toolIDs, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) - app.post(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) - app.delete(ExperimentalPaths.worktree, (c) => handler(c.req.raw, context)) - app.post(ExperimentalPaths.worktreeReset, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.session, (c) => handler(c.req.raw, context)) - app.get(ExperimentalPaths.resource, (c) => handler(c.req.raw, context)) - app.get("/provider", (c) => handler(c.req.raw, context)) - app.get("/provider/auth", (c) => handler(c.req.raw, context)) - app.post("/provider/:providerID/oauth/authorize", (c) => handler(c.req.raw, context)) - app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context)) - app.get("/project", (c) => handler(c.req.raw, context)) - app.get("/project/current", (c) => handler(c.req.raw, context)) - app.post("/project/git/init", (c) => handler(c.req.raw, context)) - app.patch("/project/:projectID", (c) => handler(c.req.raw, context)) - app.get(FilePaths.findText, (c) => handler(c.req.raw, context)) - app.get(FilePaths.findFile, (c) => handler(c.req.raw, context)) - app.get(FilePaths.findSymbol, (c) => handler(c.req.raw, context)) - app.get(FilePaths.list, (c) => handler(c.req.raw, context)) - app.get(FilePaths.content, (c) => handler(c.req.raw, context)) - app.get(FilePaths.status, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.path, (c) => handler(c.req.raw, context)) - app.post(InstancePaths.dispose, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.vcs, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.vcsDiff, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.command, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.agent, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.skill, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.lsp, (c) => handler(c.req.raw, context)) - app.get(InstancePaths.formatter, (c) => handler(c.req.raw, context)) - app.get(McpPaths.status, (c) => handler(c.req.raw, context)) - app.post(McpPaths.status, (c) => handler(c.req.raw, context)) - app.post(McpPaths.auth, (c) => handler(c.req.raw, context)) - app.post(McpPaths.authCallback, (c) => handler(c.req.raw, context)) - app.post(McpPaths.authAuthenticate, (c) => handler(c.req.raw, context)) - app.delete(McpPaths.auth, (c) => handler(c.req.raw, context)) - app.post(McpPaths.connect, (c) => handler(c.req.raw, context)) - app.post(McpPaths.disconnect, (c) => handler(c.req.raw, context)) - app.post(SyncPaths.start, (c) => handler(c.req.raw, context)) - app.post(SyncPaths.replay, (c) => handler(c.req.raw, context)) - app.post(SyncPaths.history, (c) => handler(c.req.raw, context)) - app.get(PtyPaths.shells, (c) => handler(c.req.raw, context)) - app.get(PtyPaths.list, (c) => handler(c.req.raw, context)) - app.post(PtyPaths.create, (c) => handler(c.req.raw, context)) - app.get(PtyPaths.get, (c) => handler(c.req.raw, context)) - app.put(PtyPaths.update, (c) => handler(c.req.raw, context)) - app.delete(PtyPaths.remove, (c) => handler(c.req.raw, context)) - app.get(PtyPaths.connect, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.list, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.status, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.get, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.children, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.todo, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.diff, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.messages, (c) => handler(c.req.raw, context)) - app.get(SessionPaths.message, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.create, (c) => handler(c.req.raw, context)) - app.delete(SessionPaths.remove, (c) => handler(c.req.raw, context)) - app.patch(SessionPaths.update, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.init, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.fork, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.abort, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.share, (c) => handler(c.req.raw, context)) - app.delete(SessionPaths.share, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.summarize, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.prompt, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.promptAsync, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.command, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.shell, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.revert, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.unrevert, (c) => handler(c.req.raw, context)) - app.post(SessionPaths.permissions, (c) => handler(c.req.raw, context)) - app.delete(SessionPaths.deleteMessage, (c) => handler(c.req.raw, context)) - app.delete(SessionPaths.deletePart, (c) => handler(c.req.raw, context)) - app.patch(SessionPaths.updatePart, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.appendPrompt, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.openHelp, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.openSessions, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.openThemes, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.openModels, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.submitPrompt, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.clearPrompt, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.executeCommand, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.showToast, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.publish, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.selectSession, (c) => handler(c.req.raw, context)) - app.get(TuiPaths.controlNext, (c) => handler(c.req.raw, context)) - app.post(TuiPaths.controlResponse, (c) => handler(c.req.raw, context)) - app.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context)) - app.post(WorkspacePaths.list, (c) => handler(c.req.raw, context)) - app.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) - app.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) - app.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context)) - app.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context)) - } - return app .route("/project", ProjectRoutes()) .route("/pty", PtyRoutes(upgrade)) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 7d5373dd96..92d844fbfe 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -17,8 +17,6 @@ import { WorkspaceRouterMiddleware } from "./workspace" import { InstanceMiddleware } from "./routes/instance/middleware" import { WorkspaceRoutes } from "./routes/control/workspace" import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server" -import { WorkspacePaths } from "./routes/instance/httpapi/workspace" -import { Context } from "effect" // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85 globalThis.AI_SDK_LOG_WARNINGS = false @@ -34,9 +32,35 @@ export type Listener = { stop: (close?: boolean) => Promise } -export const Default = lazy(() => create({})) +type ServerApp = { + fetch(request: Request): Response | Promise + request(input: string | URL | Request, init?: RequestInit): Response | Promise +} + +const DefaultHono = lazy(() => createHono({})) +const DefaultHttpApi = lazy(() => createHttpApi()) +export const Default = () => (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI ? DefaultHttpApi() : DefaultHono()) function create(opts: { cors?: string[] }) { + if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return createHttpApi() + return createHono(opts) +} + +function createHttpApi() { + const handler = ExperimentalHttpApiServer.webHandler().handler + const app: ServerApp = { + fetch: (request: Request) => handler(request, ExperimentalHttpApiServer.context), + request(input, init) { + return app.fetch(input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init)) + }, + } + return { + app, + runtime: adapter.createFetch(app), + } +} + +function createHono(opts: { cors?: string[] }) { const app = new Hono() .onError(ErrorMiddleware) .use(AuthMiddleware) @@ -62,16 +86,6 @@ function create(opts: { cors?: string[] }) { .use(InstanceMiddleware()) .route("/experimental/workspace", WorkspaceRoutes()) .use(WorkspaceRouterMiddleware(runtime.upgradeWebSocket)) - if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) { - const handler = ExperimentalHttpApiServer.webHandler().handler - const context = Context.empty() as Context.Context - workspaceApp.get(WorkspacePaths.adaptors, (c) => handler(c.req.raw, context)) - workspaceApp.get(WorkspacePaths.list, (c) => handler(c.req.raw, context)) - workspaceApp.post(WorkspacePaths.list, (c) => handler(c.req.raw, context)) - workspaceApp.get(WorkspacePaths.status, (c) => handler(c.req.raw, context)) - workspaceApp.delete(WorkspacePaths.remove, (c) => handler(c.req.raw, context)) - workspaceApp.post(WorkspacePaths.sessionRestore, (c) => handler(c.req.raw, context)) - } workspaceApp.route("/", workspaceLegacyApp) return { @@ -89,7 +103,7 @@ export async function openapi() { // hono-openapi can see describeRoute metadata (`.route()` wraps // handlers when the sub-app has a custom errorHandler, which // strips the metadata symbol). - const { app } = create({}) + const { app } = createHono({}) const result = await generateSpecs(app, { documentation: { info: { diff --git a/packages/opencode/test/server/httpapi-bridge.test.ts b/packages/opencode/test/server/httpapi-bridge.test.ts index c0482293b1..150cf53c04 100644 --- a/packages/opencode/test/server/httpapi-bridge.test.ts +++ b/packages/opencode/test/server/httpapi-bridge.test.ts @@ -1,28 +1,11 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" -import { WorkspaceRoutes } from "../../src/server/routes/control/workspace" -import { ConfigApi } from "../../src/server/routes/instance/httpapi/config" -import { EventPaths } from "../../src/server/routes/instance/httpapi/event" -import { ExperimentalApi } from "../../src/server/routes/instance/httpapi/experimental" import { FileApi, FilePaths } from "../../src/server/routes/instance/httpapi/file" -import { InstanceApi } from "../../src/server/routes/instance/httpapi/instance" -import { McpApi } from "../../src/server/routes/instance/httpapi/mcp" -import { PermissionApi } from "../../src/server/routes/instance/httpapi/permission" -import { ProjectApi } from "../../src/server/routes/instance/httpapi/project" -import { ProviderApi } from "../../src/server/routes/instance/httpapi/provider" -import { PtyApi, PtyPaths } from "../../src/server/routes/instance/httpapi/pty" -import { QuestionApi } from "../../src/server/routes/instance/httpapi/question" -import { SessionApi } from "../../src/server/routes/instance/httpapi/session" -import { SyncApi } from "../../src/server/routes/instance/httpapi/sync" -import { TuiApi } from "../../src/server/routes/instance/httpapi/tui" -import { WorkspaceApi } from "../../src/server/routes/instance/httpapi/workspace" import { PublicApi } from "../../src/server/routes/instance/httpapi/public" import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" -import { HttpApi, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" +import { OpenApi } from "effect/unstable/httpapi" import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" @@ -34,48 +17,13 @@ const original = { OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME, } -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket const methods = ["get", "post", "put", "delete", "patch"] as const function app(input?: { password?: string; username?: string }) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true Flag.OPENCODE_SERVER_PASSWORD = input?.password Flag.OPENCODE_SERVER_USERNAME = input?.username - return InstanceRoutes(websocket) -} - -function routeKey(route: ReturnType["routes"][number]) { - return `${route.method} ${route.path}` -} - -function reflectedHttpApiRoutes() { - const routes = [`GET ${EventPaths.event}`, `GET ${PtyPaths.connect}`] - - function addRoutes(api: HttpApi.HttpApi) { - HttpApi.reflect(api, { - onGroup() {}, - onEndpoint({ endpoint }) { - routes.push(`${endpoint.method} ${endpoint.path}`) - }, - }) - } - - addRoutes(ConfigApi) - addRoutes(ExperimentalApi) - addRoutes(FileApi) - addRoutes(InstanceApi) - addRoutes(McpApi) - addRoutes(PermissionApi) - addRoutes(ProjectApi) - addRoutes(ProviderApi) - addRoutes(PtyApi) - addRoutes(QuestionApi) - addRoutes(SessionApi) - addRoutes(SyncApi) - addRoutes(TuiApi) - addRoutes(WorkspaceApi) - - return [...new Set(routes)] + return Server.Default().app } function openApiRouteKeys(spec: { paths: Record>> }) { @@ -106,40 +54,7 @@ afterEach(async () => { await resetDatabase() }) -describe("HttpApi Hono bridge", () => { - test("mounts experimental handlers for every legacy instance route", () => { - Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false - const legacy = InstanceRoutes(websocket) - Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - const experimental = InstanceRoutes(websocket) - - const bridge = experimental.routes.slice(0, experimental.routes.length - legacy.routes.length) - const workspaceRoutes = WorkspaceRoutes().routes.map((route) => ({ - ...route, - path: `/experimental/workspace${route.path === "/" ? "" : route.path}`, - })) - const legacyRoutes = [...new Set([...legacy.routes, ...workspaceRoutes].map(routeKey))] - const bridgeRoutes = new Set(bridge.map(routeKey)) - - expect(legacyRoutes.filter((route) => !bridgeRoutes.has(route))).toEqual([]) - expect([...bridgeRoutes].filter((route) => !legacyRoutes.includes(route)).sort()).toEqual([]) - }) - - test("mounts every Effect HttpApi route through the Hono bridge", () => { - Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false - const legacy = InstanceRoutes(websocket) - Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - const experimental = InstanceRoutes(websocket) - - const bridgeRoutes = new Set( - experimental.routes.slice(0, experimental.routes.length - legacy.routes.length).map(routeKey), - ) - const httpApiRoutes = reflectedHttpApiRoutes() - - expect(httpApiRoutes.filter((route) => !bridgeRoutes.has(route))).toEqual([]) - expect([...bridgeRoutes].filter((route) => !httpApiRoutes.includes(route)).sort()).toEqual([]) - }) - +describe("HttpApi server", () => { test("covers every generated OpenAPI route with Effect HttpApi contracts", async () => { const honoRoutes = openApiRouteKeys(await Server.openapi()) const effectRoutes = openApiRouteKeys(OpenApi.fromApi(PublicApi)) diff --git a/packages/opencode/test/server/httpapi-config.test.ts b/packages/opencode/test/server/httpapi-config.test.ts index 10a1684144..9469a66fd5 100644 --- a/packages/opencode/test/server/httpapi-config.test.ts +++ b/packages/opencode/test/server/httpapi-config.test.ts @@ -1,10 +1,9 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import path from "path" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" @@ -12,11 +11,10 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } async function waitDisposed(directory: string) { diff --git a/packages/opencode/test/server/httpapi-event.test.ts b/packages/opencode/test/server/httpapi-event.test.ts index 4930ce7e78..6fe92a2346 100644 --- a/packages/opencode/test/server/httpapi-event.test.ts +++ b/packages/opencode/test/server/httpapi-event.test.ts @@ -1,8 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { EventPaths } from "../../src/server/routes/instance/httpapi/event" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" @@ -11,11 +10,10 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } async function readFirstChunk(response: Response) { diff --git a/packages/opencode/test/server/httpapi-experimental.test.ts b/packages/opencode/test/server/httpapi-experimental.test.ts index cf02420486..3978631b87 100644 --- a/packages/opencode/test/server/httpapi-experimental.test.ts +++ b/packages/opencode/test/server/httpapi-experimental.test.ts @@ -1,10 +1,9 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" import { Session } from "@/session/session" import { Database } from "@/storage/db" @@ -16,12 +15,11 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket const testWorktreeMutations = process.platform === "win32" ? test.skip : test function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } function runSession(fx: Effect.Effect) { diff --git a/packages/opencode/test/server/httpapi-instance.test.ts b/packages/opencode/test/server/httpapi-instance.test.ts index 65814ebde2..4ab1da11e6 100644 --- a/packages/opencode/test/server/httpapi-instance.test.ts +++ b/packages/opencode/test/server/httpapi-instance.test.ts @@ -1,10 +1,9 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import path from "path" import { Flag } from "@opencode-ai/core/flag/flag" import { GlobalBus } from "@/bus/global" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { InstancePaths } from "../../src/server/routes/instance/httpapi/instance" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" @@ -13,11 +12,10 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } async function waitDisposed(directory: string) { diff --git a/packages/opencode/test/server/httpapi-json-parity.test.ts b/packages/opencode/test/server/httpapi-json-parity.test.ts index d7d252a5e1..555c717cf0 100644 --- a/packages/opencode/test/server/httpapi-json-parity.test.ts +++ b/packages/opencode/test/server/httpapi-json-parity.test.ts @@ -1,10 +1,9 @@ import { afterEach, describe, expect } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { ModelID, ProviderID } from "../../src/provider/schema" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { ExperimentalPaths } from "../../src/server/routes/instance/httpapi/experimental" import { SessionPaths } from "../../src/server/routes/instance/httpapi/session" import { MessageID, PartID } from "../../src/session/schema" @@ -17,12 +16,12 @@ import { it } from "../lib/effect" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app(experimental: boolean) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental - return InstanceRoutes(websocket) + return Server.Default().app } +type TestApp = ReturnType function pathFor(path: string, params: Record) { return Object.entries(params).reduce((result, [key, value]) => result.replace(`:${key}`, value), path) @@ -60,9 +59,9 @@ function withTmp( ).pipe(Effect.flatMap((tmp) => fn(tmp).pipe(provideInstance(tmp.path)))) } -function readJson(label: string, app: ReturnType, path: string, headers: HeadersInit) { +function readJson(label: string, serverApp: TestApp, path: string, headers: HeadersInit) { return Effect.promise(async () => { - const response = await app.request(path, { headers }) + const response = await serverApp.request(path, { headers }) if (response.status !== 200) throw new Error(`${label} returned ${response.status}: ${await response.text()}`) return await response.json() }) @@ -70,8 +69,8 @@ function readJson(label: string, app: ReturnType, path: s function expectJsonParity(input: { label: string - legacy: ReturnType - httpapi: ReturnType + legacy: TestApp + httpapi: TestApp path: string headers: HeadersInit }) { diff --git a/packages/opencode/test/server/httpapi-mcp.test.ts b/packages/opencode/test/server/httpapi-mcp.test.ts index 6d6314dfee..d6dbc56192 100644 --- a/packages/opencode/test/server/httpapi-mcp.test.ts +++ b/packages/opencode/test/server/httpapi-mcp.test.ts @@ -1,12 +1,11 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Context, Effect, FileSystem, Layer, Path } from "effect" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Flag } from "@opencode-ai/core/flag/flag" import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server" import { McpPaths } from "../../src/server/routes/instance/httpapi/mcp" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" import { provideInstance, tmpdir } from "../fixture/fixture" @@ -16,13 +15,13 @@ void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI const context = Context.empty() as Context.Context -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)) function app(experimental: boolean) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental - return InstanceRoutes(websocket) + return Server.Default().app } +type TestApp = ReturnType function request(route: string, directory: string, init?: RequestInit) { const headers = new Headers(init?.headers) @@ -66,7 +65,7 @@ function withMcpProject(self: (dir: string) => Effect.Effect) } const readResponse = Effect.fnUntraced(function* (input: { - app: ReturnType + app: TestApp path: string headers: HeadersInit }) { diff --git a/packages/opencode/test/server/httpapi-provider.test.ts b/packages/opencode/test/server/httpapi-provider.test.ts index 8a07935146..8d03311d91 100644 --- a/packages/opencode/test/server/httpapi-provider.test.ts +++ b/packages/opencode/test/server/httpapi-provider.test.ts @@ -1,10 +1,9 @@ import { afterEach, describe, expect } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Effect, FileSystem, Layer, Path } from "effect" import { NodeFileSystem, NodePath } from "@effect/platform-node" import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" import { provideInstance } from "../fixture/fixture" @@ -13,7 +12,6 @@ import { testEffect } from "../lib/effect" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)) const providerID = "test-oauth-parity" const oauthURL = "https://example.com/oauth" @@ -21,11 +19,11 @@ const oauthInstructions = "Finish OAuth" function app(experimental: boolean) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental - return InstanceRoutes(websocket) + return Server.Default().app } function requestAuthorize(input: { - app: ReturnType + app: ReturnType providerID: string method: number headers: HeadersInit diff --git a/packages/opencode/test/server/httpapi-pty.test.ts b/packages/opencode/test/server/httpapi-pty.test.ts index ffaea3b751..87e2a94120 100644 --- a/packages/opencode/test/server/httpapi-pty.test.ts +++ b/packages/opencode/test/server/httpapi-pty.test.ts @@ -1,9 +1,8 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Flag } from "@opencode-ai/core/flag/flag" import { PtyID } from "../../src/pty/schema" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { PtyPaths } from "../../src/server/routes/instance/httpapi/pty" import * as Log from "@opencode-ai/core/util/log" import { resetDatabase } from "../fixture/db" @@ -12,12 +11,11 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket const testPty = process.platform === "win32" ? test.skip : test function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } afterEach(async () => { diff --git a/packages/opencode/test/server/httpapi-session.test.ts b/packages/opencode/test/server/httpapi-session.test.ts index aa7e33a034..3e3fb35731 100644 --- a/packages/opencode/test/server/httpapi-session.test.ts +++ b/packages/opencode/test/server/httpapi-session.test.ts @@ -1,11 +1,10 @@ import { afterEach, describe, expect } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { PermissionID } from "../../src/permission/schema" import { ModelID, ProviderID } from "../../src/provider/schema" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { SessionPaths } from "../../src/server/routes/instance/httpapi/session" import { Session } from "@/session/session" import { MessageID, PartID, type SessionID } from "../../src/session/schema" @@ -18,11 +17,10 @@ import { it } from "../lib/effect" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } function runSession(fx: Effect.Effect) { diff --git a/packages/opencode/test/server/httpapi-sync.test.ts b/packages/opencode/test/server/httpapi-sync.test.ts index 692dee002c..2758191057 100644 --- a/packages/opencode/test/server/httpapi-sync.test.ts +++ b/packages/opencode/test/server/httpapi-sync.test.ts @@ -1,9 +1,8 @@ import { afterEach, describe, expect, test } from "bun:test" -import type { UpgradeWebSocket } from "hono/ws" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { SyncPaths } from "../../src/server/routes/instance/httpapi/sync" import { Session } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" @@ -14,11 +13,10 @@ void Log.init({ print: false }) const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app(httpapi = true) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = httpapi - return InstanceRoutes(websocket) + return Server.Default().app } function runSession(fx: Effect.Effect) { diff --git a/packages/opencode/test/server/httpapi-tui.test.ts b/packages/opencode/test/server/httpapi-tui.test.ts index e6364fc88d..81a2105095 100644 --- a/packages/opencode/test/server/httpapi-tui.test.ts +++ b/packages/opencode/test/server/httpapi-tui.test.ts @@ -1,10 +1,8 @@ import { afterEach, describe, expect, test } from "bun:test" import type { Context } from "hono" -import type { UpgradeWebSocket } from "hono/ws" import { Flag } from "@opencode-ai/core/flag/flag" import { SessionID } from "../../src/session/schema" import { Instance } from "../../src/project/instance" -import { InstanceRoutes } from "../../src/server/routes/instance" import { TuiApi, TuiPaths } from "../../src/server/routes/instance/httpapi/tui" import { callTui } from "../../src/server/routes/instance/tui" import { Server } from "../../src/server/server" @@ -16,11 +14,10 @@ import { tmpdir } from "../fixture/fixture" void Log.init({ print: false }) const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function app() { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true - return InstanceRoutes(websocket) + return Server.Default().app } async function expectTrue(path: string, headers: Record, body?: unknown) { diff --git a/packages/opencode/test/server/httpapi-workspace.test.ts b/packages/opencode/test/server/httpapi-workspace.test.ts index 180d83ee4d..cb549c6497 100644 --- a/packages/opencode/test/server/httpapi-workspace.test.ts +++ b/packages/opencode/test/server/httpapi-workspace.test.ts @@ -2,7 +2,6 @@ import { afterEach, describe, expect, test } from "bun:test" import { mkdir } from "node:fs/promises" import path from "node:path" import { Effect } from "effect" -import type { UpgradeWebSocket } from "hono/ws" import { Flag } from "@opencode-ai/core/flag/flag" import { registerAdaptor } from "../../src/control-plane/adaptors" import type { WorkspaceAdaptor } from "../../src/control-plane/types" @@ -10,7 +9,7 @@ import { Workspace } from "../../src/control-plane/workspace" import { WorkspacePaths } from "../../src/server/routes/instance/httpapi/workspace" import { Session } from "@/session/session" import * as Log from "@opencode-ai/core/util/log" -import { InstanceRoutes } from "../../src/server/routes/instance" +import { Server } from "../../src/server/server" import { resetDatabase } from "../fixture/db" import { tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" @@ -19,13 +18,12 @@ void Log.init({ print: false }) const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI -const websocket = (() => () => new Response(null, { status: 501 })) as unknown as UpgradeWebSocket function request(path: string, directory: string, init: RequestInit = {}) { Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true const headers = new Headers(init.headers) headers.set("x-opencode-directory", directory) - return InstanceRoutes(websocket).request(path, { ...init, headers }) + return Server.Default().app.request(path, { ...init, headers }) } function runSession(fx: Effect.Effect) { diff --git a/packages/sdk/js/script/build.ts b/packages/sdk/js/script/build.ts index 268233a012..e920cc0fdb 100755 --- a/packages/sdk/js/script/build.ts +++ b/packages/sdk/js/script/build.ts @@ -9,7 +9,14 @@ import path from "path" import { createClient } from "@hey-api/openapi-ts" -await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode")) +const openapiSource = process.env.OPENCODE_SDK_OPENAPI === "httpapi" ? "httpapi" : "hono" +const opencode = path.resolve(dir, "../../opencode") + +if (openapiSource === "httpapi") { + await $`bun dev generate --httpapi > ${dir}/openapi.json`.cwd(opencode) +} else { + await $`bun dev generate > ${dir}/openapi.json`.cwd(opencode) +} await createClient({ input: "./openapi.json",