Skip to content

Commit 3845044

Browse files
authored
feat(core): remove workspace server, WorkspaceContext, start work towards better routing (#19316)
1 parent da1d372 commit 3845044

13 files changed

Lines changed: 72 additions & 541 deletions

File tree

packages/opencode/src/cli/cmd/tui/worker.ts

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type { Event } from "@opencode-ai/sdk/v2"
1212
import { Flag } from "@/flag/flag"
1313
import { setTimeout as sleep } from "node:timers/promises"
1414
import { writeHeapSnapshot } from "node:v8"
15-
import { WorkspaceContext } from "@/control-plane/workspace-context"
1615
import { WorkspaceID } from "@/control-plane/schema"
1716

1817
await Log.init({
@@ -53,45 +52,39 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
5352
eventStream.abort = abort
5453
const signal = abort.signal
5554

56-
const workspaceID = input.workspaceID ? WorkspaceID.make(input.workspaceID) : undefined
57-
5855
;(async () => {
5956
while (!signal.aborted) {
60-
const shouldReconnect = await WorkspaceContext.provide({
61-
workspaceID,
57+
const shouldReconnect = await Instance.provide({
58+
directory: input.directory,
59+
init: InstanceBootstrap,
6260
fn: () =>
63-
Instance.provide({
64-
directory: input.directory,
65-
init: InstanceBootstrap,
66-
fn: () =>
67-
new Promise<boolean>((resolve) => {
68-
Rpc.emit("event", {
69-
type: "server.connected",
70-
properties: {},
71-
} satisfies Event)
72-
73-
let settled = false
74-
const settle = (value: boolean) => {
75-
if (settled) return
76-
settled = true
77-
signal.removeEventListener("abort", onAbort)
78-
unsub()
79-
resolve(value)
80-
}
81-
82-
const unsub = Bus.subscribeAll((event) => {
83-
Rpc.emit("event", event as Event)
84-
if (event.type === Bus.InstanceDisposed.type) {
85-
settle(true)
86-
}
87-
})
88-
89-
const onAbort = () => {
90-
settle(false)
91-
}
92-
93-
signal.addEventListener("abort", onAbort, { once: true })
94-
}),
61+
new Promise<boolean>((resolve) => {
62+
Rpc.emit("event", {
63+
type: "server.connected",
64+
properties: {},
65+
} satisfies Event)
66+
67+
let settled = false
68+
const settle = (value: boolean) => {
69+
if (settled) return
70+
settled = true
71+
signal.removeEventListener("abort", onAbort)
72+
unsub()
73+
resolve(value)
74+
}
75+
76+
const unsub = Bus.subscribeAll((event) => {
77+
Rpc.emit("event", event as Event)
78+
if (event.type === Bus.InstanceDisposed.type) {
79+
settle(true)
80+
}
81+
})
82+
83+
const onAbort = () => {
84+
settle(false)
85+
}
86+
87+
signal.addEventListener("abort", onAbort, { once: true })
9588
}),
9689
}).catch((error) => {
9790
Log.Default.error("event stream subscribe error", {

packages/opencode/src/cli/cmd/workspace-serve.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/opencode/src/control-plane/adaptors/worktree.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ export const WorktreeAdaptor: Adaptor = {
3333
await Worktree.remove({ directory: config.directory })
3434
},
3535
async fetch(info, input: RequestInfo | URL, init?: RequestInit) {
36+
const { Server } = await import("../../server/server")
37+
3638
const config = Config.parse(info)
37-
const { WorkspaceServer } = await import("../workspace-server/server")
3839
const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://opencode.internal")
3940
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined))
4041
headers.set("x-opencode-directory", config.directory)
4142

4243
const request = new Request(url, { ...init, headers })
43-
return WorkspaceServer.App().fetch(request)
44+
return Server.Default().fetch(request)
4445
},
4546
}

packages/opencode/src/control-plane/workspace-context.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

packages/opencode/src/control-plane/workspace-router-middleware.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
11
import type { MiddlewareHandler } from "hono"
22
import { Flag } from "../flag/flag"
33
import { getAdaptor } from "./adaptors"
4+
import { WorkspaceID } from "./schema"
45
import { Workspace } from "./workspace"
5-
import { WorkspaceContext } from "./workspace-context"
66

7-
// This middleware forwards all non-GET requests if the workspace is a
8-
// remote. The remote workspace needs to handle session mutations
7+
type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" }
8+
9+
const RULES: Array<Rule> = [
10+
{ path: "/session/status", action: "forward" },
11+
{ method: "GET", path: "/session", action: "local" },
12+
]
13+
14+
function local(method: string, path: string) {
15+
for (const rule of RULES) {
16+
if (rule.method && rule.method !== method) continue
17+
const match = rule.exact ? path === rule.path : path === rule.path || path.startsWith(rule.path + "/")
18+
if (match) return rule.action === "local"
19+
}
20+
return false
21+
}
22+
923
async function routeRequest(req: Request) {
10-
// Right now, we need to forward all requests to the workspace
11-
// because we don't have syncing. In the future all GET requests
12-
// which don't mutate anything will be handled locally
13-
//
14-
// if (req.method === "GET") return
24+
const url = new URL(req.url)
25+
const raw = url.searchParams.get("workspace") || req.headers.get("x-opencode-workspace")
26+
27+
if (!raw) return
1528

16-
if (!WorkspaceContext.workspaceID) return
29+
if (local(req.method, url.pathname)) return
1730

18-
const workspace = await Workspace.get(WorkspaceContext.workspaceID)
31+
const workspaceID = WorkspaceID.make(raw)
32+
33+
const workspace = await Workspace.get(workspaceID)
1934
if (!workspace) {
20-
return new Response(`Workspace not found: ${WorkspaceContext.workspaceID}`, {
35+
return new Response(`Workspace not found: ${workspaceID}`, {
2136
status: 500,
2237
headers: {
2338
"content-type": "text/plain; charset=utf-8",
@@ -27,11 +42,14 @@ async function routeRequest(req: Request) {
2742

2843
const adaptor = await getAdaptor(workspace.type)
2944

30-
return adaptor.fetch(workspace, `${new URL(req.url).pathname}${new URL(req.url).search}`, {
45+
const headers = new Headers(req.headers)
46+
headers.delete("x-opencode-workspace")
47+
48+
return adaptor.fetch(workspace, `${url.pathname}${url.search}`, {
3149
method: req.method,
3250
body: req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer(),
3351
signal: req.signal,
34-
headers: req.headers,
52+
headers,
3553
})
3654
}
3755

packages/opencode/src/control-plane/workspace-server/routes.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

packages/opencode/src/control-plane/workspace-server/server.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

packages/opencode/src/index.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { Installation } from "./installation"
1414
import { NamedError } from "@opencode-ai/util/error"
1515
import { FormatError } from "./cli/error"
1616
import { ServeCommand } from "./cli/cmd/serve"
17-
import { WorkspaceServeCommand } from "./cli/cmd/workspace-serve"
1817
import { Filesystem } from "./util/filesystem"
1918
import { DebugCommand } from "./cli/cmd/debug"
2019
import { StatsCommand } from "./cli/cmd/stats"
@@ -47,7 +46,7 @@ process.on("uncaughtException", (e) => {
4746
})
4847
})
4948

50-
let cli = yargs(hideBin(process.argv))
49+
const cli = yargs(hideBin(process.argv))
5150
.parserConfiguration({ "populate--": true })
5251
.scriptName("opencode")
5352
.wrap(100)
@@ -145,12 +144,6 @@ let cli = yargs(hideBin(process.argv))
145144
.command(PrCommand)
146145
.command(SessionCommand)
147146
.command(DbCommand)
148-
149-
if (Installation.isLocal()) {
150-
cli = cli.command(WorkspaceServeCommand)
151-
}
152-
153-
cli = cli
154147
.fail((msg, err) => {
155148
if (
156149
msg?.startsWith("Unknown argument") ||

packages/opencode/src/server/server.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Auth } from "../auth"
1919
import { Flag } from "../flag/flag"
2020
import { Command } from "../command"
2121
import { Global } from "../global"
22-
import { WorkspaceContext } from "../control-plane/workspace-context"
2322
import { WorkspaceID } from "../control-plane/schema"
2423
import { ProviderID } from "../provider/schema"
2524
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
@@ -204,7 +203,6 @@ export namespace Server {
204203
)
205204
.use(async (c, next) => {
206205
if (c.req.path === "/log") return next()
207-
const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
208206
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
209207
const directory = Filesystem.resolve(
210208
(() => {
@@ -216,20 +214,14 @@ export namespace Server {
216214
})(),
217215
)
218216

219-
return WorkspaceContext.provide({
220-
workspaceID: rawWorkspaceID ? WorkspaceID.make(rawWorkspaceID) : undefined,
217+
return Instance.provide({
218+
directory,
219+
init: InstanceBootstrap,
221220
async fn() {
222-
return Instance.provide({
223-
directory,
224-
init: InstanceBootstrap,
225-
async fn() {
226-
return next()
227-
},
228-
})
221+
return next()
229222
},
230223
})
231224
})
232-
.use(WorkspaceRouterMiddleware)
233225
.get(
234226
"/doc",
235227
openAPIRouteHandler(app, {
@@ -252,6 +244,7 @@ export namespace Server {
252244
}),
253245
),
254246
)
247+
.use(WorkspaceRouterMiddleware)
255248
.route("/project", ProjectRoutes())
256249
.route("/pty", PtyRoutes())
257250
.route("/config", ConfigRoutes())

0 commit comments

Comments
 (0)