From 61676a1d3ce80b5b5bf8410d6c134ff740bc8d56 Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 8 Jul 2026 14:45:30 +0530 Subject: [PATCH] refactor(search): simplify provider flow --- packages/core/src/plugin/host.ts | 5 +- packages/core/src/plugin/promise.ts | 8 +-- packages/core/src/search.ts | 50 +++++++++---------- packages/core/test/plugin/host.ts | 5 +- packages/server/src/handlers/search.ts | 6 +-- .../tui/src/component/dialog-integration.tsx | 2 +- 6 files changed, 33 insertions(+), 43 deletions(-) diff --git a/packages/core/src/plugin/host.ts b/packages/core/src/plugin/host.ts index 46ec89c437..a7563a5683 100644 --- a/packages/core/src/plugin/host.ts +++ b/packages/core/src/plugin/host.ts @@ -192,10 +192,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int connection.type === "credential" ? { ...connection, id: Credential.ID.make(connection.id) } : connection, ), }, - register: (definition) => - integration.transform((draft) => { - registerIntegration(draft, definition) - }), + register: (definition) => integration.transform((draft) => registerIntegration(draft, definition)), transform: (callback) => integration.transform((draft) => { callback({ diff --git a/packages/core/src/plugin/promise.ts b/packages/core/src/plugin/promise.ts index ae0703f094..e2d14d641b 100644 --- a/packages/core/src/plugin/promise.ts +++ b/packages/core/src/plugin/promise.ts @@ -120,14 +120,14 @@ export function fromPromise(plugin: Plugin) { } function adaptIntegration(definition: IntegrationDefinition) { - const { methods, search, ...info } = definition + const { methods, search, ...definitionInfo } = definition return { - ...info, + ...definitionInfo, methods: methods?.map((method) => { if (method.type !== "oauth") return method - const { authorize, refresh, ...info } = method + const { authorize, refresh, ...methodInfo } = method return { - ...info, + ...methodInfo, authorize: (inputs: Parameters[0]) => Effect.tryPromise({ try: () => authorize(inputs), catch: (cause) => cause }).pipe( Effect.map((authorization) => { diff --git a/packages/core/src/search.ts b/packages/core/src/search.ts index 8cfb9e4bbc..8ac18db54f 100644 --- a/packages/core/src/search.ts +++ b/packages/core/src/search.ts @@ -74,7 +74,8 @@ const layer = Layer.effect( const integrations = yield* Integration.Service const onboarding = Semaphore.makeUnsafe(1) const decodeOutput = Schema.decodeUnknownEffect(ProviderOutput) - let pending: Integration.ID | undefined + const globalConfigPath = path.resolve(global.config) + let pendingProviderID: Integration.ID | undefined const requireProvider = ( providers: Map, @@ -84,45 +85,31 @@ const layer = Layer.effect( return provider ? Effect.succeed(provider) : Effect.fail(new ProviderNotFoundError({ providerID })) } - const configuredProvider = Effect.fn("Search.configuredProvider")(function* () { - const providerID = Config.latest(yield* config.entries(), "search")?.provider - if (providerID) return providerID - if (process.env.OPENCODE_WEBSEARCH_PROVIDER) { - return Integration.ID.make(process.env.OPENCODE_WEBSEARCH_PROVIDER) - } - if (truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL")) { - return Integration.ID.make("parallel") - } - if (truthy("OPENCODE_EXPERIMENTAL") || truthy("OPENCODE_ENABLE_EXA") || truthy("OPENCODE_EXPERIMENTAL_EXA")) { - return Integration.ID.make("exa") - } - }) - - const globalProvider = Effect.fn("Search.globalProvider")(function* () { + const globalProviderID = Effect.fn("Search.globalProviderID")(function* () { const entries = (yield* config.entries()).filter( - (entry) => entry.type === "document" && entry.path && path.dirname(entry.path) === path.resolve(global.config), + (entry) => entry.type === "document" && entry.path && path.dirname(entry.path) === globalConfigPath, ) return Config.latest(entries, "search")?.provider }) const selected = Effect.fn("Search.selected")(function* () { - return pending ?? (yield* globalProvider()) + return pendingProviderID ?? (yield* globalProviderID()) }) - const save = Effect.fn("Search.save")(function* (providerID: Integration.ID) { - pending = providerID + const saveProvider = Effect.fn("Search.saveProvider")(function* (providerID: Integration.ID) { + pendingProviderID = providerID yield* configGlobal.update(["search"], new ConfigSearch.Info({ provider: providerID })).pipe( - Effect.tapError(() => Effect.sync(() => (pending = undefined))), + Effect.tapError(() => Effect.sync(() => (pendingProviderID = undefined))), Effect.orDie, ) }) yield* events.subscribe(ConfigSchema.Event.Updated).pipe( Stream.runForEach(() => - globalProvider().pipe( + globalProviderID().pipe( Effect.tap((providerID) => Effect.sync(() => { - if (providerID === pending) pending = undefined + if (providerID === pendingProviderID) pendingProviderID = undefined }), ), Effect.ignore, @@ -201,8 +188,17 @@ const layer = Layer.effect( (yield* integrations.search.list()).map((provider) => [provider.integrationID, provider]), ) if (input.providerID) return yield* requireProvider(providers, input.providerID) - const override = yield* configuredProvider() - if (override) return yield* requireProvider(providers, override) + const configuredProviderID = Config.latest(yield* config.entries(), "search")?.provider + if (configuredProviderID) return yield* requireProvider(providers, configuredProviderID) + if (process.env.OPENCODE_WEBSEARCH_PROVIDER) { + return yield* requireProvider(providers, Integration.ID.make(process.env.OPENCODE_WEBSEARCH_PROVIDER)) + } + if (truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL")) { + return yield* requireProvider(providers, Integration.ID.make("parallel")) + } + if (truthy("OPENCODE_EXPERIMENTAL") || truthy("OPENCODE_ENABLE_EXA") || truthy("OPENCODE_EXPERIMENTAL_EXA")) { + return yield* requireProvider(providers, Integration.ID.make("exa")) + } const providerID = yield* selected() const provider = providerID ? providers.get(providerID) : undefined if (provider) return provider @@ -215,7 +211,7 @@ const layer = Layer.effect( if (selectedProvider) return selectedProvider const provider = yield* ask(providers, sessionID) yield* connect(provider, sessionID) - yield* save(provider.integrationID) + yield* saveProvider(provider.integrationID) return provider }), ) @@ -226,7 +222,7 @@ const layer = Layer.effect( select: Effect.fn("Search.select")(function* (providerID) { const provider = yield* integrations.search.get(providerID) if (!provider) return yield* new ProviderNotFoundError({ providerID }) - yield* save(providerID) + yield* saveProvider(providerID) }), query: Effect.fn("Search.query")(function* (input) { const provider = yield* resolve(input) diff --git a/packages/core/test/plugin/host.ts b/packages/core/test/plugin/host.ts index 938db47f25..81a5dae6b0 100644 --- a/packages/core/test/plugin/host.ts +++ b/packages/core/test/plugin/host.ts @@ -207,10 +207,7 @@ export function integrationHost(integration: Integration.Interface): PluginConte connection.type === "credential" ? { ...connection, id: Credential.ID.make(connection.id) } : connection, ), }, - register: (definition) => - integration.transform((draft) => { - registerIntegration(draft, definition) - }), + register: (definition) => integration.transform((draft) => registerIntegration(draft, definition)), transform: (callback) => integration.transform((draft) => callback({ diff --git a/packages/server/src/handlers/search.ts b/packages/server/src/handlers/search.ts index 5938af35b3..1e6d85e1f5 100644 --- a/packages/server/src/handlers/search.ts +++ b/packages/server/src/handlers/search.ts @@ -8,7 +8,7 @@ import { response } from "../location" export const SearchHandler = HttpApiBuilder.group(Api, "server.search", (handlers) => Effect.gen(function* () { - const ready = Effect.fn("server.search.ready")(function* () { + const awaitPlugins = Effect.fn("server.search.awaitPlugins")(function* () { const plugins = yield* PluginSupervisor.Service yield* plugins.ready.pipe( Effect.timeoutOrElse({ @@ -34,7 +34,7 @@ export const SearchHandler = HttpApiBuilder.group(Api, "server.search", (handler .handle( "search.provider.select", Effect.fn("server.search.provider.select")(function* (request) { - yield* ready() + yield* awaitPlugins() const search = yield* Search.Service yield* search.select(request.payload.providerID).pipe( Effect.mapError( @@ -52,7 +52,7 @@ export const SearchHandler = HttpApiBuilder.group(Api, "server.search", (handler .handle( "search.query", Effect.fn("server.search.query")(function* (request) { - yield* ready() + yield* awaitPlugins() const search = yield* Search.Service return yield* response( search.query(request.payload).pipe( diff --git a/packages/tui/src/component/dialog-integration.tsx b/packages/tui/src/component/dialog-integration.tsx index 5e89327348..f2d10dc109 100644 --- a/packages/tui/src/component/dialog-integration.tsx +++ b/packages/tui/src/component/dialog-integration.tsx @@ -121,7 +121,7 @@ function manageIntegration( const sdk = useSDK() const toast = useToast() const credentials = credentialConnections(integration) - const selected = () => data.location.search.provider() === integration.id + const selected = createMemo(() => data.location.search.provider() === integration.id) const selectSearch = () => { void sdk.api.search .selectProvider({