diff --git a/packages/types/src/providers/roo.ts b/packages/types/src/providers/roo.ts index f1dec45f3bf..af88d145ee5 100644 --- a/packages/types/src/providers/roo.ts +++ b/packages/types/src/providers/roo.ts @@ -39,6 +39,9 @@ export const RooModelSchema = z.object({ pricing: RooPricingSchema, deprecated: z.boolean().optional(), default_temperature: z.number().optional(), + // Dynamic settings that map directly to ModelInfo properties + // Allows the API to configure model-specific defaults like includedTools, excludedTools, reasoningEffort, etc. + settings: z.record(z.string(), z.unknown()).optional(), }) export const RooModelsResponseSchema = z.object({ diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index a9404efd534..e5f0b1d1532 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -439,12 +439,12 @@ describe("RooHandler", () => { } }) - it("should not override existing properties when applying MODEL_DEFAULTS", () => { + it("should return cached model info with settings applied from API", () => { const handlerWithMinimax = new RooHandler({ apiModelId: "minimax/minimax-m2:free", }) const modelInfo = handlerWithMinimax.getModel() - // The defaults should be merged, but not overwrite existing cached values + // The settings from API should already be applied in the cached model info expect(modelInfo.info.supportsNativeTools).toBe(true) expect(modelInfo.info.inputPrice).toBe(0.15) expect(modelInfo.info.outputPrice).toBe(0.6) diff --git a/src/api/providers/fetchers/__tests__/roo.spec.ts b/src/api/providers/fetchers/__tests__/roo.spec.ts index e3945ef8849..7cc479f9ba4 100644 --- a/src/api/providers/fetchers/__tests__/roo.spec.ts +++ b/src/api/providers/fetchers/__tests__/roo.spec.ts @@ -77,7 +77,7 @@ describe("getRooModels", () => { description: "Fast coding model", deprecated: false, isFree: false, - defaultToolProtocol: "native", // Applied from MODEL_DEFAULTS + defaultToolProtocol: "native", }, }) }) @@ -719,4 +719,86 @@ describe("getRooModels", () => { expect(models["test/non-stealth-model"].isStealthModel).toBeUndefined() }) + + it("should apply API-provided settings to model info", async () => { + const mockResponse = { + object: "list", + data: [ + { + id: "test/model-with-settings", + object: "model", + created: 1234567890, + owned_by: "test", + name: "Model with Settings", + description: "Model with API-provided settings", + context_window: 128000, + max_tokens: 8192, + type: "language", + tags: ["tool-use"], + pricing: { + input: "0.0001", + output: "0.0002", + }, + settings: { + includedTools: ["apply_patch"], + excludedTools: ["apply_diff", "write_to_file"], + reasoningEffort: "high", + }, + }, + ], + } + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }) + + const models = await getRooModels(baseUrl, apiKey) + + expect(models["test/model-with-settings"].includedTools).toEqual(["apply_patch"]) + expect(models["test/model-with-settings"].excludedTools).toEqual(["apply_diff", "write_to_file"]) + expect(models["test/model-with-settings"].reasoningEffort).toBe("high") + }) + + it("should handle arbitrary settings properties dynamically", async () => { + const mockResponse = { + object: "list", + data: [ + { + id: "test/dynamic-settings-model", + object: "model", + created: 1234567890, + owned_by: "test", + name: "Dynamic Settings Model", + description: "Model with arbitrary settings", + context_window: 128000, + max_tokens: 8192, + type: "language", + tags: [], + pricing: { + input: "0.0001", + output: "0.0002", + }, + settings: { + customProperty: "custom-value", + anotherSetting: 42, + nestedConfig: { key: "value" }, + }, + }, + ], + } + + mockFetch.mockResolvedValueOnce({ + ok: true, + json: async () => mockResponse, + }) + + const models = await getRooModels(baseUrl, apiKey) + const model = models["test/dynamic-settings-model"] as any + + // Arbitrary settings should be passed through + expect(model.customProperty).toBe("custom-value") + expect(model.anotherSetting).toBe(42) + expect(model.nestedConfig).toEqual({ key: "value" }) + }) }) diff --git a/src/api/providers/fetchers/roo.ts b/src/api/providers/fetchers/roo.ts index b86715f77bd..735d4bed922 100644 --- a/src/api/providers/fetchers/roo.ts +++ b/src/api/providers/fetchers/roo.ts @@ -5,31 +5,6 @@ import { parseApiPrice } from "../../../shared/cost" import { DEFAULT_HEADERS } from "../constants" -// Model-specific defaults that should be applied even when models come from API cache -// These override API-provided values for specific models -// Exported so RooHandler.getModel() can also apply these for fallback cases -export const MODEL_DEFAULTS: Record> = { - "minimax/minimax-m2:free": { - includedTools: ["search_and_replace"], - excludedTools: ["apply_diff"], - }, - "openai/gpt-5.1": { - includedTools: ["apply_patch"], - excludedTools: ["apply_diff", "write_to_file"], - reasoningEffort: "medium", - }, - "openai/gpt-5": { - includedTools: ["apply_patch"], - excludedTools: ["apply_diff", "write_to_file"], - reasoningEffort: "medium", - }, - "openai/gpt-5-mini": { - includedTools: ["apply_patch"], - excludedTools: ["apply_diff", "write_to_file"], - reasoningEffort: "medium", - }, -} - /** * Fetches available models from the Roo Code Cloud provider * @@ -150,9 +125,12 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise | undefined + + models[modelId] = apiSettings ? { ...baseModelInfo, ...apiSettings } : baseModelInfo } return models diff --git a/src/api/providers/roo.ts b/src/api/providers/roo.ts index f7f9a9db183..8bfc31991bd 100644 --- a/src/api/providers/roo.ts +++ b/src/api/providers/roo.ts @@ -15,7 +15,6 @@ import { getRooReasoning } from "../transform/reasoning" import type { ApiHandlerCreateMessageMetadata } from "../index" import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider" import { getModels, getModelsFromCache } from "../providers/fetchers/modelCache" -import { MODEL_DEFAULTS } from "../providers/fetchers/roo" import { handleOpenAIError } from "./utils/openai-error-handler" import { generateImageWithProvider, generateImageWithImagesApi, ImageGenerationResult } from "./utils/image-generation" import { t } from "../../i18n" @@ -335,17 +334,12 @@ export class RooHandler extends BaseOpenAiCompatibleProvider { override getModel() { const modelId = this.options.apiModelId || rooDefaultModelId - // Get models from shared cache + // Get models from shared cache (settings are already applied by the fetcher) const models = getModelsFromCache("roo") || {} const modelInfo = models[modelId] - // Get model-specific defaults if they exist - const modelDefaults = MODEL_DEFAULTS[modelId] - if (modelInfo) { - // Merge model-specific defaults with cached model info - const mergedInfo = modelDefaults ? { ...modelInfo, ...modelDefaults } : modelInfo - return { id: modelId, info: mergedInfo } + return { id: modelId, info: modelInfo } } // Return the requested model ID even if not found, with fallback info.