Procházet zdrojové kódy

feat(core): remove workspace server and reuse core server in workspaces

James Long před 3 týdny
rodič
revize
77aabd9c57

+ 0 - 16
packages/opencode/src/cli/cmd/workspace-serve.ts

@@ -1,16 +0,0 @@
-import { cmd } from "./cmd"
-import { withNetworkOptions, resolveNetworkOptions } from "../network"
-import { WorkspaceServer } from "../../control-plane/workspace-server/server"
-
-export const WorkspaceServeCommand = cmd({
-  command: "workspace-serve",
-  builder: (yargs) => withNetworkOptions(yargs),
-  describe: "starts a remote workspace event server",
-  handler: async (args) => {
-    const opts = await resolveNetworkOptions(args)
-    const server = WorkspaceServer.Listen(opts)
-    console.log(`workspace event server listening on http://${server.hostname}:${server.port}/event`)
-    await new Promise(() => {})
-    await server.stop()
-  },
-})

+ 3 - 2
packages/opencode/src/control-plane/adaptors/worktree.ts

@@ -2,6 +2,8 @@ import z from "zod"
 import { Worktree } from "@/worktree"
 import { type Adaptor, WorkspaceInfo } from "../types"
 
+import { Server } from "../../server/server"
+
 const Config = WorkspaceInfo.extend({
   name: WorkspaceInfo.shape.name.unwrap(),
   branch: WorkspaceInfo.shape.branch.unwrap(),
@@ -34,12 +36,11 @@ export const WorktreeAdaptor: Adaptor = {
   },
   async fetch(info, input: RequestInfo | URL, init?: RequestInit) {
     const config = Config.parse(info)
-    const { WorkspaceServer } = await import("../workspace-server/server")
     const url = input instanceof Request || input instanceof URL ? input : new URL(input, "http://opencode.internal")
     const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined))
     headers.set("x-opencode-directory", config.directory)
 
     const request = new Request(url, { ...init, headers })
-    return WorkspaceServer.App().fetch(request)
+    return Server.Default().fetch(request)
   },
 }

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

@@ -1,33 +0,0 @@
-import { GlobalBus } from "../../bus/global"
-import { Hono } from "hono"
-import { streamSSE } from "hono/streaming"
-
-export function WorkspaceServerRoutes() {
-  return new Hono().get("/event", async (c) => {
-    c.header("X-Accel-Buffering", "no")
-    c.header("X-Content-Type-Options", "nosniff")
-    return streamSSE(c, async (stream) => {
-      const send = async (event: unknown) => {
-        await stream.writeSSE({
-          data: JSON.stringify(event),
-        })
-      }
-      const handler = async (event: { directory?: string; payload: unknown }) => {
-        await send(event.payload)
-      }
-      GlobalBus.on("event", handler)
-      await send({ type: "server.connected", properties: {} })
-      const heartbeat = setInterval(() => {
-        void send({ type: "server.heartbeat", properties: {} })
-      }, 10_000)
-
-      await new Promise<void>((resolve) => {
-        stream.onAbort(() => {
-          clearInterval(heartbeat)
-          GlobalBus.off("event", handler)
-          resolve()
-        })
-      })
-    })
-  })
-}

+ 0 - 65
packages/opencode/src/control-plane/workspace-server/server.ts

@@ -1,65 +0,0 @@
-import { Hono } from "hono"
-import { Instance } from "../../project/instance"
-import { InstanceBootstrap } from "../../project/bootstrap"
-import { SessionRoutes } from "../../server/routes/session"
-import { WorkspaceServerRoutes } from "./routes"
-import { WorkspaceContext } from "../workspace-context"
-import { WorkspaceID } from "../schema"
-
-export namespace WorkspaceServer {
-  export function App() {
-    const session = new Hono()
-      .use(async (c, next) => {
-        // Right now, we need handle all requests because we don't
-        // have syncing. In the future all GET requests will handled
-        // by the control plane
-        //
-        // if (c.req.method === "GET") return c.notFound()
-        await next()
-      })
-      .route("/", SessionRoutes())
-
-    return new Hono()
-      .use(async (c, next) => {
-        const rawWorkspaceID = c.req.query("workspace") || c.req.header("x-opencode-workspace")
-        const raw = c.req.query("directory") || c.req.header("x-opencode-directory")
-        if (rawWorkspaceID == null) {
-          throw new Error("workspaceID parameter is required")
-        }
-        if (raw == null) {
-          throw new Error("directory parameter is required")
-        }
-
-        const directory = (() => {
-          try {
-            return decodeURIComponent(raw)
-          } catch {
-            return raw
-          }
-        })()
-
-        return WorkspaceContext.provide({
-          workspaceID: WorkspaceID.make(rawWorkspaceID),
-          async fn() {
-            return Instance.provide({
-              directory,
-              init: InstanceBootstrap,
-              async fn() {
-                return next()
-              },
-            })
-          },
-        })
-      })
-      .route("/session", session)
-      .route("/", WorkspaceServerRoutes())
-  }
-
-  export function Listen(opts: { hostname: string; port: number }) {
-    return Bun.serve({
-      hostname: opts.hostname,
-      port: opts.port,
-      fetch: App().fetch,
-    })
-  }
-}

+ 1 - 8
packages/opencode/src/index.ts

@@ -14,7 +14,6 @@ import { Installation } from "./installation"
 import { NamedError } from "@opencode-ai/util/error"
 import { FormatError } from "./cli/error"
 import { ServeCommand } from "./cli/cmd/serve"
-import { WorkspaceServeCommand } from "./cli/cmd/workspace-serve"
 import { Filesystem } from "./util/filesystem"
 import { DebugCommand } from "./cli/cmd/debug"
 import { StatsCommand } from "./cli/cmd/stats"
@@ -47,7 +46,7 @@ process.on("uncaughtException", (e) => {
   })
 })
 
-let cli = yargs(hideBin(process.argv))
+const cli = yargs(hideBin(process.argv))
   .parserConfiguration({ "populate--": true })
   .scriptName("opencode")
   .wrap(100)
@@ -145,12 +144,6 @@ let cli = yargs(hideBin(process.argv))
   .command(PrCommand)
   .command(SessionCommand)
   .command(DbCommand)
-
-if (Installation.isLocal()) {
-  cli = cli.command(WorkspaceServeCommand)
-}
-
-cli = cli
   .fail((msg, err) => {
     if (
       msg?.startsWith("Unknown argument") ||

+ 0 - 70
packages/opencode/test/control-plane/workspace-server-sse.test.ts

@@ -1,70 +0,0 @@
-import { afterEach, describe, expect, test } from "bun:test"
-import { Log } from "../../src/util/log"
-import { WorkspaceServer } from "../../src/control-plane/workspace-server/server"
-import { parseSSE } from "../../src/control-plane/sse"
-import { GlobalBus } from "../../src/bus/global"
-import { resetDatabase } from "../fixture/db"
-import { tmpdir } from "../fixture/fixture"
-
-afterEach(async () => {
-  await resetDatabase()
-})
-
-Log.init({ print: false })
-
-describe("control-plane/workspace-server SSE", () => {
-  test("streams GlobalBus events and parseSSE reads them", async () => {
-    await using tmp = await tmpdir({ git: true })
-    const app = WorkspaceServer.App()
-    const stop = new AbortController()
-    const seen: unknown[] = []
-    try {
-      const response = await app.request("/event", {
-        signal: stop.signal,
-        headers: {
-          "x-opencode-workspace": "wrk_test_workspace",
-          "x-opencode-directory": tmp.path,
-        },
-      })
-
-      expect(response.status).toBe(200)
-      expect(response.body).toBeDefined()
-
-      const done = new Promise<void>((resolve, reject) => {
-        const timeout = setTimeout(() => {
-          reject(new Error("timed out waiting for workspace.test event"))
-        }, 3000)
-
-        void parseSSE(response.body!, stop.signal, (event) => {
-          seen.push(event)
-          const next = event as { type?: string }
-          if (next.type === "server.connected") {
-            GlobalBus.emit("event", {
-              payload: {
-                type: "workspace.test",
-                properties: { ok: true },
-              },
-            })
-            return
-          }
-          if (next.type !== "workspace.test") return
-          clearTimeout(timeout)
-          resolve()
-        }).catch((error) => {
-          clearTimeout(timeout)
-          reject(error)
-        })
-      })
-
-      await done
-
-      expect(seen.some((event) => (event as { type?: string }).type === "server.connected")).toBe(true)
-      expect(seen).toContainEqual({
-        type: "workspace.test",
-        properties: { ok: true },
-      })
-    } finally {
-      stop.abort()
-    }
-  })
-})