|
|
@@ -1,55 +1,55 @@
|
|
|
import React, { memo, useCallback, useEffect, useMemo, useState } from "react"
|
|
|
import { useDebounce } from "react-use"
|
|
|
-import { VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
|
|
-import { ExternalLinkIcon } from "@radix-ui/react-icons"
|
|
|
+import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
|
|
|
|
|
import {
|
|
|
- ApiConfiguration,
|
|
|
- glamaDefaultModelId,
|
|
|
- mistralDefaultModelId,
|
|
|
+ type ProviderName,
|
|
|
+ type ApiConfiguration,
|
|
|
openRouterDefaultModelId,
|
|
|
- unboundDefaultModelId,
|
|
|
requestyDefaultModelId,
|
|
|
- ApiProvider,
|
|
|
+ glamaDefaultModelId,
|
|
|
+ unboundDefaultModelId,
|
|
|
} from "@roo/shared/api"
|
|
|
|
|
|
import { vscode } from "@src/utils/vscode"
|
|
|
-import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@src/utils/validate"
|
|
|
+import { validateApiConfiguration, validateModelId } from "@src/utils/validate"
|
|
|
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
|
|
import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
|
|
|
import { useSelectedModel } from "@src/components/ui/hooks/useSelectedModel"
|
|
|
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@src/components/ui"
|
|
|
+
|
|
|
import {
|
|
|
- useOpenRouterModelProviders,
|
|
|
- OPENROUTER_DEFAULT_PROVIDER_NAME,
|
|
|
-} from "@src/components/ui/hooks/useOpenRouterModelProviders"
|
|
|
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button } from "@src/components/ui"
|
|
|
-import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink"
|
|
|
-import { getRequestyApiKeyUrl, getGlamaAuthUrl } from "@src/oauth/urls"
|
|
|
-
|
|
|
-// Providers
|
|
|
-import { Anthropic } from "./providers/Anthropic"
|
|
|
-import { Bedrock } from "./providers/Bedrock"
|
|
|
-import { Gemini } from "./providers/Gemini"
|
|
|
-import { LMStudio } from "./providers/LMStudio"
|
|
|
-import { Ollama } from "./providers/Ollama"
|
|
|
-import { OpenAI } from "./providers/OpenAI"
|
|
|
-import { OpenAICompatible } from "./providers/OpenAICompatible"
|
|
|
-import { OpenRouter } from "./providers/OpenRouter"
|
|
|
-import { Vertex } from "./providers/Vertex"
|
|
|
-import { VSCodeLM } from "./providers/VSCodeLM"
|
|
|
+ Anthropic,
|
|
|
+ Bedrock,
|
|
|
+ Chutes,
|
|
|
+ DeepSeek,
|
|
|
+ Gemini,
|
|
|
+ Glama,
|
|
|
+ Groq,
|
|
|
+ LMStudio,
|
|
|
+ Mistral,
|
|
|
+ Ollama,
|
|
|
+ OpenAI,
|
|
|
+ OpenAICompatible,
|
|
|
+ OpenRouter,
|
|
|
+ Requesty,
|
|
|
+ Unbound,
|
|
|
+ Vertex,
|
|
|
+ VSCodeLM,
|
|
|
+ XAI,
|
|
|
+} from "./providers"
|
|
|
|
|
|
import { MODELS_BY_PROVIDER, PROVIDERS, REASONING_MODELS } from "./constants"
|
|
|
import { inputEventTransform, noTransform } from "./transforms"
|
|
|
import { ModelInfoView } from "./ModelInfoView"
|
|
|
-import { ModelPicker } from "./ModelPicker"
|
|
|
import { ApiErrorMessage } from "./ApiErrorMessage"
|
|
|
import { ThinkingBudget } from "./ThinkingBudget"
|
|
|
-import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay"
|
|
|
import { ReasoningEffort } from "./ReasoningEffort"
|
|
|
import { PromptCachingControl } from "./PromptCachingControl"
|
|
|
import { DiffSettingsControl } from "./DiffSettingsControl"
|
|
|
import { TemperatureControl } from "./TemperatureControl"
|
|
|
import { RateLimitSecondsControl } from "./RateLimitSecondsControl"
|
|
|
+import { BedrockCustomArn } from "./providers/BedrockCustomArn"
|
|
|
|
|
|
export interface ApiOptionsProps {
|
|
|
uriScheme: string | undefined
|
|
|
@@ -75,8 +75,6 @@ const ApiOptions = ({
|
|
|
return Object.entries(headers)
|
|
|
})
|
|
|
|
|
|
- const [requestyShowRefreshHint, setRequestyShowRefreshHint] = useState<boolean>()
|
|
|
-
|
|
|
useEffect(() => {
|
|
|
const propHeaders = apiConfiguration?.openAiHeaders || {}
|
|
|
|
|
|
@@ -106,13 +104,14 @@ const ApiOptions = ({
|
|
|
return result
|
|
|
}
|
|
|
|
|
|
- // Debounced effect to update the main configuration when local customHeaders state stabilizes.
|
|
|
+ // Debounced effect to update the main configuration when local
|
|
|
+ // customHeaders state stabilizes.
|
|
|
useDebounce(
|
|
|
() => {
|
|
|
const currentConfigHeaders = apiConfiguration?.openAiHeaders || {}
|
|
|
const newHeadersObject = convertHeadersToObject(customHeaders)
|
|
|
|
|
|
- // Only update if the processed object is different from the current config
|
|
|
+ // Only update if the processed object is different from the current config.
|
|
|
if (JSON.stringify(currentConfigHeaders) !== JSON.stringify(newHeadersObject)) {
|
|
|
setApiConfigurationField("openAiHeaders", newHeadersObject)
|
|
|
}
|
|
|
@@ -142,7 +141,7 @@ const ApiOptions = ({
|
|
|
|
|
|
const { data: routerModels, refetch: refetchRouterModels } = useRouterModels()
|
|
|
|
|
|
- // Update apiConfiguration.aiModelId whenever selectedModelId changes.
|
|
|
+ // Update `apiModelId` whenever `selectedModelId` changes.
|
|
|
useEffect(() => {
|
|
|
if (selectedModelId) {
|
|
|
setApiConfigurationField("apiModelId", selectedModelId)
|
|
|
@@ -193,16 +192,7 @@ const ApiOptions = ({
|
|
|
setErrorMessage(apiValidationResult)
|
|
|
}, [apiConfiguration, routerModels, setErrorMessage])
|
|
|
|
|
|
- const { data: openRouterModelProviders } = useOpenRouterModelProviders(apiConfiguration?.openRouterModelId, {
|
|
|
- enabled:
|
|
|
- selectedProvider === "openrouter" &&
|
|
|
- !!apiConfiguration?.openRouterModelId &&
|
|
|
- routerModels?.openrouter &&
|
|
|
- Object.keys(routerModels.openrouter).length > 1 &&
|
|
|
- apiConfiguration.openRouterModelId in routerModels.openrouter,
|
|
|
- })
|
|
|
-
|
|
|
- const selectedProviderModelOptions = useMemo(
|
|
|
+ const selectedProviderModels = useMemo(
|
|
|
() =>
|
|
|
MODELS_BY_PROVIDER[selectedProvider]
|
|
|
? Object.keys(MODELS_BY_PROVIDER[selectedProvider]).map((modelId) => ({
|
|
|
@@ -213,37 +203,8 @@ const ApiOptions = ({
|
|
|
[selectedProvider],
|
|
|
)
|
|
|
|
|
|
- // Custom URL path mappings for providers with different slugs.
|
|
|
- const providerUrlSlugs: Record<string, string> = {
|
|
|
- "openai-native": "openai",
|
|
|
- openai: "openai-compatible",
|
|
|
- }
|
|
|
-
|
|
|
- // Helper function to get provider display name from PROVIDERS constant.
|
|
|
- const getProviderDisplayName = (providerKey: string): string | undefined => {
|
|
|
- const provider = PROVIDERS.find((p) => p.value === providerKey)
|
|
|
- return provider?.label
|
|
|
- }
|
|
|
-
|
|
|
- // Helper function to get the documentation URL and name for the currently selected provider
|
|
|
- const getSelectedProviderDocUrl = (): { url: string; name: string } | undefined => {
|
|
|
- const displayName = getProviderDisplayName(selectedProvider)
|
|
|
-
|
|
|
- if (!displayName) {
|
|
|
- return undefined
|
|
|
- }
|
|
|
-
|
|
|
- // Get the URL slug - use custom mapping if available, otherwise use the provider key
|
|
|
- const urlSlug = providerUrlSlugs[selectedProvider] || selectedProvider
|
|
|
-
|
|
|
- return {
|
|
|
- url: `https://docs.roocode.com/providers/${urlSlug}`,
|
|
|
- name: displayName,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const onApiProviderChange = useCallback(
|
|
|
- (value: ApiProvider) => {
|
|
|
+ const onProviderChange = useCallback(
|
|
|
+ (value: ProviderName) => {
|
|
|
// It would be much easier to have a single attribute that stores
|
|
|
// the modelId, but we have a separate attribute for each of
|
|
|
// OpenRouter, Glama, Unbound, and Requesty.
|
|
|
@@ -285,25 +246,40 @@ const ApiOptions = ({
|
|
|
],
|
|
|
)
|
|
|
|
|
|
+ const docs = useMemo(() => {
|
|
|
+ const provider = PROVIDERS.find(({ value }) => value === selectedProvider)
|
|
|
+ const name = provider?.label
|
|
|
+
|
|
|
+ if (!name) {
|
|
|
+ return undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the URL slug - use custom mapping if available, otherwise use the provider key.
|
|
|
+ const slugs: Record<string, string> = {
|
|
|
+ "openai-native": "openai",
|
|
|
+ openai: "openai-compatible",
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ url: `https://docs.roocode.com/providers/${slugs[selectedProvider] || selectedProvider}`,
|
|
|
+ name,
|
|
|
+ }
|
|
|
+ }, [selectedProvider])
|
|
|
+
|
|
|
return (
|
|
|
<div className="flex flex-col gap-3">
|
|
|
<div className="flex flex-col gap-1 relative">
|
|
|
<div className="flex justify-between items-center">
|
|
|
<label className="block font-medium mb-1">{t("settings:providers.apiProvider")}</label>
|
|
|
- {getSelectedProviderDocUrl() && (
|
|
|
+ {docs && (
|
|
|
<div className="text-xs text-vscode-descriptionForeground">
|
|
|
- <VSCodeLink
|
|
|
- href={getSelectedProviderDocUrl()!.url}
|
|
|
- className="hover:text-vscode-foreground"
|
|
|
- target="_blank">
|
|
|
- {t("settings:providers.providerDocumentation", {
|
|
|
- provider: getSelectedProviderDocUrl()!.name,
|
|
|
- })}
|
|
|
+ <VSCodeLink href={docs.url} className="hover:text-vscode-foreground" target="_blank">
|
|
|
+ {t("settings:providers.providerDocumentation", { provider: docs.name })}
|
|
|
</VSCodeLink>
|
|
|
</div>
|
|
|
)}
|
|
|
</div>
|
|
|
- <Select value={selectedProvider} onValueChange={(value) => onApiProviderChange(value as ApiProvider)}>
|
|
|
+ <Select value={selectedProvider} onValueChange={(value) => onProviderChange(value as ProviderName)}>
|
|
|
<SelectTrigger className="w-full">
|
|
|
<SelectValue placeholder={t("settings:common.select")} />
|
|
|
</SelectTrigger>
|
|
|
@@ -323,81 +299,41 @@ const ApiOptions = ({
|
|
|
<OpenRouter
|
|
|
apiConfiguration={apiConfiguration}
|
|
|
setApiConfigurationField={setApiConfigurationField}
|
|
|
+ routerModels={routerModels}
|
|
|
+ selectedModelId={selectedModelId}
|
|
|
uriScheme={uriScheme}
|
|
|
fromWelcomeView={fromWelcomeView}
|
|
|
/>
|
|
|
)}
|
|
|
|
|
|
- {selectedProvider === "anthropic" && (
|
|
|
- <Anthropic apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
+ {selectedProvider === "requesty" && (
|
|
|
+ <Requesty
|
|
|
+ apiConfiguration={apiConfiguration}
|
|
|
+ setApiConfigurationField={setApiConfigurationField}
|
|
|
+ routerModels={routerModels}
|
|
|
+ refetchRouterModels={refetchRouterModels}
|
|
|
+ />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "glama" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.glamaApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("glamaApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.glamaApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.glamaApiKey && (
|
|
|
- <VSCodeButtonLink
|
|
|
- href={getGlamaAuthUrl(uriScheme)}
|
|
|
- style={{ width: "100%" }}
|
|
|
- appearance="primary">
|
|
|
- {t("settings:providers.getGlamaApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <Glama
|
|
|
+ apiConfiguration={apiConfiguration}
|
|
|
+ setApiConfigurationField={setApiConfigurationField}
|
|
|
+ routerModels={routerModels}
|
|
|
+ uriScheme={uriScheme}
|
|
|
+ />
|
|
|
)}
|
|
|
|
|
|
- {selectedProvider === "requesty" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.requestyApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("requestyApiKey")}
|
|
|
- placeholder={t("settings:providers.getRequestyApiKey")}
|
|
|
- className="w-full">
|
|
|
- <div className="flex justify-between items-center mb-1">
|
|
|
- <label className="block font-medium">{t("settings:providers.requestyApiKey")}</label>
|
|
|
- {apiConfiguration?.requestyApiKey && (
|
|
|
- <RequestyBalanceDisplay apiKey={apiConfiguration.requestyApiKey} />
|
|
|
- )}
|
|
|
- </div>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.requestyApiKey && (
|
|
|
- <VSCodeButtonLink href={getRequestyApiKeyUrl()} style={{ width: "100%" }} appearance="primary">
|
|
|
- {t("settings:providers.getRequestyApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- <Button
|
|
|
- variant="outline"
|
|
|
- title={t("settings:providers.refetchModels")}
|
|
|
- onClick={() => {
|
|
|
- vscode.postMessage({ type: "flushRouterModels", text: "requesty" })
|
|
|
- refetchRouterModels()
|
|
|
- setRequestyShowRefreshHint(true)
|
|
|
- }}>
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <span className="codicon codicon-refresh" />
|
|
|
- {t("settings:providers.flushModelsCache")}
|
|
|
- </div>
|
|
|
- </Button>
|
|
|
- {requestyShowRefreshHint && (
|
|
|
- <div className="flex items-center text-vscode-errorForeground">
|
|
|
- {t("settings:providers.flushedModelsCache")}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ {selectedProvider === "unbound" && (
|
|
|
+ <Unbound
|
|
|
+ apiConfiguration={apiConfiguration}
|
|
|
+ setApiConfigurationField={setApiConfigurationField}
|
|
|
+ routerModels={routerModels}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {selectedProvider === "anthropic" && (
|
|
|
+ <Anthropic apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "openai-native" && (
|
|
|
@@ -405,42 +341,7 @@ const ApiOptions = ({
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "mistral" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.mistralApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("mistralApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <span className="font-medium">{t("settings:providers.mistralApiKey")}</span>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.mistralApiKey && (
|
|
|
- <VSCodeButtonLink href="https://console.mistral.ai/" appearance="secondary">
|
|
|
- {t("settings:providers.getMistralApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- {(apiConfiguration?.apiModelId?.startsWith("codestral-") ||
|
|
|
- (!apiConfiguration?.apiModelId && mistralDefaultModelId.startsWith("codestral-"))) && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.mistralCodestralUrl || ""}
|
|
|
- type="url"
|
|
|
- onInput={handleInputChange("mistralCodestralUrl")}
|
|
|
- placeholder="https://codestral.mistral.ai"
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">
|
|
|
- {t("settings:providers.codestralBaseUrl")}
|
|
|
- </label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.codestralBaseUrlDesc")}
|
|
|
- </div>
|
|
|
- </>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <Mistral apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "bedrock" && (
|
|
|
@@ -471,24 +372,7 @@ const ApiOptions = ({
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "deepseek" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.deepSeekApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("deepSeekApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.deepSeekApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.deepSeekApiKey && (
|
|
|
- <VSCodeButtonLink href="https://platform.deepseek.com/" appearance="secondary">
|
|
|
- {t("settings:providers.getDeepSeekApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <DeepSeek apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "vscode-lm" && (
|
|
|
@@ -500,87 +384,15 @@ const ApiOptions = ({
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "xai" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.xaiApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("xaiApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.xaiApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.xaiApiKey && (
|
|
|
- <VSCodeButtonLink href="https://api.x.ai/docs" appearance="secondary">
|
|
|
- {t("settings:providers.getXaiApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <XAI apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "groq" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.groqApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("groqApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.groqApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.groqApiKey && (
|
|
|
- <VSCodeButtonLink href="https://console.groq.com/keys" appearance="secondary">
|
|
|
- {t("settings:providers.getGroqApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <Groq apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "chutes" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.chutesApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("chutesApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.chutesApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.chutesApiKey && (
|
|
|
- <VSCodeButtonLink href="https://chutes.ai/app/api" appearance="secondary">
|
|
|
- {t("settings:providers.getChutesApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProvider === "unbound" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.unboundApiKey || ""}
|
|
|
- type="password"
|
|
|
- onInput={handleInputChange("unboundApiKey")}
|
|
|
- placeholder={t("settings:placeholders.apiKey")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:providers.unboundApiKey")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.apiKeyStorageNotice")}
|
|
|
- </div>
|
|
|
- {!apiConfiguration?.unboundApiKey && (
|
|
|
- <VSCodeButtonLink href="https://gateway.getunbound.ai" appearance="secondary">
|
|
|
- {t("settings:providers.getUnboundApiKey")}
|
|
|
- </VSCodeButtonLink>
|
|
|
- )}
|
|
|
- </>
|
|
|
+ <Chutes apiConfiguration={apiConfiguration} setApiConfigurationField={setApiConfigurationField} />
|
|
|
)}
|
|
|
|
|
|
{selectedProvider === "human-relay" && (
|
|
|
@@ -594,99 +406,10 @@ const ApiOptions = ({
|
|
|
</>
|
|
|
)}
|
|
|
|
|
|
- {/* Model Pickers */}
|
|
|
-
|
|
|
- {selectedProvider === "openrouter" && (
|
|
|
- <ModelPicker
|
|
|
- apiConfiguration={apiConfiguration}
|
|
|
- setApiConfigurationField={setApiConfigurationField}
|
|
|
- defaultModelId={openRouterDefaultModelId}
|
|
|
- models={routerModels?.openrouter ?? {}}
|
|
|
- modelIdKey="openRouterModelId"
|
|
|
- serviceName="OpenRouter"
|
|
|
- serviceUrl="https://openrouter.ai/models"
|
|
|
- />
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProvider === "openrouter" &&
|
|
|
- openRouterModelProviders &&
|
|
|
- Object.keys(openRouterModelProviders).length > 0 && (
|
|
|
- <div>
|
|
|
- <div className="flex items-center gap-1">
|
|
|
- <label className="block font-medium mb-1">
|
|
|
- {t("settings:providers.openRouter.providerRouting.title")}
|
|
|
- </label>
|
|
|
- <a href={`https://openrouter.ai/${selectedModelId}/providers`}>
|
|
|
- <ExternalLinkIcon className="w-4 h-4" />
|
|
|
- </a>
|
|
|
- </div>
|
|
|
- <Select
|
|
|
- value={apiConfiguration?.openRouterSpecificProvider || OPENROUTER_DEFAULT_PROVIDER_NAME}
|
|
|
- onValueChange={(value) => setApiConfigurationField("openRouterSpecificProvider", value)}>
|
|
|
- <SelectTrigger className="w-full">
|
|
|
- <SelectValue placeholder={t("settings:common.select")} />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- <SelectItem value={OPENROUTER_DEFAULT_PROVIDER_NAME}>
|
|
|
- {OPENROUTER_DEFAULT_PROVIDER_NAME}
|
|
|
- </SelectItem>
|
|
|
- {Object.entries(openRouterModelProviders).map(([value, { label }]) => (
|
|
|
- <SelectItem key={value} value={value}>
|
|
|
- {label}
|
|
|
- </SelectItem>
|
|
|
- ))}
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground mt-1">
|
|
|
- {t("settings:providers.openRouter.providerRouting.description")}{" "}
|
|
|
- <a href="https://openrouter.ai/docs/features/provider-routing">
|
|
|
- {t("settings:providers.openRouter.providerRouting.learnMore")}.
|
|
|
- </a>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProvider === "glama" && (
|
|
|
- <ModelPicker
|
|
|
- apiConfiguration={apiConfiguration}
|
|
|
- setApiConfigurationField={setApiConfigurationField}
|
|
|
- defaultModelId={glamaDefaultModelId}
|
|
|
- models={routerModels?.glama ?? {}}
|
|
|
- modelIdKey="glamaModelId"
|
|
|
- serviceName="Glama"
|
|
|
- serviceUrl="https://glama.ai/models"
|
|
|
- />
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProvider === "unbound" && (
|
|
|
- <ModelPicker
|
|
|
- apiConfiguration={apiConfiguration}
|
|
|
- defaultModelId={unboundDefaultModelId}
|
|
|
- models={routerModels?.unbound ?? {}}
|
|
|
- modelIdKey="unboundModelId"
|
|
|
- serviceName="Unbound"
|
|
|
- serviceUrl="https://api.getunbound.ai/models"
|
|
|
- setApiConfigurationField={setApiConfigurationField}
|
|
|
- />
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProvider === "requesty" && (
|
|
|
- <ModelPicker
|
|
|
- apiConfiguration={apiConfiguration}
|
|
|
- setApiConfigurationField={setApiConfigurationField}
|
|
|
- defaultModelId={requestyDefaultModelId}
|
|
|
- models={routerModels?.requesty ?? {}}
|
|
|
- modelIdKey="requestyModelId"
|
|
|
- serviceName="Requesty"
|
|
|
- serviceUrl="https://requesty.ai"
|
|
|
- />
|
|
|
- )}
|
|
|
-
|
|
|
- {selectedProviderModelOptions.length > 0 && (
|
|
|
+ {selectedProviderModels.length > 0 && (
|
|
|
<>
|
|
|
<div>
|
|
|
<label className="block font-medium mb-1">{t("settings:providers.model")}</label>
|
|
|
-
|
|
|
<Select
|
|
|
value={selectedModelId === "custom-arn" ? "custom-arn" : selectedModelId}
|
|
|
onValueChange={(value) => {
|
|
|
@@ -701,7 +424,7 @@ const ApiOptions = ({
|
|
|
<SelectValue placeholder={t("settings:common.select")} />
|
|
|
</SelectTrigger>
|
|
|
<SelectContent>
|
|
|
- {selectedProviderModelOptions.map((option) => (
|
|
|
+ {selectedProviderModels.map((option) => (
|
|
|
<SelectItem key={option.value} value={option.value}>
|
|
|
{option.label}
|
|
|
</SelectItem>
|
|
|
@@ -714,58 +437,10 @@ const ApiOptions = ({
|
|
|
</div>
|
|
|
|
|
|
{selectedProvider === "bedrock" && selectedModelId === "custom-arn" && (
|
|
|
- <>
|
|
|
- <VSCodeTextField
|
|
|
- value={apiConfiguration?.awsCustomArn || ""}
|
|
|
- onInput={(e) => {
|
|
|
- const value = (e.target as HTMLInputElement).value
|
|
|
- setApiConfigurationField("awsCustomArn", value)
|
|
|
- }}
|
|
|
- placeholder={t("settings:placeholders.customArn")}
|
|
|
- className="w-full">
|
|
|
- <label className="block font-medium mb-1">{t("settings:labels.customArn")}</label>
|
|
|
- </VSCodeTextField>
|
|
|
- <div className="text-sm text-vscode-descriptionForeground -mt-2">
|
|
|
- {t("settings:providers.awsCustomArnUse")}
|
|
|
- <ul className="list-disc pl-5 mt-1">
|
|
|
- <li>
|
|
|
- arn:aws:bedrock:eu-west-1:123456789012:inference-profile/eu.anthropic.claude-3-7-sonnet-20250219-v1:0
|
|
|
- </li>
|
|
|
- <li>
|
|
|
- arn:aws:bedrock:us-west-2:123456789012:provisioned-model/my-provisioned-model
|
|
|
- </li>
|
|
|
- <li>
|
|
|
- arn:aws:bedrock:us-east-1:123456789012:default-prompt-router/anthropic.claude:1
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
- {t("settings:providers.awsCustomArnDesc")}
|
|
|
- </div>
|
|
|
- {apiConfiguration?.awsCustomArn &&
|
|
|
- (() => {
|
|
|
- const validation = validateBedrockArn(
|
|
|
- apiConfiguration.awsCustomArn,
|
|
|
- apiConfiguration.awsRegion,
|
|
|
- )
|
|
|
-
|
|
|
- if (!validation.isValid) {
|
|
|
- return (
|
|
|
- <div className="text-sm text-vscode-errorForeground mt-2">
|
|
|
- {validation.errorMessage || t("settings:providers.invalidArnFormat")}
|
|
|
- </div>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- if (validation.errorMessage) {
|
|
|
- return (
|
|
|
- <div className="text-sm text-vscode-errorForeground mt-2">
|
|
|
- {validation.errorMessage}
|
|
|
- </div>
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- return null
|
|
|
- })()}
|
|
|
- </>
|
|
|
+ <BedrockCustomArn
|
|
|
+ apiConfiguration={apiConfiguration}
|
|
|
+ setApiConfigurationField={setApiConfigurationField}
|
|
|
+ />
|
|
|
)}
|
|
|
|
|
|
<ModelInfoView
|