diff --git a/.changeset/lovely-jeans-worry.md b/.changeset/lovely-jeans-worry.md new file mode 100644 index 0000000000..9ed8af3a1f --- /dev/null +++ b/.changeset/lovely-jeans-worry.md @@ -0,0 +1,5 @@ +--- +"roo-cline": minor +--- + +API provider: Choose specific provider when using OpenRouter diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index d681c42ec3..3823400455 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -15,6 +15,8 @@ import { getModelParams, SingleCompletionHandler } from ".." import { BaseProvider } from "./base-provider" import { defaultHeaders } from "./openai" +const OPENROUTER_DEFAULT_PROVIDER_NAME = "[default]" + // Add custom interface for OpenRouter params. type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & { transforms?: string[] @@ -109,6 +111,11 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH messages: openAiMessages, stream: true, include_reasoning: true, + // Only include provider if openRouterSpecificProvider is not "[default]". + ...(this.options.openRouterSpecificProvider && + this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && { + provider: { order: [this.options.openRouterSpecificProvider] }, + }), // This way, the transforms field will only be included in the parameters when openRouterUseMiddleOutTransform is true. ...((this.options.openRouterUseMiddleOutTransform ?? true) && { transforms: ["middle-out"] }), } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 13e6c0979c..ed54f56466 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -203,6 +203,7 @@ export type GlobalStateKey = | "openRouterModelId" | "openRouterModelInfo" | "openRouterBaseUrl" + | "openRouterSpecificProvider" | "openRouterUseMiddleOutTransform" | "googleGeminiBaseUrl" | "allowedCommands" diff --git a/src/shared/api.ts b/src/shared/api.ts index 624db18612..1bc432157b 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -30,6 +30,7 @@ export interface ApiHandlerOptions { openRouterModelId?: string openRouterModelInfo?: ModelInfo openRouterBaseUrl?: string + openRouterSpecificProvider?: string awsAccessKey?: string awsSecretKey?: string awsSessionToken?: string @@ -97,6 +98,7 @@ export const API_CONFIG_KEYS: GlobalStateKey[] = [ "openRouterModelId", "openRouterModelInfo", "openRouterBaseUrl", + "openRouterSpecificProvider", "awsRegion", "awsUseCrossRegionInference", // "awsUsePromptCache", // NOT exist on GlobalStateKey diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index c2b78f1ef7..9175bf22ee 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -71,6 +71,7 @@ export const GLOBAL_STATE_KEYS = [ "openRouterModelId", "openRouterModelInfo", "openRouterBaseUrl", + "openRouterSpecificProvider", "openRouterUseMiddleOutTransform", "googleGeminiBaseUrl", "allowedCommands", diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index 439e5bd797..3b491a7fdd 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", + "@tanstack/react-query": "^5.68.0", "@vscode/webview-ui-toolkit": "^1.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -48,7 +49,8 @@ "tailwind-merge": "^2.6.0", "tailwindcss": "^4.0.0", "tailwindcss-animate": "^1.0.7", - "vscrui": "^0.2.2" + "vscrui": "^0.2.2", + "zod": "^3.24.2" }, "devDependencies": { "@storybook/addon-essentials": "^8.5.6", @@ -6533,6 +6535,32 @@ "vite": "^5.2.0 || ^6" } }, + "node_modules/@tanstack/query-core": { + "version": "5.68.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.68.0.tgz", + "integrity": "sha512-r8rFYYo8/sY/LNaOqX84h12w7EQev4abFXDWy4UoDVUJzJ5d9Fbmb8ayTi7ScG+V0ap44SF3vNs/45mkzDGyGw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.68.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.68.0.tgz", + "integrity": "sha512-mMOdGDKlwTP/WV72QqSNf4PAMeoBp/DqBHQ222wBfb51Looi8QUqnCnb9O98ZgvNISmy6fzxRGBJdZ+9IBvX2Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.68.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -21967,6 +21995,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 2f9c8eb002..ac0bb935ff 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.0.0", + "@tanstack/react-query": "^5.68.0", "@vscode/webview-ui-toolkit": "^1.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -56,7 +57,8 @@ "tailwind-merge": "^2.6.0", "tailwindcss": "^4.0.0", "tailwindcss-animate": "^1.0.7", - "vscrui": "^0.2.2" + "vscrui": "^0.2.2", + "zod": "^3.24.2" }, "devDependencies": { "@storybook/addon-essentials": "^8.5.6", diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 4276d404cd..59a4047251 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,5 +1,7 @@ import { useCallback, useEffect, useRef, useState } from "react" import { useEvent } from "react-use" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + import { ExtensionMessage } from "../../src/shared/ExtensionMessage" import TranslationProvider from "./i18n/TranslationContext" @@ -16,12 +18,6 @@ import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog" type Tab = "settings" | "history" | "mcp" | "prompts" | "chat" -type HumanRelayDialogState = { - isOpen: boolean - requestId: string - promptText: string -} - const tabsByMessageAction: Partial, Tab>> = { chatButtonClicked: "chat", settingsButtonClicked: "settings", @@ -36,7 +32,12 @@ const App = () => { const [showAnnouncement, setShowAnnouncement] = useState(false) const [tab, setTab] = useState("chat") - const [humanRelayDialogState, setHumanRelayDialogState] = useState({ + + const [humanRelayDialogState, setHumanRelayDialogState] = useState<{ + isOpen: boolean + requestId: string + promptText: string + }>({ isOpen: false, requestId: "", promptText: "", @@ -122,10 +123,14 @@ const App = () => { ) } +const queryClient = new QueryClient() + const AppWithProviders = () => ( - + + + ) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 02cd1c9375..8ea5f8ab45 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -5,6 +5,7 @@ import { useDebounce, useEvent } from "react-use" import { Checkbox, Dropdown, type DropdownOption } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import * as vscodemodels from "vscode" +import { ExternalLinkIcon } from "@radix-ui/react-icons" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Button } from "@/components/ui" @@ -38,7 +39,12 @@ import { } from "../../../../src/shared/api" import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" -import { vscode } from "../../utils/vscode" +import { vscode } from "@/utils/vscode" +import { + useOpenRouterModelProviders, + OPENROUTER_DEFAULT_PROVIDER_NAME, +} from "@/components/ui/hooks/useOpenRouterModelProviders" + import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" @@ -94,6 +100,7 @@ const ApiOptions = ({ setErrorMessage, }: ApiOptionsProps) => { const { t } = useAppTranslation() + const [ollamaModels, setOllamaModels] = useState([]) const [lmStudioModels, setLmStudioModels] = useState([]) const [vsCodeLmModels, setVsCodeLmModels] = useState([]) @@ -192,6 +199,13 @@ const ApiOptions = ({ setErrorMessage(apiValidationResult) }, [apiConfiguration, glamaModels, openRouterModels, setErrorMessage, unboundModels, requestyModels]) + const { data: openRouterModelProviders } = useOpenRouterModelProviders(apiConfiguration?.openRouterModelId, { + enabled: + selectedProvider === "openrouter" && + !!apiConfiguration?.openRouterModelId && + apiConfiguration.openRouterModelId in openRouterModels, + }) + const onMessage = useCallback((event: MessageEvent) => { const message: ExtensionMessage = event.data @@ -1365,6 +1379,52 @@ const ApiOptions = ({ /> )} + {openRouterModelProviders && ( + <> +
+
+ + + + +
+ { + const provider = typeof event == "string" ? event : event?.value + const providerModelInfo = provider ? openRouterModelProviders[provider] : undefined + + if (providerModelInfo) { + setApiConfigurationField("openRouterModelInfo", { + ...apiConfiguration.openRouterModelInfo, + ...providerModelInfo, + }) + } + + setApiConfigurationField("openRouterSpecificProvider", provider) + }} + options={[ + { value: OPENROUTER_DEFAULT_PROVIDER_NAME, label: OPENROUTER_DEFAULT_PROVIDER_NAME }, + ...Object.entries(openRouterModelProviders).map(([value, { label }]) => ({ + value, + label, + })), + ]} + className="w-full" + /> +
+
+ {t("settings:providers.openRouter.providerRouting.description")}{" "} + + {t("settings:providers.openRouter.providerRouting.learnMore")}. + +
+ + )} + {selectedProvider === "glama" && ( ({ ) : null, })) -describe("ApiOptions", () => { - const renderApiOptions = (props = {}) => { - render( - +const renderApiOptions = (props = {}) => { + const queryClient = new QueryClient() + + render( + + {}} @@ -97,10 +101,12 @@ describe("ApiOptions", () => { setApiConfigurationField={() => {}} {...props} /> - , - ) - } + + , + ) +} +describe("ApiOptions", () => { it("shows temperature control by default", () => { renderApiOptions() expect(screen.getByTestId("temperature-control")).toBeInTheDocument() diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx index 95b3bb8fa5..3add06154d 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx @@ -1,9 +1,12 @@ // npx jest src/components/settings/__tests__/SettingsView.test.ts import { render, screen, fireEvent } from "@testing-library/react" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +import { vscode } from "@/utils/vscode" +import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext" + import SettingsView from "../SettingsView" -import { ExtensionStateContextProvider } from "../../../context/ExtensionStateContext" -import { vscode } from "../../../utils/vscode" // Mock vscode API jest.mock("../../../utils/vscode", () => ({ @@ -124,13 +127,19 @@ const mockPostMessage = (state: any) => { const renderSettingsView = () => { const onDone = jest.fn() + const queryClient = new QueryClient() + render( - + + + , ) - // Hydrate initial state + + // Hydrate initial state. mockPostMessage({}) + return { onDone } } diff --git a/webview-ui/src/components/ui/hooks/useOpenRouterModelProviders.ts b/webview-ui/src/components/ui/hooks/useOpenRouterModelProviders.ts new file mode 100644 index 0000000000..b8111d5d35 --- /dev/null +++ b/webview-ui/src/components/ui/hooks/useOpenRouterModelProviders.ts @@ -0,0 +1,116 @@ +import axios from "axios" +import { z } from "zod" +import { useQuery, UseQueryOptions } from "@tanstack/react-query" + +import { ModelInfo } from "../../../../../src/shared/api" +import { parseApiPrice } from "../../../../../src/utils/cost" + +export const OPENROUTER_DEFAULT_PROVIDER_NAME = "[default]" + +const openRouterEndpointsSchema = z.object({ + data: z.object({ + id: z.string(), + name: z.string(), + description: z.string().optional(), + architecture: z + .object({ + modality: z.string().optional(), + tokenizer: z.string().optional(), + }) + .optional(), + endpoints: z.array( + z.object({ + name: z.string(), + context_length: z.number(), + max_completion_tokens: z.number().optional(), + pricing: z + .object({ + prompt: z.union([z.string(), z.number()]).optional(), + completion: z.union([z.string(), z.number()]).optional(), + }) + .optional(), + }), + ), + }), +}) + +type OpenRouterModelProvider = ModelInfo & { + label: string +} + +async function getOpenRouterProvidersForModel(modelId: string) { + const models: Record = {} + + try { + const response = await axios.get(`https://openrouter.ai/api/v1/models/${modelId}/endpoints`) + const result = openRouterEndpointsSchema.safeParse(response.data) + + if (!result.success) { + console.error("OpenRouter API response validation failed:", result.error) + return models + } + + const { id, description, architecture, endpoints } = result.data.data + + for (const endpoint of endpoints) { + const providerName = endpoint.name.split("|")[0].trim() + const inputPrice = parseApiPrice(endpoint.pricing?.prompt) + const outputPrice = parseApiPrice(endpoint.pricing?.completion) + + const modelInfo: OpenRouterModelProvider = { + maxTokens: endpoint.max_completion_tokens, + contextWindow: endpoint.context_length, + supportsImages: architecture?.modality?.includes("image"), + supportsPromptCache: false, + inputPrice, + outputPrice, + description, + thinking: modelId === "anthropic/claude-3.7-sonnet:thinking", + label: providerName, + } + + switch (true) { + case modelId.startsWith("anthropic/claude-3.7-sonnet"): + modelInfo.supportsComputerUse = true + modelInfo.supportsPromptCache = true + modelInfo.cacheWritesPrice = 3.75 + modelInfo.cacheReadsPrice = 0.3 + modelInfo.maxTokens = id === "anthropic/claude-3.7-sonnet:thinking" ? 64_000 : 16_384 + break + case modelId.startsWith("anthropic/claude-3.5-sonnet-20240620"): + modelInfo.supportsPromptCache = true + modelInfo.cacheWritesPrice = 3.75 + modelInfo.cacheReadsPrice = 0.3 + modelInfo.maxTokens = 8192 + break + default: + modelInfo.supportsPromptCache = true + modelInfo.cacheWritesPrice = 0.3 + modelInfo.cacheReadsPrice = 0.03 + break + } + + models[providerName] = modelInfo + } + } catch (error) { + if (error instanceof z.ZodError) { + console.error(`OpenRouter API response validation failed:`, error.errors) + } else { + console.error(`Error fetching OpenRouter providers:`, error) + } + } + + return models +} + +type UseOpenRouterModelProvidersOptions = Omit< + UseQueryOptions>, + "queryKey" | "queryFn" +> + +export const useOpenRouterModelProviders = (modelId?: string, options?: UseOpenRouterModelProvidersOptions) => + useQuery>({ + queryKey: ["openrouter-model-providers", modelId], + queryFn: () => (modelId ? getOpenRouterProvidersForModel(modelId) : {}), + ...options, + }) diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index 8ddd8d01c5..d93e69670e 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -152,6 +152,13 @@ "description": "No es requereix clau API, però l'usuari necessita ajuda per copiar i enganxar informació al xat d'IA web.", "instructions": "Durant l'ús, apareixerà un diàleg i el missatge actual es copiarà automàticament al porta-retalls. Necessiteu enganxar-lo a les versions web d'IA (com ChatGPT o Claude), després copiar la resposta de l'IA de nou al diàleg i fer clic al botó de confirmació." }, + "openRouter": { + "providerRouting": { + "title": "Encaminament de Proveïdors d'OpenRouter", + "description": "OpenRouter dirigeix les sol·licituds als millors proveïdors disponibles per al vostre model. Per defecte, les sol·licituds s'equilibren entre els principals proveïdors per maximitzar el temps de funcionament. No obstant això, podeu triar un proveïdor específic per utilitzar amb aquest model.", + "learnMore": "Més informació sobre l'encaminament de proveïdors" + } + }, "customModel": { "capabilities": "Configureu les capacitats i preus per al vostre model personalitzat compatible amb OpenAI. Tingueu cura en especificar les capacitats del model, ja que poden afectar com funciona Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8dc7633302..34b81c678a 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -152,6 +152,13 @@ "description": "Es ist kein API-Schlüssel erforderlich, aber der Benutzer muss beim Kopieren und Einfügen der Informationen in den Web-Chat-KI helfen.", "instructions": "Während der Verwendung wird ein Dialogfeld angezeigt und die aktuelle Nachricht wird automatisch in die Zwischenablage kopiert. Sie müssen diese in Web-Versionen von KI (wie ChatGPT oder Claude) einfügen, dann die Antwort der KI zurück in das Dialogfeld kopieren und auf die Bestätigungsschaltfläche klicken." }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter Anbieter-Routing", + "description": "OpenRouter leitet Anfragen an die besten verfügbaren Anbieter für Ihr Modell weiter. Standardmäßig werden Anfragen über die Top-Anbieter lastverteilt, um maximale Verfügbarkeit zu gewährleisten. Sie können jedoch einen bestimmten Anbieter für dieses Modell auswählen.", + "learnMore": "Mehr über Anbieter-Routing erfahren" + } + }, "customModel": { "capabilities": "Konfigurieren Sie die Fähigkeiten und Preise für Ihr benutzerdefiniertes OpenAI-kompatibles Modell. Seien Sie vorsichtig bei der Angabe der Modellfähigkeiten, da diese beeinflussen können, wie Roo Code funktioniert.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 3d3ae02a58..c73b144eec 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -152,6 +152,13 @@ "description": "No API key is required, but the user needs to help copy and paste the information to the web chat AI.", "instructions": "During use, a dialog box will pop up and the current message will be copied to the clipboard automatically. You need to paste these to web versions of AI (such as ChatGPT or Claude), then copy the AI's reply back to the dialog box and click the confirm button." }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter Provider Routing", + "description": "OpenRouter routes requests to the best available providers for your model. By default, requests are load balanced across the top providers to maximize uptime. However, you can choose a specific provider to use for this model.", + "learnMore": "Learn more about provider routing" + } + }, "customModel": { "capabilities": "Configure the capabilities and pricing for your custom OpenAI-compatible model. Be careful when specifying the model capabilities, as they can affect how Roo Code performs.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index e28b428921..91e2cdfdde 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -152,6 +152,13 @@ "description": "No se requiere clave API, pero el usuario necesita ayudar a copiar y pegar la información en el chat web de IA.", "instructions": "Durante el uso, aparecerá un cuadro de diálogo y el mensaje actual se copiará automáticamente al portapapeles. Debe pegarlo en las versiones web de IA (como ChatGPT o Claude), luego copiar la respuesta de la IA de vuelta al cuadro de diálogo y hacer clic en el botón de confirmar." }, + "openRouter": { + "providerRouting": { + "title": "Enrutamiento de Proveedores de OpenRouter", + "description": "OpenRouter dirige las solicitudes a los mejores proveedores disponibles para su modelo. Por defecto, las solicitudes se equilibran entre los principales proveedores para maximizar el tiempo de actividad. Sin embargo, puede elegir un proveedor específico para este modelo.", + "learnMore": "Más información sobre el enrutamiento de proveedores" + } + }, "customModel": { "capabilities": "Configure las capacidades y precios para su modelo personalizado compatible con OpenAI. Tenga cuidado al especificar las capacidades del modelo, ya que pueden afectar cómo funciona Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 07e4264677..cd95789781 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -152,6 +152,13 @@ "description": "Aucune clé API n'est requise, mais l'utilisateur doit aider à copier et coller les informations dans le chat web de l'IA.", "instructions": "Pendant l'utilisation, une boîte de dialogue apparaîtra et le message actuel sera automatiquement copié dans le presse-papiers. Vous devez le coller dans les versions web de l'IA (comme ChatGPT ou Claude), puis copier la réponse de l'IA dans la boîte de dialogue et cliquer sur le bouton de confirmation." }, + "openRouter": { + "providerRouting": { + "title": "Routage des fournisseurs OpenRouter", + "description": "OpenRouter dirige les requêtes vers les meilleurs fournisseurs disponibles pour votre modèle. Par défaut, les requêtes sont équilibrées entre les principaux fournisseurs pour maximiser la disponibilité. Cependant, vous pouvez choisir un fournisseur spécifique à utiliser pour ce modèle.", + "learnMore": "En savoir plus sur le routage des fournisseurs" + } + }, "customModel": { "capabilities": "Configurez les capacités et les prix pour votre modèle personnalisé compatible OpenAI. Soyez prudent lors de la spécification des capacités du modèle, car elles peuvent affecter le fonctionnement de Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 639628dd07..b05778af1d 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -152,6 +152,13 @@ "description": "कोई API कुंजी आवश्यक नहीं है, लेकिन उपयोगकर्ता को वेब चैट AI में जानकारी कॉपी और पेस्ट करने में मदद करनी होगी।", "instructions": "उपयोग के दौरान, एक डायलॉग बॉक्स पॉप अप होगा और वर्तमान संदेश स्वचालित रूप से क्लिपबोर्ड पर कॉपी हो जाएगा। आपको इन्हें AI के वेब संस्करणों (जैसे ChatGPT या Claude) में पेस्ट करना होगा, फिर AI की प्रतिक्रिया को डायलॉग बॉक्स में वापस कॉपी करें और पुष्टि बटन पर क्लिक करें।" }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter प्रदाता रूटिंग", + "description": "OpenRouter आपके मॉडल के लिए सर्वोत्तम उपलब्ध प्रदाताओं को अनुरोध भेजता है। डिफ़ॉल्ट रूप से, अपटाइम को अधिकतम करने के लिए अनुरोधों को शीर्ष प्रदाताओं के बीच संतुलित किया जाता है। हालांकि, आप इस मॉडल के लिए उपयोग करने के लिए एक विशिष्ट प्रदाता चुन सकते हैं।", + "learnMore": "प्रदाता रूटिंग के बारे में अधिक जानें" + } + }, "customModel": { "capabilities": "अपने कस्टम OpenAI-संगत मॉडल के लिए क्षमताओं और मूल्य निर्धारण को कॉन्फ़िगर करें। मॉडल क्षमताओं को निर्दिष्ट करते समय सावधान रहें, क्योंकि वे Roo Code के प्रदर्शन को प्रभावित कर सकती हैं।", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index d1d0ffb0ee..37b5b8583d 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -152,6 +152,13 @@ "description": "Non è richiesta alcuna chiave API, ma l'utente dovrà aiutare a copiare e incollare le informazioni nella chat web AI.", "instructions": "Durante l'uso, apparirà una finestra di dialogo e il messaggio corrente verrà automaticamente copiato negli appunti. Dovrai incollarlo nelle versioni web dell'AI (come ChatGPT o Claude), quindi copiare la risposta dell'AI nella finestra di dialogo e fare clic sul pulsante di conferma." }, + "openRouter": { + "providerRouting": { + "title": "Routing dei fornitori OpenRouter", + "description": "OpenRouter indirizza le richieste ai migliori fornitori disponibili per il tuo modello. Per impostazione predefinita, le richieste sono bilanciate tra i principali fornitori per massimizzare il tempo di attività. Tuttavia, puoi scegliere un fornitore specifico da utilizzare per questo modello.", + "learnMore": "Scopri di più sul routing dei fornitori" + } + }, "customModel": { "capabilities": "Configura le capacità e i prezzi del tuo modello personalizzato compatibile con OpenAI. Fai attenzione quando specifichi le capacità del modello, poiché possono influenzare le prestazioni di Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 17ce51c7e3..14e93195f3 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -152,6 +152,13 @@ "description": "APIキーは不要ですが、ユーザーはウェブチャットAIに情報をコピー&ペーストする必要があります。", "instructions": "使用中にダイアログボックスが表示され、現在のメッセージが自動的にクリップボードにコピーされます。これらをウェブ版のAI(ChatGPTやClaudeなど)に貼り付け、AIの返答をダイアログボックスにコピーして確認ボタンをクリックする必要があります。" }, + "openRouter": { + "providerRouting": { + "title": "OpenRouterプロバイダールーティング", + "description": "OpenRouterはあなたのモデルに最適な利用可能なプロバイダーにリクエストを転送します。デフォルトでは、稼働時間を最大化するために、リクエストはトッププロバイダー間でロードバランスされます。ただし、このモデルに使用する特定のプロバイダーを選択することもできます。", + "learnMore": "プロバイダールーティングについて詳しく知る" + } + }, "customModel": { "capabilities": "カスタムOpenAI互換モデルの機能と価格を設定します。モデルの機能はRoo Codeのパフォーマンスに影響を与える可能性があるため、慎重に指定してください。", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 7ea2082d18..369dd62a1a 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -152,6 +152,13 @@ "description": "API 키가 필요하지 않지만, 사용자가 웹 채팅 AI에 정보를 복사하여 붙여넣어야 합니다.", "instructions": "사용 중에 대화 상자가 나타나고 현재 메시지가 자동으로 클립보드에 복사됩니다. 이를 웹 버전 AI(예: ChatGPT 또는 Claude)에 붙여넣은 다음, AI의 응답을 대화 상자에 복사하고 확인 버튼을 클릭해야 합니다." }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter 제공자 라우팅", + "description": "OpenRouter는 귀하의 모델에 가장 적합한 사용 가능한 제공자에게 요청을 전달합니다. 기본적으로 요청은 가동 시간을 최대화하기 위해 상위 제공자 간에 부하 분산됩니다. 그러나 이 모델에 사용할 특정 제공자를 선택할 수 있습니다.", + "learnMore": "제공자 라우팅에 대해 자세히 알아보기" + } + }, "customModel": { "capabilities": "사용자 정의 OpenAI 호환 모델의 기능과 가격을 구성하세요. 모델 기능이 Roo Code의 성능에 영향을 미칠 수 있으므로 신중하게 지정하세요.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 3534915c98..22c7d41fec 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -152,6 +152,13 @@ "description": "Nie jest wymagany klucz API, ale użytkownik będzie musiał pomóc w kopiowaniu i wklejaniu informacji do czatu internetowego AI.", "instructions": "Podczas użytkowania pojawi się okno dialogowe, a bieżąca wiadomość zostanie automatycznie skopiowana do schowka. Będziesz musiał wkleić ją do internetowych wersji AI (takich jak ChatGPT lub Claude), a następnie skopiować odpowiedź AI z powrotem do okna dialogowego i kliknąć przycisk potwierdzenia." }, + "openRouter": { + "providerRouting": { + "title": "Routing dostawców OpenRouter", + "description": "OpenRouter kieruje żądania do najlepszych dostępnych dostawców dla Twojego modelu. Domyślnie żądania są równoważone między najlepszymi dostawcami, aby zmaksymalizować czas działania. Możesz jednak wybrać konkretnego dostawcę do użycia z tym modelem.", + "learnMore": "Dowiedz się więcej o routingu dostawców" + } + }, "customModel": { "capabilities": "Skonfiguruj możliwości i ceny swojego niestandardowego modelu zgodnego z OpenAI. Zachowaj ostrożność podczas określania możliwości modelu, ponieważ mogą one wpływać na wydajność Roo Code.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index 677e005e3f..14ec9ee480 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -152,6 +152,13 @@ "description": "Não é necessária chave de API, mas o usuário precisa ajudar a copiar e colar as informações para a IA do chat web.", "instructions": "Durante o uso, uma caixa de diálogo será exibida e a mensagem atual será copiada para a área de transferência automaticamente. Você precisa colar isso nas versões web de IA (como ChatGPT ou Claude), depois copiar a resposta da IA de volta para a caixa de diálogo e clicar no botão confirmar." }, + "openRouter": { + "providerRouting": { + "title": "Roteamento de Provedores OpenRouter", + "description": "OpenRouter direciona solicitações para os melhores provedores disponíveis para seu modelo. Por padrão, as solicitações são balanceadas entre os principais provedores para maximizar o tempo de atividade. No entanto, você pode escolher um provedor específico para usar com este modelo.", + "learnMore": "Saiba mais sobre roteamento de provedores" + } + }, "customModel": { "capabilities": "Configure as capacidades e preços para seu modelo personalizado compatível com OpenAI. Tenha cuidado ao especificar as capacidades do modelo, pois elas podem afetar como o Roo Code funciona.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index a1babd3156..9478b03e89 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -152,6 +152,13 @@ "description": "API anahtarı gerekmez, ancak kullanıcının bilgileri web sohbet yapay zekasına kopyalayıp yapıştırması gerekir.", "instructions": "Kullanım sırasında bir iletişim kutusu açılacak ve mevcut mesaj otomatik olarak panoya kopyalanacaktır. Bunları web yapay zekalarına (ChatGPT veya Claude gibi) yapıştırmanız, ardından yapay zekanın yanıtını iletişim kutusuna kopyalayıp onay düğmesine tıklamanız gerekir." }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter Sağlayıcı Yönlendirmesi", + "description": "OpenRouter, modeliniz için mevcut en iyi sağlayıcılara istekleri yönlendirir. Varsayılan olarak, istekler çalışma süresini en üst düzeye çıkarmak için en iyi sağlayıcılar arasında dengelenir. Ancak, bu model için kullanılacak belirli bir sağlayıcı seçebilirsiniz.", + "learnMore": "Sağlayıcı yönlendirmesi hakkında daha fazla bilgi edinin" + } + }, "customModel": { "capabilities": "Özel OpenAI uyumlu modelinizin yeteneklerini ve fiyatlandırmasını yapılandırın. Model yeteneklerini belirtirken dikkatli olun, çünkü bunlar Roo Code'un performansını etkileyebilir.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 5a56bbc7cc..103d3ace66 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -152,6 +152,13 @@ "description": "Không cần khóa API, nhưng người dùng cần giúp sao chép và dán thông tin vào AI trò chuyện web.", "instructions": "Trong quá trình sử dụng, một hộp thoại sẽ xuất hiện và tin nhắn hiện tại sẽ được tự động sao chép vào clipboard. Bạn cần dán chúng vào các phiên bản web của AI (như ChatGPT hoặc Claude), sau đó sao chép phản hồi của AI trở lại hộp thoại và nhấp vào nút xác nhận." }, + "openRouter": { + "providerRouting": { + "title": "Định tuyến nhà cung cấp OpenRouter", + "description": "OpenRouter chuyển hướng yêu cầu đến các nhà cung cấp tốt nhất hiện có cho mô hình của bạn. Theo mặc định, các yêu cầu được cân bằng giữa các nhà cung cấp hàng đầu để tối đa hóa thời gian hoạt động. Tuy nhiên, bạn có thể chọn một nhà cung cấp cụ thể để sử dụng cho mô hình này.", + "learnMore": "Tìm hiểu thêm về định tuyến nhà cung cấp" + } + }, "customModel": { "capabilities": "Cấu hình các khả năng và giá cả cho mô hình tương thích OpenAI tùy chỉnh của bạn. Hãy cẩn thận khi chỉ định khả năng của mô hình, vì chúng có thể ảnh hưởng đến cách Roo Code hoạt động.", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 85220108be..4bed550959 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -152,6 +152,13 @@ "description": "不需要 API 密钥,但用户需要帮助将信息复制并粘贴到网页聊天 AI。", "instructions": "使用期间,将弹出对话框并自动将当前消息复制到剪贴板。您需要将这些内容粘贴到 AI 的网页版本(如 ChatGPT 或 Claude),然后将 AI 的回复复制回对话框并点击确认按钮。" }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter 提供商路由", + "description": "OpenRouter 将请求路由到适合您模型的最佳可用提供商。默认情况下,请求会在顶级提供商之间进行负载均衡以最大化正常运行时间。但是,您可以为此模型选择特定的提供商。", + "learnMore": "了解更多关于提供商路由的信息" + } + }, "customModel": { "capabilities": "配置您的自定义 OpenAI 兼容模型的功能和定价。在指定模型功能时要小心,因为它们会影响 Roo Code 的性能。", "maxTokens": { diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index a46ff308a6..7fe534bf3d 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -152,6 +152,13 @@ "description": "不需要 API 金鑰,但使用者需要協助將資訊複製並貼上到網頁聊天 AI。", "instructions": "使用期間會彈出對話框,並自動將目前訊息複製到剪貼簿。您需要將這些內容貼上到網頁版 AI(如 ChatGPT 或 Claude),然後將 AI 的回覆複製回對話框並點擊確認按鈕。" }, + "openRouter": { + "providerRouting": { + "title": "OpenRouter 提供者路由", + "description": "OpenRouter 將請求路由到適合您模型的最佳可用提供者。預設情況下,請求會在頂級提供者之間進行負載平衡以最大化正常運行時間。但是,您可以為此模型選擇特定的提供者。", + "learnMore": "了解更多關於提供者路由的資訊" + } + }, "customModel": { "capabilities": "設定您的自訂 OpenAI 相容模型的功能和定價。請謹慎指定模型功能,因為它們會影響 Roo Code 的效能。", "maxTokens": {