import { Button } from "@opencode-ai/ui/button" import { useDialog } from "@opencode-ai/ui/context/dialog" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { Tag } from "@opencode-ai/ui/tag" import { showToast } from "@opencode-ai/ui/toast" import { iconNames, type IconName } from "@opencode-ai/ui/icons/provider" import { popularProviders, useProviders } from "@/hooks/use-providers" import { createMemo, type Component, For, Show } from "solid-js" import { useLanguage } from "@/context/language" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" import { DialogConnectProvider } from "./dialog-connect-provider" import { DialogSelectProvider } from "./dialog-select-provider" import { DialogCustomProvider } from "./dialog-custom-provider" type ProviderSource = "env" | "api" | "config" | "custom" type ProviderMeta = { source?: ProviderSource } export const SettingsProviders: Component = () => { const dialog = useDialog() const language = useLanguage() const globalSDK = useGlobalSDK() const globalSync = useGlobalSync() const providers = useProviders() const icon = (id: string): IconName => { if (iconNames.includes(id as IconName)) return id as IconName return "synthetic" } const connected = createMemo(() => { return providers .connected() .filter((p) => p.id !== "opencode" || Object.values(p.models).find((m) => m.cost?.input)) }) const popular = createMemo(() => { const connectedIDs = new Set(connected().map((p) => p.id)) const items = providers .popular() .filter((p) => !connectedIDs.has(p.id)) .slice() items.sort((a, b) => popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)) return items }) const source = (item: unknown) => (item as ProviderMeta).source const type = (item: unknown) => { const current = source(item) if (current === "env") return language.t("settings.providers.tag.environment") if (current === "api") return language.t("provider.connect.method.apiKey") if (current === "config") { const id = (item as { id?: string }).id if (id && isConfigCustom(id)) return language.t("settings.providers.tag.custom") return language.t("settings.providers.tag.config") } if (current === "custom") return language.t("settings.providers.tag.custom") return language.t("settings.providers.tag.other") } const canDisconnect = (item: unknown) => source(item) !== "env" const isConfigCustom = (providerID: string) => { const provider = globalSync.data.config.provider?.[providerID] if (!provider) return false if (provider.npm !== "@ai-sdk/openai-compatible") return false if (!provider.models || Object.keys(provider.models).length === 0) return false return true } const disableProvider = async (providerID: string, name: string) => { const before = globalSync.data.config.disabled_providers ?? [] const next = before.includes(providerID) ? before : [...before, providerID] globalSync.set("config", "disabled_providers", next) await globalSync .updateConfig({ disabled_providers: next }) .then(() => { showToast({ variant: "success", icon: "circle-check", title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }), description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }), }) }) .catch((err: unknown) => { globalSync.set("config", "disabled_providers", before) const message = err instanceof Error ? err.message : String(err) showToast({ title: language.t("common.requestFailed"), description: message }) }) } const disconnect = async (providerID: string, name: string) => { if (isConfigCustom(providerID)) { await globalSDK.client.auth.remove({ providerID }).catch(() => undefined) await disableProvider(providerID, name) return } await globalSDK.client.auth .remove({ providerID }) .then(async () => { await globalSDK.client.global.dispose() showToast({ variant: "success", icon: "circle-check", title: language.t("provider.disconnect.toast.disconnected.title", { provider: name }), description: language.t("provider.disconnect.toast.disconnected.description", { provider: name }), }) }) .catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err) showToast({ title: language.t("common.requestFailed"), description: message }) }) } return (
) }