diff --git a/packages/opencode/src/util/rpc.ts b/packages/opencode/src/util/rpc.ts index 02586ebcfc60..a86540ca664e 100644 --- a/packages/opencode/src/util/rpc.ts +++ b/packages/opencode/src/util/rpc.ts @@ -10,6 +10,7 @@ export function listen(rpc: Definition) { postMessage(JSON.stringify({ type: "rpc.result", result, id: parsed.id })) } } + postMessage(JSON.stringify({ type: "rpc.ready" })) } export function emit(event: string, data: unknown) { @@ -23,8 +24,13 @@ export function client(target: { const pending = new Map void>() const listeners = new Map void>>() let id = 0 + // Bun drops messages that reach a worker before its module (which may have a + // top-level await) has set onmessage, so requests wait for the worker to say it listens. + let markReady!: () => void + const ready = new Promise((resolve) => (markReady = resolve)) target.onmessage = async (evt) => { const parsed = JSON.parse(evt.data) + if (parsed.type === "rpc.ready") markReady() if (parsed.type === "rpc.result") { const resolve = pending.get(parsed.id) if (resolve) { @@ -46,7 +52,7 @@ export function client(target: { const requestId = id++ return new Promise((resolve) => { pending.set(requestId, resolve) - target.postMessage(JSON.stringify({ type: "rpc.request", method, input, id: requestId })) + ready.then(() => target.postMessage(JSON.stringify({ type: "rpc.request", method, input, id: requestId }))) }) }, on(event: string, handler: (data: Data) => void) { diff --git a/packages/opencode/test/fixture/rpc-worker.ts b/packages/opencode/test/fixture/rpc-worker.ts new file mode 100644 index 000000000000..0e17d37b3469 --- /dev/null +++ b/packages/opencode/test/fixture/rpc-worker.ts @@ -0,0 +1,8 @@ +import { Rpc } from "../../src/util/rpc" + +Rpc.emit("started", undefined) +await new Promise((resolve) => setTimeout(resolve, 300)) + +Rpc.listen({ + echo: (input: string) => input, +}) diff --git a/packages/opencode/test/util/rpc.test.ts b/packages/opencode/test/util/rpc.test.ts new file mode 100644 index 000000000000..341e1249c40f --- /dev/null +++ b/packages/opencode/test/util/rpc.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from "bun:test" +import { Rpc } from "../../src/util/rpc" + +describe("util.rpc", () => { + test("delivers calls made while the worker is still in its top-level await", async () => { + const worker = new Worker(new URL("../fixture/rpc-worker.ts", import.meta.url).href) + try { + const client = Rpc.client<{ echo: (input: string) => string }>(worker) + await new Promise((resolve) => client.on("started", () => resolve())) + expect(await client.call("echo", "hello")).toBe("hello") + } finally { + worker.terminate() + } + }) +})