summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-05-03 15:44:23 -0400
committerGitHub <[email protected]>2026-05-03 19:44:23 +0000
commit6312c55d55e83a3d9a68ffd56f9cc4298b245901 (patch)
tree89643bfc5e765cbbf1660ea9c7880b47de5d14a6
parenta9dc0fae3d808baf3cbb6f5529877da20db164e7 (diff)
downloadopencode-6312c55d55e83a3d9a68ffd56f9cc4298b245901.tar.gz
opencode-6312c55d55e83a3d9a68ffd56f9cc4298b245901.zip
fix(server): serve embedded UI from bunfs (#25632)
-rw-r--r--packages/opencode/src/server/shared/ui.ts39
-rw-r--r--packages/opencode/test/server/httpapi-ui.test.ts35
2 files changed, 60 insertions, 14 deletions
diff --git a/packages/opencode/src/server/shared/ui.ts b/packages/opencode/src/server/shared/ui.ts
index db67749e0..c1558a1a4 100644
--- a/packages/opencode/src/server/shared/ui.ts
+++ b/packages/opencode/src/server/shared/ui.ts
@@ -45,6 +45,31 @@ export function embeddedUI() {
return embeddedUIPromise
}
+function notFound() {
+ return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
+}
+
+function embeddedUIResponse(file: string, body: Uint8Array) {
+ const mime = AppFileSystem.mimeType(file)
+ const headers = new Headers({ "content-type": mime })
+ if (mime.startsWith("text/html")) headers.set("content-security-policy", DEFAULT_CSP)
+ return HttpServerResponse.raw(body, { headers })
+}
+
+export function serveEmbeddedUIEffect(
+ requestPath: string,
+ fs: AppFileSystem.Interface,
+ embeddedWebUI: Record<string, string>,
+) {
+ const file = embeddedWebUI[requestPath.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
+ if (!file) return Effect.succeed(notFound())
+
+ return fs.readFile(file).pipe(
+ Effect.map((body) => embeddedUIResponse(file, body)),
+ Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(notFound())),
+ )
+}
+
export function serveUIEffect(
request: HttpServerRequest.HttpServerRequest,
services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient },
@@ -53,19 +78,7 @@ export function serveUIEffect(
const embeddedWebUI = yield* Effect.promise(() => embeddedUI())
const path = new URL(request.url, "http://localhost").pathname
- if (embeddedWebUI) {
- const match = embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
- if (!match) return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
-
- if (yield* services.fs.existsSafe(match)) {
- const mime = AppFileSystem.mimeType(match)
- const headers = new Headers({ "content-type": mime })
- if (mime.startsWith("text/html")) headers.set("content-security-policy", DEFAULT_CSP)
- return HttpServerResponse.raw(yield* services.fs.readFile(match), { headers })
- }
-
- return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 })
- }
+ if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI)
const response = yield* services.client.execute(
HttpClientRequest.make(request.method)(upstreamURL(path), {
diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts
index 8b7a6a1ac..332ad16c6 100644
--- a/packages/opencode/test/server/httpapi-ui.test.ts
+++ b/packages/opencode/test/server/httpapi-ui.test.ts
@@ -15,7 +15,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { ServerAuth } from "../../src/server/auth"
import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
-import { serveUIEffect } from "../../src/server/shared/ui"
+import { serveEmbeddedUIEffect, serveUIEffect } from "../../src/server/shared/ui"
import { Server } from "../../src/server/server"
void Log.init({ print: false })
@@ -184,6 +184,39 @@ describe("HttpApi UI fallback", () => {
expect(await response.text()).toBe("console.log('ok')")
})
+ test("serves embedded UI assets when Bun can read them but access reports missing", async () => {
+ Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true
+ let readPath: string | undefined
+
+ const response = await Effect.runPromise(
+ Effect.gen(function* () {
+ const fs = yield* AppFileSystem.Service
+ return yield* serveEmbeddedUIEffect(
+ "/assets/app.js",
+ {
+ ...fs,
+ existsSafe: () => Effect.die("embedded UI should not rely on filesystem access checks"),
+ readFile: (path) => {
+ readPath = path
+ return path === "/$bunfs/root/assets/app.js"
+ ? Effect.succeed(new TextEncoder().encode("console.log('embedded')"))
+ : Effect.die(`unexpected embedded UI path: ${path}`)
+ },
+ },
+ { "assets/app.js": "/$bunfs/root/assets/app.js" },
+ )
+ }).pipe(
+ Effect.provide(AppFileSystem.defaultLayer),
+ Effect.map(HttpServerResponse.toWeb),
+ ),
+ )
+
+ expect(response.status).toBe(200)
+ expect(readPath).toBe("/$bunfs/root/assets/app.js")
+ expect(response.headers.get("content-type")).toContain("text/javascript")
+ expect(await response.text()).toBe("console.log('embedded')")
+ })
+
test("keeps matched API routes ahead of the UI fallback", async () => {
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = true