From dcd5bf56e2d46c9d947bdd0907743d29f247ebed Mon Sep 17 00:00:00 2001 From: Joe Hsu Date: Wed, 24 Dec 2025 08:49:04 -0800 Subject: [PATCH 1/2] fix: use window.location.origin for web SDK URL fallback Simplify SDK URL resolution to use window.location.origin as fallback, enabling opencode web to work with custom --hostname and --port options. Env vars (VITE_OPENCODE_SERVER_HOST/PORT) are only used when explicitly set, for dev mode where frontend and API run on different ports. --- packages/app/src/app.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 11216643e5b..4ab22f21422 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -35,10 +35,12 @@ const url = iife(() => { if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (window.__OPENCODE__) return `http://127.0.0.1:${window.__OPENCODE__.port}` - if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` - return "http://localhost:4096" + const host = import.meta.env.VITE_OPENCODE_SERVER_HOST + const port = import.meta.env.VITE_OPENCODE_SERVER_PORT + if (host || port) return `http://${host ?? "localhost"}:${port ?? "4096"}` + + return window.location.origin }) export function App() { From 683946118a58e0c3054c984932c341bf5bbd3455 Mon Sep 17 00:00:00 2001 From: Joe Hsu Date: Wed, 24 Dec 2025 18:35:32 -0800 Subject: [PATCH 2/2] feat: add --app-dir flag to serve local app assets Allows testing local app builds with opencode web instead of proxying from app.opencode.ai. Useful for development and testing changes to the web app before deployment. --- packages/opencode/src/cli/cmd/web.ts | 6 ++++++ packages/opencode/src/server/server.ts | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 3d3036b1b07..8840f373bb0 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -40,14 +40,20 @@ export const WebCommand = cmd({ type: "string", describe: "hostname to listen on", default: "127.0.0.1", + }) + .option("app-dir", { + type: "string", + describe: "serve local app assets from this directory instead of proxying", }), describe: "starts a headless opencode server", handler: async (args) => { const hostname = args.hostname const port = args.port + const appDir = args.appDir const server = Server.listen({ port, hostname, + appDir, }) UI.empty() UI.println(UI.logo(" ")) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index c74dbbb41ef..47b33cdb22a 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -48,12 +48,14 @@ import { upgradeWebSocket, websocket } from "hono/bun" import { errors } from "./error" import { Pty } from "@/pty" import { Installation } from "@/installation" +import { join } from "path" // @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 export namespace Server { const log = Log.create({ service: "server" }) + let appDir: string | undefined export const Event = { Connected: BusEvent.define("server.connected", z.object({})), @@ -2600,6 +2602,23 @@ export namespace Server { }, ) .all("/*", async (c) => { + if (appDir) { + const path = c.req.path === "/" ? "/index.html" : c.req.path + const file = Bun.file(join(appDir, path)) + if (await file.exists()) { + return new Response(await file.arrayBuffer(), { + headers: { "Content-Type": file.type }, + }) + } + // SPA fallback - serve index.html for client-side routing + const index = Bun.file(join(appDir, "index.html")) + if (await index.exists()) { + return new Response(await index.arrayBuffer(), { + headers: { "Content-Type": index.type }, + }) + } + return c.notFound() + } return proxy(`https://app.opencode.ai${c.req.path}`, { ...c.req, headers: { @@ -2623,7 +2642,10 @@ export namespace Server { return result } - export function listen(opts: { port: number; hostname: string }) { + export function listen(opts: { port: number; hostname: string; appDir?: string }) { + if (opts.appDir) { + appDir = opts.appDir + } const args = { hostname: opts.hostname, idleTimeout: 0,