diff options
| author | Kit Langton <[email protected]> | 2026-04-30 17:43:18 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-30 17:43:18 -0400 |
| commit | 5518ecaefe69d5c35d9b0cda227f2dba733dba03 (patch) | |
| tree | 3b99734f1f81975ac288558b8dc5a7800929f2a1 /packages | |
| parent | 924ba97055adaa02b6684131ae537eddb2b7bfe5 (diff) | |
| download | opencode-5518ecaefe69d5c35d9b0cda227f2dba733dba03.tar.gz opencode-5518ecaefe69d5c35d9b0cda227f2dba733dba03.zip | |
Fix HttpApi web UI fallback (#25163)
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/opencode/src/server/routes/instance/httpapi/server.ts | 19 | ||||
| -rw-r--r-- | packages/opencode/test/server/httpapi-ui.test.ts | 45 |
2 files changed, 62 insertions, 2 deletions
diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index f53ddb3ec..f62636bca 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -1,6 +1,6 @@ import { Context, Effect, Layer } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" -import { HttpMiddleware, HttpRouter, HttpServer } from "effect/unstable/http" +import { HttpMiddleware, HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http" import * as Socket from "effect/unstable/socket/Socket" import { Account } from "@/account/account" import { Agent } from "@/agent/agent" @@ -38,6 +38,7 @@ import { Vcs } from "@/project/vcs" import { Worktree } from "@/worktree" import { Workspace } from "@/control-plane/workspace" import { isAllowedCorsOrigin } from "@/server/cors" +import { UIRoutes } from "@/server/routes/ui" import { InstanceHttpApi, RootHttpApi } from "./api" import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization" import { eventRoute } from "./event" @@ -119,7 +120,21 @@ const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe ]), ) -export const routes = Layer.mergeAll(rootApiRoutes, instanceRoutes).pipe( +const uiRoutes = lazy(() => UIRoutes()) +const uiRoute = HttpRouter.add("*", "/*", (request) => + Effect.promise(async () => + uiRoutes().fetch( + request.source instanceof Request + ? request.source + : new Request(new URL(request.originalUrl, "http://localhost"), { + method: request.method, + headers: request.headers, + }), + ), + ).pipe(Effect.map(HttpServerResponse.fromWeb)), +) + +export const routes = Layer.mergeAll(rootApiRoutes, instanceRoutes, uiRoute).pipe( Layer.provide([ cors, runtime, diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts new file mode 100644 index 000000000..9ca8b49f1 --- /dev/null +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -0,0 +1,45 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { Flag } from "@opencode-ai/core/flag/flag" +import * as Log from "@opencode-ai/core/util/log" +import { Server } from "../../src/server/server" + +void Log.init({ print: false }) + +const original = { + OPENCODE_EXPERIMENTAL_HTTPAPI: Flag.OPENCODE_EXPERIMENTAL_HTTPAPI, + fetch: globalThis.fetch, +} + +afterEach(() => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = original.OPENCODE_EXPERIMENTAL_HTTPAPI + globalThis.fetch = original.fetch +}) + +describe("HttpApi UI fallback", () => { + test("serves the web UI through the experimental backend", async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + let proxiedUrl: string | undefined + globalThis.fetch = ((input: RequestInfo | URL) => { + proxiedUrl = String(input instanceof Request ? input.url : input) + return Promise.resolve(new Response("<html>opencode</html>", { headers: { "content-type": "text/html" } })) + }) as typeof fetch + + const response = await Server.Default().app.request("/") + + expect(response.status).toBe(200) + expect(response.headers.get("content-type")).toContain("text/html") + expect(await response.text()).toBe("<html>opencode</html>") + expect(proxiedUrl).toBe("https://app.opencode.ai/") + }) + + test("keeps matched API routes ahead of the UI fallback", async () => { + Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true + globalThis.fetch = (() => { + throw new Error("UI fallback should not handle matched API routes") + }) as unknown as typeof fetch + + const response = await Server.Default().app.request("/session/nope") + + expect(response.status).toBe(404) + }) +}) |
