瀏覽代碼

refactor(core): support multiple event streams in worker and remove workspaces from plugin api (#21348)

James Long 1 周之前
父節點
當前提交
5d48e7bd44

+ 0 - 3
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -289,9 +289,6 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
     toast,
     renderer,
   })
-  onCleanup(() => {
-    api.dispose()
-  })
   const [ready, setReady] = createSignal(false)
   TuiPluginRuntime.init(api)
     .catch((error) => {

+ 3 - 16
packages/opencode/src/cli/cmd/tui/context/sdk.tsx

@@ -4,8 +4,7 @@ import { createGlobalEmitter } from "@solid-primitives/event-bus"
 import { batch, onCleanup, onMount } from "solid-js"
 
 export type EventSource = {
-  on: (handler: (event: Event) => void) => () => void
-  setWorkspace?: (workspaceID?: string) => void
+  subscribe: (directory: string | undefined, handler: (event: Event) => void) => Promise<() => void>
 }
 
 export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
@@ -18,7 +17,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
     events?: EventSource
   }) => {
     const abort = new AbortController()
-    let workspaceID: string | undefined
     let sse: AbortController | undefined
 
     function createSDK() {
@@ -28,7 +26,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
         directory: props.directory,
         fetch: props.fetch,
         headers: props.headers,
-        experimental_workspaceID: workspaceID,
       })
     }
 
@@ -90,9 +87,9 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
       })().catch(() => {})
     }
 
-    onMount(() => {
+    onMount(async () => {
       if (props.events) {
-        const unsub = props.events.on(handleEvent)
+        const unsub = await props.events.subscribe(props.directory, handleEvent)
         onCleanup(unsub)
       } else {
         startSSE()
@@ -109,19 +106,9 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
       get client() {
         return sdk
       },
-      get workspaceID() {
-        return workspaceID
-      },
       directory: props.directory,
       event: emitter,
       fetch: props.fetch ?? fetch,
-      setWorkspace(next?: string) {
-        if (workspaceID === next) return
-        workspaceID = next
-        sdk = createSDK()
-        props.events?.setWorkspace?.(next)
-        if (!props.events) startSSE()
-      },
       url: props.url,
     }
   },

+ 2 - 35
packages/opencode/src/cli/cmd/tui/plugin/api.tsx

@@ -18,7 +18,7 @@ import { Prompt } from "../component/prompt"
 import { Slot as HostSlot } from "./slots"
 import type { useToast } from "../ui/toast"
 import { Installation } from "@/installation"
-import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
+import { type OpencodeClient } from "@opencode-ai/sdk/v2"
 
 type RouteEntry = {
   key: symbol
@@ -43,11 +43,6 @@ type Input = {
   renderer: TuiPluginApi["renderer"]
 }
 
-type TuiHostPluginApi = TuiPluginApi & {
-  map: Map<string | undefined, OpencodeClient>
-  dispose: () => void
-}
-
 function routeRegister(routes: RouteMap, list: TuiRouteDefinition[], bump: () => void) {
   const key = Symbol()
   for (const item of list) {
@@ -206,29 +201,7 @@ function appApi(): TuiPluginApi["app"] {
   }
 }
 
-export function createTuiApi(input: Input): TuiHostPluginApi {
-  const map = new Map<string | undefined, OpencodeClient>()
-  const scoped: TuiPluginApi["scopedClient"] = (workspaceID) => {
-    const hit = map.get(workspaceID)
-    if (hit) return hit
-
-    const next = createOpencodeClient({
-      baseUrl: input.sdk.url,
-      fetch: input.sdk.fetch,
-      directory: input.sync.data.path.directory || input.sdk.directory,
-      experimental_workspaceID: workspaceID,
-    })
-    map.set(workspaceID, next)
-    return next
-  }
-  const workspace: TuiPluginApi["workspace"] = {
-    current() {
-      return input.sdk.workspaceID
-    },
-    set(workspaceID) {
-      input.sdk.setWorkspace(workspaceID)
-    },
-  }
+export function createTuiApi(input: Input): TuiPluginApi {
   const lifecycle: TuiPluginApi["lifecycle"] = {
     signal: new AbortController().signal,
     onDispose() {
@@ -369,8 +342,6 @@ export function createTuiApi(input: Input): TuiHostPluginApi {
     get client() {
       return input.sdk.client
     },
-    scopedClient: scoped,
-    workspace,
     event: input.sdk.event,
     renderer: input.renderer,
     slots: {
@@ -422,9 +393,5 @@ export function createTuiApi(input: Input): TuiHostPluginApi {
         return input.theme.ready
       },
     },
-    map,
-    dispose() {
-      map.clear()
-    },
   }
 }

+ 0 - 2
packages/opencode/src/cli/cmd/tui/plugin/runtime.ts

@@ -543,8 +543,6 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop
     get client() {
       return api.client
     },
-    scopedClient: api.scopedClient,
-    workspace: api.workspace,
     event,
     renderer: api.renderer,
     slots,

+ 0 - 6
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

@@ -167,12 +167,6 @@ export function Session() {
 
   const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
 
-  createEffect(() => {
-    if (session()?.workspaceID) {
-      sdk.setWorkspace(session()?.workspaceID)
-    }
-  })
-
   createEffect(async () => {
     await sync.session
       .sync(route.sessionID)

+ 12 - 3
packages/opencode/src/cli/cmd/tui/thread.ts

@@ -43,9 +43,18 @@ function createWorkerFetch(client: RpcClient): typeof fetch {
 
 function createEventSource(client: RpcClient): EventSource {
   return {
-    on: (handler) => client.on<Event>("event", handler),
-    setWorkspace: (workspaceID) => {
-      void client.call("setWorkspace", { workspaceID })
+    subscribe: async (directory, handler) => {
+      const id = await client.call("subscribe", { directory })
+      const unsub = client.on<{ id: string; event: Event }>("event", (e) => {
+        if (e.id === id) {
+          handler(e.event)
+        }
+      })
+
+      return () => {
+        unsub()
+        client.call("unsubscribe", { id })
+      }
     },
   }
 }

+ 34 - 14
packages/opencode/src/cli/cmd/tui/worker.ts

@@ -45,20 +45,20 @@ GlobalBus.on("event", (event) => {
 
 let server: Awaited<ReturnType<typeof Server.listen>> | undefined
 
-const eventStream = {
-  abort: undefined as AbortController | undefined,
-}
+const eventStreams = new Map<string, AbortController>()
+
+function startEventStream(directory: string) {
+  const id = crypto.randomUUID()
 
-const startEventStream = (input: { directory: string; workspaceID?: string }) => {
-  if (eventStream.abort) eventStream.abort.abort()
   const abort = new AbortController()
-  eventStream.abort = abort
   const signal = abort.signal
 
-  ;(async () => {
+  eventStreams.set(id, abort)
+
+  async function run() {
     while (!signal.aborted) {
       const shouldReconnect = await Instance.provide({
-        directory: input.directory,
+        directory,
         init: InstanceBootstrap,
         fn: () =>
           new Promise<boolean>((resolve) => {
@@ -77,7 +77,10 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
             }
 
             const unsub = Bus.subscribeAll((event) => {
-              Rpc.emit("event", event as Event)
+              Rpc.emit("event", {
+                id,
+                event: event as Event,
+              })
               if (event.type === Bus.InstanceDisposed.type) {
                 settle(true)
               }
@@ -104,14 +107,24 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
         await sleep(250)
       }
     }
-  })().catch((error) => {
+  }
+
+  run().catch((error) => {
     Log.Default.error("event stream error", {
       error: error instanceof Error ? error.message : error,
     })
   })
+
+  return id
 }
 
-startEventStream({ directory: process.cwd() })
+function stopEventStream(id: string) {
+  const abortController = eventStreams.get(id)
+  if (!abortController) return
+
+  abortController.abort()
+  eventStreams.delete(id)
+}
 
 export const rpc = {
   async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
@@ -154,12 +167,19 @@ export const rpc = {
   async reload() {
     await Config.invalidate(true)
   },
-  async setWorkspace(input: { workspaceID?: string }) {
-    startEventStream({ directory: process.cwd(), workspaceID: input.workspaceID })
+  async subscribe(input: { directory: string | undefined }) {
+    return startEventStream(input.directory || process.cwd())
+  },
+  async unsubscribe(input: { id: string }) {
+    stopEventStream(input.id)
   },
   async shutdown() {
     Log.Default.info("worker shutting down")
-    if (eventStream.abort) eventStream.abort.abort()
+
+    for (const id of [...eventStreams.keys()]) {
+      stopEventStream(id)
+    }
+
     await Instance.disposeAll()
     if (server) await server.stop(true)
   },

+ 0 - 9
packages/opencode/test/fixture/tui-plugin.ts

@@ -82,8 +82,6 @@ function themeCurrent(): HostPluginApi["theme"]["current"] {
 
 type Opts = {
   client?: HostPluginApi["client"] | (() => HostPluginApi["client"])
-  scopedClient?: HostPluginApi["scopedClient"]
-  workspace?: Partial<HostPluginApi["workspace"]>
   renderer?: HostPluginApi["renderer"]
   count?: Count
   keybind?: Partial<HostPluginApi["keybind"]>
@@ -127,11 +125,6 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
         ? () => opts.client as HostPluginApi["client"]
         : fallback
   const client = () => read()
-  const scopedClient = opts.scopedClient ?? ((_workspaceID?: string) => client())
-  const workspace: HostPluginApi["workspace"] = {
-    current: opts.workspace?.current ?? (() => undefined),
-    set: opts.workspace?.set ?? (() => {}),
-  }
   let depth = 0
   let size: "medium" | "large" | "xlarge" = "medium"
   const has = opts.theme?.has ?? (() => false)
@@ -171,8 +164,6 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
     get client() {
       return client()
     },
-    scopedClient,
-    workspace,
     event: {
       on: () => {
         if (count) count.event_add += 1

+ 0 - 2
packages/plugin/src/tui.ts

@@ -484,8 +484,6 @@ export type TuiPluginApi = {
   state: TuiState
   theme: TuiTheme
   client: OpencodeClient
-  scopedClient: (workspaceID?: string) => OpencodeClient
-  workspace: TuiWorkspace
   event: TuiEventBus
   renderer: CliRenderer
   slots: TuiSlots