diff --git a/packages/sdk/test/embedded.test.ts b/packages/sdk/test/embedded.test.ts index f3c0c8547176..71052da333c3 100644 --- a/packages/sdk/test/embedded.test.ts +++ b/packages/sdk/test/embedded.test.ts @@ -202,37 +202,61 @@ it.live( 10_000, ) -it.live("embedded client exposes plugin-backed web search", () => - withEmbedded("opencode-embedded-websearch-", (fixture) => - Effect.gen(function* () { - const opencode = yield* fixture.sdk.OpenCode.create() - const providerID = fixture.sdk.WebSearch.ID.make("embedded-websearch") - yield* opencode.plugin({ - id: `embedded-websearch-${crypto.randomUUID()}`, - effect: (ctx) => - ctx.websearch.transform((editor) => { - editor.add({ - id: providerID, - name: "Embedded web search", - execute: (input) => - Effect.succeed([{ url: "https://example.com", content: `Found ${input.query}`, time: {} }]), - }) - }), - }) - - const result = yield* opencode.websearch.query({ - query: "opencode", - providerID, - location: location(fixture), - }) - - expect(result.data).toEqual({ - providerID, - results: [{ url: "https://example.com", content: "Found opencode", time: {} }], - }) - }), - ), -) +for (const phase of ["startup", "reload"] as const) { + it.live( + `embedded web search waits for plugin activation during ${phase}`, + () => + withEmbedded("opencode-embedded-websearch-", (fixture) => + Effect.gen(function* () { + const opencode = yield* fixture.sdk.OpenCode.create({ + config: { directory: fixture.directory, project: false, content: "{}" }, + models: { fetch: false }, + fs: { filewatcher: false }, + }) + if (phase === "reload") yield* opencode.plugin.awaitActivation({ location: location(fixture) }) + const started = yield* Latch.make(false) + const release = yield* Latch.make(false) + yield* Effect.addFinalizer(() => release.open) + const providerID = fixture.sdk.WebSearch.ID.make("embedded-websearch") + yield* opencode.plugin({ + id: `embedded-websearch-${crypto.randomUUID()}`, + effect: (ctx) => + Effect.gen(function* () { + yield* started.open + yield* release.await + yield* ctx.websearch.transform((editor) => { + editor.add({ + id: providerID, + name: "Embedded web search", + execute: (input) => + Effect.succeed([{ url: "https://example.com", content: `Found ${input.query}`, time: {} }]), + }) + }) + }), + }) + + const query = yield* opencode.websearch + .query({ query: "opencode", providerID, location: location(fixture) }) + .pipe(Effect.forkScoped({ startImmediately: true })) + const providers = yield* opencode.websearch + .providers({ location: location(fixture) }) + .pipe(Effect.forkScoped({ startImmediately: true })) + + yield* started.await + expect(query.pollUnsafe()).toBeUndefined() + expect(providers.pollUnsafe()).toBeUndefined() + yield* release.open + + expect((yield* Fiber.join(query)).data).toEqual({ + providerID, + results: [{ url: "https://example.com", content: "Found opencode", time: {} }], + }) + expect((yield* Fiber.join(providers)).data).toContainEqual({ id: providerID, name: "Embedded web search" }) + }), + ), + 10_000, + ) +} it.live( "Location-owned runner events reach the ready global client", diff --git a/packages/sdk/test/promise.test.ts b/packages/sdk/test/promise.test.ts index e28d836dcea4..e286c5c7be57 100644 --- a/packages/sdk/test/promise.test.ts +++ b/packages/sdk/test/promise.test.ts @@ -33,7 +33,7 @@ test("Promise host uses the embedded router", async () => { } }) -test("Promise event streams support cancellation", async () => { +test("aborting a Promise event subscription completes it", async () => { await using directory = await tmpdir("opencode-promise-stream-") const config = join(directory.path, "config") await mkdir(config) @@ -44,8 +44,7 @@ test("Promise event streams support cancellation", async () => { expect(await events.next()).toMatchObject({ value: { type: "server.connected" }, done: false }) const pending = events.next() controller.abort() - const error = await pending.catch((error: unknown) => error) - expect(error).toMatchObject({ name: "ClientError", reason: "Transport" }) + expect(await pending).toEqual({ done: true, value: undefined }) await events.return?.() } }) diff --git a/packages/server/src/handlers/websearch.ts b/packages/server/src/handlers/websearch.ts index 71979d77d9d0..7701cf945f0d 100644 --- a/packages/server/src/handlers/websearch.ts +++ b/packages/server/src/handlers/websearch.ts @@ -1,3 +1,4 @@ +import { Plugin } from "@opencode-ai/core/plugin" import { WebSearch } from "@opencode-ai/core/websearch" import { InvalidRequestError, ServiceUnavailableError } from "@opencode-ai/protocol/errors" import { Effect } from "effect" @@ -11,6 +12,7 @@ export const WebSearchHandler = HttpApiBuilder.group(Api, "server.websearch", (h .handle( "websearch.providers", Effect.fn("server.websearch.providers")(function* () { + yield* Plugin.awaitActivation const websearch = yield* WebSearch.Service return yield* response(websearch.providers()) }), @@ -18,6 +20,7 @@ export const WebSearchHandler = HttpApiBuilder.group(Api, "server.websearch", (h .handle( "websearch.query", Effect.fn("server.websearch.query")(function* (request) { + yield* Plugin.awaitActivation const websearch = yield* WebSearch.Service return yield* response( websearch.query(request.payload).pipe(