Skip to content
Merged
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
3 changes: 3 additions & 0 deletions packages/types/src/providers/roo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
4 changes: 2 additions & 2 deletions src/api/providers/__tests__/roo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
84 changes: 83 additions & 1 deletion src/api/providers/fetchers/__tests__/roo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe("getRooModels", () => {
description: "Fast coding model",
deprecated: false,
isFree: false,
defaultToolProtocol: "native", // Applied from MODEL_DEFAULTS
defaultToolProtocol: "native",
},
})
})
Expand Down Expand Up @@ -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" })
})
})
34 changes: 6 additions & 28 deletions src/api/providers/fetchers/roo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Partial<ModelInfo>> = {
"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
*
Expand Down Expand Up @@ -150,9 +125,12 @@ export async function getRooModels(baseUrl: string, apiKey?: string): Promise<Mo
isStealthModel: isStealthModel || undefined,
}

// Apply model-specific defaults (e.g., defaultToolProtocol)
const modelDefaults = MODEL_DEFAULTS[modelId]
models[modelId] = modelDefaults ? { ...baseModelInfo, ...modelDefaults } : baseModelInfo
// Apply API-provided settings on top of base model info
// Settings allow the proxy to dynamically configure model-specific options
// like includedTools, excludedTools, reasoningEffort, etc.
const apiSettings = model.settings as Partial<ModelInfo> | undefined

models[modelId] = apiSettings ? { ...baseModelInfo, ...apiSettings } : baseModelInfo
}

return models
Expand Down
10 changes: 2 additions & 8 deletions src/api/providers/roo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -335,17 +334,12 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
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.
Expand Down
Loading