Skip to content
Open
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
867 changes: 842 additions & 25 deletions src/api/providers/__tests__/requesty.spec.ts

Large diffs are not rendered by default.

30 changes: 28 additions & 2 deletions src/api/providers/fetchers/__tests__/modelCache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,29 @@ describe("getModels with new GetModelsOptions", () => {

const result = await getModels({ provider: providerIdentifiers.requesty, apiKey: DUMMY_REQUESTY_KEY })

expect(mockGetRequestyModels).toHaveBeenCalledWith(undefined, DUMMY_REQUESTY_KEY)
expect(mockGetRequestyModels).toHaveBeenCalledWith(undefined, DUMMY_REQUESTY_KEY, undefined)
expect(result).toEqual(mockModels)
})

it("forwards the caller's cancellation signal to Requesty model discovery", async () => {
const mockModels = {
"requesty/model": {
maxTokens: 4096,
contextWindow: 8192,
supportsPromptCache: false,
description: "Requesty model",
},
}
mockGetRequestyModels.mockResolvedValue(mockModels)

const controller = new AbortController()
const result = await getModels({
provider: providerIdentifiers.requesty,
apiKey: DUMMY_REQUESTY_KEY,
signal: controller.signal,
})

expect(mockGetRequestyModels).toHaveBeenCalledWith(undefined, DUMMY_REQUESTY_KEY, controller.signal)
expect(result).toEqual(mockModels)
})

Expand All @@ -179,7 +201,11 @@ describe("getModels with new GetModelsOptions", () => {
baseUrl: "https://router.requesty.ai/v1",
})

expect(mockGetRequestyModels).toHaveBeenCalledWith("https://router.requesty.ai/v1", DUMMY_REQUESTY_KEY)
expect(mockGetRequestyModels).toHaveBeenCalledWith(
"https://router.requesty.ai/v1",
DUMMY_REQUESTY_KEY,
undefined,
)
expect(result).toEqual(mockModels)
})

Expand Down
25 changes: 25 additions & 0 deletions src/api/providers/fetchers/__tests__/requesty.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,29 @@ describe("getRequestyModels", () => {
expect(sonnet.supportsReasoningBinary).toBeUndefined()
expect(sonnet.supportsTemperature).toBeUndefined()
})

it("threads the cancellation signal and the bounded timeout into the models request", async () => {
const controller = new AbortController()
mockAxiosGet.mockResolvedValueOnce({ data: { data: [] } })

await getRequestyModels(undefined, undefined, controller.signal)

// The shared axios mock accumulates calls across this file's tests, so assert on
// the call this test just made (the last one) rather than a global call count.
const calls = mockAxiosGet.mock.calls
const config = calls[calls.length - 1]?.[1]
expect(config?.signal).toBe(controller.signal)
expect(config?.timeout).toBe(10_000)
})

it("applies the bounded timeout without a signal key when no signal is provided", async () => {
mockAxiosGet.mockResolvedValueOnce({ data: { data: [] } })

await getRequestyModels()

const calls = mockAxiosGet.mock.calls
const config = calls[calls.length - 1]?.[1]
expect(config?.signal).toBeUndefined()
expect(config?.timeout).toBe(10_000)
})
})
5 changes: 4 additions & 1 deletion src/api/providers/fetchers/modelCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ async function fetchModelsFromProvider(options: GetModelsOptions): Promise<Model
break
case providerIdentifiers.requesty:
// Requesty models endpoint requires an API key for per-user custom policies.
models = await getRequestyModels(options.baseUrl, options.apiKey)
// The caller's cancellation signal (when provided) threads into the fetcher so an
// aborted request cancels the in-flight models lookup instead of letting it run
// unbounded.
models = await getRequestyModels(options.baseUrl, options.apiKey, options.signal)
break
case providerIdentifiers.unbound:
models = await getUnboundModels(options.apiKey)
Expand Down
17 changes: 15 additions & 2 deletions src/api/providers/fetchers/requesty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@ import type { ModelInfo } from "@roo-code/types"
import { parseApiPrice } from "../../../shared/cost"
import { toRequestyServiceUrl } from "../../../shared/utils/requesty"

export async function getRequestyModels(baseUrl?: string, apiKey?: string): Promise<Record<string, ModelInfo>> {
// Bounded wall-clock limit for the models discovery request. Without it a hung connection
// would keep the request alive indefinitely; 10_000 ms matches the other in-tree axios
// fetchers (kenari, opencode-go, nanogpt).
const REQUESTY_MODELS_TIMEOUT_MS = 10_000

export async function getRequestyModels(
baseUrl?: string,
apiKey?: string,
signal?: AbortSignal,
): Promise<Record<string, ModelInfo>> {
const models: Record<string, ModelInfo> = {}

try {
Expand All @@ -18,7 +27,11 @@ export async function getRequestyModels(baseUrl?: string, apiKey?: string): Prom
const resolvedBaseUrl = toRequestyServiceUrl(baseUrl)
const modelsUrl = new URL("v1/models", resolvedBaseUrl)

const response = await axios.get(modelsUrl.toString(), { headers })
const response = await axios.get(modelsUrl.toString(), {
headers,
...(signal && { signal }),
timeout: REQUESTY_MODELS_TIMEOUT_MS,
})
const rawModels = response.data.data

for (const rawModel of rawModels) {
Expand Down
Loading
Loading