diff --git a/biome.jsonc b/biome.jsonc index 586c8f6b08..d8254af863 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -43,7 +43,7 @@ "a11y": "info", "style": { "useNodejsImportProtocol": "off", - "useImportType": "info", + "useImportType": "off", "useBlockStatements": "off", "useNamingConvention": "off", "useThrowOnlyError": "info", diff --git a/cli/src/components/AuthView.tsx b/cli/src/components/AuthView.tsx index 42ff7949e2..dee4b5d088 100644 --- a/cli/src/components/AuthView.tsx +++ b/cli/src/components/AuthView.tsx @@ -19,6 +19,7 @@ import { useScrollableList } from "../hooks/useScrollableList" import { type DetectedSources, detectImportSources, type ImportSource } from "../utils/import-configs" import { isMouseEscapeSequence } from "../utils/input" import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config" +import { useValidProviders } from "../utils/providers" import { ApiKeyInput } from "./ApiKeyInput" import { StaticRobotFrame } from "./AsciiMotionCli" import { type BedrockConfig, BedrockSetup } from "./BedrockSetup" @@ -30,7 +31,7 @@ import { } from "./FeaturedModelPicker" import { ImportView } from "./ImportView" import { getDefaultModelId, hasModelPicker, ModelPicker } from "./ModelPicker" -import { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } from "./ProviderPicker" +import { getProviderLabel } from "./ProviderPicker" type AuthStep = | "menu" @@ -146,6 +147,9 @@ const TextInput: React.FC<{ export const AuthView: React.FC = ({ controller, onComplete, onError, onNavigateToWelcome }) => { const { exit } = useApp() + + const providers = useValidProviders() + const [step, setStep] = useState("menu") const [selectedProvider, setSelectedProvider] = useState( StateManager.get().getApiConfiguration().actModeApiProvider || @@ -187,11 +191,6 @@ export const AuthView: React.FC = ({ controller, onComplete, onEr onError: handleOcaAuthError, }) - // Use providers.json order, filtered to exclude CLI-incompatible providers - const sortedProviders = useMemo(() => { - return getProviderOrder().filter((p) => !CLI_EXCLUDED_PROVIDERS.has(p)) - }, []) - // Main menu items - conditionally include import options const mainMenuItems: SelectItem[] = useMemo(() => { const items: SelectItem[] = [{ label: "Sign in with Cline", value: "cline_auth" }] @@ -217,15 +216,13 @@ export const AuthView: React.FC = ({ controller, onComplete, onEr const providerItems: SelectItem[] = useMemo(() => { const search = providerSearch.toLowerCase() const filtered = providerSearch - ? sortedProviders.filter( - (p) => p.toLowerCase().includes(search) || getProviderLabel(p).toLowerCase().includes(search), - ) - : sortedProviders + ? providers.filter((p) => p.toLowerCase().includes(search) || getProviderLabel(p).toLowerCase().includes(search)) + : providers return filtered.map((p: string) => ({ label: getProviderLabel(p), value: p, })) - }, [sortedProviders, providerSearch]) + }, [providers, providerSearch]) // Use shared scrollable list hook for provider windowing const TOTAL_PROVIDER_ROWS = 8 diff --git a/cli/src/components/ProviderPicker.tsx b/cli/src/components/ProviderPicker.tsx index 7e0576ae1a..2dc5896eb7 100644 --- a/cli/src/components/ProviderPicker.tsx +++ b/cli/src/components/ProviderPicker.tsx @@ -5,11 +5,11 @@ import React, { useMemo } from "react" import { StateManager } from "@/core/storage/StateManager" import type { ApiConfiguration } from "@/shared/api" -import { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } from "../utils/providers" -import { SearchableList, SearchableListItem } from "./SearchableList" +import { getProviderLabel, useValidProviders } from "../utils/providers" +import { SearchableList, type SearchableListItem } from "./SearchableList" // Re-export for backwards compatibility -export { CLI_EXCLUDED_PROVIDERS, getProviderLabel, getProviderOrder } +export { getProviderLabel } /** * Check if a provider is configured (has required credentials/settings) @@ -125,17 +125,16 @@ interface ProviderPickerProps { export const ProviderPicker: React.FC = ({ onSelect, isActive = true }) => { // Get API configuration to check which providers are configured const apiConfig = StateManager.get().getApiConfiguration() + const sorted = useValidProviders() // Use providers.json order, filtered to exclude CLI-incompatible providers const items: SearchableListItem[] = useMemo(() => { - const sorted = getProviderOrder().filter((p: string) => !CLI_EXCLUDED_PROVIDERS.has(p)) - return sorted.map((providerId: string) => ({ id: providerId, label: getProviderLabel(providerId), suffix: isProviderConfigured(providerId, apiConfig) ? "(Configured)" : undefined, })) - }, [apiConfig]) + }, [apiConfig, sorted]) return onSelect(item.id)} /> } diff --git a/cli/src/utils/providers.ts b/cli/src/utils/providers.ts index b92a3b39a7..22f4ca53c4 100644 --- a/cli/src/utils/providers.ts +++ b/cli/src/utils/providers.ts @@ -3,7 +3,10 @@ * Used by both UI components and CLI commands */ +import { useMemo } from "react" +import { StateManager } from "@/core/storage/StateManager" import providersData from "@/shared/providers/providers.json" +import type { RemoteConfigFields } from "@/shared/storage/state-keys" // Create a lookup map from provider value to display label const providerLabels: Record = Object.fromEntries( @@ -17,7 +20,7 @@ const providerOrder: string[] = providersData.list.map((p: { value: string }) => * Providers that are not supported in CLI. * - vscode-lm: Requires VS Code's Language Model API (see ENG-1490 for OAuth-based support) */ -export const CLI_EXCLUDED_PROVIDERS = new Set(["vscode-lm"]) +const CLI_EXCLUDED_PROVIDERS = new Set(["vscode-lm"]) /** * Get the display label for a provider ID @@ -29,7 +32,7 @@ export function getProviderLabel(providerId: string): string { /** * Get the ordered list of all provider IDs (from providers.json) */ -export function getProviderOrder(): string[] { +function getProviderOrder(): string[] { return providerOrder } @@ -46,3 +49,19 @@ export function getValidCliProviders(): string[] { export function isValidCliProvider(providerId: string): boolean { return providerOrder.includes(providerId) && !CLI_EXCLUDED_PROVIDERS.has(providerId) } + +const getValidProviders = (remoteConfig: Partial | undefined) => { + if (remoteConfig?.remoteConfiguredProviders?.length) { + return remoteConfig.remoteConfiguredProviders + } + + return getProviderOrder().filter((p: string) => !CLI_EXCLUDED_PROVIDERS.has(p)) +} + +export const useValidProviders = () => { + const remoteConfig = StateManager.get().getRemoteConfigSettings() + + return useMemo(() => { + return getValidProviders(remoteConfig) + }, [remoteConfig]) +} diff --git a/webview-ui/src/components/chat/ModelPickerModal.tsx b/webview-ui/src/components/chat/ModelPickerModal.tsx index 283cf93c9c..89e2584f45 100644 --- a/webview-ui/src/components/chat/ModelPickerModal.tsx +++ b/webview-ui/src/components/chat/ModelPickerModal.tsx @@ -1,10 +1,11 @@ import type { ModelInfo as ModelInfoType } from "@shared/api" -import { ANTHROPIC_MIN_THINKING_BUDGET, ApiProvider } from "@shared/api" +import { ANTHROPIC_MIN_THINKING_BUDGET, type ApiProvider } from "@shared/api" import { StringRequest } from "@shared/proto/cline/common" import { UpdateSettingsRequest } from "@shared/proto/cline/state" -import { Mode } from "@shared/storage/types" +import type { Mode } from "@shared/storage/types" import { ArrowLeftRight, Brain, Check, ChevronDownIcon, Search, Settings } from "lucide-react" -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react" +import type React from "react" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { createPortal } from "react-dom" import { useWindowSize } from "react-use" import styled from "styled-components" @@ -163,12 +164,8 @@ const ModelPickerModal: React.FC = ({ isOpen, onOpenChang // Get configured providers const configuredProviders = useMemo(() => { - if (remoteConfigSettings?.remoteConfiguredProviders?.length) { - return remoteConfigSettings.remoteConfiguredProviders - } - - return getConfiguredProviders(apiConfiguration) - }, [apiConfiguration, remoteConfigSettings?.remoteConfiguredProviders]) + return getConfiguredProviders(remoteConfigSettings, apiConfiguration) + }, [apiConfiguration, remoteConfigSettings]) // Get models for current provider const allModels = useMemo((): ModelItem[] => { diff --git a/webview-ui/src/utils/getConfiguredProviders.ts b/webview-ui/src/utils/getConfiguredProviders.ts index b3cf138dbb..65110e4963 100644 --- a/webview-ui/src/utils/getConfiguredProviders.ts +++ b/webview-ui/src/utils/getConfiguredProviders.ts @@ -1,11 +1,19 @@ -import { ApiConfiguration, ApiProvider } from "@shared/api" +import type { ApiConfiguration, ApiProvider } from "@shared/api" import PROVIDERS from "@shared/providers/providers.json" +import type { RemoteConfigFields } from "@shared/storage/state-keys" /** * Returns a list of API providers that are configured (have required credentials/settings) * Based on validation logic from validate.ts */ -export function getConfiguredProviders(apiConfiguration: ApiConfiguration | undefined): ApiProvider[] { +export function getConfiguredProviders( + remoteConfig: Partial | undefined, + apiConfiguration: ApiConfiguration | undefined, +): ApiProvider[] { + if (remoteConfig?.remoteConfiguredProviders?.length) { + return remoteConfig.remoteConfiguredProviders + } + const configured: ApiProvider[] = [] if (!apiConfiguration) {