mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-21 05:10:58 +08:00
155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
import type { MiddlewareHandler } from "hono"
|
|
import type { UpgradeWebSocket } from "hono/ws"
|
|
import { getAdaptor } from "@/control-plane/adaptors"
|
|
import { WorkspaceID } from "@/control-plane/schema"
|
|
import { Workspace } from "@/control-plane/workspace"
|
|
import { ServerProxy } from "../proxy"
|
|
import { Instance } from "@/project/instance"
|
|
import { InstanceBootstrap } from "@/project/bootstrap"
|
|
import { Flag } from "@/flag/flag"
|
|
import { Session } from "@/session"
|
|
import { SessionID } from "@/session/schema"
|
|
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
|
import { AppRuntime } from "@/effect/app-runtime"
|
|
import { Log } from "@/util/log"
|
|
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
|
|
|
|
type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" }
|
|
|
|
const OPENCODE_WORKSPACE = process.env.OPENCODE_WORKSPACE
|
|
|
|
const RULES: Array<Rule> = [
|
|
{ path: "/session/status", action: "forward" },
|
|
{ method: "GET", path: "/session", action: "local" },
|
|
]
|
|
|
|
function local(method: string, path: string) {
|
|
for (const rule of RULES) {
|
|
if (rule.method && rule.method !== method) continue
|
|
const match = rule.exact ? path === rule.path : path === rule.path || path.startsWith(rule.path + "/")
|
|
if (match) return rule.action === "local"
|
|
}
|
|
return false
|
|
}
|
|
|
|
function getSessionID(url: URL) {
|
|
if (url.pathname === "/session/status") return null
|
|
|
|
const id = url.pathname.match(/^\/session\/([^/]+)(?:\/|$)/)?.[1]
|
|
if (!id) return null
|
|
|
|
return SessionID.make(id)
|
|
}
|
|
|
|
async function getSessionWorkspace(url: URL) {
|
|
const id = getSessionID(url)
|
|
if (!id) return null
|
|
|
|
const session = await AppRuntime.runPromise(Session.Service.use((svc) => svc.get(id))).catch(() => undefined)
|
|
return session?.workspaceID
|
|
}
|
|
|
|
export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler {
|
|
const log = Log.create({ service: "workspace-router" })
|
|
|
|
return async (c, next) => {
|
|
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
|
const directory = AppFileSystem.resolve(
|
|
(() => {
|
|
try {
|
|
return decodeURIComponent(raw)
|
|
} catch {
|
|
return raw
|
|
}
|
|
})(),
|
|
)
|
|
|
|
const url = new URL(c.req.url)
|
|
|
|
const sessionWorkspaceID = await getSessionWorkspace(url)
|
|
const workspaceID = sessionWorkspaceID || url.searchParams.get("workspace")
|
|
|
|
if (!workspaceID || url.pathname.startsWith("/console") || Flag.OPENCODE_WORKSPACE_ID) {
|
|
if (Flag.OPENCODE_WORKSPACE_ID) {
|
|
return WorkspaceContext.provide({
|
|
workspaceID: WorkspaceID.make(Flag.OPENCODE_WORKSPACE_ID),
|
|
async fn() {
|
|
return Instance.provide({
|
|
directory,
|
|
init: () => AppRuntime.runPromise(InstanceBootstrap),
|
|
async fn() {
|
|
return next()
|
|
},
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
return Instance.provide({
|
|
directory,
|
|
init: () => AppRuntime.runPromise(InstanceBootstrap),
|
|
async fn() {
|
|
return next()
|
|
},
|
|
})
|
|
}
|
|
|
|
const workspace = await Workspace.get(WorkspaceID.make(workspaceID))
|
|
|
|
if (!workspace) {
|
|
return new Response(`Workspace not found: ${workspaceID}`, {
|
|
status: 500,
|
|
headers: {
|
|
"content-type": "text/plain; charset=utf-8",
|
|
},
|
|
})
|
|
}
|
|
|
|
if (local(c.req.method, url.pathname)) {
|
|
// No instance provided because we are serving cached data; there
|
|
// is no instance to work with
|
|
return next()
|
|
}
|
|
|
|
const adaptor = await getAdaptor(workspace.projectID, workspace.type)
|
|
const target = await adaptor.target(workspace)
|
|
|
|
if (target.type === "local") {
|
|
return WorkspaceContext.provide({
|
|
workspaceID: WorkspaceID.make(workspaceID),
|
|
fn: () =>
|
|
Instance.provide({
|
|
directory: target.directory,
|
|
init: () => AppRuntime.runPromise(InstanceBootstrap),
|
|
async fn() {
|
|
return next()
|
|
},
|
|
}),
|
|
})
|
|
}
|
|
|
|
const proxyURL = new URL(target.url)
|
|
proxyURL.pathname = `${proxyURL.pathname.replace(/\/$/, "")}${url.pathname}`
|
|
proxyURL.search = url.search
|
|
proxyURL.hash = url.hash
|
|
proxyURL.searchParams.delete("workspace")
|
|
|
|
log.info("workspace proxy forwarding", {
|
|
workspaceID,
|
|
request: url.toString(),
|
|
target: String(target.url),
|
|
proxy: proxyURL.toString(),
|
|
})
|
|
|
|
if (c.req.header("upgrade")?.toLowerCase() === "websocket") {
|
|
return ServerProxy.websocket(upgrade, proxyURL, target.headers, c.req.raw, c.env)
|
|
}
|
|
|
|
const headers = new Headers(c.req.raw.headers)
|
|
headers.delete("x-opencode-workspace")
|
|
|
|
const req = new Request(c.req.raw, { headers })
|
|
return ServerProxy.http(proxyURL, target.headers, req, workspace.id)
|
|
}
|
|
}
|