Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/api/providers/__tests__/requesty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,25 @@ describe("RequestyHandler", () => {
})
})

it("initializes with custom base URL when provided", () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good test coverage for the custom base URL! Consider adding edge case tests for:

  • URL with trailing slash
  • Invalid URL format
  • Empty string handling when checkbox is unchecked

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)
Expand Down
2 changes: 1 addition & 1 deletion src/api/providers/fetchers/modelCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions src/api/providers/fetchers/requesty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider normalizing the URL to handle trailing slashes:
/models
This prevents potential double slashes if the base URL ends with a slash.

const response = await axios.get(url, { headers })
const rawModels = response.data.data

Expand Down
8 changes: 6 additions & 2 deletions src/api/providers/requesty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
9 changes: 8 additions & 1 deletion src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } },
]
Expand Down
2 changes: 1 addition & 1 deletion src/shared/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
24 changes: 24 additions & 0 deletions webview-ui/src/components/settings/providers/Requesty.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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>(
Expand Down Expand Up @@ -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")}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? The checkbox label says "Use custom base URL" but it could be more specific like "Use custom Requesty base URL" to match the field naming convention . This would provide better clarity when multiple providers are configured.

</Checkbox>
{requestyBaseUrlSelected && (
<VSCodeTextField
value={apiConfiguration?.requestyBaseUrl || ""}
type="url"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding URL validation before saving. While provides basic browser validation, you might want to ensure the URL is properly formatted (e.g., starts with http:// or https://) before allowing it to be saved.

onInput={handleInputChange("requestyBaseUrl")}
placeholder="Default: https://router.requesty.ai/v1"
className="w-full mt-1"
/>
)}
</div>
<Button
variant="outline"
onClick={() => {
Expand Down
Loading