Преглед изворни кода

fix(app): derive popular providers from integrations (#42713)

Luke Parker пре 1 дан
родитељ
комит
c8584ec0c8

+ 5 - 3
packages/app/src/components/provider-connection-controller.ts

@@ -119,9 +119,11 @@ export function createProviderConnectionController(options: {
   const finish = async () => {
     cancelPolling()
     const directory = options.directory()
-    await queryClient
-      .refetchQueries(serverSync.queryOptions.providers(directory ? pathKey(directory) : null))
-      .catch(() => undefined)
+    const key = directory ? pathKey(directory) : null
+    await Promise.all([
+      queryClient.refetchQueries(serverSync.queryOptions.providers(key)).catch(() => undefined),
+      queryClient.refetchQueries(serverSync.queryOptions.integrations(key)).catch(() => undefined),
+    ])
     if (polling.disposed) return
     options.onComplete()
   }

+ 15 - 1
packages/app/src/components/settings-v2/providers.tsx

@@ -4,6 +4,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
 import { showToast } from "@/utils/toast"
 import { popularProviders, useProviders } from "@/hooks/use-providers"
+import { useIntegrations } from "@/hooks/use-integrations"
 import { createMemo, type Component, For, Show } from "solid-js"
 import { useLanguage } from "@/context/language"
 import { useServerSDK } from "@/context/server-sdk"
@@ -40,7 +41,9 @@ export const SettingsProvidersV2: Component<{
   const serverSdk = useServerSDK()
   const serverSync = useServerSync()
   const providers = useProviders(() => props.directory)
+  const integrations = useIntegrations(() => props.directory)
   const providerConnect = useProviderConnectController({ onBack: props.onBack })
+  const integration = (providerID: string) => integrations.list().find((item) => item.id === providerID)
 
   const connect = (provider?: string) => {
     providerConnect.select(provider)
@@ -73,7 +76,14 @@ export const SettingsProvidersV2: Component<{
     return items
   })
 
+  // Connection state comes from the integration list like the TUI: credential
+  // connections mean an API key or OAuth grant, env connections mean detected
+  // environment variables, and a connectionless integration is config-provided.
   const source = (item: ProviderItem): ProviderSource | undefined => {
+    const current = integration(item.id)
+    if (current?.connections.some((connection) => connection.type === "credential")) return "api"
+    if (current?.connections.some((connection) => connection.type === "env")) return "env"
+    if (current) return "config"
     if (!("source" in item)) return
     const value = item.source
     if (value === "env" || value === "api" || value === "config" || value === "custom") return value
@@ -92,7 +102,11 @@ export const SettingsProvidersV2: Component<{
     return language.t("settings.providers.tag.other")
   }
 
-  const canDisconnect = (item: ProviderItem) => source(item) !== "env" && !isConfigCustom(item.id)
+  const canDisconnect = (item: ProviderItem) => {
+    const current = integration(item.id)
+    if (current) return current.connections.some((connection) => connection.type === "credential")
+    return source(item) !== "env" && !isConfigCustom(item.id)
+  }
 
   const note = (id: string) => PROVIDER_NOTES.find((item) => item.match(id))?.key
 

+ 16 - 5
packages/app/src/hooks/use-providers.ts

@@ -2,6 +2,7 @@ import { useQueryOptions } from "@/context/server-sync"
 import { Iterable, pipe } from "effect"
 import { type Accessor } from "solid-js"
 import { emptyProviderCatalog } from "./provider-catalog"
+import { useIntegrations } from "./use-integrations"
 import { useQuery } from "@tanstack/solid-query"
 import { pathKey } from "@/utils/path-key"
 
@@ -23,6 +24,7 @@ export function useProviders(directory: Accessor<string | undefined>) {
     const dir = directory()
     return queryOpts.providers(dir ? pathKey(dir) : null)
   })
+  const integrations = useIntegrations(directory)
 
   const providers = () => (!providersQuery.isSuccess ? emptyProviderCatalog : providersQuery.data)
 
@@ -30,13 +32,22 @@ export function useProviders(directory: Accessor<string | undefined>) {
     ready: () => providersQuery.isSuccess,
     all: () => providers().all,
     default: () => providers().default,
-    popular: () =>
-      pipe(
+    // V2 servers list only available providers, so the connectable catalog
+    // comes from the integration list, with the provider catalog as fallback.
+    popular: () => {
+      const catalog = integrations
+        .list()
+        .filter((integration) => popularProviderSet.has(integration.id))
+        .map((integration) => ({ id: integration.id, name: integration.name }))
+      const seen = new Set(catalog.map((integration) => integration.id))
+      return pipe(
         providers().all,
         Iterable.map(([, p]) => p),
-        Iterable.filter((p) => popularProviderSet.has(p.id)),
-        (v) => Array.from(v),
-      ),
+        Iterable.filter((p) => popularProviderSet.has(p.id) && !seen.has(p.id)),
+        Iterable.map((p) => ({ id: p.id, name: p.name })),
+        (v) => [...catalog, ...v],
+      )
+    },
     connected: () => {
       const connected = new Set(providers().connected)
       return pipe(