diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 7faa107856..f07d72e5f4 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -1,4 +1,4 @@ -import { Duration, Effect, Schema, Stream } from "effect" +import { Duration, Effect, Schema, Semaphore, Stream } from "effect" import type { Scope } from "effect" import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration" import { define } from "@opencode-ai/plugin/v2/effect/plugin" @@ -79,6 +79,7 @@ export const OpencodePlugin = define { for (const [providerID, item] of Object.entries(providers ?? {})) { catalog.provider.update(providerID, (provider) => { @@ -176,11 +177,13 @@ export const OpencodePlugin = define loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload()))) yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe( Stream.filter((event) => event.data.integrationID === Integration.ID.make("opencode")), - Stream.runForEach(() => load().pipe(Effect.andThen(ctx.catalog.reload()))), + Stream.runForEach(refresh), Effect.forkScoped({ startImmediately: true }), ) + yield* refresh().pipe(Effect.forkScoped) }), }) diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index 30757a042c..45d423f958 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -25,6 +25,20 @@ function required(value: T | undefined): T { return value } +function eventually( + effect: Effect.Effect, + predicate: (value: A) => boolean, + remaining = 1000, +): Effect.Effect { + return Effect.gen(function* () { + const value = yield* effect + if (predicate(value)) return value + if (remaining === 0) return yield* Effect.fail(new Error("Timed out waiting for value")) + yield* Effect.promise(() => Bun.sleep(1)) + return yield* eventually(effect, predicate, remaining - 1) + }) +} + function withEnv(vars: Record, effect: () => Effect.Effect) { return Effect.acquireUseRelease( Effect.sync(() => { @@ -67,11 +81,14 @@ describe("OpencodePlugin", () => { Effect.acquireUseRelease( Effect.sync(() => { const authorization: Array = [] + const gate = Promise.withResolvers() return { authorization, + release: gate.resolve, server: Bun.serve({ port: 0, - fetch: (request) => { + fetch: async (request) => { + await gate.promise authorization.push(request.headers.get("authorization")) const origin = new URL(request.url).origin return Response.json({ @@ -110,7 +127,7 @@ describe("OpencodePlugin", () => { }), } }), - ({ authorization, server }) => + ({ authorization, release, server }) => Effect.gen(function* () { const credentials = yield* Credential.Service const catalog = yield* Catalog.Service @@ -128,8 +145,15 @@ describe("OpencodePlugin", () => { }) yield* addPlugin() + expect(authorization).toEqual([]) + release() - const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("remote"))) + const provider = required( + yield* eventually( + catalog.provider.get(ProviderV2.ID.make("remote")), + (item) => item?.integrationID === Integration.ID.make("opencode"), + ), + ) expect(provider).toMatchObject({ name: "Remote", integrationID: "opencode",