feat: add custom URL option for Requesty provider

- Add requestyBaseUrl field to provider settings schema
- Update RequestyHandler to use custom base URL when provided
- Update fetcher functions to support custom base URL parameter
- Add UI checkbox and text field for custom URL configuration
- Update webview message handler to pass base URL to model fetcher
- Add tests for custom base URL functionality

Fixes #6983
This commit is contained in:
Roo Code 2025-08-12 11:09:35 +00:00
parent 12d1959bbd
commit f0aa301ee6
8 changed files with 63 additions and 7 deletions

View file

@ -227,6 +227,7 @@ const unboundSchema = baseProviderSettingsSchema.extend({
const requestySchema = baseProviderSettingsSchema.extend({
requestyApiKey: z.string().optional(),
requestyModelId: z.string().optional(),
requestyBaseUrl: z.string().optional(),
})
const humanRelaySchema = baseProviderSettingsSchema

View file

@ -65,6 +65,25 @@ describe("RequestyHandler", () => {
})
})
it("initializes with custom base URL when provided", () => {
const customOptions: ApiHandlerOptions = {
...mockOptions,
requestyBaseUrl: "https://custom.requesty.ai/v1",
}
const handler = new RequestyHandler(customOptions)
expect(handler).toBeInstanceOf(RequestyHandler)
expect(OpenAI).toHaveBeenCalledWith({
baseURL: "https://custom.requesty.ai/v1",
apiKey: customOptions.requestyApiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline",
"X-Title": "Roo Code",
"User-Agent": `RooCode/${Package.version}`,
},
})
})
describe("fetchModel", () => {
it("returns correct model info when options are provided", async () => {
const handler = new RequestyHandler(mockOptions)

View file

@ -59,7 +59,7 @@ export const getModels = async (options: GetModelsOptions): Promise<ModelRecord>
break
case "requesty":
// Requesty models endpoint requires an API key for per-user custom policies
models = await getRequestyModels(options.apiKey)
models = await getRequestyModels(options.apiKey, options.baseUrl)
break
case "glama":
models = await getGlamaModels()

View file

@ -4,7 +4,7 @@ import type { ModelInfo } from "@roo-code/types"
import { parseApiPrice } from "../../../shared/cost"
export async function getRequestyModels(apiKey?: string): Promise<Record<string, ModelInfo>> {
export async function getRequestyModels(apiKey?: string, baseUrl?: string): Promise<Record<string, ModelInfo>> {
const models: Record<string, ModelInfo> = {}
try {
@ -14,7 +14,8 @@ export async function getRequestyModels(apiKey?: string): Promise<Record<string,
headers["Authorization"] = `Bearer ${apiKey}`
}
const url = "https://router.requesty.ai/v1/models"
const apiBaseUrl = baseUrl || "https://router.requesty.ai/v1"
const url = `${apiBaseUrl}/models`
const response = await axios.get(url, { headers })
const rawModels = response.data.data

View file

@ -47,14 +47,18 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
this.options = options
this.client = new OpenAI({
baseURL: "https://router.requesty.ai/v1",
baseURL: this.options.requestyBaseUrl || "https://router.requesty.ai/v1",
apiKey: this.options.requestyApiKey ?? "not-provided",
defaultHeaders: DEFAULT_HEADERS,
})
}
public async fetchModel() {
this.models = await getModels({ provider: "requesty" })
this.models = await getModels({
provider: "requesty",
apiKey: this.options.requestyApiKey,
baseUrl: this.options.requestyBaseUrl,
})
return this.getModel()
}

View file

@ -543,7 +543,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 } },
]

View file

@ -135,7 +135,7 @@ export const getModelMaxOutputTokens = ({
export type GetModelsOptions =
| { provider: "openrouter" }
| { provider: "glama" }
| { provider: "requesty"; apiKey?: string }
| { provider: "requesty"; apiKey?: string; baseUrl?: string }
| { provider: "unbound"; apiKey?: string }
| { provider: "litellm"; apiKey: string; baseUrl: string }
| { provider: "ollama"; baseUrl?: string }

View file

@ -1,4 +1,5 @@
import { useCallback, useState } from "react"
import { Checkbox } from "vscrui"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { type ProviderSettings, type OrganizationAllowList, requestyDefaultModelId } from "@roo-code/types"
@ -34,6 +35,7 @@ export const Requesty = ({
const { t } = useAppTranslation()
const [didRefetch, setDidRefetch] = useState<boolean>()
const [requestyBaseUrlSelected, setRequestyBaseUrlSelected] = useState(!!apiConfiguration?.requestyBaseUrl)
const handleInputChange = useCallback(
<K extends keyof ProviderSettings, E>(
@ -72,6 +74,28 @@ export const Requesty = ({
{t("settings:providers.getRequestyApiKey")}
</VSCodeButtonLink>
)}
<div>
<Checkbox
checked={requestyBaseUrlSelected}
onChange={(checked: boolean) => {
setRequestyBaseUrlSelected(checked)
if (!checked) {
setApiConfigurationField("requestyBaseUrl", "")
}
}}>
{t("settings:providers.useCustomBaseUrl")}
</Checkbox>
{requestyBaseUrlSelected && (
<VSCodeTextField
value={apiConfiguration?.requestyBaseUrl || ""}
type="url"
onInput={handleInputChange("requestyBaseUrl")}
placeholder="Default: https://router.requesty.ai/v1"
className="w-full mt-1"
/>
)}
</div>
<Button
variant="outline"
onClick={() => {