-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: add dynamic model loading for Roo Code Cloud provider #8728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
4444328
626d97b
382764c
eeabc50
dbdc8e4
b2c02de
b3054a6
26978df
dfecaa0
18aeb4f
d442484
60e900c
b941b65
e78aace
6a1c35e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,53 +1,49 @@ | ||
| import { z } from "zod" | ||
|
|
||
| import type { ModelInfo } from "../model.js" | ||
|
|
||
| export type RooModelId = | ||
| | "xai/grok-code-fast-1" | ||
| | "roo/code-supernova-1-million" | ||
| | "xai/grok-4-fast" | ||
| | "deepseek/deepseek-chat-v3.1" | ||
|
|
||
| export const rooDefaultModelId: RooModelId = "xai/grok-code-fast-1" | ||
|
|
||
| export const rooModels = { | ||
| "xai/grok-code-fast-1": { | ||
| maxTokens: 16_384, | ||
| contextWindow: 262_144, | ||
| supportsImages: false, | ||
| supportsPromptCache: true, | ||
| inputPrice: 0, | ||
| outputPrice: 0, | ||
| description: | ||
| "A reasoning model that is blazing fast and excels at agentic coding, accessible for free through Roo Code Cloud for a limited time. (Note: the free prompts and completions are logged by xAI and used to improve the model.)", | ||
| }, | ||
| "roo/code-supernova-1-million": { | ||
| maxTokens: 30_000, | ||
| contextWindow: 1_000_000, | ||
| supportsImages: true, | ||
| supportsPromptCache: true, | ||
| inputPrice: 0, | ||
| outputPrice: 0, | ||
| description: | ||
| "A versatile agentic coding stealth model with a 1M token context window that supports image inputs, accessible for free through Roo Code Cloud for a limited time. (Note: the free prompts and completions are logged by the model provider and used to improve the model.)", | ||
| }, | ||
| "xai/grok-4-fast": { | ||
| maxTokens: 30_000, | ||
| contextWindow: 2_000_000, | ||
| supportsImages: false, | ||
| supportsPromptCache: false, | ||
| inputPrice: 0, | ||
| outputPrice: 0, | ||
| description: | ||
| "Grok 4 Fast is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. (Note: prompts and completions are logged by xAI and used to improve the model.)", | ||
| deprecated: true, | ||
| }, | ||
| "deepseek/deepseek-chat-v3.1": { | ||
| maxTokens: 16_384, | ||
| contextWindow: 163_840, | ||
| supportsImages: false, | ||
| supportsPromptCache: false, | ||
| inputPrice: 0, | ||
| outputPrice: 0, | ||
| description: | ||
| "DeepSeek-V3.1 is a large hybrid reasoning model (671B parameters, 37B active). It extends the DeepSeek-V3 base with a two-phase long-context training process, reaching up to 128K tokens, and uses FP8 microscaling for efficient inference.", | ||
| }, | ||
| } as const satisfies Record<string, ModelInfo> | ||
| /** | ||
| * Roo Code Cloud is a dynamic provider - models are loaded from the /v1/models API endpoint. | ||
| * Default model ID used as fallback when no model is specified. | ||
| */ | ||
| export const rooDefaultModelId = "xai/grok-code-fast-1" | ||
|
|
||
| /** | ||
| * Empty models object maintained for type compatibility. | ||
| * All model data comes dynamically from the API. | ||
| */ | ||
| export const rooModels = {} as const satisfies Record<string, ModelInfo> | ||
|
|
||
| /** | ||
| * Roo Code Cloud API response schemas | ||
| */ | ||
|
|
||
| export const RooPricingSchema = z.object({ | ||
| input: z.string(), | ||
| output: z.string(), | ||
| input_cache_read: z.string().optional(), | ||
| input_cache_write: z.string().optional(), | ||
| }) | ||
|
|
||
| export const RooModelSchema = z.object({ | ||
| id: z.string(), | ||
| object: z.literal("model"), | ||
| created: z.number(), | ||
| owned_by: z.string(), | ||
| name: z.string(), | ||
| description: z.string(), | ||
| context_window: z.number(), | ||
| max_tokens: z.number(), | ||
| type: z.literal("language"), | ||
| tags: z.array(z.string()).optional(), | ||
| pricing: RooPricingSchema, | ||
| deprecated: z.boolean().optional(), | ||
| }) | ||
|
|
||
| export const RooModelsResponseSchema = z.object({ | ||
| object: z.literal("list"), | ||
| data: z.array(RooModelSchema), | ||
| }) | ||
|
|
||
| export type RooModel = z.infer<typeof RooModelSchema> | ||
| export type RooModelsResponse = z.infer<typeof RooModelsResponseSchema> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import axios from "axios" | ||
|
|
||
| import { RooModelsResponseSchema } from "@roo-code/types" | ||
|
|
||
| import type { ModelRecord } from "../../../shared/api" | ||
|
|
||
| import { DEFAULT_HEADERS } from "../constants" | ||
|
|
||
| /** | ||
| * Fetches available models from the Roo Code Cloud provider | ||
| * | ||
| * @param baseUrl The base URL of the Roo Code Cloud provider | ||
| * @param apiKey The API key (session token) for the Roo Code Cloud provider | ||
| * @returns A promise that resolves to a record of model IDs to model info | ||
| * @throws Will throw an error if the request fails or the response is not as expected. | ||
| */ | ||
| export async function getRooModels(baseUrl: string, apiKey?: string): Promise<ModelRecord> { | ||
| try { | ||
| const headers: Record<string, string> = { | ||
| "Content-Type": "application/json", | ||
| ...DEFAULT_HEADERS, | ||
| } | ||
|
|
||
| if (apiKey) { | ||
| headers["Authorization"] = `Bearer ${apiKey}` | ||
| } | ||
|
|
||
| // Normalize the URL to ensure proper /v1/models endpoint construction | ||
|
||
| // Remove any trailing /v1 to avoid duplication | ||
| const urlObj = new URL(baseUrl) | ||
| let pathname = urlObj.pathname.replace(/\/+$/, "").replace(/\/+/g, "/") | ||
| // Remove trailing /v1 if present to avoid /v1/v1/models | ||
| if (pathname.endsWith("/v1")) { | ||
| pathname = pathname.slice(0, -3) | ||
| } | ||
| urlObj.pathname = pathname + "/v1/models" | ||
| const url = urlObj.href | ||
|
|
||
| // Added timeout to prevent indefinite hanging | ||
| const response = await axios.get(url, { headers, timeout: 10000 }) | ||
|
||
| const models: ModelRecord = {} | ||
|
|
||
| // Validate response against schema | ||
| const parsed = RooModelsResponseSchema.safeParse(response.data) | ||
|
|
||
| if (!parsed.success) { | ||
| console.error("Error fetching Roo Code Cloud models: Unexpected response format", response.data) | ||
| console.error("Validation errors:", parsed.error.format()) | ||
| throw new Error("Failed to fetch Roo Code Cloud models: Unexpected response format.") | ||
| } | ||
|
|
||
| // Process the validated model data | ||
| for (const model of parsed.data.data) { | ||
| const modelId = model.id | ||
|
|
||
| if (!modelId) continue | ||
|
|
||
| // Extract model data from the validated API response | ||
|
||
| // All required fields are guaranteed by the schema | ||
| const contextWindow = model.context_window | ||
| const maxTokens = model.max_tokens | ||
| const tags = model.tags || [] | ||
| const pricing = model.pricing | ||
|
|
||
| // Determine if the model supports images based on tags | ||
| const supportsImages = tags.includes("vision") | ||
|
|
||
| // Parse pricing (API returns strings, convert to numbers) | ||
| const inputPrice = parseFloat(pricing.input) | ||
| const outputPrice = parseFloat(pricing.output) | ||
| const cacheReadPrice = pricing.input_cache_read ? parseFloat(pricing.input_cache_read) : undefined | ||
| const cacheWritePrice = pricing.input_cache_write ? parseFloat(pricing.input_cache_write) : undefined | ||
|
|
||
| models[modelId] = { | ||
| maxTokens, | ||
| contextWindow, | ||
| supportsImages, | ||
| supportsPromptCache: Boolean(cacheReadPrice !== undefined), | ||
| inputPrice, | ||
| outputPrice, | ||
| cacheWritesPrice: cacheWritePrice, | ||
| cacheReadsPrice: cacheReadPrice, | ||
| description: model.description || model.name, | ||
| deprecated: model.deprecated || false, | ||
| } | ||
| } | ||
|
|
||
| return models | ||
| } catch (error: any) { | ||
| console.error("Error fetching Roo Code Cloud models:", error.message ? error.message : error) | ||
| if (axios.isAxiosError(error) && error.response) { | ||
| throw new Error( | ||
| `Failed to fetch Roo Code Cloud models: ${error.response.status} ${error.response.statusText}. Check base URL and API key.`, | ||
| ) | ||
| } else if (axios.isAxiosError(error) && error.request) { | ||
| throw new Error( | ||
| "Failed to fetch Roo Code Cloud models: No response from server. Check Roo Code Cloud server status and base URL.", | ||
| ) | ||
| } else { | ||
| throw new Error(`Failed to fetch Roo Code Cloud models: ${error.message || "An unknown error occurred."}`) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need some kind of tiered pricing support (or is that allowed with this pattern/standard)?