From 5e812cfb4c82301af7524618d9f718c324485739 Mon Sep 17 00:00:00 2001 From: John Costa Date: Fri, 15 Aug 2025 10:49:45 +0100 Subject: [PATCH 1/4] fix: changing base URL for fetching models --- src/api/providers/fetchers/modelCache.ts | 2 +- src/api/providers/fetchers/requesty.ts | 9 ++++++--- src/api/providers/requesty.ts | 7 +++++-- src/shared/api.ts | 2 +- src/shared/utils/requesty.ts | 17 +++++++++++++++++ .../providers/RequestyBalanceDisplay.tsx | 15 ++++++++++++--- .../components/ui/hooks/useRequestyKeyInfo.ts | 12 ++++++++---- 7 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 src/shared/utils/requesty.ts diff --git a/src/api/providers/fetchers/modelCache.ts b/src/api/providers/fetchers/modelCache.ts index a21e75ded9..f4c240a61c 100644 --- a/src/api/providers/fetchers/modelCache.ts +++ b/src/api/providers/fetchers/modelCache.ts @@ -59,7 +59,7 @@ export const getModels = async (options: GetModelsOptions): Promise break case "requesty": // Requesty models endpoint requires an API key for per-user custom policies - models = await getRequestyModels(options.apiKey) + models = await getRequestyModels(options.baseUrl, options.apiKey) break case "glama": models = await getGlamaModels() diff --git a/src/api/providers/fetchers/requesty.ts b/src/api/providers/fetchers/requesty.ts index e339dae1aa..308539f2e4 100644 --- a/src/api/providers/fetchers/requesty.ts +++ b/src/api/providers/fetchers/requesty.ts @@ -3,8 +3,9 @@ import axios from "axios" import type { ModelInfo } from "@roo-code/types" import { parseApiPrice } from "../../../shared/cost" +import { toRequestyServiceUrl } from "../../../shared/utils/requesty" -export async function getRequestyModels(apiKey?: string): Promise> { +export async function getRequestyModels(baseUrl?: string, apiKey?: string): Promise> { const models: Record = {} try { @@ -14,8 +15,10 @@ export async function getRequestyModels(apiKey?: string): Promise { + if (type === "router") { + return baseUrl + } else { + return baseUrl.replace("router", type).replace("v1", "") + } +} + +export const toRequestyServiceUrl = (baseUrl?: string, service: URLType = "router"): string => { + let url = replaceCname(baseUrl ?? REQUESTY_BASE_URL, service) + + return new URL(url).toString() +} diff --git a/webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx b/webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx index 9eb9734499..8c77c35ed6 100644 --- a/webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx +++ b/webview-ui/src/components/settings/providers/RequestyBalanceDisplay.tsx @@ -1,9 +1,15 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { useRequestyKeyInfo } from "@/components/ui/hooks/useRequestyKeyInfo" +import { toRequestyServiceUrl } from "@roo/utils/requesty" -export const RequestyBalanceDisplay = ({ apiKey }: { apiKey: string }) => { - const { data: keyInfo } = useRequestyKeyInfo(apiKey) +type RequestyBalanceDisplayProps = { + apiKey: string + baseUrl?: string +} + +export const RequestyBalanceDisplay = ({ baseUrl, apiKey }: RequestyBalanceDisplayProps) => { + const { data: keyInfo } = useRequestyKeyInfo(baseUrl, apiKey) if (!keyInfo) { return null @@ -13,8 +19,11 @@ export const RequestyBalanceDisplay = ({ apiKey }: { apiKey: string }) => { const balance = parseFloat(keyInfo.org_balance) const formattedBalance = balance.toFixed(2) + const resolvedBaseUrl = toRequestyServiceUrl(baseUrl, "app") + const settingsUrl = new URL("settings", resolvedBaseUrl) + return ( - + ${formattedBalance} ) diff --git a/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts b/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts index e44aa5fcd1..8505157a0f 100644 --- a/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts +++ b/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts @@ -1,6 +1,7 @@ import axios from "axios" import { z } from "zod" import { useQuery, UseQueryOptions } from "@tanstack/react-query" +import { toRequestyServiceUrl } from "@roo/utils/requesty" const requestyKeyInfoSchema = z.object({ name: z.string(), @@ -14,11 +15,14 @@ const requestyKeyInfoSchema = z.object({ export type RequestyKeyInfo = z.infer -async function getRequestyKeyInfo(apiKey?: string) { +async function getRequestyKeyInfo(baseUrl?: string, apiKey?: string) { if (!apiKey) return null + const url = toRequestyServiceUrl(baseUrl, "api") + const apiKeyUrl = new URL("x/apikey", url) + try { - const response = await axios.get("https://api.requesty.ai/x/apikey", { + const response = await axios.get(apiKeyUrl.toString(), { headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", @@ -39,10 +43,10 @@ async function getRequestyKeyInfo(apiKey?: string) { } type UseRequestyKeyInfoOptions = Omit, "queryKey" | "queryFn"> -export const useRequestyKeyInfo = (apiKey?: string, options?: UseRequestyKeyInfoOptions) => { +export const useRequestyKeyInfo = (baseUrl?: string, apiKey?: string, options?: UseRequestyKeyInfoOptions) => { return useQuery({ queryKey: ["requesty-key-info", apiKey], - queryFn: () => getRequestyKeyInfo(apiKey), + queryFn: () => getRequestyKeyInfo(baseUrl, apiKey), staleTime: 30 * 1000, // 30 seconds enabled: !!apiKey, ...options, From f0a5bd68da622cb278fa354d53e0e16660d6aa42 Mon Sep 17 00:00:00 2001 From: Thibault Jaigu Date: Tue, 19 Aug 2025 11:32:17 +0100 Subject: [PATCH 2/4] added configurations for base url api keys fixed URL construction for models and balance endpoints --- src/core/webview/webviewMessageHandler.ts | 9 ++++- src/shared/utils/requesty.ts | 8 ++++- .../src/components/settings/ApiOptions.tsx | 1 + .../settings/providers/Requesty.tsx | 36 +++++++++++++++---- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index d16ed06132..e302108b67 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -548,7 +548,14 @@ export const webviewMessageHandler = async ( const modelFetchPromises: Array<{ key: RouterName; options: GetModelsOptions }> = [ { key: "openrouter", options: { provider: "openrouter" } }, - { key: "requesty", options: { provider: "requesty", apiKey: apiConfiguration.requestyApiKey } }, + { + key: "requesty", + options: { + provider: "requesty", + apiKey: apiConfiguration.requestyApiKey, + baseUrl: apiConfiguration.requestyBaseUrl, + }, + }, { key: "glama", options: { provider: "glama" } }, { key: "unbound", options: { provider: "unbound", apiKey: apiConfiguration.unboundApiKey } }, ] diff --git a/src/shared/utils/requesty.ts b/src/shared/utils/requesty.ts index 6b7b4c8788..78fcfc4e87 100644 --- a/src/shared/utils/requesty.ts +++ b/src/shared/utils/requesty.ts @@ -13,5 +13,11 @@ const replaceCname = (baseUrl: string, type: URLType): string => { export const toRequestyServiceUrl = (baseUrl?: string, service: URLType = "router"): string => { let url = replaceCname(baseUrl ?? REQUESTY_BASE_URL, service) - return new URL(url).toString() + try { + return new URL(url).toString() + } catch (error) { + // If the provided baseUrl is invalid, fall back to the default + console.warn(`Invalid base URL "${baseUrl}", falling back to default`) + return new URL(replaceCname(REQUESTY_BASE_URL, service)).toString() + } } diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index b51b171354..fbb729f9d2 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -419,6 +419,7 @@ const ApiOptions = ({ {selectedProvider === "requesty" && ( void organizationAllowList: OrganizationAllowList modelValidationError?: string + uriScheme?: string } export const Requesty = ({ @@ -31,6 +33,7 @@ export const Requesty = ({ refetchRouterModels, organizationAllowList, modelValidationError, + uriScheme, }: RequestyProps) => { const { t } = useAppTranslation() @@ -54,6 +57,15 @@ export const Requesty = ({ [setApiConfigurationField], ) + const getApiKeyUrl = () => { + const callbackUrl = getCallbackUrl("requesty", uriScheme) + const baseUrl = toRequestyServiceUrl(apiConfiguration.requestyBaseUrl, "app") + + const authUrl = new URL(`oauth/authorize?callback_url=${callbackUrl}`, baseUrl) + + return authUrl.toString() + } + return ( <> {apiConfiguration?.requestyApiKey && ( - + )} @@ -73,12 +88,19 @@ export const Requesty = ({ {t("settings:providers.apiKeyStorageNotice")} {!apiConfiguration?.requestyApiKey && ( - + {t("settings:providers.getRequestyApiKey")} - + )} Date: Thu, 21 Aug 2025 14:07:30 +0100 Subject: [PATCH 3/4] Update webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts b/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts index 8505157a0f..dd61c17eee 100644 --- a/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts +++ b/webview-ui/src/components/ui/hooks/useRequestyKeyInfo.ts @@ -45,7 +45,7 @@ async function getRequestyKeyInfo(baseUrl?: string, apiKey?: string) { type UseRequestyKeyInfoOptions = Omit, "queryKey" | "queryFn"> export const useRequestyKeyInfo = (baseUrl?: string, apiKey?: string, options?: UseRequestyKeyInfoOptions) => { return useQuery({ - queryKey: ["requesty-key-info", apiKey], + queryKey: ["requesty-key-info", baseUrl, apiKey], queryFn: () => getRequestyKeyInfo(baseUrl, apiKey), staleTime: 30 * 1000, // 30 seconds enabled: !!apiKey, From 64ac30d0abaa9771749e96f0f32b46d8eb70e85d Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 22 Aug 2025 19:16:27 +0000 Subject: [PATCH 4/4] fix: update tests to match new baseUrl parameter order in getRequestyModels --- src/api/providers/__tests__/requesty.spec.ts | 4 ++-- src/api/providers/fetchers/__tests__/modelCache.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/providers/__tests__/requesty.spec.ts b/src/api/providers/__tests__/requesty.spec.ts index 72d4aa790b..4d5037ed9e 100644 --- a/src/api/providers/__tests__/requesty.spec.ts +++ b/src/api/providers/__tests__/requesty.spec.ts @@ -66,11 +66,11 @@ describe("RequestyHandler", () => { }) it("can use a base URL instead of the default", () => { - const handler = new RequestyHandler({ ...mockOptions, requestyBaseUrl: "some-base-url" }) + const handler = new RequestyHandler({ ...mockOptions, requestyBaseUrl: "https://custom.requesty.ai/v1" }) expect(handler).toBeInstanceOf(RequestyHandler) expect(OpenAI).toHaveBeenCalledWith({ - baseURL: "some-base-url", + baseURL: "https://custom.requesty.ai/v1", apiKey: mockOptions.requestyApiKey, defaultHeaders: { "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", diff --git a/src/api/providers/fetchers/__tests__/modelCache.spec.ts b/src/api/providers/fetchers/__tests__/modelCache.spec.ts index 21f5ce8bff..2a72ef1cc5 100644 --- a/src/api/providers/fetchers/__tests__/modelCache.spec.ts +++ b/src/api/providers/fetchers/__tests__/modelCache.spec.ts @@ -103,7 +103,7 @@ describe("getModels with new GetModelsOptions", () => { const result = await getModels({ provider: "requesty", apiKey: DUMMY_REQUESTY_KEY }) - expect(mockGetRequestyModels).toHaveBeenCalledWith(DUMMY_REQUESTY_KEY) + expect(mockGetRequestyModels).toHaveBeenCalledWith(undefined, DUMMY_REQUESTY_KEY) expect(result).toEqual(mockModels) })