From 915da3ee145a2a75afaf43bb34aa2aa39fdb5888 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 19 May 2026 11:30:39 +0200 Subject: [PATCH 01/13] feat(server): add --base-path CLI flag and config option --- packages/opencode/src/cli/network.ts | 20 +++++++++++++++++++- packages/opencode/src/config/server.ts | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/cli/network.ts b/packages/opencode/src/cli/network.ts index 41f8184ef5d7..d50ef1cb4cad 100644 --- a/packages/opencode/src/cli/network.ts +++ b/packages/opencode/src/cli/network.ts @@ -29,6 +29,20 @@ const options = { describe: "additional domains to allow for CORS", default: [] as string[], }, + "base-path": { + type: "string" as const, + describe: "base URL path prefix for reverse proxy (e.g., /opencode)", + default: "", + }, +} + +function normalizeBasePath(input: string | undefined): string { + if (!input) return "" + let p = input.trim() + if (!p || p === "/") return "" + if (!p.startsWith("/")) p = "/" + p + p = p.replace(/\/+$/, "") + return p } export type NetworkOptions = InferredOptionTypes @@ -57,6 +71,10 @@ export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: Con const configCors = config?.server?.cors ?? [] const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] + const basePathExplicitlySet = process.argv.includes("--base-path") + const basePath = normalizeBasePath( + basePathExplicitlySet ? args["base-path"] : (config?.server?.basePath ?? args["base-path"]), + ) - return { hostname, port, mdns, mdnsDomain, cors } + return { hostname, port, mdns, mdnsDomain, cors, basePath } } diff --git a/packages/opencode/src/config/server.ts b/packages/opencode/src/config/server.ts index 642adbe51dcb..6617cbdab8f4 100644 --- a/packages/opencode/src/config/server.ts +++ b/packages/opencode/src/config/server.ts @@ -13,6 +13,9 @@ export const Server = Schema.Struct({ cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Additional domains to allow for CORS", }), + basePath: Schema.optional(Schema.String).annotate({ + description: "Base path prefix for hosting behind a reverse proxy (e.g., '/opencode')", + }), }).annotate({ identifier: "ServerConfig" }) export type Server = Schema.Schema.Type From 0abe073a58652ebb407910735c6d791929072e2b Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 19 May 2026 11:30:46 +0200 Subject: [PATCH 02/13] feat(server): base path URL rewriting and prefix stripping --- packages/opencode/src/cli/cmd/serve.ts | 2 +- packages/opencode/src/cli/cmd/web.ts | 7 +-- .../server/routes/instance/httpapi/server.ts | 30 +++++------ packages/opencode/src/server/server.ts | 50 ++++++++++++++++--- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/packages/opencode/src/cli/cmd/serve.ts b/packages/opencode/src/cli/cmd/serve.ts index 76f6276af5da..b667627507d1 100644 --- a/packages/opencode/src/cli/cmd/serve.ts +++ b/packages/opencode/src/cli/cmd/serve.ts @@ -17,7 +17,7 @@ export const ServeCommand = effectCmd({ } const opts = yield* resolveNetworkOptions(args) const server = yield* Effect.promise(() => Server.listen(opts)) - console.log(`opencode server listening on http://${server.hostname}:${server.port}`) + console.log(`opencode server listening on http://${server.hostname}:${server.port}${server.basePath}`) yield* Effect.never }), diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts index 384290c6ac39..75082c0c1e6c 100644 --- a/packages/opencode/src/cli/cmd/web.ts +++ b/packages/opencode/src/cli/cmd/web.ts @@ -46,9 +46,10 @@ export const WebCommand = effectCmd({ UI.println(UI.logo(" ")) UI.empty() + const suffix = server.basePath || "" if (opts.hostname === "0.0.0.0") { // Show localhost for local access - const localhostUrl = `http://localhost:${server.port}` + const localhostUrl = `http://localhost:${server.port}${suffix}` UI.println(UI.Style.TEXT_INFO_BOLD + " Local access: ", UI.Style.TEXT_NORMAL, localhostUrl) // Show network IPs for remote access @@ -58,7 +59,7 @@ export const WebCommand = effectCmd({ UI.println( UI.Style.TEXT_INFO_BOLD + " Network access: ", UI.Style.TEXT_NORMAL, - `http://${ip}:${server.port}`, + `http://${ip}:${server.port}${suffix}`, ) } } @@ -67,7 +68,7 @@ export const WebCommand = effectCmd({ UI.println( UI.Style.TEXT_INFO_BOLD + " mDNS: ", UI.Style.TEXT_NORMAL, - `${opts.mdnsDomain}:${server.port}`, + `${opts.mdnsDomain}:${server.port}${suffix}`, ) } diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 12761ab7ff39..08b701ca4fc9 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -164,16 +164,18 @@ const docRoute = HttpRouter.use((router) => router.add("GET", "/doc", () => Effe Layer.provide(authOnlyRouterLayer), ) -const uiRoute = HttpRouter.use((router) => - Effect.gen(function* () { - const fs = yield* AppFileSystem.Service - const client = yield* HttpClient.HttpClient - const flags = yield* RuntimeFlags.Service - yield* router.add("*", "/*", (request) => - serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi }), - ) - }), -).pipe(Layer.provide(authOnlyRouterLayer)) +function uiRouteWithBasePath(basePath?: string) { + return HttpRouter.use((router) => + Effect.gen(function* () { + const fs = yield* AppFileSystem.Service + const client = yield* HttpClient.HttpClient + const flags = yield* RuntimeFlags.Service + yield* router.add("*", "/*", (request) => + serveUIEffect(request, { fs, client, disableEmbeddedWebUi: flags.disableEmbeddedWebUi, basePath }), + ) + }), + ).pipe(Layer.provide(authOnlyRouterLayer)) +} type RouteRequirements = | HttpRouter.HttpRouter @@ -183,15 +185,15 @@ type RouteRequirements = | HttpRouter.Request<"GlobalRequires", never> export function createRoutes( - corsOptions?: CorsOptions, + options?: CorsOptions & { basePath?: string }, ): Layer.Layer { - return Layer.mergeAll(rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, docRoute, uiRoute).pipe( + return Layer.mergeAll(rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, docRoute, uiRouteWithBasePath(options?.basePath)).pipe( Layer.provide([ errorLayer, compressionLayer, corsVaryFix, fenceLayer.pipe(Layer.provide(Database.defaultLayer)), - cors(corsOptions), + cors(options), Database.defaultLayer, Account.defaultLayer, Agent.defaultLayer, @@ -236,7 +238,7 @@ export function createRoutes( FetchHttpClient.layer, HttpServer.layerServices, ]), - Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), + Layer.provide(Layer.succeed(CorsConfig)(options)), Layer.provide(InstanceLayer.layer), Layer.provide(Observability.layer), ) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 9a448b78d626..5af7640069ad 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -5,7 +5,8 @@ import * as Log from "@opencode-ai/core/util/log" import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect" import { HttpRouter, HttpServer } from "effect/unstable/http" import { OpenApi } from "effect/unstable/httpapi" -import { createServer } from "node:http" +import { createServer, type IncomingMessage, type ServerResponse } from "node:http" +import type { Duplex } from "node:stream" import { MDNS } from "./mdns" import { HttpApiApp } from "./routes/instance/httpapi/server" import { disposeMiddleware } from "./routes/instance/httpapi/lifecycle" @@ -23,6 +24,7 @@ export type Listener = { hostname: string port: number url: URL + basePath: string stop: (close?: boolean) => Promise } @@ -36,6 +38,7 @@ type ListenOptions = CorsOptions & { hostname: string mdns?: boolean mdnsDomain?: string + basePath?: string } type ListenerState = { scope: Scope.Scope @@ -43,7 +46,8 @@ type ListenerState = { http: ListenerServer websockets: WebSocketTracker.Interface } -type EffectListener = Omit & { +type EffectListener = Omit & { + basePath: string stop: (close?: boolean) => Effect.Effect } @@ -78,15 +82,17 @@ export async function listen(opts: ListenOptions): Promise { hostname: listener.hostname, port: listener.port, url: listener.url, + basePath: listener.basePath, stop: (close?: boolean) => Effect.runPromiseExit(listener.stop(close)).then(() => undefined), } } const listenEffect: (opts: ListenOptions) => Effect.Effect = Effect.fn("Server.listen")( function* (opts: ListenOptions) { + const basePath = opts.basePath ?? "" const state = yield* startWithPortFallback(opts) const address = yield* tcpAddress(state) - const listenerUrl = makeURL(opts.hostname, address.port) + const listenerUrl = makeURL(opts.hostname, address.port, basePath) url = listenerUrl const unpublishMdns = yield* setupMdns(opts, address.port, state.scope) @@ -95,6 +101,7 @@ const listenEffect: (opts: ListenOptions) => Effect.Effect { + if (event === "request" || event === "upgrade") { + const req = args[0] as IncomingMessage + if (req.url === bp && event === "request") { + const res = args[1] as ServerResponse + res.writeHead(301, { Location: bp + "/" }) + res.end() + return true + } else if (req.url && req.url.startsWith(bp + "/")) { + req.url = req.url.slice(bp.length) || "/" + } else { + if (event === "request") { + const res = args[1] as ServerResponse + res.writeHead(404, { "Content-Type": "text/plain" }) + res.end("Not Found") + } else { + const socket = args[1] as Duplex + socket.destroy() + } + return true + } + } + return originalEmit(event, ...args) + }) as typeof server.emit + } + const serverRef = { closeStarted: false, forceStop: false } const close = server.close.bind(server) // Keep shutdown owned by NodeHttpServer, but honor listener.stop(true) by From 735365c79e5df6ebcb020b93da81f61ddb0d3e80 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 19 May 2026 11:30:54 +0200 Subject: [PATCH 03/13] feat(server): runtime HTML injection and CSP for base path --- packages/opencode/src/server/shared/ui.ts | 45 +++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/opencode/src/server/shared/ui.ts b/packages/opencode/src/server/shared/ui.ts index fd4c73188063..c551c36a5c80 100644 --- a/packages/opencode/src/server/shared/ui.ts +++ b/packages/opencode/src/server/shared/ui.ts @@ -8,17 +8,24 @@ let embeddedUIPromise: Promise | null> | undefined export const UI_UPSTREAM = new URL("https://app.opencode.ai") -export const csp = (hash = "") => - `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src * data:` +export const csp = (...hashes: string[]) => { + const hashParts = hashes.filter(Boolean).map((h) => ` 'sha256-${h}'`).join("") + return `default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hashParts}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src * data:` +} export const DEFAULT_CSP = csp() -export function themePreloadHash(body: string) { - return body.match(/]*\bsrc\s*=)[^>]*\bid=(['"])oc-theme-preload-script\1[^>]*>([\s\S]*?)<\/script>/i) +function inlineScriptHashes(body: string): string[] { + const hashes: string[] = [] + const re = /]*\bsrc\s*=)[^>]*>([\s\S]*?)<\/script>/gi + let m + while ((m = re.exec(body))) { + if (m[1].trim()) hashes.push(createHash("sha256").update(m[1]).digest("base64")) + } + return hashes } export function cspForHtml(body: string) { - const match = themePreloadHash(body) - return csp(match ? createHash("sha256").update(match[2]).digest("base64") : "") + return csp(...inlineScriptHashes(body)) } function requestBody(request: HttpServerRequest.HttpServerRequest) { @@ -48,15 +55,26 @@ export function embeddedUI(disableEmbeddedWebUi: boolean) { import("opencode-web-ui.gen.ts").then((module) => module.default as Record).catch(() => null)) } +export function injectBasePath(html: string, basePath: string): string { + if (!basePath) return html + const script = `` + html = html.replace("", `\n${script}`) + html = html.replace(/((?:href|src|content)=["'])\/(?!\/)/g, `$1${basePath}/`) + return html +} + function notFound() { return HttpServerResponse.jsonUnsafe({ error: "Not Found" }, { status: 404 }) } -function embeddedUIResponse(file: string, body: Uint8Array) { +function embeddedUIResponse(file: string, body: Uint8Array, basePath = "") { const mime = AppFileSystem.mimeType(file) const headers = new Headers({ "content-type": mime }) if (mime.startsWith("text/html")) { - headers.set("content-security-policy", cspForHtml(new TextDecoder().decode(body))) + let html = new TextDecoder().decode(body) + if (basePath) html = injectBasePath(html, basePath) + headers.set("content-security-policy", cspForHtml(html)) + return HttpServerResponse.text(html, { headers }) } return HttpServerResponse.raw(body, { headers }) } @@ -65,25 +83,27 @@ export function serveEmbeddedUIEffect( requestPath: string, fs: AppFileSystem.Interface, embeddedWebUI: Record, + basePath = "", ) { 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.map((body) => embeddedUIResponse(file, body, basePath)), Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(notFound())), ) } export function serveUIEffect( request: HttpServerRequest.HttpServerRequest, - services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient; disableEmbeddedWebUi: boolean }, + services: { fs: AppFileSystem.Interface; client: HttpClient.HttpClient; disableEmbeddedWebUi: boolean; basePath?: string }, ) { return Effect.gen(function* () { const embeddedWebUI = yield* Effect.promise(() => embeddedUI(services.disableEmbeddedWebUi)) const path = new URL(request.url, "http://localhost").pathname + const bp = services.basePath ?? "" - if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI) + if (embeddedWebUI) return yield* serveEmbeddedUIEffect(path, services.fs, embeddedWebUI, bp) const response = yield* services.client.execute( HttpClientRequest.make(request.method)(upstreamURL(path), { @@ -94,7 +114,8 @@ export function serveUIEffect( const headers = proxyResponseHeaders(response.headers) if (response.headers["content-type"]?.includes("text/html")) { - const body = yield* response.text + let body = yield* response.text + if (bp) body = injectBasePath(body, bp) headers.set("Content-Security-Policy", cspForHtml(body)) return HttpServerResponse.text(body, { status: response.status, headers }) } From 3ed473b45f1ac00a52fd573ba016ee8ef660fc32 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 19 May 2026 11:31:01 +0200 Subject: [PATCH 04/13] feat(app): base path support for router and API URLs --- packages/app/src/app.tsx | 1 + packages/app/src/entry.tsx | 8 ++++++-- packages/app/src/env.d.ts | 7 +++++++ packages/app/vite.config.ts | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 9554a6363112..fc019f06ea51 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -325,6 +325,7 @@ export function AppInterface(props: { {routerProps.children}} + base={(window.__OPENCODE_BASE_PATH__ || import.meta.env.BASE_URL).replace(/\/$/, "") || undefined} > diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index 5115f0348ad4..09a5673a8ada 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -100,10 +100,14 @@ if (!(root instanceof HTMLElement) && import.meta.env.DEV) { } const getCurrentUrl = () => { + const runtimeBasePath = window.__OPENCODE_BASE_PATH__ ?? "" + let serverBaseUrl = runtimeBasePath || (import.meta.env.VITE_OPENCODE_SERVER_BASE_URL ?? "") + serverBaseUrl = ("/" + serverBaseUrl.replace(/^\//, "")).replace(/\/$/, "") + if (serverBaseUrl === "/") serverBaseUrl = "" if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}` - return location.origin + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}${serverBaseUrl}` + return location.origin + serverBaseUrl } const getDefaultUrl = () => { diff --git a/packages/app/src/env.d.ts b/packages/app/src/env.d.ts index 39f827eb64a9..29d4cd31a6f0 100644 --- a/packages/app/src/env.d.ts +++ b/packages/app/src/env.d.ts @@ -1,4 +1,5 @@ interface ImportMetaEnv { + readonly BASE_URL: string readonly VITE_OPENCODE_SERVER_HOST: string readonly VITE_OPENCODE_SERVER_PORT: string readonly VITE_OPENCODE_CHANNEL?: "dev" | "beta" | "prod" @@ -12,6 +13,12 @@ interface ImportMeta { readonly env: ImportMetaEnv } +declare global { + interface Window { + __OPENCODE_BASE_PATH__?: string + } +} + export declare module "solid-js" { namespace JSX { interface Directives { diff --git a/packages/app/vite.config.ts b/packages/app/vite.config.ts index 8df324ddc916..1cd118fb5667 100644 --- a/packages/app/vite.config.ts +++ b/packages/app/vite.config.ts @@ -20,6 +20,7 @@ const sentry = : false export default defineConfig({ + base: process.env.VITE_BASE_URL || "./", plugins: [desktopPlugin, sentry] as any, server: { host: "0.0.0.0", From 67f424d2483741018b8b2c2b052d22148fc5b72c Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Mon, 1 Jun 2026 11:19:50 +0200 Subject: [PATCH 05/13] fix(server): prevent XSS via script tag breakout in base path injection --- packages/opencode/src/server/shared/ui.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/server/shared/ui.ts b/packages/opencode/src/server/shared/ui.ts index c551c36a5c80..03a409213763 100644 --- a/packages/opencode/src/server/shared/ui.ts +++ b/packages/opencode/src/server/shared/ui.ts @@ -57,9 +57,10 @@ export function embeddedUI(disableEmbeddedWebUi: boolean) { export function injectBasePath(html: string, basePath: string): string { if (!basePath) return html - const script = `` + const safe = JSON.stringify(basePath).replace(/window.__OPENCODE_BASE_PATH__=${safe}` html = html.replace("", `\n${script}`) - html = html.replace(/((?:href|src|content)=["'])\/(?!\/)/g, `$1${basePath}/`) + html = html.replace(/((?:href|src)=["'])\/(?!\/)/g, `$1${basePath}/`) return html } From 9df3ba8c6c5c1293390a9c6cc47bc22e5c1c9eb5 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Mon, 1 Jun 2026 11:20:03 +0200 Subject: [PATCH 06/13] fix(server): preserve query string in base path redirect and harden argv detection --- packages/app/src/entry.tsx | 11 +++++------ packages/opencode/src/cli/network.ts | 4 ++-- .../src/server/routes/instance/httpapi/server.ts | 3 ++- packages/opencode/src/server/server.ts | 5 +++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index 09a5673a8ada..36567ab6bfe9 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -100,14 +100,13 @@ if (!(root instanceof HTMLElement) && import.meta.env.DEV) { } const getCurrentUrl = () => { - const runtimeBasePath = window.__OPENCODE_BASE_PATH__ ?? "" - let serverBaseUrl = runtimeBasePath || (import.meta.env.VITE_OPENCODE_SERVER_BASE_URL ?? "") - serverBaseUrl = ("/" + serverBaseUrl.replace(/^\//, "")).replace(/\/$/, "") - if (serverBaseUrl === "/") serverBaseUrl = "" + let basePath = window.__OPENCODE_BASE_PATH__ || import.meta.env.VITE_OPENCODE_SERVER_BASE_URL || "" + if (basePath && !basePath.startsWith("/")) basePath = "/" + basePath + basePath = basePath.replace(/\/+$/, "") if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (import.meta.env.DEV) - return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}${serverBaseUrl}` - return location.origin + serverBaseUrl + return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}${basePath}` + return location.origin + basePath } const getDefaultUrl = () => { diff --git a/packages/opencode/src/cli/network.ts b/packages/opencode/src/cli/network.ts index d50ef1cb4cad..18a08c861734 100644 --- a/packages/opencode/src/cli/network.ts +++ b/packages/opencode/src/cli/network.ts @@ -36,7 +36,7 @@ const options = { }, } -function normalizeBasePath(input: string | undefined): string { +export function normalizeBasePath(input: string | undefined): string { if (!input) return "" let p = input.trim() if (!p || p === "/") return "" @@ -71,7 +71,7 @@ export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: Con const configCors = config?.server?.cors ?? [] const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] - const basePathExplicitlySet = process.argv.includes("--base-path") + const basePathExplicitlySet = process.argv.some((a) => a === "--base-path" || a.startsWith("--base-path=")) const basePath = normalizeBasePath( basePathExplicitlySet ? args["base-path"] : (config?.server?.basePath ?? args["base-path"]), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 08b701ca4fc9..dc801041f9e1 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -187,7 +187,8 @@ type RouteRequirements = export function createRoutes( options?: CorsOptions & { basePath?: string }, ): Layer.Layer { - return Layer.mergeAll(rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, docRoute, uiRouteWithBasePath(options?.basePath)).pipe( + const uiRoute = uiRouteWithBasePath(options?.basePath) + return Layer.mergeAll(rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, docRoute, uiRoute).pipe( Layer.provide([ errorLayer, compressionLayer, diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 5af7640069ad..20653d194370 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -205,9 +205,10 @@ function serverLayer(opts: { port: number; hostname: string; basePath?: string } server.emit = ((event: string, ...args: unknown[]) => { if (event === "request" || event === "upgrade") { const req = args[0] as IncomingMessage - if (req.url === bp && event === "request") { + const [pathname, query] = (req.url ?? "").split("?", 2) + if (pathname === bp && event === "request") { const res = args[1] as ServerResponse - res.writeHead(301, { Location: bp + "/" }) + res.writeHead(301, { Location: bp + "/" + (query ? "?" + query : "") }) res.end() return true } else if (req.url && req.url.startsWith(bp + "/")) { From 9dc842c67efc5cd611c974a590cb1e58ac526edc Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Mon, 1 Jun 2026 11:20:08 +0200 Subject: [PATCH 07/13] test(server): add base path unit tests --- .../opencode/test/server/base-path.test.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/opencode/test/server/base-path.test.ts diff --git a/packages/opencode/test/server/base-path.test.ts b/packages/opencode/test/server/base-path.test.ts new file mode 100644 index 000000000000..7c0e2ec67d6e --- /dev/null +++ b/packages/opencode/test/server/base-path.test.ts @@ -0,0 +1,112 @@ +import { createHash } from "node:crypto" +import { describe, expect, test } from "bun:test" +import { normalizeBasePath } from "../../src/cli/network" +import { cspForHtml, injectBasePath } from "../../src/server/shared/ui" + +describe("normalizeBasePath", () => { + test("returns empty for undefined", () => { + expect(normalizeBasePath(undefined)).toBe("") + }) + + test("returns empty for empty string", () => { + expect(normalizeBasePath("")).toBe("") + }) + + test("returns empty for whitespace", () => { + expect(normalizeBasePath(" ")).toBe("") + }) + + test("returns empty for single slash", () => { + expect(normalizeBasePath("/")).toBe("") + }) + + test("ensures leading slash", () => { + expect(normalizeBasePath("opencode")).toBe("/opencode") + }) + + test("keeps existing leading slash", () => { + expect(normalizeBasePath("/opencode")).toBe("/opencode") + }) + + test("strips trailing slashes", () => { + expect(normalizeBasePath("/opencode/")).toBe("/opencode") + expect(normalizeBasePath("/opencode///")).toBe("/opencode") + }) + + test("handles nested paths", () => { + expect(normalizeBasePath("/apps/opencode")).toBe("/apps/opencode") + }) + + test("trims whitespace around path", () => { + expect(normalizeBasePath(" /opencode ")).toBe("/opencode") + }) +}) + +describe("injectBasePath", () => { + const minimalHtml = `Test` + + test("returns html unchanged when basePath is empty", () => { + expect(injectBasePath(minimalHtml, "")).toBe(minimalHtml) + }) + + test("injects global script into head", () => { + const result = injectBasePath(minimalHtml, "/opencode") + expect(result).toContain(``) + }) + + test("rewrites href attributes", () => { + const html = `` + const result = injectBasePath(html, "/opencode") + expect(result).toContain(`href="/opencode/assets/style.css"`) + }) + + test("rewrites src attributes", () => { + const html = `` + const result = injectBasePath(html, "/opencode") + expect(result).toContain(`src="/opencode/assets/app.js"`) + }) + + test("does not rewrite protocol-relative URLs", () => { + const html = `` + const result = injectBasePath(html, "/opencode") + expect(result).toContain(`href="//cdn.example.com/style.css"`) + }) + + test("does not rewrite content attributes", () => { + const html = `` + const result = injectBasePath(html, "/opencode") + expect(result).toContain(`content="/not-a-url"`) + }) + + test("escapes basePath to prevent XSS via script tag breakout", () => { + const result = injectBasePath(minimalHtml, '/test"') + expect(result).not.toContain("alert(1)") + expect(result).toContain("\\u003c") + }) +}) + +describe("cspForHtml with injected base path", () => { + test("includes hash for injected base path script", () => { + const html = `Test` + const injected = injectBasePath(html, "/opencode") + const csp = cspForHtml(injected) + + const scriptContent = `window.__OPENCODE_BASE_PATH__="/opencode"` + const expectedHash = createHash("sha256").update(scriptContent).digest("base64") + expect(csp).toContain(`'sha256-${expectedHash}'`) + }) + + test("includes hashes for both injected and existing inline scripts", () => { + const existingScript = 'document.documentElement.dataset.theme = "dark"' + const html = `` + const injected = injectBasePath(html, "/opencode") + const csp = cspForHtml(injected) + + const existingHash = createHash("sha256").update(existingScript).digest("base64") + const injectedScript = `window.__OPENCODE_BASE_PATH__="/opencode"` + const injectedHash = createHash("sha256").update(injectedScript).digest("base64") + + expect(csp).toContain(`'sha256-${existingHash}'`) + expect(csp).toContain(`'sha256-${injectedHash}'`) + }) +}) From 0d337d9e75f2e3f9ac971ed0c492b97b146deb02 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Mon, 22 Jun 2026 11:52:15 +0200 Subject: [PATCH 08/13] fix(app): use router navigate instead of native hrefs in titlebar --- packages/app/src/components/titlebar.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index a4fcde5aac21..f4f6f4359712 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -428,10 +428,9 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { } + onClick={() => navigate("/")} state={!!homeMatch() ? "pressed" : undefined} /> @@ -510,6 +509,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { ref={ref} href={`/${params.dir}/session`} title={language.t("command.session.new")} + onNavigate={() => navigate(`/${params.dir}/session`)} onClose={() => { const tab = tabsStore.at(-1) if (tab) navigateTab(tab) @@ -529,8 +529,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { size="large" class="shrink-0" icon={} - as="a" - href={newSessionHref()} + onClick={() => navigate(newSessionHref())} aria-label={language.t("command.session.new")} /> @@ -926,7 +925,13 @@ function DraftTabItem(props: { ) } -function NewSessionTabItem(props: { ref?: HTMLDivElement; href: string; title: string; onClose: () => void }) { +function NewSessionTabItem(props: { + ref?: HTMLDivElement + href: string + title: string + onNavigate: () => void + onClose: () => void +}) { const closeTab = (event: MouseEvent) => { event.preventDefault() event.stopPropagation() @@ -943,6 +948,10 @@ function NewSessionTabItem(props: { ref?: HTMLDivElement; href: string; title: s > { + event.preventDefault() + props.onNavigate() + }} aria-current="page" class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 overflow-hidden text-[13px] font-medium leading-5 text-[var(--v2-text-text-base)]" > From 413fd3d603ee00eb104a517bc17404cd6d6a7011 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Mon, 22 Jun 2026 15:32:08 +0200 Subject: [PATCH 09/13] fix(app): use only server-injected base path for router base --- packages/app/src/app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 33ee144a4926..e8820d213f1a 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -447,7 +447,7 @@ export function AppInterface(props: { {routerProps.children} )} - base={(window.__OPENCODE_BASE_PATH__ || import.meta.env.BASE_URL).replace(/\/$/, "") || undefined} + base={window.__OPENCODE_BASE_PATH__?.replace(/\/$/, "") || undefined} > From 089077d76f19c642bf59f4c24982d8fd927ee4a3 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 26 Jun 2026 11:33:08 +0200 Subject: [PATCH 10/13] feat(app): add document.baseURI fallback for proxy-injected base paths --- packages/app/src/app.tsx | 7 ++++++- packages/app/src/entry.tsx | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index e8820d213f1a..d28a84b177d0 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -447,7 +447,12 @@ export function AppInterface(props: { {routerProps.children} )} - base={window.__OPENCODE_BASE_PATH__?.replace(/\/$/, "") || undefined} + base={(() => { + const bp = window.__OPENCODE_BASE_PATH__?.replace(/\/$/, "") + if (bp) return bp + const baseUri = new URL(document.baseURI).pathname.replace(/\/+$/, "") + return baseUri && baseUri !== "/" ? baseUri : undefined + })()} > diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index 21ac11b7bd29..fd796303aa40 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -103,6 +103,10 @@ const getCurrentUrl = () => { let basePath = window.__OPENCODE_BASE_PATH__ || import.meta.env.VITE_OPENCODE_SERVER_BASE_URL || "" if (basePath && !basePath.startsWith("/")) basePath = "/" + basePath basePath = basePath.replace(/\/+$/, "") + if (!basePath) { + const baseUriPath = new URL(document.baseURI).pathname.replace(/\/+$/, "") + if (baseUriPath && baseUriPath !== "/") basePath = baseUriPath + } if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (import.meta.env.DEV) return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}${basePath}` From 449d3bb05480bc2b3fced729a39472d6355ef806 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 26 Jun 2026 12:06:02 +0200 Subject: [PATCH 11/13] fix: use correct parameter name in CorsConfig --- packages/opencode/src/server/routes/instance/httpapi/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 555b83c9b94b..eee775d9ac0e 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -283,7 +283,7 @@ export function createRoutes( HttpServer.layerServices, ]), Layer.provide(LayerNode.buildLayer(app)), - Layer.provide(Layer.succeed(CorsConfig)(corsOptions)), + Layer.provide(Layer.succeed(CorsConfig)(options)), Layer.provideMerge(Observability.layer), ) } From 3cd15295212ab36db6d66758304f3adffe013761 Mon Sep 17 00:00:00 2001 From: link-seek-bot Date: Wed, 26 Aug 2026 08:20:55 +0000 Subject: [PATCH 12/13] fix: preserve base path in v1 client URL construction The v1 generated client used new URL(descriptor.path, options.baseUrl) which drops the path component of baseUrl when descriptor.path is absolute. This caused all v1 client API requests to miss the --base-path prefix, breaking reverse proxy deployments. Fix: use string concatenation (baseUrl + path) instead of new URL(path, baseUrl), matching the v2 SDK approach. Also fixes server-protocol.ts which had the same new URL() pattern. Added test: 'preserves base path in request URLs' --- packages/app/src/utils/server-protocol.ts | 2 +- .../vendor/opencode-ai-client-1.17.13-v2.tgz | Bin 67724 -> 67822 bytes packages/client/src/generated/client.ts | 2 +- packages/client/test/promise.test.ts | 13 +++++++++++++ packages/httpapi-codegen/src/index.ts | 2 +- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/app/src/utils/server-protocol.ts b/packages/app/src/utils/server-protocol.ts index 27b8dc208eac..622e2689ce1e 100644 --- a/packages/app/src/utils/server-protocol.ts +++ b/packages/app/src/utils/server-protocol.ts @@ -11,7 +11,7 @@ function headers(server: ServerConnection.HttpBase) { } async function probe(server: ServerConnection.HttpBase, fetch: typeof globalThis.fetch, path: string) { - const response = await fetch(new URL(path, server.url), { + const response = await fetch(new URL(server.url.replace(/[/]+$/, "") + path), { headers: headers(server), signal: AbortSignal.timeout(5_000), }) diff --git a/packages/app/vendor/opencode-ai-client-1.17.13-v2.tgz b/packages/app/vendor/opencode-ai-client-1.17.13-v2.tgz index bc1b664f82dcc55a95fc45b4c3b2c5b0cafaec55..11ea61e98631200b2990e513e16c0301a429cf98 100644 GIT binary patch literal 67822 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 ZQk^r5=6?=RZfIVX#2s4lTw?DhTQpAP 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 literal 67724 zcmYJaQl@s%1CNf6+tu< z(Ekh&z)kOu`?h!zCAYwN{lVf~Cqw6a8hAnq?IunAF{gVI&*b~2)ay?2ak3yODG>-T z0+6(l`AdKQ6$I2#u|O3LkeJkIZY*VEe|1+?7k~QR%~bb41KOwjul{n!=kz=P9xg7f zJUPiL>Mxr=KBvi>p$lUkK0fE%;o+bun7<>!PJax-Z&Rp$HR}_KrZi%9jqP3F_t!dR z@#3Dr@@8rOzgKy2?{uv;Sq1$bLyv}h0N3o%$bNisGqaj283VADSX`zDSQE2i{oom=E^bB0Y4(0J!&p{P*GH+9nW<$({7%=2v>pEBUR( zG_2n^>e35s=`@bnq?;QmWLWvm4uF5Ex<@4Dy|Z*!3U$?A0WHAeQ{W6k>e?1ROJ@>F0=1+j8xPb9pRx8#jMl zV{GT$hql8hIt%lx#MJ1S@w?p_?D#%lep)=&54>R#?DzkieZ74}e*igKUI09p`Tj2W zuR9_{{MsMy_s4>jd_^@I$USZ%2CI032Am{ zbw@#h-=S|CnNWlEE$QRJUf}oL$R%;4+RRTf{~HW@0D1PvGVR@A#a_UtUBEavuQtd5 z!QX&3FqpJ>7s*DVKO%BUs)yO&Sq z*dg_Upy%ds(=t|TUYhvZCdFj!f?lGCkIQ!F>j<_lh>iIB>w^7XAj;3ZQ^QZF`>Fp& z{bFYNrvRDJX{X}U>My~bF*L}}#a%T+)E!|aIwr?#y-*jTCJ^TRv@RB0v{-&j6{ZQF z232nU%4?Q??^{sfJ(|Pb{ufM{sqnG+(OQd)9~z_OL;8K6C;Jq@O&NFDYi$Mq`rG!# zApmF6hRIHy^Nks={(D^XlBMm`>1F-6ALx9+e$pT5e3J0p9_!C#4I}^FyJgOdk?30EDlQ)s)aG){FCP=#?S)k0Pt4;Jf?s30FH|vQtkwH z{Xg4Rzw$x*R%&dof8+JaCjo~XU4ZR(4PMd>9n^?{HwSAGc~Jfn?RYRhDu!47;h0vx z*HujL%L6??dlU2mq<}_zPq4c!mcc+p{r)g0iLrLlvM|*Q8s0aax}|PX@)fmyzksir zeVbu4AJzH{P69|r`4`{|&>`*DKR!>hg@8ebpr>q0IFSyKu$ zc2j2D{W`z-%Zu4B1QHZn)zsp1@e-U)zpH+~!m$e2CDpV#`bb%Tdlevmv^Krt`2vif1nV5QGF3#`CPj(gz!zbkN5_Qhg;$t~E z7Kz`DR^$hNUVuC~*0>$pK3>j^zm~3ni)HG@y+SxBf=a)eAO!|KN|}?7%sbcvRBy_F zXIjApf#v;pu6P#p5H�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%0ADAsYp?N1>Lhg`3sh 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

7Y+QD}T8_DVKLjGZ(7vq z#Xd2`Fs0hm(hZT~)tH&d$nR831}tMZfXg?Vl}2#9+z zxsc83+HgvsbbL)@0S}a4zed0EhSAsQ8iMLInheJE8d|Kv36WSGs3vqeYY*0+=(<-0 ztgODVNLNBb?B$pYy(%l9-LM6qYe9tbZ^svK5l|1>ETIWBfuM>%9w5akkm(hPQmyA} zjlNpTnf=jWRmP%#s^zv#avKOzR%#gy7s#>QAFY`{yn z4s#R!wqg@5DvwyYne+f`~YC}(nYnN%`zX2(>?-n3^m2YN%^ zjz1H+>v)_Q`N}^7V*ZYL%nKeqg8l%MJ%01+G5MPx4$$cwfp5#$>X@L~n(0x{G+{&C z!HWm3atoc4 z6a})pw9vxQHoW|ZN)g_`hUMa;xv{3`B;DyiLzT|&uE3hZ4-73WgPMzE}fZX49-Nbae74Z^s6BXgSU!v_~Yw~lXNi6;5lh&T_PbV z=(2LqdWZjc;*imONrn<`dpYAJIVlid`la|O=*z$MM9)~PMjE4M%&L!uNqg3_;J`qz zo>^i5m#n7<2%#7napw+oW#@1a>;78WL$~NpeeU7|c|;!L$`iKq!7m4_SmY^F<5ev7 zj78+FZUHHM#6R@~wvYr>-K7XdxG3v@P^L$qN_R zrSK3d!a$U;4^n3d*fc zoMWK{2_fzfzL*Vqj!wyi;TUyN_Pa0xB{^GGhNqm2y-uzsIBf&GUilm|5c&Xq=IDT- ziAdZI;5HZUrxKFuH8q#&8PsuJe^*?`437J=`?4+uZVX>1IblsJ?QD1DwpKob-c3bw zVPaK!#Fay0Ey8tJ=#AQn31_W+X1TQwS|3&C@2ys+)1fO?KRo?3Co~o-R>kLHwX%eJ znw3`YIAg##TqZOK7-%fEWf+R{FmUs@0Crc+QhaLUudc0kr)?-PI0rod@bkyzX6KJ8 zAk@HbiS3oJnoMih_7@FTCwvQfE#ryFclMiE&>f|gEnZBmh998lP-W3}IC;Zr+{nStaAVk6mkO!_vI`a=Jgr&=tPXfQgD zh&cQNP6?HZ$0INYk9Z_=#FR@eBaer~DL^{~g9O47sS$aHlzr4G5#d4(t66Y(y;k0! zHvM%xK2O~=ni1C&T!cljb?7+(u6P4ELqlrFUTmF4{1?=OL!#(CM~`rr&%TS#Z;E;)#l}x zlj}0-nzvEcypFn-`>1QVkh+c=sat*}2_?ZYkZakP<;N@B!79dhLNB7Q=_XTcnxnb3 zsOJeJY#ZvDN*-HNQ`y+FVtXGAmY|qcjRw;;6=sv{5LxMIX*^G2{S1NnPsmlkavmgj zQL)&!+;WdDUpE>`2gHJttnLVZaPB!C>6+fcS}s4ZDW+VHz8pyL2>ugmkxwb!NNOr! z4no7c$TwJgnWU3kzGa4!!Jt61A@H&GuT=#83yVG~F(|eWP+UYkb?_&tA4epriZkdo z7WR~!c-^xj751d%hltB1^n*K+1rJq%qmIOJt0>Kog0CL9mmtF; z&A;)AziTQu!7%r5mcXylb5J}(9q&`+?Z|w)ZJ!{%nynao|7o$~W=Em-NAXLNh>0>3 zhFOsDx2~6B=aBQ#cq-e})#w+WgJ8x~bMqvV*XF%+SV`AzdsRSY(S%*Dmb}!l;}cO{ zSz6%R;;{U2QHWFyZH_F5sx9nFd#PaRBW1pmY+#jiS(N!c%LG5SiKJe(0!?VM>W)TH*pg0=})<7|P`1W0e)Cbm>>?J{25!cJpnTVTFz9(-Ck3ak4 zvsq6h$$7*K`;H%#C>ti{Y#_F9_|zWD-gmX2vt5PIc5Rfl>map^fnfC3!eiYyW54TP z3hBLXq+%Q0%+F0poX$!d%yP^qO73b2hjbTHw@gU4tYV5zg%LOSH{h%dzQhvL{>H}u zJ~c8be3LCEGuv5)9jhI7S@ACfmNlh0hM6)c@AB7tE)`Sv2AI*^E#GgR|p!*}us&5Fa+8W_v5xS=w`T=H^WaGGrf zSk-guP6C#tNbW8evTBy(y9HTp4!Ka2Wjx=yqO4$!9C$*m}&Ro~Hch8t)bkRw#aqJEk%dXQw zA}I=-acHeinQF#U>I;iAjAtZo+knTtRKF6w-}zO2ov}j8>r8w>1%+As1){Q=r5KLf z{WkCzZaHWU%Rfc_E@FCGWUW@|sxD78a@jAGWu%$xr*^Q%<_87ID2qRWnf;#AlT)CN z3vV0VNW6^~JG`GS_oao_q)#B$X? zyyS_{)j5b%AUWq2#7E0%Aoz|^tuD2ya{i4QOVWrNIUkf+5ehB!^tIrt+-Qr1T3aK` z|BWMz{EciGO!mXRW!l1AR18>@Sj zR(gbakE>%}rGbS4kTxk@^KGMR9MM7jH_6KAU|sezs&fJSgb& ztcgbLwD47WhOW#78B~blO^f`5N^tCZ`ff5H5yMIR6EI+MG6t&)_Dh^A_FKq}ZweS= za3gm1rQbgj!4)rHqB8dLZ8YxjPlRTR(RhF?jDW;Z91E<^ApEeLrw~f>*Y7s0Ts4XYmCM?S&n!lGQ9#hZxT!b?bE6Au zZVVyAjs7?m1@GbucGFw%Mky?6&V`})piVEa8LPr(z!rdy?2Md;*2xSUi9Vg0oC=@{ z&>fvsSuX#gEot$L4PIQ-813jBW4ra`a0LbGnn8YVtP$Oy6s#>007qZ?CG}&(@Rw*G zwJ4DbAX>Ua3&|;w`YIx&-N<3a$d4Ca6Y;l1N#ulI?5C(neie0f^WhsyXf3tuGpBp< zBlAXa8DQVIe}Q~1X>62&g!BoM@_v#qx8o5yI?xgZDN2J_t@mk^kB`sOEBcdnONcSK zcYx^{42;`IFMW0i_{A6`NCNQ_m>2cVrAtAi-w@a$rKFHbrI^45x|q<@A#B!=psR%1 zym6$n*PIT9=sN@W_Fo=(Qp&>9Plto+*6(nFfRiz57U`L3JtHei!4&l4-T=-U>eawl zI?0W`4q6*59}81DZnl1WN>j8zb#s=wJ`idqeP&1MUxuY_=|YsR7|YRPks#`wvgAen zFt0jfEYmB2jirSZI!ELMRS7s!)fqO~?5x^Kqz2a4%daW8r~okiOR-hf7vu7#nPRcF z;YyZkYhr!1R9mpE@{|j10wA{OlmNum#7M1lI!JmYM_j2@?{M=#v$fByGG@s-mAd)@ z$^BJr$8DAM<+9X6v+t*Eu+nv!O{~j(fla6-Y?TFT)#trrZMbTS)*5Mq4B54cWXz~) zoN27l00A=wDkEP;1D#rDmkDsn!-z9ZWdz$=zux)HX(zRhuVe613T|y>y|sRt@>nvmu>DC=fJBfNoMMcG+g#z&Pk z)nNcnSjYwc)Ad+4VK1N*ef55>!%hoQM=ZmukvwZ{9bPVD^J(B=9k2uh z);5mhjTQMfYxTB;!0B-?r((~nqm?me)<%|C#g!Ip)YR9SG;oI-gOn`MsBg)CHNc5n zA45cvL0n;tmiqHrsb#1N>$SA9ndOkj8I62UzG$=hyw+@vRAI?xBUcEAiZsh!zir%Q z)>KZ$SvdoISw^4N-IBbqm#*G;b z?C$r~7HO?LN4&L;SF&Pj8*2*OqjcorvZ*sbTd$}uRo9OCxl~o+mxAQroe`_N#NXe- zjVKc=XGE0hDp>vzM}zVCFY%a$b|D&n@@AnQY?Y6}YlJ9|Ob{k06vv&{I4nUYs$^u_$y&AL}-n(vhU*;cN<_hZQ_tqX&;uRP}-iwV8tJSul2%!C~ z421seacq%mn@Yw}y^3WsEfEaFl8c<^HSf)q7Rz1tJ*6wLZz{JNbMLAQV9T~vg@1tu z7;vy8SEQ(nMu+HV2(F+#7T}Qn;cJw~=+RwuK1TEcW=~kstSYj~at+++s@3)Q`X$w= z6{Rnz*~hlns`e+1HR5{`_II84mq6awTMpu|x9hyO1enI&3TTDRUFXfEa4~jQ0zlZ` zb>3gTQe%hZwS+BR=PedfHczpbTiD^XKE!ke<{d1C3j4d(moV+p*k92$Z0|a6FRf#d z^GFg9Nl2~6clAW!aS7D>c8p2-Zm-6|WJHP7=dNc)@AK-gY`fx&>!(z-2l?Y-HOK5~ z2>eQh_lwZfDWFQCZyaI{(Xb;+*%2DSS>F^BSYcBZxG4lIrYO)W#1H#2|9$c3B$S&+ z*Z9dnuW8^Ye3w9J+aA}F_+Hw3ad<*24GYDecNDiORbFH9gr17W{blhq-Mry$%K%pnW$MAdFaWc#XO`Y9 z0nwl*QtG2BIQ#NdzMeD9bKRfczN>S)R=8QcB006$JjRwjx;N!@B|=YKp%=C(Gd*0* zywjTFEDdq|julYz1P7^@WJw7LP$Gys>bUdESZ=YQmETiAY7eYv)U!rftOzeHed+B} z7mr;TmS(u*^Ns}p(QDd8|N{FEWyLVD6~u(K^GnlSvX|jkTrA2 zaFxa}<0l~;v~bYEL2Krq5u~?2f5TA=M=cz+=8hWODT^%5vV?Hj!f6YqZI;u<=W0r+ zis8a}3+FAIw`R^8@6|YOqG32~;k1R**34<6-`%PRs$}qKUZotAWeQjpC~ZAd7S@<@ zDwbzw6Q1(uewe-l5*bKjBdcGSMn#)UJ-kk_4U?cFJ`!RTq-$O`R z-GkNT2AJ07oW;tP;G=eT;kraeq`Qo)OY%0kyC5xPMmU#Ifn64LOL=C@khn%XvsDN% zs|oWo14_n{w{FOm^N#QYg0NQO)!qUD;9K7sD&Qrn2?b?{`R^!MLYzw+wMytZIQ`?} zF~(k8c*ID5z{m=IdcFsd5>c|MhQF!7g+}qV4C+2iqt(izkz%bQm`k>dkfdVps>p_t zl4TqSdGsp8Y>3zQ;-|?5w&Deq*cJ^It|ZCrTV1$bZdP(X>_SdoiFiq4SG&$H#icM8Dp?y$d*yq7meSA zZpeoky9NL)CksZa=3_yx-cH=s0Yy0se~E{2=1z~%Ji{>}9?aJ6_$RF|TI>rVPM4r( z1>xm9If(a2ze>**7litB?8@}WKUK;*bD9nY=~W5$LwYpXF5Yw@bD)(xIbG5-ee-ZX z#_@#%*gp%g0%bcjDgN>izRWjSC6{6cS zkocYh62s@@7FeBmGeT7}1kXRG)m)O_uEwgB60Zd zO?G*)+I3GZ&+<;#a?6uX&*Rf*GPp;Z$`-u7`zm%+Nc^>Bat8RlTXwSM`#Q_ZVztfj zly^^6=g75OrYrf2jJ@KJ%7y-S-CH)*dvjCC3tsojim&CjUHgs2zq^x`$(U+CDONuK} z1dzYpal)Vibbawh!yFfzELHnA*j9WQrdPvf{r)}Rm?xRto|}|w2LQ_as3OD8BXyKo zfOd5Vu=rC3MI-=W#XHQQ?rwhjx8A zkfPIQ4BAZ4SXy?%xNXS^=tHNTU|Nq6RbU-s(k^Pk_`tdyzwNT2a#4a-1f~VCFD}Oxr{Ep+@A-A zwC#7|q4A7vAze`g0(UVQ#7^s*fbn;OX+rXBUkCf*Q{W1>afJou;)xgA5ibE z5?i|MRbS}D8znrRR^#zB%VTG@M?zCCcNv}T#C^|}#IF4ciw`XBeWSX{#@|7tFXwgn zxepA-{Y3pzmg9AAbbl-g=~~y`p0GDMjx+G$dum&uTYr|5Mh<&t`pGbM#^O8vKm_60 znd611e^SF50Kh>Kh&>P5j!6q&b2S7Ro&Qr;qpN-$z{JDLBuj@EdIpbr@?;0-EPIzn zCxf`}^EL!v-oS=n=sriYk2EMPd`Fs z0IG59!YHfATB&gkf5ch709;7%QR^S4H%I_4e^07lw5?$tgY!iIW6+NYL$!f!}ljDwKG*hO5;M*HUj- zsN)xM1=zY^t4njNzI3e+V6>K4q1j;L61NseZ13;E+VkDuY-Qk0OJyH>$~DPrbSinMIJV=;`!I% z=>h#)dUzGli*PoEs2-Z=Ej z-FL>(0I9=Y`lL4fIn6HK#SkVkAKx1xsi8cS@L}C?w45O zh?pF%N$~PyJczyVMCK9k^)!hGeXyx4I#K%G@JK+cmiG2#9agAVo_<4u5HZBNBh zI5QCnzS{9clnltoAjzZgK;R^aKOUfr%-7RJGJF*e&xl;KG`q(|^tF2B5o$5BN;ona z07*Sh2Q;!t1q>wd7^+lF37+P4aZ1_x5;U2zE;7N|{-6a+~@kQ4+`5F`cRWfjmlND2Zz2Ye2af`Ac0QV=8scVANQRQ&wp%~Vbe zAA;RPpr}C_5~LwP8WKoBkcNcUV?gI14GH)h@Ht3B0!9RBNRWmEyNN(;f}|iw3WB5{ zkb)p72rsLE&OuTT@HyafkQ4-r2$F&zDY)Bq6QqAzItSCXA(&4D!Wx7kK`0W0B7qbH zp-6Z{26PTWk$}$upMy{&U_=m#1ffVUp9s_@ND6|aAV>-VDF~8+@UjZ%93%w+p94Mz zNkPDfASnovg1cxw!L1k!3kq(C5X>h6VGTl&AQTBgkw6N9P$WDe13CwxNWkZS&p{{> zFd_&=f>0!wPXuZcBn3fI5F`bG6a+~@cv%H>4w8a^&jFuWZe3ZK?iLf4#;OXDk-%4*yi>Zdi!7E@ynsy zR?Ie!^BiXS^10PML;C78zrY8tUIC^enws_?tU-G&E+MDRhU~P=KAuv1SxE zgD2_R?_JM0{ER%2Xd69F#iD-OWy^Z%S^05v8TUOq&ZbP3W7%@~vukf)6GS!dE{M5U zNw>Z{QX4PBr%qa~Rel#=q?fLn5jVkV@o5}4ny>7(!cdh?9p#mpq)}cHL`+zxHEoT0 z^ei|jR2JvyB1yD?uN0+(jJ6;z6<*-fNg9wgK602yRgj&xp~Cv47VXK0PI}0QtfGf|QB3ed)8- z^KftE<#_8GxY8?K5kizLXP#pJj83E0TUlz;_^9HG7tSt4fCa!6;5%Qr_DVxoYXfkw zj>x{WQxKXGMQ${>imr3=XBK}}Rk1o<^2@+T*B17VH(l&(SfvuFXo-Y98N_&}+)ZJz zZh?AUAlR)P4gyqXgY+aCygyI!&Ihnl`WqpNW}7=CXg7f6$38Ed@93JbhD}Gq5MkZ* z4!Dlv>l}OEMVTn_U&FUH3gD3$gF$i%XE29Zycsu=J#mje&d%KWNX*@%8;NIHGG^ zdlRFvg*ro_*OKKfNdtP6oY|LBe01^&QFfNskjM9~yuG1X@qiC}Z-8LWzNz7zePQHHw#a?znla_QP@e{FY>-U!F{~8 zhf0_7&KGey8JlFdc@sI$)4J?cxi;&wJ{Q>x)_-|yw3>$9Yqm()sM$?-)|b2%gw?)2 z_rtHQ-{4)pX4m?~SB3`f>7fg#Q1;|=Q+F%fQput1*94_}fevJ0=WV9zjlSdU+rbpdv-rDFSn&r%V5EB6HE zG8{s!n&hWibgkTbbo>rLMLiPTC%EqEAo0NNU3?m6F*tVJ8-lE?c-={{Sd85Q@-3ah zQ9ANl#fiXJ{^(k!9!n0Wb!X)W&=r90%} z3TFa+Qmp=>CxoRo{iBZi;;hSG;D63$ewA-HGM*#j$ImKz4bdcfvHno=VIOPI>~N&m zk6h(3_T;*!5I5sIpwx601qL8JQ2(u$V}#vFelM6BvD$TKXka141B)!?scoIllhf0? z=)8N8_A&4bc89sL&~NV~gQ&UZw%`iA6!MOIomR$7hYBD9g);PLG3wwg1BlQrgjBK< z9XyP8!Q+z>726Z)d3-WCd&SnIJQ-S)|u%!L_I0Cwx4B0nc z=_CE{iFRR(*%pV0PwV9n+9iD^p@MrNh$!(CYucy<_cd8##XOuUHd@f^=m1ohy_$hLiX%=_Pd# zIYoh>NN8zgGodL#r0f%X`tO~YeQ;ltY}C~Ss01%-M~kFvx|bSPDF!{TUE%*X*FB1 z`p_VKq&}73%h7GQI_uPc3u%Aw7pHVSEFFfTmpP%W?^R;x0}hCWK6!KH!V!iXK(U^!95)AKz;a+6)XJ@{2h>MQ6$7jqw;|KIF?B@#rBSkhE;j%k%QTOMz3ZfbxmV^=&Y z>Z@4?-@}K4`Ey81(tdBjWG{A`!!{9^a$K<*H)%&DPub?S0yNv0aFT>>8`aUCm{Q+SXSaf|>o{Eoc87QIuG3U1XI@3V3G_KHW6y!y1joT)3fC z2s06U%dEIULGeykMcwL#*!LF;s2_?;@d&A`AK-(~=VxK~_Pm<$QX*rPd zzqAZ*_mRU3r8LA`AtnI@lt$~T<|%&IAW2d=L!oN&A&aT!aSAi7MOHAPe0-D`RaG=D3+yM}z0G!~_YoJmJ z`+RZ2p0M4$oi8B6&y1#2_*as}gZwJL1#3~qiD*%909}?al|IL{8fHbsn@2@f)jg7z zBev7rCJ}ngi);q>4;y&+4E7uOi2IM>Gx|QVTpPdni1|X)AE6xKQ9slYOOVh?qMlie{hX5lkJH zrYWAi3n^+1SUzLN$LwS6z=xKDG%_U%^3;w6c^0 zkE8dv`2re3v79;*EHU4qpE+fSl37p{0_HU9SkDP3)B$ZbfPt++#5{dG&ZpFK{tF{yA@0A*=gWdM0;^SLs0$ zn464wdxgd>#wAD`SuvEsizBTimhcOnhBLs!85nA6GRIeX89;E#f=7oB%lbCl8+-vr7dvqu2Jkja2%pKqf>X{NleGbvT;X|JxNJ=VrTSEhw4`{z!%P=fCGQZjgvINJ1QEsunu|xyaNFDLH_pcx8h{Nc2RR<$%*MsEqFdmIe#cia zE;xKX8=O_}ezxcyNF!{!_ffcNc0sq`RRr|!XK6r1XjJfg9`>B=A@drRXUkyB7e@wt z7EAQCa9g5)$QdxR!63GMvN%~Hq-~-Rl2#cKA-3_Zv0`)Z^tF5L%1c8N+e=q&i+-Ha z9fb!{GbC7U0AhGh)&jUpY!*-~Y_#KnX!Q11Q8&h9ILxpkNFs)x4Bb}t(_P!x^RG!e zmKFGjSz+P%04!=ODJpSqhh5A9C^VyMB>djG!QoeY882S%0DATuS}r9ma1I~XBvi7HEk@ZMQL!$3Uni5hpw17iRJQw zLvr&u`q%4Uzx2*e&QHI*IeUK!H9in7Q7f5-i4zEvQX&M!PUw_Gc+ZWfM3M#GYDyGE zA0->yngv!fBf_bU2&OG+TbNjARHTzi29K)B`z(Sm|K)TD#Re&+mTZVA1P&7{k%C51 z7n|CT$5~auD`SHstw%jdO~(=DpPwE8CG{z4Zng*A%9ye>7Bk`dgodLpl5o@xm#$fO zSe9u${9Clx#X{eMtUOdLH=X0{H}lLGeP@ z5)lQ%1v&5d$q#rre+)l(qb@iuAnNH)AK%3hma%M$oEMC&rUnb%Oj_?9cv90b&iO-K z2rqJ8fSmQ{B9$k&#(QLhC2os3NZ@=i8qCbJqnN=lX@t0K0D}jqzn3;t@dni-QWLy6 zhEoC64L20Wdnq)au>`5}Uoe~kLkfqv$VnI{U3T52ztZ1-M zZwkb_O$!|_8}}6PoXP5j0)rM!m;`5QIT*qHhs7x(V}HQ#Dul*}L83ZzoRAvXHV4YR zAU~yP8!!j9e~(Vo7|=n1z30GYWT$ornuOFs)6+zi0Ra;P?E9;O6#4|-O40LiLYF0` z6n+iC;JBuIguZxw#CCU@>nBfK1!c}Ms4(nsms0U+Y#iF|V+Kx#I~I*?dYE*E)qn{M zL5e2%^(;ZwZ)z^?xqrFJhVez_^Of&a10zIa%Ei^`76gzoa5O9j_M3i~9y7oFaQ%&7a zW`mYQK29y^r>kV~9*h)Avqwte5%u2wdVc!xeU!M`S>WoixjRG>CX$$W0UEffm&G)y zRd~+g>EbDykQYw%x3}-!MF!}N`%klh=4MpgnE0nVxAz=(7axIp85wR<2&vp+{6oi~D}oDA){_rsG1^FQ0r2cbJkrad-Td*r zT%i{fCEc}APfUmE018uDA06I*{lcF0y&dZf-2=Qp?T2OkJ|CloYjwmLMQJ%Fk?I*) zHbq#VYmfn``~bWMuGfL=CQGkkB;=^L5!;RxujO|k)a?$l#?k47|E6-ws@THtP}fp) z#Ow8AH%**S)9=Y!Kp7p=`PxlVo`v0B1y>a?eo*f&aHFoNzt))J>07XBoxm~~qFP#R|$+$cV)sKt3?Wko5RSg-gOvV0zx*D8*?N za6j5O6{Gqrk48og1VM$`cEdB~yiN7+DhNBZU=3QT+zj~5Vz|rmS7Uj4b_Elo|HQ>u%@(}Rm@K&V4 z8stBfIYcuJ<4I>6&K5)^G|XX}#6v9~1f)}wPvsGakF?%yU0`2UJv)*Opi!Myo)x&K zNjVdqB$o8eARBP4L%>Co9OLPV=C^Z%=bGQ(x@oqR`d6AGM|Xo1^AZo)30No9(_Z2~ s-T(VmJt~Z)?|;ID?f>n=3t(&iZ~eFaTmP;9{+ECM0go{8pa6gm046CwdH?_b diff --git a/packages/client/src/generated/client.ts b/packages/client/src/generated/client.ts index 27ec3d81ba2c..823187a59180 100644 --- a/packages/client/src/generated/client.ts +++ b/packages/client/src/generated/client.ts @@ -141,7 +141,7 @@ export function make(options: ClientOptions) { const fetch = options.fetch ?? globalThis.fetch const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => { - const url = new URL(descriptor.path, options.baseUrl) + const url = new URL(options.baseUrl.replace(/[/]+$/, "") + descriptor.path) for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value) const headers = new Headers(options.headers) for (const [key, value] of Object.entries(descriptor.headers ?? {})) { diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index 322a39cd6b29..50908c35dd1b 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -171,6 +171,19 @@ test("session methods use the public HTTP contract", async () => { }) }) + +test("preserves base path in request URLs", async () => { + const client = OpenCode.make({ + baseUrl: "http://localhost:3000/opencode", + fetch: async (input) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url + expect(url).toBe("http://localhost:3000/opencode/api/session/ses_test") + return Response.json(session) + }, + }) + await client.sessions.get({ sessionID: "ses_test" }) +}) + test("middleware errors remain declared client errors", async () => { const client = OpenCode.make({ baseUrl: "http://localhost:3000", diff --git a/packages/httpapi-codegen/src/index.ts b/packages/httpapi-codegen/src/index.ts index 298a1211dddc..af78e5bbe923 100644 --- a/packages/httpapi-codegen/src/index.ts +++ b/packages/httpapi-codegen/src/index.ts @@ -534,7 +534,7 @@ function renderPromiseClient(groups: ReadonlyArray) { if (group.endpoints[0]?.topLevel) return methods.join(", ") return `${JSON.stringify(group.identifier)}: { ${methods.join(", ")} }` }) - return `import type { ${imports.join(", ")} } from "./types"\nimport { ClientError } from "./client-error"\n\nexport interface ClientOptions {\n readonly baseUrl: string\n readonly fetch?: typeof globalThis.fetch\n readonly headers?: HeadersInit\n}\n\nexport interface RequestOptions {\n readonly signal?: AbortSignal\n readonly headers?: HeadersInit\n}\n\ninterface RequestDescriptor {\n readonly method: string\n readonly path: string\n readonly query?: Record\n readonly headers?: Record\n readonly body?: unknown\n readonly successStatus: number\n readonly declaredStatuses: ReadonlyArray\n readonly empty: boolean\n}\n\nexport function make(options: ClientOptions) {\n const fetch = options.fetch ?? globalThis.fetch\n\n const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n const url = new URL(descriptor.path, options.baseUrl)\n for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value)\n const headers = new Headers(options.headers)\n for (const [key, value] of Object.entries(descriptor.headers ?? {})) {\n if (value !== undefined && value !== null) headers.set(key, String(value))\n }\n for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)\n if (descriptor.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json")\n return {\n url,\n init: {\n method: descriptor.method,\n signal: requestOptions?.signal,\n headers,\n body: descriptor.body === undefined ? undefined : JSON.stringify(descriptor.body),\n } satisfies RequestInit,\n }\n }\n\n const execute = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n try {\n const prepared = prepare(descriptor, requestOptions)\n return await fetch(prepared.url, prepared.init)\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n }\n\n const responseError = async (response: Response, descriptor: RequestDescriptor): Promise => {\n if (descriptor.declaredStatuses.includes(response.status)) throw await json(response)\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnexpectedStatus", { cause: { status: response.status } })\n }\n\n const request = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise => {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) return responseError(response, descriptor)\n if (descriptor.empty) {\n try {\n await response.body?.cancel()\n } catch {}\n return undefined as A\n }\n return await json(response) as A\n }\n\n const sse = (descriptor: RequestDescriptor, requestOptions?: RequestOptions): AsyncIterable => ({\n async *[Symbol.asyncIterator]() {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) await responseError(response, descriptor)\n if (!isContentType(response, "text/event-stream")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n if (response.body === null) throw new ClientError("MalformedResponse")\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ""\n try {\n while (true) {\n let next: ReadableStreamReadResult\n try {\n next = await reader.read()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n buffer += decoder.decode(next.value, { stream: !next.done })\n if (buffer.length > 1_048_576) throw new ClientError("MalformedResponse")\n const trailingCarriageReturn = !next.done && buffer.endsWith("\\r")\n if (trailingCarriageReturn) buffer = buffer.slice(0, -1)\n buffer = buffer.replaceAll("\\r\\n", "\\n").replaceAll("\\r", "\\n")\n if (trailingCarriageReturn) buffer += "\\r"\n if (next.done && buffer !== "") buffer += "\\n\\n"\n let boundary = buffer.indexOf("\\n\\n")\n while (boundary >= 0) {\n const block = buffer.slice(0, boundary)\n buffer = buffer.slice(boundary + 2)\n const data = block.split("\\n").flatMap((line) => line.startsWith("data:") ? [line.slice(5).trimStart()] : []).join("\\n")\n if (data !== "") {\n try {\n yield JSON.parse(data) as A\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n }\n boundary = buffer.indexOf("\\n\\n")\n }\n if (next.done) return\n }\n } finally {\n try {\n await reader.cancel()\n } catch {}\n reader.releaseLock()\n }\n },\n })\n\n return { ${fields.join(", ")} }\n}\n\nfunction appendQuery(params: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (Array.isArray(value)) {\n for (const item of value) appendQuery(params, key, item)\n return\n }\n if (typeof value === "object") {\n for (const [child, item] of Object.entries(value)) appendQuery(params, \`\${key}[\${child}]\`, item)\n return\n }\n params.append(key, String(value))\n}\n\nasync function json(response: Response): Promise {\n if (!isContentType(response, "application/json") && !response.headers.get("content-type")?.includes("+json")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n let text: string\n try {\n text = await response.text()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n if (text === "") throw new ClientError("MalformedResponse")\n try {\n return JSON.parse(text)\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n}\n\nfunction isContentType(response: Response, expected: string) {\n return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === expected\n}\n` + return `import type { ${imports.join(", ")} } from "./types"\nimport { ClientError } from "./client-error"\n\nexport interface ClientOptions {\n readonly baseUrl: string\n readonly fetch?: typeof globalThis.fetch\n readonly headers?: HeadersInit\n}\n\nexport interface RequestOptions {\n readonly signal?: AbortSignal\n readonly headers?: HeadersInit\n}\n\ninterface RequestDescriptor {\n readonly method: string\n readonly path: string\n readonly query?: Record\n readonly headers?: Record\n readonly body?: unknown\n readonly successStatus: number\n readonly declaredStatuses: ReadonlyArray\n readonly empty: boolean\n}\n\nexport function make(options: ClientOptions) {\n const fetch = options.fetch ?? globalThis.fetch\n\n const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n const url = new URL(options.baseUrl.replace(/[/]+$/, "") + descriptor.path)\n for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value)\n const headers = new Headers(options.headers)\n for (const [key, value] of Object.entries(descriptor.headers ?? {})) {\n if (value !== undefined && value !== null) headers.set(key, String(value))\n }\n for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)\n if (descriptor.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json")\n return {\n url,\n init: {\n method: descriptor.method,\n signal: requestOptions?.signal,\n headers,\n body: descriptor.body === undefined ? undefined : JSON.stringify(descriptor.body),\n } satisfies RequestInit,\n }\n }\n\n const execute = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n try {\n const prepared = prepare(descriptor, requestOptions)\n return await fetch(prepared.url, prepared.init)\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n }\n\n const responseError = async (response: Response, descriptor: RequestDescriptor): Promise => {\n if (descriptor.declaredStatuses.includes(response.status)) throw await json(response)\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnexpectedStatus", { cause: { status: response.status } })\n }\n\n const request = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise => {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) return responseError(response, descriptor)\n if (descriptor.empty) {\n try {\n await response.body?.cancel()\n } catch {}\n return undefined as A\n }\n return await json(response) as A\n }\n\n const sse = (descriptor: RequestDescriptor, requestOptions?: RequestOptions): AsyncIterable => ({\n async *[Symbol.asyncIterator]() {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) await responseError(response, descriptor)\n if (!isContentType(response, "text/event-stream")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n if (response.body === null) throw new ClientError("MalformedResponse")\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ""\n try {\n while (true) {\n let next: ReadableStreamReadResult\n try {\n next = await reader.read()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n buffer += decoder.decode(next.value, { stream: !next.done })\n if (buffer.length > 1_048_576) throw new ClientError("MalformedResponse")\n const trailingCarriageReturn = !next.done && buffer.endsWith("\\r")\n if (trailingCarriageReturn) buffer = buffer.slice(0, -1)\n buffer = buffer.replaceAll("\\r\\n", "\\n").replaceAll("\\r", "\\n")\n if (trailingCarriageReturn) buffer += "\\r"\n if (next.done && buffer !== "") buffer += "\\n\\n"\n let boundary = buffer.indexOf("\\n\\n")\n while (boundary >= 0) {\n const block = buffer.slice(0, boundary)\n buffer = buffer.slice(boundary + 2)\n const data = block.split("\\n").flatMap((line) => line.startsWith("data:") ? [line.slice(5).trimStart()] : []).join("\\n")\n if (data !== "") {\n try {\n yield JSON.parse(data) as A\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n }\n boundary = buffer.indexOf("\\n\\n")\n }\n if (next.done) return\n }\n } finally {\n try {\n await reader.cancel()\n } catch {}\n reader.releaseLock()\n }\n },\n })\n\n return { ${fields.join(", ")} }\n}\n\nfunction appendQuery(params: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (Array.isArray(value)) {\n for (const item of value) appendQuery(params, key, item)\n return\n }\n if (typeof value === "object") {\n for (const [child, item] of Object.entries(value)) appendQuery(params, \`\${key}[\${child}]\`, item)\n return\n }\n params.append(key, String(value))\n}\n\nasync function json(response: Response): Promise {\n if (!isContentType(response, "application/json") && !response.headers.get("content-type")?.includes("+json")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n let text: string\n try {\n text = await response.text()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n if (text === "") throw new ClientError("MalformedResponse")\n try {\n return JSON.parse(text)\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n}\n\nfunction isContentType(response: Response, expected: string) {\n return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === expected\n}\n` } function promiseTypePrefix(group: string, endpoint: string) { From be99479968f22d01f1b0799e094d46aee7b5bc9b Mon Sep 17 00:00:00 2001 From: fromelicks <108532072+fromelicks@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:33:40 +0200 Subject: [PATCH 13/13] fix(web): complete prefix routing through stripping proxies Port anomalyco/opencode#47442 onto dev, including generated and vendored clients, raw CLI requests, and terminal URLs. Support proxies that remove the public URL prefix and complete assets, PWA, deep-link, tab, and session-path handling. Validate client generation and archive reproducibility, server/client/app regressions, real HTTP listeners and PTYs, package typechecks, standalone builds, and browser navigation through a reverse proxy. --- bun.lock | 8 +- packages/app/package.json | 2 +- packages/app/script/patch-client-prefix.ts | 26 ++++ packages/app/src/app.tsx | 8 +- packages/app/src/components/debug-bar.tsx | 3 +- .../app/src/components/titlebar-tab-nav.tsx | 7 +- packages/app/src/components/titlebar.tsx | 5 +- packages/app/src/context/layout.tsx | 3 +- packages/app/src/context/tabs.tsx | 5 +- packages/app/src/entry.tsx | 9 +- packages/app/src/pages/directory-layout.tsx | 3 +- .../pages/session/use-session-hash-scroll.ts | 5 +- packages/app/src/utils/base-path.test.ts | 52 ++++++++ packages/app/src/utils/base-path.ts | 23 ++++ .../app/src/utils/server-protocol.test.ts | 11 ++ packages/app/src/utils/server-protocol.ts | 3 +- packages/app/src/utils/server.test.ts | 29 ++++- .../src/utils/terminal-websocket-url.test.ts | 14 ++ .../app/src/utils/terminal-websocket-url.ts | 3 +- .../opencode-ai-client-1.17.13-prefix.tgz | Bin 0 -> 70960 bytes packages/cli/src/commands/handlers/api.ts | 10 +- packages/client/src/generated/client.ts | 4 +- packages/client/test/base-url.test.ts | 41 ++++++ packages/client/test/promise.test.ts | 1 - packages/core/src/v1/config/server.ts | 3 + packages/httpapi-codegen/src/index.ts | 2 +- packages/opencode/src/cli/network.ts | 25 ++-- packages/opencode/src/server/base-path.ts | 54 ++++++++ packages/opencode/src/server/server.ts | 45 +------ packages/opencode/src/server/shared/ui.ts | 16 +-- .../test/server/base-path-proxy.test.ts | 120 ++++++++++++++++++ .../opencode/test/server/base-path.test.ts | 18 ++- .../test/server/network-prefix.test.ts | 35 +++++ packages/session-ui/package.json | 2 +- .../ui/src/assets/favicon/site.webmanifest | 10 +- 35 files changed, 501 insertions(+), 104 deletions(-) create mode 100644 packages/app/script/patch-client-prefix.ts create mode 100644 packages/app/src/utils/base-path.test.ts create mode 100644 packages/app/src/utils/base-path.ts create mode 100644 packages/app/vendor/opencode-ai-client-1.17.13-prefix.tgz create mode 100644 packages/client/test/base-url.test.ts create mode 100644 packages/opencode/src/server/base-path.ts create mode 100644 packages/opencode/test/server/base-path-proxy.test.ts create mode 100644 packages/opencode/test/server/network-prefix.test.ts diff --git a/bun.lock b/bun.lock index 0efe1b76b05f..24a8ff886cb6 100644 --- a/bun.lock +++ b/bun.lock @@ -37,7 +37,7 @@ "@dnd-kit/helpers": "0.5.0", "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", - "@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-v2.tgz", + "@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-prefix.tgz", "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", @@ -801,7 +801,7 @@ "version": "1.18.29", "dependencies": { "@kobalte/core": "catalog:", - "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", + "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-prefix.tgz", "@opencode-ai/core": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -6018,7 +6018,7 @@ "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - "@opencode-ai/app/@opencode-ai/client": ["@opencode-ai/client@vendor/opencode-ai-client-1.17.13-v2.tgz", {}, "sha512-332kgNifvpQOF9e3UA+pIa5xPrMhLaQkUiNiO+meS0Ba9HjSE6hfsWnEojMkD0DPSLqPP6rCF1dDoF7U0Y0OCQ=="], + "@opencode-ai/app/@opencode-ai/client": ["@opencode-ai/client@vendor/opencode-ai-client-1.17.13-prefix.tgz", {}, "sha512-xxrTEXfkoi9mlNQTHNScfNhO8CGtbsNK+YczGTgI0FyjD16/7GsJSJXI6JiYIVYLDz+98/h6pWT23+uALE/aKg=="], "@opencode-ai/core/@ai-sdk/openai": ["@ai-sdk/openai@3.0.88", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-6M4+bxK/UMijDYK2ia1hCKYma1iajFYxHDlhbinFUdhH2WbFsjoZRMpSjEMlTOBptqmh2J1JclKnprGulF8WwQ=="], @@ -6036,7 +6036,7 @@ "@opencode-ai/script/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], - "@opencode-ai/session-ui/@opencode-ai/client": ["@opencode-ai/client@../app/vendor/opencode-ai-client-1.17.13-v2.tgz", {}, "sha512-332kgNifvpQOF9e3UA+pIa5xPrMhLaQkUiNiO+meS0Ba9HjSE6hfsWnEojMkD0DPSLqPP6rCF1dDoF7U0Y0OCQ=="], + "@opencode-ai/session-ui/@opencode-ai/client": ["@opencode-ai/client@../app/vendor/opencode-ai-client-1.17.13-prefix.tgz", {}, "sha512-xxrTEXfkoi9mlNQTHNScfNhO8CGtbsNK+YczGTgI0FyjD16/7GsJSJXI6JiYIVYLDz+98/h6pWT23+uALE/aKg=="], "@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], diff --git a/packages/app/package.json b/packages/app/package.json index 4accd2c499e1..3fe5c7d3a992 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -54,7 +54,7 @@ "@dnd-kit/helpers": "0.5.0", "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", - "@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-v2.tgz", + "@opencode-ai/client": "file:vendor/opencode-ai-client-1.17.13-prefix.tgz", "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", "@opencode-ai/sdk": "workspace:*", diff --git a/packages/app/script/patch-client-prefix.ts b/packages/app/script/patch-client-prefix.ts new file mode 100644 index 000000000000..d084e6772ccd --- /dev/null +++ b/packages/app/script/patch-client-prefix.ts @@ -0,0 +1,26 @@ +import { $ } from "bun" +import { mkdtemp, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import path from "node:path" + +// The app consumes a frozen V2 client, not the workspace's current client. +// Port anomalyco/opencode#47442 without replacing that client's API contract. +const archive = path.resolve(import.meta.dir, "../vendor/opencode-ai-client-1.17.13-v2.tgz") +const output = archive.replace("-v2.tgz", "-prefix.tgz") +const directory = await mkdtemp(path.join(tmpdir(), "opencode-client-prefix-")) +try { + await $`tar -xzf ${archive} -C ${directory}` + const file = Bun.file(path.join(directory, "package/dist/promise/generated/client.js")) + const source = await file.text() + const previous = 'const url = new URL(options.baseUrl.replace(/[/]+$/, "") + descriptor.path);' + const patched = `const base = new URL(options.baseUrl); + base.pathname = base.pathname.replace(/[/]+$/, "") + "/"; + const url = new URL(descriptor.path.replace(/^[/]+/, ""), base);` + if (!source.includes(patched)) { + if (!source.includes(previous)) throw new Error("Vendored client changed: review the prefix patch before repacking") + await Bun.write(file, source.replace(previous, patched)) + await $`tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner -czf ${output} -C ${directory} package` + } +} finally { + await rm(directory, { recursive: true, force: true }) +} diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 816e2830c357..0e9898574575 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -1,3 +1,4 @@ +import { appBasePath } from "./utils/base-path" import "@/index.css" import * as Sentry from "@sentry/solid" import { I18nProvider } from "@opencode-ai/ui/context" @@ -601,12 +602,7 @@ export function AppInterface(props: { )} - base={(() => { - const bp = window.__OPENCODE_BASE_PATH__?.replace(/\/$/, "") - if (bp) return bp - const baseUri = new URL(document.baseURI).pathname.replace(/\/+$/, "") - return baseUri && baseUri !== "/" ? baseUri : undefined - })()} + base={appBasePath() || undefined} > diff --git a/packages/app/src/components/debug-bar.tsx b/packages/app/src/components/debug-bar.tsx index 9465374cbb06..b4a43777e067 100644 --- a/packages/app/src/components/debug-bar.tsx +++ b/packages/app/src/components/debug-bar.tsx @@ -1,3 +1,4 @@ +import { appPath } from "@/utils/base-path" import { useIsRouting, useLocation } from "@solidjs/router" import { batch, createEffect, onCleanup, onMount } from "solid-js" import { createStore } from "solid-js/store" @@ -222,7 +223,7 @@ export function DebugBar(props: { inline?: boolean } = {}) { createEffect(() => { const busy = routing() - const next = `${location.pathname}${location.search}` + const next = `${appPath(location.pathname)}${location.search}` if (!init) { init = true diff --git a/packages/app/src/components/titlebar-tab-nav.tsx b/packages/app/src/components/titlebar-tab-nav.tsx index b016f286a250..f8eb4bdb1cf0 100644 --- a/packages/app/src/components/titlebar-tab-nav.tsx +++ b/packages/app/src/components/titlebar-tab-nav.tsx @@ -1,3 +1,4 @@ +import { A } from "@solidjs/router" import { createEffect, createMemo, createSignal, onCleanup, Show, type Ref } from "solid-js" import { createStore } from "solid-js/store" import { makeEventListener } from "@solid-primitives/event-listener" @@ -201,7 +202,7 @@ export function TabNavItem(props: { }} > - {props.title} - +

`${location.pathname}${location.search}${location.hash}` + const path = () => `${appPath(location.pathname)}${location.search}${location.hash}` const creating = createMemo(() => { const route = layout.route() if (route.type === "draft" || route.type === "dir-new-sesssion") return true if (!params.dir) return false if (params.id) return false - const parts = location.pathname.replace(/\/+$/, "").split("/") + const parts = appPath(location.pathname).replace(/\/+$/, "").split("/") return parts.at(-1) === "session" }) diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 6235f35c45ae..4825b97e277f 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -1,3 +1,4 @@ +import { appPath } from "@/utils/base-path" import { createStore, produce, reconcile } from "solid-js/store" import { batch, createEffect, createMemo, onCleanup, onMount, type Accessor } from "solid-js" import { useLocation } from "@solidjs/router" @@ -167,7 +168,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const platform = usePlatform() const location = useLocation() const route = createMemo(() => { - const value = currentRoute(location.pathname, location.search) + const value = currentRoute(appPath(location.pathname), location.search) if (value.type === "home") return value if (value.server) return value if (value.type === "draft") { diff --git a/packages/app/src/context/tabs.tsx b/packages/app/src/context/tabs.tsx index b6608e868112..c1351cb87e65 100644 --- a/packages/app/src/context/tabs.tsx +++ b/packages/app/src/context/tabs.tsx @@ -1,3 +1,4 @@ +import { appPath } from "@/utils/base-path" import type { Session } from "@opencode-ai/sdk/v2/client" import { createSimpleContext } from "@opencode-ai/ui/context" import { createStore, produce } from "solid-js/store" @@ -157,7 +158,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({ if (!tab) return const key = tabKey(tab) const draftID = tab.type === "draft" ? tab.draftID : undefined - const nextTab = nextTabAfterClose(store, index, recentKey() === key && location.pathname !== "/") + const nextTab = nextTabAfterClose(store, index, recentKey() === key && appPath(location.pathname) !== "/") closing.add(key) void startTransition(() => { setStore( @@ -231,7 +232,7 @@ export const { use: useTabs, provider: TabsProvider } = createSimpleContext({ promoteDraft(draftID: string, session: Omit) { // Keep the replacement and navigation atomic so /new-session never renders // after its backing draft tab has been removed from the store. - const active = location.pathname === "/new-session" && location.query.draftId === draftID + const active = appPath(location.pathname) === "/new-session" && location.query.draftId === draftID const next = { type: "session" as const, ...session } void startTransition(() => { setStore( diff --git a/packages/app/src/entry.tsx b/packages/app/src/entry.tsx index caca49f95f56..13b9aa5f9d32 100644 --- a/packages/app/src/entry.tsx +++ b/packages/app/src/entry.tsx @@ -9,6 +9,7 @@ import { createBrowserDraftStore } from "@/utils/draft-store" import { dict as en } from "@/i18n/en" import { dict as zh } from "@/i18n/zh" import { authFromToken } from "@/utils/server" +import { appBasePath } from "./utils/base-path" import pkg from "../package.json" import { ServerConnection } from "./context/server" @@ -97,13 +98,7 @@ if (!(root instanceof HTMLElement) && import.meta.env.DEV) { } const getCurrentUrl = () => { - let basePath = window.__OPENCODE_BASE_PATH__ || import.meta.env.VITE_OPENCODE_SERVER_BASE_URL || "" - if (basePath && !basePath.startsWith("/")) basePath = "/" + basePath - basePath = basePath.replace(/\/+$/, "") - if (!basePath) { - const baseUriPath = new URL(document.baseURI).pathname.replace(/\/+$/, "") - if (baseUriPath && baseUriPath !== "/") basePath = baseUriPath - } + const basePath = appBasePath() if (location.hostname.includes("opencode.ai")) return "http://localhost:4096" if (import.meta.env.DEV) return `http://${import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "localhost"}:${import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"}${basePath}` diff --git a/packages/app/src/pages/directory-layout.tsx b/packages/app/src/pages/directory-layout.tsx index a0b357639655..be038a3ab5ab 100644 --- a/packages/app/src/pages/directory-layout.tsx +++ b/packages/app/src/pages/directory-layout.tsx @@ -1,3 +1,4 @@ +import { appPath } from "@/utils/base-path" import { DataProvider } from "@opencode-ai/session-ui/context" import { showToast } from "@/utils/toast" import { base64Encode } from "@opencode-ai/core/util/encode" @@ -38,7 +39,7 @@ export function DirectoryDataProvider( if (props.draftID || props.server?.()) return const next = sync().data.path.directory if (!next || next === directory()) return - const path = location.pathname.slice(slug().length + 1) + const path = appPath(location.pathname).slice(slug().length + 1) navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true }) }) diff --git a/packages/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts index 65577abae229..bf2f48faa6ab 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.ts @@ -1,3 +1,4 @@ +import { appPath } from "@/utils/base-path" import type { UserMessage } from "@opencode-ai/sdk/v2" import { useLocation, useNavigate } from "@solidjs/router" import { createEffect, createMemo, onCleanup, onMount } from "solid-js" @@ -49,14 +50,14 @@ export const useSessionHashScroll = (input: { if (input.pendingMessage()) input.setPendingMessage(undefined) if (!location.hash) return clearing = true - navigate(location.pathname + location.search, { replace: true }) + navigate(appPath(location.pathname) + location.search, { replace: true }) } const updateHash = (id: string) => { const hash = `#${input.anchor(id)}` if (location.hash === hash) return clearing = false - navigate(location.pathname + location.search + hash, { + navigate(appPath(location.pathname) + location.search + hash, { replace: true, }) } diff --git a/packages/app/src/utils/base-path.test.ts b/packages/app/src/utils/base-path.test.ts new file mode 100644 index 000000000000..eb18bfcffbea --- /dev/null +++ b/packages/app/src/utils/base-path.test.ts @@ -0,0 +1,52 @@ +import { beforeEach, afterEach, describe, expect, test } from "bun:test" +import { appBasePath, appPath, serverRequestURL } from "./base-path" + +const originalURL = location.href +beforeEach(() => { + location.href = "http://localhost/" +}) + +afterEach(() => { + delete window.__OPENCODE_BASE_PATH__ + document.querySelectorAll("base").forEach((base) => base.remove()) + location.href = originalURL +}) + +describe("appBasePath", () => { + test("does not confuse a reloaded route with the server prefix", () => { + history.replaceState(null, "", "/project/session/test") + expect(appBasePath()).toBe("") + }) + test("uses the injected prefix, including an explicitly empty prefix", () => { + history.replaceState(null, "", "/apps/opencode/project/session/test") + window.__OPENCODE_BASE_PATH__ = "/apps/opencode/" + expect(appBasePath()).toBe("/apps/opencode") + window.__OPENCODE_BASE_PATH__ = "" + expect(appBasePath()).toBe("") + }) + test("supports an explicit same-origin proxy base element", () => { + const base = document.createElement("base") + base.href = "/nested/proxy/service/" + document.head.append(base) + expect(appBasePath()).toBe("/nested/proxy/service") + base.href = "https://unrelated.example/other/" + expect(appBasePath()).toBe("") + }) +}) + +test.each(["", "/", "/proxy", "/proxy/", "/nested/proxy%20path/"])("server URL preserves prefix %j", (prefix) => { + expect(serverRequestURL("https://example.com" + prefix, "/api/health?test=a%3Fb").href).toBe( + "https://example.com" + prefix.replace(/\/+$/, "") + "/api/health?test=a%3Fb", + ) +}) + +test("application route matching strips exactly one prefix at a segment boundary", () => { + window.__OPENCODE_BASE_PATH__ = "/apps/opencode" + expect(appPath("/apps/opencode/new-session")).toBe("/new-session") + expect(appPath("/apps/opencode/server/test/session/ses_test")).toBe("/server/test/session/ses_test") + expect(appPath("/apps/opencode")).toBe("/") + expect(appPath("/apps/opencode/")).toBe("/") + expect(appPath("/apps/opencode-other/new-session")).toBe("/apps/opencode-other/new-session") + window.__OPENCODE_BASE_PATH__ = "" + expect(appPath("/new-session")).toBe("/new-session") +}) diff --git a/packages/app/src/utils/base-path.ts b/packages/app/src/utils/base-path.ts new file mode 100644 index 000000000000..faf52049d0f2 --- /dev/null +++ b/packages/app/src/utils/base-path.ts @@ -0,0 +1,23 @@ +export function appBasePath() { + if (typeof window === "undefined") return "" + const injected = window.__OPENCODE_BASE_PATH__ + if (injected !== undefined) return injected.replace(/\/+$/, "") + const base = document.querySelector("base[href]") + if (!base) return "" + const url = new URL(base.getAttribute("href")!, location.href) + return url.origin === location.origin ? url.pathname.replace(/\/+$/, "") : "" +} + +export function serverRequestURL(server: string, path: string) { + const base = new URL(server) + base.pathname = base.pathname.replace(/\/+$/, "") + "/" + return new URL(path.replace(/^\/+/, ""), base) +} + +// Solid Router's location includes its base, while application route parsers do not. +export function appPath(pathname: string) { + const base = appBasePath() + if (base && pathname === base) return "/" + if (base && pathname.startsWith(base + "/")) return pathname.slice(base.length) + return pathname +} diff --git a/packages/app/src/utils/server-protocol.test.ts b/packages/app/src/utils/server-protocol.test.ts index 2130a968c4bc..0a204c1a571b 100644 --- a/packages/app/src/utils/server-protocol.test.ts +++ b/packages/app/src/utils/server-protocol.test.ts @@ -38,3 +38,14 @@ describe("detectServerProtocol", () => { expect(await detectServerProtocol(server, fetcher)).toBe("v1") }) }) + +test.each(["/proxy", "/nested/proxy/service/"])("health probes keep the prefix %j", async (prefix) => { + const paths: string[] = [] + const fetcher = mockFetch(async (input) => { + const url = new URL(input instanceof Request ? input.url : input) + paths.push(url.pathname) + return url.pathname.endsWith("/global/health") ? json({}, 404) : json({ healthy: true, pid: 1 }) + }) + expect(await detectServerProtocol({ url: "https://example.com" + prefix }, fetcher)).toBe("v2") + expect(paths).toEqual([prefix.replace(/\/+$/, "") + "/global/health", prefix.replace(/\/+$/, "") + "/api/health"]) +}) diff --git a/packages/app/src/utils/server-protocol.ts b/packages/app/src/utils/server-protocol.ts index 622e2689ce1e..5294ff1a9a22 100644 --- a/packages/app/src/utils/server-protocol.ts +++ b/packages/app/src/utils/server-protocol.ts @@ -1,3 +1,4 @@ +import { serverRequestURL } from "./base-path" import type { ServerConnection } from "@/context/server" import { authTokenFromCredentials } from "./server" @@ -11,7 +12,7 @@ function headers(server: ServerConnection.HttpBase) { } async function probe(server: ServerConnection.HttpBase, fetch: typeof globalThis.fetch, path: string) { - const response = await fetch(new URL(server.url.replace(/[/]+$/, "") + path), { + const response = await fetch(serverRequestURL(server.url, path), { headers: headers(server), signal: AbortSignal.timeout(5_000), }) diff --git a/packages/app/src/utils/server.test.ts b/packages/app/src/utils/server.test.ts index 4666b7d6d03c..97654caae7d4 100644 --- a/packages/app/src/utils/server.test.ts +++ b/packages/app/src/utils/server.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { authFromToken, authTokenFromCredentials } from "./server" +import { authFromToken, authTokenFromCredentials, createApiForServer } from "./server" describe("authFromToken", () => { test("decodes basic auth credentials from auth_token", () => { @@ -21,3 +21,30 @@ describe("authTokenFromCredentials", () => { expect(authTokenFromCredentials({ password: "secret" })).toBe(btoa("opencode:secret")) }) }) + +test("the vendored web client keeps the public prefix for HTTP and SSE", async () => { + const paths: string[] = [] + const client = createApiForServer({ + server: { url: "https://example.com/nested/proxy/service/", password: "secret" }, + fetch: Object.assign( + async (input: string | URL | Request, init?: RequestInit) => { + const url = new URL(input instanceof Request ? input.url : input) + paths.push(url.pathname) + expect(new Headers(init?.headers).get("authorization")).toBe("Basic " + btoa("opencode:secret")) + if (url.pathname.endsWith("/api/health")) return Response.json({ healthy: true, version: "test", pid: 1 }) + return new Response('data: {"id":"evt_test","type":"server.connected","data":{}}\n\n', { + headers: { "content-type": "text/event-stream" }, + }) + }, + { preconnect: globalThis.fetch.preconnect }, + ), + }) + expect((await client.health.get()).healthy).toBe(true) + const events = client.event.subscribe()[Symbol.asyncIterator]() + try { + expect((await events.next()).value?.type).toBe("server.connected") + } finally { + await events.return?.() + } + expect(paths).toEqual(["/nested/proxy/service/api/health", "/nested/proxy/service/api/event"]) +}) diff --git a/packages/app/src/utils/terminal-websocket-url.test.ts b/packages/app/src/utils/terminal-websocket-url.test.ts index aac854ca82d6..ea3b7cccc486 100644 --- a/packages/app/src/utils/terminal-websocket-url.test.ts +++ b/packages/app/src/utils/terminal-websocket-url.test.ts @@ -78,3 +78,17 @@ describe("terminalWebSocketURL", () => { expect(url.searchParams.get("auth_token")).toBe(btoa("opencode:secret")) }) }) + +test.each(["v1", "v2"] as const)("preserves nested prefixes in %s WebSocket connections", (protocol) => { + const url = terminalWebSocketURL({ + protocol, + url: "https://example.com/nested/proxy/service/", + id: "pty_test", + directory: "/tmp/project", + cursor: 42, + ticket: "ticket", + }) + expect(url.pathname).toBe(`/nested/proxy/service${protocol === "v1" ? "" : "/api"}/pty/pty_test/connect`) + expect(url.protocol).toBe("wss:") + expect(url.searchParams.get("ticket")).toBe("ticket") +}) diff --git a/packages/app/src/utils/terminal-websocket-url.ts b/packages/app/src/utils/terminal-websocket-url.ts index a32b239cc932..e3b70ceab4d1 100644 --- a/packages/app/src/utils/terminal-websocket-url.ts +++ b/packages/app/src/utils/terminal-websocket-url.ts @@ -1,3 +1,4 @@ +import { serverRequestURL } from "./base-path" import { authTokenFromCredentials } from "@/utils/server" export function terminalWebSocketURL(input: { @@ -13,7 +14,7 @@ export function terminalWebSocketURL(input: { authToken?: boolean }) { const isV1 = input.protocol === "v1" - const next = new URL(`${input.url}${isV1 ? `/pty/${input.id}/connect` : `/api/pty/${input.id}/connect`}`) + const next = serverRequestURL(input.url, `${isV1 ? "" : "/api"}/pty/${encodeURIComponent(input.id)}/connect`) if (isV1) { next.searchParams.set("directory", input.directory) } else { diff --git a/packages/app/vendor/opencode-ai-client-1.17.13-prefix.tgz b/packages/app/vendor/opencode-ai-client-1.17.13-prefix.tgz new file mode 100644 index 0000000000000000000000000000000000000000..a664e1c256be84278a87d661bb7357d0c5d7b78d GIT binary patch literal 70960 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 R$32_!{6B*3Nnrq>4**@~Xq5l} literal 0 HcmV?d00001 diff --git a/packages/cli/src/commands/handlers/api.ts b/packages/cli/src/commands/handlers/api.ts index cf00394cb902..2f8439d1f9ba 100644 --- a/packages/cli/src/commands/handlers/api.ts +++ b/packages/cli/src/commands/handlers/api.ts @@ -31,7 +31,7 @@ export default Runtime.handler( if (body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json") const response = yield* Effect.tryPromise(() => - fetch(new URL(request.path, transport.url), { + fetch(requestURL(transport.url, request.path), { method: request.method, headers, body, @@ -66,7 +66,7 @@ function resolveRequest( if (raw) return Effect.succeed(raw) if (input.length !== 1) return Effect.fail(new Error("Expected an operation name or an HTTP method and path")) return Effect.tryPromise(async () => { - const response = await fetch(new URL("/openapi.json", transport.url), { headers: transport.headers }) + const response = await fetch(requestURL(transport.url, "/openapi.json"), { headers: transport.headers }) if (!response.ok) throw new Error(`Failed to load OpenAPI document: HTTP ${response.status}`) return resolveOperation((await response.json()) as OpenApi, input[0], params) }) @@ -83,3 +83,9 @@ function interpolate(path: string, params: Record) { const query = new URLSearchParams(Object.entries(params).filter(([name]) => !used.has(name))).toString() return query ? `${pathname}?${query}` : pathname } + +export function requestURL(server: string, path: string) { + const base = new URL(server) + base.pathname = base.pathname.replace(/\/+$/, "") + "/" + return new URL(path.replace(/^\/+/, ""), base) +} diff --git a/packages/client/src/generated/client.ts b/packages/client/src/generated/client.ts index 823187a59180..6d3f2a58be50 100644 --- a/packages/client/src/generated/client.ts +++ b/packages/client/src/generated/client.ts @@ -141,7 +141,9 @@ export function make(options: ClientOptions) { const fetch = options.fetch ?? globalThis.fetch const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => { - const url = new URL(options.baseUrl.replace(/[/]+$/, "") + descriptor.path) + const base = new URL(options.baseUrl) + base.pathname = base.pathname.replace(/[/]+$/, "") + "/" + const url = new URL(descriptor.path.replace(/^[/]+/, ""), base) for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value) const headers = new Headers(options.headers) for (const [key, value] of Object.entries(descriptor.headers ?? {})) { diff --git a/packages/client/test/base-url.test.ts b/packages/client/test/base-url.test.ts new file mode 100644 index 000000000000..f6167884a0a0 --- /dev/null +++ b/packages/client/test/base-url.test.ts @@ -0,0 +1,41 @@ +import { expect, test } from "bun:test" +import { OpenCode } from "../src" + +test.each(["", "/", "/proxy", "/proxy/", "/nested/proxy%20path", "/nested/proxy/service/"])( + "preserves server base path %j for HTTP, queries, auth and SSE", + async (prefix) => { + const requests: string[] = [] + const base = prefix.replace(/\/+$/, "") + const event = { id: "evt_connected", type: "server.connected", data: {} } + using server = Bun.serve({ + hostname: "127.0.0.1", + port: 0, + fetch(request) { + const url = new URL(request.url) + requests.push(url.pathname + url.search) + expect(request.headers.get("authorization")).toBe("Bearer test") + if (url.pathname === `${base}/api/health`) return Response.json({ healthy: true }) + if (url.pathname === `${base}/api/session`) return Response.json([]) + if (url.pathname === `${base}/api/event`) + return new Response(`data: ${JSON.stringify(event)}\n\n`, { + headers: { "content-type": "text/event-stream" }, + }) + return new Response("Not found", { status: 404 }) + }, + }) + const client = OpenCode.make({ baseUrl: server.url.origin + prefix, headers: { authorization: "Bearer test" } }) + expect(await client.health.get()).toEqual({ healthy: true }) + await client.sessions.list({ directory: "/tmp/project" }) + const events = client.events.subscribe()[Symbol.asyncIterator]() + try { + expect((await events.next()).value).toEqual(event) + } finally { + await events.return?.() + } + expect(requests).toEqual([ + `${base}/api/health`, + `${base}/api/session?directory=%2Ftmp%2Fproject`, + `${base}/api/event`, + ]) + }, +) diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index 50908c35dd1b..6a48f669afbe 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -171,7 +171,6 @@ test("session methods use the public HTTP contract", async () => { }) }) - test("preserves base path in request URLs", async () => { const client = OpenCode.make({ baseUrl: "http://localhost:3000/opencode", diff --git a/packages/core/src/v1/config/server.ts b/packages/core/src/v1/config/server.ts index 808f931c0455..f69f98820d00 100644 --- a/packages/core/src/v1/config/server.ts +++ b/packages/core/src/v1/config/server.ts @@ -15,6 +15,9 @@ export const Server = Schema.Struct({ cors: Schema.optional(Schema.mutable(Schema.Array(Schema.String))).annotate({ description: "Additional domains to allow for CORS", }), + basePathStripped: Schema.optional(Schema.Boolean).annotate({ + description: "Reverse proxy strips basePath before forwarding requests", + }), basePath: Schema.optional(Schema.String).annotate({ description: "Base path prefix for hosting behind a reverse proxy (e.g., '/opencode')", }), diff --git a/packages/httpapi-codegen/src/index.ts b/packages/httpapi-codegen/src/index.ts index af78e5bbe923..b195d4f9f026 100644 --- a/packages/httpapi-codegen/src/index.ts +++ b/packages/httpapi-codegen/src/index.ts @@ -534,7 +534,7 @@ function renderPromiseClient(groups: ReadonlyArray) { if (group.endpoints[0]?.topLevel) return methods.join(", ") return `${JSON.stringify(group.identifier)}: { ${methods.join(", ")} }` }) - return `import type { ${imports.join(", ")} } from "./types"\nimport { ClientError } from "./client-error"\n\nexport interface ClientOptions {\n readonly baseUrl: string\n readonly fetch?: typeof globalThis.fetch\n readonly headers?: HeadersInit\n}\n\nexport interface RequestOptions {\n readonly signal?: AbortSignal\n readonly headers?: HeadersInit\n}\n\ninterface RequestDescriptor {\n readonly method: string\n readonly path: string\n readonly query?: Record\n readonly headers?: Record\n readonly body?: unknown\n readonly successStatus: number\n readonly declaredStatuses: ReadonlyArray\n readonly empty: boolean\n}\n\nexport function make(options: ClientOptions) {\n const fetch = options.fetch ?? globalThis.fetch\n\n const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n const url = new URL(options.baseUrl.replace(/[/]+$/, "") + descriptor.path)\n for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value)\n const headers = new Headers(options.headers)\n for (const [key, value] of Object.entries(descriptor.headers ?? {})) {\n if (value !== undefined && value !== null) headers.set(key, String(value))\n }\n for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)\n if (descriptor.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json")\n return {\n url,\n init: {\n method: descriptor.method,\n signal: requestOptions?.signal,\n headers,\n body: descriptor.body === undefined ? undefined : JSON.stringify(descriptor.body),\n } satisfies RequestInit,\n }\n }\n\n const execute = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n try {\n const prepared = prepare(descriptor, requestOptions)\n return await fetch(prepared.url, prepared.init)\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n }\n\n const responseError = async (response: Response, descriptor: RequestDescriptor): Promise => {\n if (descriptor.declaredStatuses.includes(response.status)) throw await json(response)\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnexpectedStatus", { cause: { status: response.status } })\n }\n\n const request = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise => {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) return responseError(response, descriptor)\n if (descriptor.empty) {\n try {\n await response.body?.cancel()\n } catch {}\n return undefined as A\n }\n return await json(response) as A\n }\n\n const sse = (descriptor: RequestDescriptor, requestOptions?: RequestOptions): AsyncIterable => ({\n async *[Symbol.asyncIterator]() {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) await responseError(response, descriptor)\n if (!isContentType(response, "text/event-stream")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n if (response.body === null) throw new ClientError("MalformedResponse")\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ""\n try {\n while (true) {\n let next: ReadableStreamReadResult\n try {\n next = await reader.read()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n buffer += decoder.decode(next.value, { stream: !next.done })\n if (buffer.length > 1_048_576) throw new ClientError("MalformedResponse")\n const trailingCarriageReturn = !next.done && buffer.endsWith("\\r")\n if (trailingCarriageReturn) buffer = buffer.slice(0, -1)\n buffer = buffer.replaceAll("\\r\\n", "\\n").replaceAll("\\r", "\\n")\n if (trailingCarriageReturn) buffer += "\\r"\n if (next.done && buffer !== "") buffer += "\\n\\n"\n let boundary = buffer.indexOf("\\n\\n")\n while (boundary >= 0) {\n const block = buffer.slice(0, boundary)\n buffer = buffer.slice(boundary + 2)\n const data = block.split("\\n").flatMap((line) => line.startsWith("data:") ? [line.slice(5).trimStart()] : []).join("\\n")\n if (data !== "") {\n try {\n yield JSON.parse(data) as A\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n }\n boundary = buffer.indexOf("\\n\\n")\n }\n if (next.done) return\n }\n } finally {\n try {\n await reader.cancel()\n } catch {}\n reader.releaseLock()\n }\n },\n })\n\n return { ${fields.join(", ")} }\n}\n\nfunction appendQuery(params: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (Array.isArray(value)) {\n for (const item of value) appendQuery(params, key, item)\n return\n }\n if (typeof value === "object") {\n for (const [child, item] of Object.entries(value)) appendQuery(params, \`\${key}[\${child}]\`, item)\n return\n }\n params.append(key, String(value))\n}\n\nasync function json(response: Response): Promise {\n if (!isContentType(response, "application/json") && !response.headers.get("content-type")?.includes("+json")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n let text: string\n try {\n text = await response.text()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n if (text === "") throw new ClientError("MalformedResponse")\n try {\n return JSON.parse(text)\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n}\n\nfunction isContentType(response: Response, expected: string) {\n return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === expected\n}\n` + return `import type { ${imports.join(", ")} } from "./types"\nimport { ClientError } from "./client-error"\n\nexport interface ClientOptions {\n readonly baseUrl: string\n readonly fetch?: typeof globalThis.fetch\n readonly headers?: HeadersInit\n}\n\nexport interface RequestOptions {\n readonly signal?: AbortSignal\n readonly headers?: HeadersInit\n}\n\ninterface RequestDescriptor {\n readonly method: string\n readonly path: string\n readonly query?: Record\n readonly headers?: Record\n readonly body?: unknown\n readonly successStatus: number\n readonly declaredStatuses: ReadonlyArray\n readonly empty: boolean\n}\n\nexport function make(options: ClientOptions) {\n const fetch = options.fetch ?? globalThis.fetch\n\n const prepare = (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n const base = new URL(options.baseUrl)\n base.pathname = base.pathname.replace(/[/]+$/, "") + "/"\n const url = new URL(descriptor.path.replace(/^[/]+/, ""), base)\n for (const [key, value] of Object.entries(descriptor.query ?? {})) appendQuery(url.searchParams, key, value)\n const headers = new Headers(options.headers)\n for (const [key, value] of Object.entries(descriptor.headers ?? {})) {\n if (value !== undefined && value !== null) headers.set(key, String(value))\n }\n for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value)\n if (descriptor.body !== undefined && !headers.has("content-type")) headers.set("content-type", "application/json")\n return {\n url,\n init: {\n method: descriptor.method,\n signal: requestOptions?.signal,\n headers,\n body: descriptor.body === undefined ? undefined : JSON.stringify(descriptor.body),\n } satisfies RequestInit,\n }\n }\n\n const execute = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions) => {\n try {\n const prepared = prepare(descriptor, requestOptions)\n return await fetch(prepared.url, prepared.init)\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n }\n\n const responseError = async (response: Response, descriptor: RequestDescriptor): Promise => {\n if (descriptor.declaredStatuses.includes(response.status)) throw await json(response)\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnexpectedStatus", { cause: { status: response.status } })\n }\n\n const request = async (descriptor: RequestDescriptor, requestOptions?: RequestOptions): Promise => {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) return responseError(response, descriptor)\n if (descriptor.empty) {\n try {\n await response.body?.cancel()\n } catch {}\n return undefined as A\n }\n return await json(response) as A\n }\n\n const sse = (descriptor: RequestDescriptor, requestOptions?: RequestOptions): AsyncIterable => ({\n async *[Symbol.asyncIterator]() {\n const response = await execute(descriptor, requestOptions)\n if (response.status !== descriptor.successStatus) await responseError(response, descriptor)\n if (!isContentType(response, "text/event-stream")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n if (response.body === null) throw new ClientError("MalformedResponse")\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let buffer = ""\n try {\n while (true) {\n let next: ReadableStreamReadResult\n try {\n next = await reader.read()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n buffer += decoder.decode(next.value, { stream: !next.done })\n if (buffer.length > 1_048_576) throw new ClientError("MalformedResponse")\n const trailingCarriageReturn = !next.done && buffer.endsWith("\\r")\n if (trailingCarriageReturn) buffer = buffer.slice(0, -1)\n buffer = buffer.replaceAll("\\r\\n", "\\n").replaceAll("\\r", "\\n")\n if (trailingCarriageReturn) buffer += "\\r"\n if (next.done && buffer !== "") buffer += "\\n\\n"\n let boundary = buffer.indexOf("\\n\\n")\n while (boundary >= 0) {\n const block = buffer.slice(0, boundary)\n buffer = buffer.slice(boundary + 2)\n const data = block.split("\\n").flatMap((line) => line.startsWith("data:") ? [line.slice(5).trimStart()] : []).join("\\n")\n if (data !== "") {\n try {\n yield JSON.parse(data) as A\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n }\n boundary = buffer.indexOf("\\n\\n")\n }\n if (next.done) return\n }\n } finally {\n try {\n await reader.cancel()\n } catch {}\n reader.releaseLock()\n }\n },\n })\n\n return { ${fields.join(", ")} }\n}\n\nfunction appendQuery(params: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (Array.isArray(value)) {\n for (const item of value) appendQuery(params, key, item)\n return\n }\n if (typeof value === "object") {\n for (const [child, item] of Object.entries(value)) appendQuery(params, \`\${key}[\${child}]\`, item)\n return\n }\n params.append(key, String(value))\n}\n\nasync function json(response: Response): Promise {\n if (!isContentType(response, "application/json") && !response.headers.get("content-type")?.includes("+json")) {\n try {\n await response.body?.cancel()\n } catch {}\n throw new ClientError("UnsupportedContentType")\n }\n let text: string\n try {\n text = await response.text()\n } catch (cause) {\n throw new ClientError("Transport", { cause })\n }\n if (text === "") throw new ClientError("MalformedResponse")\n try {\n return JSON.parse(text)\n } catch (cause) {\n throw new ClientError("MalformedResponse", { cause })\n }\n}\n\nfunction isContentType(response: Response, expected: string) {\n return response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() === expected\n}\n` } function promiseTypePrefix(group: string, endpoint: string) { diff --git a/packages/opencode/src/cli/network.ts b/packages/opencode/src/cli/network.ts index 63bd030a754a..60a4bd0b4267 100644 --- a/packages/opencode/src/cli/network.ts +++ b/packages/opencode/src/cli/network.ts @@ -1,6 +1,7 @@ +import { normalizeBasePath } from "../server/base-path" +export { normalizeBasePath } from "../server/base-path" import type { Argv, InferredOptionTypes } from "yargs" import { ConfigV1 } from "@opencode-ai/core/v1/config/config" -import type { Config } from "@/config/config" import { Effect } from "effect" const options = { @@ -30,6 +31,11 @@ const options = { describe: "additional domains to allow for CORS", default: [] as string[], }, + "base-path-stripped": { + type: "boolean" as const, + describe: "reverse proxy strips the base path before forwarding requests", + default: false, + }, "base-path": { type: "string" as const, describe: "base URL path prefix for reverse proxy (e.g., /opencode)", @@ -37,15 +43,6 @@ const options = { }, } -export function normalizeBasePath(input: string | undefined): string { - if (!input) return "" - let p = input.trim() - if (!p || p === "/") return "" - if (!p.startsWith("/")) p = "/" + p - p = p.replace(/\/+$/, "") - return p -} - export type NetworkOptions = InferredOptionTypes export function withNetworkOptions(yargs: Argv) { @@ -89,10 +86,14 @@ export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: Con const configCors = config?.server?.cors ?? [] const argsCors = Array.isArray(args.cors) ? args.cors : args.cors ? [args.cors] : [] const cors = [...configCors, ...argsCors] - const basePathExplicitlySet = process.argv.some((a) => a === "--base-path" || a.startsWith("--base-path=")) + const basePathExplicitlySet = hasArg("--base-path") const basePath = normalizeBasePath( basePathExplicitlySet ? args["base-path"] : (config?.server?.basePath ?? args["base-path"]), ) - return { hostname, port, mdns, mdnsDomain, cors, basePath } + const basePathStripped = hasBooleanArg("--base-path-stripped") + ? args["base-path-stripped"] + : (config?.server?.basePathStripped ?? args["base-path-stripped"]) + + return { hostname, port, mdns, mdnsDomain, cors, basePath, basePathStripped } } diff --git a/packages/opencode/src/server/base-path.ts b/packages/opencode/src/server/base-path.ts new file mode 100644 index 000000000000..3d1cd7dde006 --- /dev/null +++ b/packages/opencode/src/server/base-path.ts @@ -0,0 +1,54 @@ +import { createServer, type IncomingMessage, type ServerResponse } from "node:http" +import type { Duplex } from "node:stream" + +export function normalizeBasePath(input: string | undefined) { + const value = input?.trim().replace(/\/+$/, "") ?? "" + if (!value) return "" + const path = value.startsWith("/") ? value : `/${value}` + if ( + path.startsWith("//") || + /[?#\\\u0000-\u0020"'<>]/.test(path) || + new URL(path, "http://localhost").pathname !== path + ) + throw new Error("base-path must be a URL-encoded path without a query, fragment, or dot segments") + return path +} + +export function basePathServer(options: { basePath?: string; basePathStripped?: boolean }) { + const server = createServer() + const base = normalizeBasePath(options.basePath) + // The public prefix is still injected into HTML when a reverse proxy removes it on ingress. + if (!base || options.basePathStripped) return server + + // EventEmitter overloads do not correlate the event name with the argument types. + /* oxlint-disable typescript-eslint/no-unsafe-type-assertion */ + const emit = server.emit.bind(server) + server.emit = ((event: string, ...args: unknown[]) => { + if (event !== "request" && event !== "upgrade") return emit(event, ...args) + const request = args[0] as IncomingMessage + const url = request.url ?? "/" + const index = url.indexOf("?") + const pathname = index === -1 ? url : url.slice(0, index) + if (pathname === base && event === "request") { + const response = args[1] as ServerResponse + response.writeHead(308, { Location: base + "/" + (index === -1 ? "" : url.slice(index)) }) + response.end() + return true + } + if (pathname.startsWith(base + "/")) { + request.url = url.slice(base.length) + return emit(event, ...args) + } + if (event === "request") { + const response = args[1] as ServerResponse + response.writeHead(404, { "Content-Type": "text/plain" }) + response.end("Not Found") + return true + } + const socket = args[1] as Duplex + socket.destroy() + return true + }) as typeof server.emit + /* oxlint-enable typescript-eslint/no-unsafe-type-assertion */ + return server +} diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 4981418f488c..fb2c7997442d 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -5,8 +5,7 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder" import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect" import { HttpRouter, HttpServer } from "effect/unstable/http" import { OpenApi } from "effect/unstable/httpapi" -import { createServer, type IncomingMessage, type ServerResponse } from "node:http" -import type { Duplex } from "node:stream" +import { basePathServer, normalizeBasePath } from "./base-path" import { MDNS } from "./mdns" import { HttpApiApp } from "./routes/instance/httpapi/server" import { disposeMiddleware } from "./routes/instance/httpapi/lifecycle" @@ -37,6 +36,7 @@ type ListenOptions = CorsOptions & { mdns?: boolean mdnsDomain?: string basePath?: string + basePathStripped?: boolean } type ListenerState = { scope: Scope.Scope @@ -44,8 +44,7 @@ type ListenerState = { http: ListenerServer websockets: WebSocketTracker.Interface } -type EffectListener = Omit & { - basePath: string +type EffectListener = Omit & { stop: (close?: boolean) => Effect.Effect } @@ -75,7 +74,7 @@ export async function openapi() { export let url: URL | undefined export async function listen(opts: ListenOptions): Promise { - const listener = await Effect.runPromise(listenEffect(opts)) + const listener = await Effect.runPromise(listenEffect({ ...opts, basePath: normalizeBasePath(opts.basePath) })) return { hostname: listener.hostname, port: listener.port, @@ -111,7 +110,7 @@ function listenerLayer(opts: ListenOptions, port: number) { disableListenLog: true, }).pipe( Layer.provideMerge(AppNodeBuilder.build(WebSocketTracker.node)), - Layer.provideMerge(serverLayer({ port, hostname: opts.hostname, basePath: opts.basePath })), + Layer.provideMerge(serverLayer({ ...opts, port })), // Install a fresh `ConfigProvider` per listener so `Config.string(...)` // reads reflect the current `process.env`. Effect's default // `ConfigProvider` snapshots `process.env` on first read and caches the @@ -204,38 +203,8 @@ function forceClose(state: ListenerState) { return Effect.all([state.http.closeAll, state.websockets.closeAll], { concurrency: "unbounded", discard: true }) } -function serverLayer(opts: { port: number; hostname: string; basePath?: string }) { - const server = createServer() - const bp = opts.basePath ?? "" - - if (bp) { - const originalEmit = server.emit.bind(server) - server.emit = ((event: string, ...args: unknown[]) => { - if (event === "request" || event === "upgrade") { - const req = args[0] as IncomingMessage - const [pathname, query] = (req.url ?? "").split("?", 2) - if (pathname === bp && event === "request") { - const res = args[1] as ServerResponse - res.writeHead(301, { Location: bp + "/" + (query ? "?" + query : "") }) - res.end() - return true - } else if (req.url && req.url.startsWith(bp + "/")) { - req.url = req.url.slice(bp.length) || "/" - } else { - if (event === "request") { - const res = args[1] as ServerResponse - res.writeHead(404, { "Content-Type": "text/plain" }) - res.end("Not Found") - } else { - const socket = args[1] as Duplex - socket.destroy() - } - return true - } - } - return originalEmit(event, ...args) - }) as typeof server.emit - } +function serverLayer(opts: ListenOptions) { + const server = basePathServer(opts) const serverRef = { closeStarted: false, forceStop: false } const close = server.close.bind(server) diff --git a/packages/opencode/src/server/shared/ui.ts b/packages/opencode/src/server/shared/ui.ts index 804ea9f57bba..d061dd87cf61 100644 --- a/packages/opencode/src/server/shared/ui.ts +++ b/packages/opencode/src/server/shared/ui.ts @@ -59,12 +59,14 @@ export function embeddedUI(disableEmbeddedWebUi: boolean) { } export function injectBasePath(html: string, basePath: string): string { - if (!basePath) return html const safe = JSON.stringify(basePath).replace(/window.__OPENCODE_BASE_PATH__=${safe}` - html = html.replace("", `\n${script}`) - html = html.replace(/((?:href|src)=["'])\/(?!\/)/g, `$1${basePath}/`) - return html + // Vite emits relative entrypoints; the base must also work on a deep-link reload. + const body = html + .replace(/]*>/gi, "") + .replace(/((?:href|src)=["'])\/(?!\/)/g, (_, attribute: string) => `${attribute}${href}`) + return body.replace(/]*>/i, (head) => `${head}\n\n${script}`) } function notFound() { @@ -75,8 +77,7 @@ function embeddedUIResponse(file: string, body: Uint8Array, basePath = "") { const mime = FSUtil.mimeType(file) const headers = new Headers({ "content-type": mime }) if (mime.startsWith("text/html")) { - let html = new TextDecoder().decode(body) - if (basePath) html = injectBasePath(html, basePath) + const html = injectBasePath(new TextDecoder().decode(body), basePath) headers.set("content-security-policy", cspForHtml(html)) return HttpServerResponse.text(html, { headers }) } @@ -118,8 +119,7 @@ export function serveUIEffect( const headers = proxyResponseHeaders(response.headers) if (response.headers["content-type"]?.includes("text/html")) { - let body = yield* response.text - if (bp) body = injectBasePath(body, bp) + const body = injectBasePath(yield* response.text, bp) headers.set("Content-Security-Policy", cspForHtml(body)) return HttpServerResponse.text(body, { status: response.status, headers }) } diff --git a/packages/opencode/test/server/base-path-proxy.test.ts b/packages/opencode/test/server/base-path-proxy.test.ts new file mode 100644 index 000000000000..2661df264684 --- /dev/null +++ b/packages/opencode/test/server/base-path-proxy.test.ts @@ -0,0 +1,120 @@ +import { NodeHttpServer } from "@effect/platform-node" +import { describe, expect } from "bun:test" +import { Context, Effect, Layer, Queue } from "effect" +import { Socket } from "effect/unstable/socket" +import { + FetchHttpClient, + HttpClient, + HttpClientRequest, + HttpServer, + HttpServerRequest, + HttpServerResponse, +} from "effect/unstable/http" +import { basePathServer } from "../../src/server/base-path" +import { HttpApiProxy } from "../../src/server/routes/instance/httpapi/middleware/proxy" +import { injectBasePath } from "../../src/server/shared/ui" +import { testEffect } from "../lib/effect" + +const prefix = "/nested/proxy/service" +const it = testEffect(Layer.mergeAll(NodeHttpServer.layerTest, Socket.layerWebSocketConstructorGlobal)) + +for (const stripped of [false, true]) { + describe(`base path through proxy (stripped=${stripped})`, () => { + it.live("forwards HTTP bodies, auth, queries, HTML and WebSocket frames", () => + Effect.gen(function* () { + const context = yield* Layer.build( + NodeHttpServer.layer(() => basePathServer({ basePath: prefix, basePathStripped: stripped }), { + host: "127.0.0.1", + port: 0, + }), + ) + const backend = Context.get(context, HttpServer.HttpServer) + yield* backend.serve( + HttpServerRequest.HttpServerRequest.use((request) => + Effect.gen(function* () { + if (request.url.startsWith("/api/pty/")) { + const socket = yield* request.upgrade + const write = yield* socket.writer + yield* socket + .runRaw((message) => write(`${request.url}:${String(message)}`)) + .pipe(Effect.catch(() => Effect.void)) + return HttpServerResponse.empty() + } + if (request.url.startsWith("/project/session/")) + return HttpServerResponse.html( + injectBasePath('', prefix), + ) + return HttpServerResponse.jsonUnsafe({ + path: request.url, + method: request.method, + authorization: request.headers.authorization, + body: yield* request.text, + }) + }), + ), + ) + const upstream = HttpServer.formatAddress(backend.address) + const client = yield* HttpClient.HttpClient + const upstreamClient = yield* Effect.provide(HttpClient.HttpClient, FetchHttpClient.layer) + // A reverse proxy can strip the entire configured prefix before forwarding. + yield* HttpServer.serveEffect()( + HttpServerRequest.HttpServerRequest.use((request) => { + const path = stripped ? request.url.slice(prefix.length) : request.url + if (request.headers.upgrade === "websocket") return HttpApiProxy.websocket(request, upstream + path) + return HttpApiProxy.http(upstreamClient, upstream + path, {}, request) + }), + ) + + const response = yield* client.execute( + HttpClientRequest.post(prefix + "/api/echo?q=a?b&value=%2F", { + headers: { authorization: "Bearer test" }, + }).pipe(HttpClientRequest.bodyText("payload")), + ) + expect(response.status).toBe(200) + expect(yield* response.json).toEqual({ + path: "/api/echo?q=a?b&value=%2F", + method: "POST", + authorization: "Bearer test", + body: "payload", + }) + const html = yield* client.get(prefix + "/project/session/test").pipe(Effect.flatMap((r) => r.text)) + expect(html).toContain(``) + expect(html).toContain(`window.__OPENCODE_BASE_PATH__="${prefix}"`) + + const server = yield* HttpServer.HttpServer + const socket = yield* Socket.makeWebSocket( + HttpServer.formatAddress(server.address).replace(/^http/, "ws") + prefix + "/api/pty/test/connect?ticket=abc", + { closeCodeIsError: () => false }, + ) + const messages = yield* Queue.unbounded() + yield* socket.runRaw((message) => Queue.offer(messages, String(message))).pipe(Effect.forkScoped) + const write = yield* socket.writer + yield* write("hello") + expect(yield* Queue.take(messages)).toBe("/api/pty/test/connect?ticket=abc:hello") + }).pipe(Effect.timeout("10 seconds")), + ) + }) +} + +it.live("redirects only the exact prefix and preserves the entire query and POST method", () => + Effect.gen(function* () { + const context = yield* Layer.build( + NodeHttpServer.layer(() => basePathServer({ basePath: prefix }), { + host: "127.0.0.1", + port: 0, + }), + ) + const server = Context.get(context, HttpServer.HttpServer) + yield* server.serve(Effect.succeed(HttpServerResponse.text("ok"))) + const client = yield* Effect.provide( + HttpClient.HttpClient, + FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ redirect: "manual" }))), + ) + const url = HttpServer.formatAddress(server.address) + const response = yield* client.post(url + prefix + "?next=a?b&value=%2F") + expect(response.status).toBe(308) + expect(response.headers.location).toBe(prefix + "/?next=a?b&value=%2F") + expect((yield* client.get(url + prefix + "-other/api/health")).status).toBe(404) + expect((yield* client.get(url + "/api/health")).status).toBe(404) + }), +) diff --git a/packages/opencode/test/server/base-path.test.ts b/packages/opencode/test/server/base-path.test.ts index 7c0e2ec67d6e..fb81f2e8bd56 100644 --- a/packages/opencode/test/server/base-path.test.ts +++ b/packages/opencode/test/server/base-path.test.ts @@ -45,8 +45,8 @@ describe("normalizeBasePath", () => { describe("injectBasePath", () => { const minimalHtml = `Test` - test("returns html unchanged when basePath is empty", () => { - expect(injectBasePath(minimalHtml, "")).toBe(minimalHtml) + test("sets the document base for deep links on an unprefixed server", () => { + expect(injectBasePath(minimalHtml, "")).toContain('') }) test("injects global script into head", () => { @@ -110,3 +110,17 @@ describe("cspForHtml with injected base path", () => { expect(csp).toContain(`'sha256-${injectedHash}'`) }) }) + +test.each(["//example.com/path", "/a/../b", "/prefix?query", "/prefix#hash", "/test\\path", '/test"bad', "/test path"])( + "rejects an invalid base path %j", + (value) => expect(() => normalizeBasePath(value)).toThrow(), +) + +test("replaces an existing base and safely handles replacement metacharacters", () => { + const html = + '' + const result = injectBasePath(html, "/apps/$&") + expect(result).toContain('') + expect(result).not.toContain("/old/") + expect(result).toContain('href="/apps/$&/icon.png"') +}) diff --git a/packages/opencode/test/server/network-prefix.test.ts b/packages/opencode/test/server/network-prefix.test.ts new file mode 100644 index 000000000000..74bd39f4ae4d --- /dev/null +++ b/packages/opencode/test/server/network-prefix.test.ts @@ -0,0 +1,35 @@ +import { afterEach, expect, test } from "bun:test" +import yargs from "yargs" +import { resolveNetworkOptionsNoConfig, withNetworkOptions } from "../../src/cli/network" + +const argv = process.argv +afterEach(() => { + process.argv = argv +}) + +async function resolve(args: string[], server = { basePath: "/configured/", basePathStripped: true }) { + process.argv = ["bun", "opencode", "web", ...args] + return resolveNetworkOptionsNoConfig(await withNetworkOptions(yargs(args)).parse(), { server }) +} + +test("reads prefix and stripping mode from config", async () => { + expect(await resolve([])).toMatchObject({ basePath: "/configured", basePathStripped: true }) +}) + +test("explicit CLI arguments override the config", async () => { + expect(await resolve(["--base-path=/nested/proxy/service/", "--no-base-path-stripped"])).toMatchObject({ + basePath: "/nested/proxy/service", + basePathStripped: false, + }) +}) + +test("an explicit empty prefix disables the configured prefix", async () => { + expect(await resolve(["--base-path="])).toMatchObject({ basePath: "" }) +}) + +test("arguments after -- do not override the config", async () => { + expect(await resolve(["--", "--base-path=/other", "--no-base-path-stripped"])).toMatchObject({ + basePath: "/configured", + basePathStripped: true, + }) +}) diff --git a/packages/session-ui/package.json b/packages/session-ui/package.json index 0c1c656bb503..24d04dbca9cd 100644 --- a/packages/session-ui/package.json +++ b/packages/session-ui/package.json @@ -38,7 +38,7 @@ }, "dependencies": { "@kobalte/core": "catalog:", - "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-v2.tgz", + "@opencode-ai/client": "file:../app/vendor/opencode-ai-client-1.17.13-prefix.tgz", "@opencode-ai/core": "workspace:*", "@opencode-ai/sdk": "workspace:*", "@opencode-ai/ui": "workspace:*", diff --git a/packages/ui/src/assets/favicon/site.webmanifest b/packages/ui/src/assets/favicon/site.webmanifest index 427c3adf423d..34ad23974676 100644 --- a/packages/ui/src/assets/favicon/site.webmanifest +++ b/packages/ui/src/assets/favicon/site.webmanifest @@ -1,18 +1,18 @@ { "name": "OpenCode", "short_name": "OpenCode", - "id": "/", - "start_url": "/", - "scope": "/", + "id": "./", + "start_url": "./", + "scope": "./", "icons": [ { - "src": "/web-app-manifest-192x192.png", + "src": "./web-app-manifest-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/web-app-manifest-512x512.png", + "src": "./web-app-manifest-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable"