|
| 1 | +import { RooModelsResponseSchema } from "@roo-code/types" |
| 2 | + |
| 3 | +import type { ModelRecord } from "../../../shared/api" |
| 4 | + |
| 5 | +import { DEFAULT_HEADERS } from "../constants" |
| 6 | + |
| 7 | +/** |
| 8 | + * Fetches available models from the Roo Code Cloud provider |
| 9 | + * |
| 10 | + * @param baseUrl The base URL of the Roo Code Cloud provider |
| 11 | + * @param apiKey The API key (session token) for the Roo Code Cloud provider |
| 12 | + * @returns A promise that resolves to a record of model IDs to model info |
| 13 | + * @throws Will throw an error if the request fails or the response is not as expected. |
| 14 | + */ |
| 15 | +export async function getRooModels(baseUrl: string, apiKey?: string): Promise<ModelRecord> { |
| 16 | + try { |
| 17 | + const headers: Record<string, string> = { |
| 18 | + "Content-Type": "application/json", |
| 19 | + ...DEFAULT_HEADERS, |
| 20 | + } |
| 21 | + |
| 22 | + if (apiKey) { |
| 23 | + headers["Authorization"] = `Bearer ${apiKey}` |
| 24 | + } |
| 25 | + |
| 26 | + // Construct the models endpoint URL |
| 27 | + // Strip trailing /v1 or /v1/ to avoid /v1/v1/models |
| 28 | + const normalizedBase = baseUrl.replace(/\/?v1\/?$/, "") |
| 29 | + const url = `${normalizedBase}/v1/models` |
| 30 | + |
| 31 | + // Use fetch with AbortController for better timeout handling |
| 32 | + const controller = new AbortController() |
| 33 | + const timeoutId = setTimeout(() => controller.abort(), 10000) |
| 34 | + |
| 35 | + try { |
| 36 | + const response = await fetch(url, { |
| 37 | + headers, |
| 38 | + signal: controller.signal, |
| 39 | + }) |
| 40 | + |
| 41 | + if (!response.ok) { |
| 42 | + throw new Error(`HTTP ${response.status}: ${response.statusText}`) |
| 43 | + } |
| 44 | + |
| 45 | + const data = await response.json() |
| 46 | + const models: ModelRecord = {} |
| 47 | + |
| 48 | + // Validate response against schema |
| 49 | + const parsed = RooModelsResponseSchema.safeParse(data) |
| 50 | + |
| 51 | + if (!parsed.success) { |
| 52 | + console.error("Error fetching Roo Code Cloud models: Unexpected response format", data) |
| 53 | + console.error("Validation errors:", parsed.error.format()) |
| 54 | + throw new Error("Failed to fetch Roo Code Cloud models: Unexpected response format.") |
| 55 | + } |
| 56 | + |
| 57 | + // Process the validated model data |
| 58 | + for (const model of parsed.data.data) { |
| 59 | + const modelId = model.id |
| 60 | + |
| 61 | + if (!modelId) continue |
| 62 | + |
| 63 | + // Extract model data from the validated API response |
| 64 | + // All required fields are guaranteed by the schema |
| 65 | + const contextWindow = model.context_window |
| 66 | + const maxTokens = model.max_tokens |
| 67 | + const tags = model.tags || [] |
| 68 | + const pricing = model.pricing |
| 69 | + |
| 70 | + // Determine if the model supports images based on tags |
| 71 | + const supportsImages = tags.includes("vision") |
| 72 | + |
| 73 | + // Parse pricing (API returns strings, convert to numbers) |
| 74 | + const inputPrice = parseFloat(pricing.input) |
| 75 | + const outputPrice = parseFloat(pricing.output) |
| 76 | + const cacheReadPrice = pricing.input_cache_read ? parseFloat(pricing.input_cache_read) : undefined |
| 77 | + const cacheWritePrice = pricing.input_cache_write ? parseFloat(pricing.input_cache_write) : undefined |
| 78 | + |
| 79 | + models[modelId] = { |
| 80 | + maxTokens, |
| 81 | + contextWindow, |
| 82 | + supportsImages, |
| 83 | + supportsPromptCache: Boolean(cacheReadPrice !== undefined), |
| 84 | + inputPrice, |
| 85 | + outputPrice, |
| 86 | + cacheWritesPrice: cacheWritePrice, |
| 87 | + cacheReadsPrice: cacheReadPrice, |
| 88 | + description: model.description || model.name, |
| 89 | + deprecated: model.deprecated || false, |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + return models |
| 94 | + } finally { |
| 95 | + clearTimeout(timeoutId) |
| 96 | + } |
| 97 | + } catch (error: any) { |
| 98 | + console.error("Error fetching Roo Code Cloud models:", error.message ? error.message : error) |
| 99 | + |
| 100 | + // Handle abort/timeout |
| 101 | + if (error.name === "AbortError") { |
| 102 | + throw new Error("Failed to fetch Roo Code Cloud models: Request timed out after 10 seconds.") |
| 103 | + } |
| 104 | + |
| 105 | + // Handle fetch errors |
| 106 | + if (error.message?.includes("HTTP")) { |
| 107 | + throw new Error(`Failed to fetch Roo Code Cloud models: ${error.message}. Check base URL and API key.`) |
| 108 | + } |
| 109 | + |
| 110 | + // Handle network errors |
| 111 | + if (error instanceof TypeError) { |
| 112 | + throw new Error( |
| 113 | + "Failed to fetch Roo Code Cloud models: No response from server. Check Roo Code Cloud server status and base URL.", |
| 114 | + ) |
| 115 | + } |
| 116 | + |
| 117 | + throw new Error(`Failed to fetch Roo Code Cloud models: ${error.message || "An unknown error occurred."}`) |
| 118 | + } |
| 119 | +} |
0 commit comments