Skip to content

Commit 5d48e7b

Browse files
authored
refactor(core): support multiple event streams in worker and remove workspaces from plugin api (anomalyco#21348)
1 parent ec8b981 commit 5d48e7b

File tree

9 files changed

+51
-90
lines changed

9 files changed

+51
-90
lines changed

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,6 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
289289
toast,
290290
renderer,
291291
})
292-
onCleanup(() => {
293-
api.dispose()
294-
})
295292
const [ready, setReady] = createSignal(false)
296293
TuiPluginRuntime.init(api)
297294
.catch((error) => {

packages/opencode/src/cli/cmd/tui/context/sdk.tsx

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { createGlobalEmitter } from "@solid-primitives/event-bus"
44
import { batch, onCleanup, onMount } from "solid-js"
55

66
export type EventSource = {
7-
on: (handler: (event: Event) => void) => () => void
8-
setWorkspace?: (workspaceID?: string) => void
7+
subscribe: (directory: string | undefined, handler: (event: Event) => void) => Promise<() => void>
98
}
109

1110
export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
@@ -18,7 +17,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
1817
events?: EventSource
1918
}) => {
2019
const abort = new AbortController()
21-
let workspaceID: string | undefined
2220
let sse: AbortController | undefined
2321

2422
function createSDK() {
@@ -28,7 +26,6 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
2826
directory: props.directory,
2927
fetch: props.fetch,
3028
headers: props.headers,
31-
experimental_workspaceID: workspaceID,
3229
})
3330
}
3431

@@ -90,9 +87,9 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
9087
})().catch(() => {})
9188
}
9289

93-
onMount(() => {
90+
onMount(async () => {
9491
if (props.events) {
95-
const unsub = props.events.on(handleEvent)
92+
const unsub = await props.events.subscribe(props.directory, handleEvent)
9693
onCleanup(unsub)
9794
} else {
9895
startSSE()
@@ -109,19 +106,9 @@ export const { use: useSDK, provider: SDKProvider } = createSimpleContext({
109106
get client() {
110107
return sdk
111108
},
112-
get workspaceID() {
113-
return workspaceID
114-
},
115109
directory: props.directory,
116110
event: emitter,
117111
fetch: props.fetch ?? fetch,
118-
setWorkspace(next?: string) {
119-
if (workspaceID === next) return
120-
workspaceID = next
121-
sdk = createSDK()
122-
props.events?.setWorkspace?.(next)
123-
if (!props.events) startSSE()
124-
},
125112
url: props.url,
126113
}
127114
},

packages/opencode/src/cli/cmd/tui/plugin/api.tsx

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Prompt } from "../component/prompt"
1818
import { Slot as HostSlot } from "./slots"
1919
import type { useToast } from "../ui/toast"
2020
import { Installation } from "@/installation"
21-
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
21+
import { type OpencodeClient } from "@opencode-ai/sdk/v2"
2222

2323
type RouteEntry = {
2424
key: symbol
@@ -43,11 +43,6 @@ type Input = {
4343
renderer: TuiPluginApi["renderer"]
4444
}
4545

46-
type TuiHostPluginApi = TuiPluginApi & {
47-
map: Map<string | undefined, OpencodeClient>
48-
dispose: () => void
49-
}
50-
5146
function routeRegister(routes: RouteMap, list: TuiRouteDefinition[], bump: () => void) {
5247
const key = Symbol()
5348
for (const item of list) {
@@ -206,29 +201,7 @@ function appApi(): TuiPluginApi["app"] {
206201
}
207202
}
208203

209-
export function createTuiApi(input: Input): TuiHostPluginApi {
210-
const map = new Map<string | undefined, OpencodeClient>()
211-
const scoped: TuiPluginApi["scopedClient"] = (workspaceID) => {
212-
const hit = map.get(workspaceID)
213-
if (hit) return hit
214-
215-
const next = createOpencodeClient({
216-
baseUrl: input.sdk.url,
217-
fetch: input.sdk.fetch,
218-
directory: input.sync.data.path.directory || input.sdk.directory,
219-
experimental_workspaceID: workspaceID,
220-
})
221-
map.set(workspaceID, next)
222-
return next
223-
}
224-
const workspace: TuiPluginApi["workspace"] = {
225-
current() {
226-
return input.sdk.workspaceID
227-
},
228-
set(workspaceID) {
229-
input.sdk.setWorkspace(workspaceID)
230-
},
231-
}
204+
export function createTuiApi(input: Input): TuiPluginApi {
232205
const lifecycle: TuiPluginApi["lifecycle"] = {
233206
signal: new AbortController().signal,
234207
onDispose() {
@@ -369,8 +342,6 @@ export function createTuiApi(input: Input): TuiHostPluginApi {
369342
get client() {
370343
return input.sdk.client
371344
},
372-
scopedClient: scoped,
373-
workspace,
374345
event: input.sdk.event,
375346
renderer: input.renderer,
376347
slots: {
@@ -422,9 +393,5 @@ export function createTuiApi(input: Input): TuiHostPluginApi {
422393
return input.theme.ready
423394
},
424395
},
425-
map,
426-
dispose() {
427-
map.clear()
428-
},
429396
}
430397
}

packages/opencode/src/cli/cmd/tui/plugin/runtime.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,6 @@ function pluginApi(runtime: RuntimeState, plugin: PluginEntry, scope: PluginScop
543543
get client() {
544544
return api.client
545545
},
546-
scopedClient: api.scopedClient,
547-
workspace: api.workspace,
548546
event,
549547
renderer: api.renderer,
550548
slots,

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,6 @@ export function Session() {
167167

168168
const scrollAcceleration = createMemo(() => getScrollAcceleration(tuiConfig))
169169

170-
createEffect(() => {
171-
if (session()?.workspaceID) {
172-
sdk.setWorkspace(session()?.workspaceID)
173-
}
174-
})
175-
176170
createEffect(async () => {
177171
await sync.session
178172
.sync(route.sessionID)

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,18 @@ function createWorkerFetch(client: RpcClient): typeof fetch {
4343

4444
function createEventSource(client: RpcClient): EventSource {
4545
return {
46-
on: (handler) => client.on<Event>("event", handler),
47-
setWorkspace: (workspaceID) => {
48-
void client.call("setWorkspace", { workspaceID })
46+
subscribe: async (directory, handler) => {
47+
const id = await client.call("subscribe", { directory })
48+
const unsub = client.on<{ id: string; event: Event }>("event", (e) => {
49+
if (e.id === id) {
50+
handler(e.event)
51+
}
52+
})
53+
54+
return () => {
55+
unsub()
56+
client.call("unsubscribe", { id })
57+
}
4958
},
5059
}
5160
}

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

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ GlobalBus.on("event", (event) => {
4545

4646
let server: Awaited<ReturnType<typeof Server.listen>> | undefined
4747

48-
const eventStream = {
49-
abort: undefined as AbortController | undefined,
50-
}
48+
const eventStreams = new Map<string, AbortController>()
49+
50+
function startEventStream(directory: string) {
51+
const id = crypto.randomUUID()
5152

52-
const startEventStream = (input: { directory: string; workspaceID?: string }) => {
53-
if (eventStream.abort) eventStream.abort.abort()
5453
const abort = new AbortController()
55-
eventStream.abort = abort
5654
const signal = abort.signal
5755

58-
;(async () => {
56+
eventStreams.set(id, abort)
57+
58+
async function run() {
5959
while (!signal.aborted) {
6060
const shouldReconnect = await Instance.provide({
61-
directory: input.directory,
61+
directory,
6262
init: InstanceBootstrap,
6363
fn: () =>
6464
new Promise<boolean>((resolve) => {
@@ -77,7 +77,10 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
7777
}
7878

7979
const unsub = Bus.subscribeAll((event) => {
80-
Rpc.emit("event", event as Event)
80+
Rpc.emit("event", {
81+
id,
82+
event: event as Event,
83+
})
8184
if (event.type === Bus.InstanceDisposed.type) {
8285
settle(true)
8386
}
@@ -104,14 +107,24 @@ const startEventStream = (input: { directory: string; workspaceID?: string }) =>
104107
await sleep(250)
105108
}
106109
}
107-
})().catch((error) => {
110+
}
111+
112+
run().catch((error) => {
108113
Log.Default.error("event stream error", {
109114
error: error instanceof Error ? error.message : error,
110115
})
111116
})
117+
118+
return id
112119
}
113120

114-
startEventStream({ directory: process.cwd() })
121+
function stopEventStream(id: string) {
122+
const abortController = eventStreams.get(id)
123+
if (!abortController) return
124+
125+
abortController.abort()
126+
eventStreams.delete(id)
127+
}
115128

116129
export const rpc = {
117130
async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
@@ -154,12 +167,19 @@ export const rpc = {
154167
async reload() {
155168
await Config.invalidate(true)
156169
},
157-
async setWorkspace(input: { workspaceID?: string }) {
158-
startEventStream({ directory: process.cwd(), workspaceID: input.workspaceID })
170+
async subscribe(input: { directory: string | undefined }) {
171+
return startEventStream(input.directory || process.cwd())
172+
},
173+
async unsubscribe(input: { id: string }) {
174+
stopEventStream(input.id)
159175
},
160176
async shutdown() {
161177
Log.Default.info("worker shutting down")
162-
if (eventStream.abort) eventStream.abort.abort()
178+
179+
for (const id of [...eventStreams.keys()]) {
180+
stopEventStream(id)
181+
}
182+
163183
await Instance.disposeAll()
164184
if (server) await server.stop(true)
165185
},

packages/opencode/test/fixture/tui-plugin.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ function themeCurrent(): HostPluginApi["theme"]["current"] {
8282

8383
type Opts = {
8484
client?: HostPluginApi["client"] | (() => HostPluginApi["client"])
85-
scopedClient?: HostPluginApi["scopedClient"]
86-
workspace?: Partial<HostPluginApi["workspace"]>
8785
renderer?: HostPluginApi["renderer"]
8886
count?: Count
8987
keybind?: Partial<HostPluginApi["keybind"]>
@@ -127,11 +125,6 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
127125
? () => opts.client as HostPluginApi["client"]
128126
: fallback
129127
const client = () => read()
130-
const scopedClient = opts.scopedClient ?? ((_workspaceID?: string) => client())
131-
const workspace: HostPluginApi["workspace"] = {
132-
current: opts.workspace?.current ?? (() => undefined),
133-
set: opts.workspace?.set ?? (() => {}),
134-
}
135128
let depth = 0
136129
let size: "medium" | "large" | "xlarge" = "medium"
137130
const has = opts.theme?.has ?? (() => false)
@@ -171,8 +164,6 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
171164
get client() {
172165
return client()
173166
},
174-
scopedClient,
175-
workspace,
176167
event: {
177168
on: () => {
178169
if (count) count.event_add += 1

packages/plugin/src/tui.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,6 @@ export type TuiPluginApi = {
484484
state: TuiState
485485
theme: TuiTheme
486486
client: OpencodeClient
487-
scopedClient: (workspaceID?: string) => OpencodeClient
488-
workspace: TuiWorkspace
489487
event: TuiEventBus
490488
renderer: CliRenderer
491489
slots: TuiSlots

0 commit comments

Comments
 (0)