mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
feat: centralize model family defaults for tool preferences across all providers
This PR addresses Issue #10214 by creating a centralized framework for applying model family defaults to tool preferences and settings. Key changes: - Added model-family-defaults.ts with MODEL_FAMILY_REGISTRY and applyModelFamilyDefaults() - Added applyModelDefaults() helper method to BaseProvider - Updated all provider getModel() methods to apply family defaults - Removed deprecated router-tool-preferences.ts (functionality consolidated) - Added comprehensive tests for the model family defaults module Model family defaults are applied granularly: provider/model-specific settings take precedence over family defaults (only undefined properties get defaults).
This commit is contained in:
parent
61903f9588
commit
0b3e7667a6
33 changed files with 565 additions and 114 deletions
|
|
@ -245,6 +245,9 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
|
|||
}
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "anthropic", modelId: id, model: info, settings: this.options })
|
||||
|
||||
// Build betas array for request headers
|
||||
|
|
|
|||
|
|
@ -361,6 +361,9 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
}
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "anthropic",
|
||||
modelId: id,
|
||||
|
|
|
|||
|
|
@ -243,6 +243,8 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
? (this.options.apiModelId as ModelName)
|
||||
: this.defaultProviderModelId
|
||||
|
||||
return { id, info: this.providerModels[id] }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, this.providerModels[id])
|
||||
return { id, info }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { ModelInfo } from "@roo-code/types"
|
|||
import type { ApiHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { countTokens } from "../../utils/countTokens"
|
||||
import { applyModelFamilyDefaults } from "./utils/model-family-defaults"
|
||||
|
||||
/**
|
||||
* Base class for API providers that implements common functionality.
|
||||
|
|
@ -103,4 +104,23 @@ export abstract class BaseProvider implements ApiHandler {
|
|||
|
||||
return countTokens(content, { useWorker: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply model family defaults to a ModelInfo object.
|
||||
*
|
||||
* This helper method allows providers to apply consistent defaults
|
||||
* for recognized model families (OpenAI, Gemini, etc.) regardless of
|
||||
* which provider is serving the model.
|
||||
*
|
||||
* Defaults are only applied when the corresponding property is not
|
||||
* already explicitly set on the model info, ensuring that provider-specific
|
||||
* or model-specific settings take precedence.
|
||||
*
|
||||
* @param modelId - The model identifier
|
||||
* @param info - The original ModelInfo object
|
||||
* @returns A new ModelInfo object with family defaults applied
|
||||
*/
|
||||
protected applyModelDefaults(modelId: string, info: ModelInfo): ModelInfo {
|
||||
return applyModelFamilyDefaults(modelId, info)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1072,15 +1072,17 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
|
|||
reasoningBudget?: number
|
||||
} {
|
||||
if (this.costModelConfig?.id?.trim().length > 0) {
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(this.costModelConfig.id, this.costModelConfig.info)
|
||||
// Get model params for cost model config
|
||||
const params = getModelParams({
|
||||
format: "anthropic",
|
||||
modelId: this.costModelConfig.id,
|
||||
model: this.costModelConfig.info,
|
||||
model: info,
|
||||
settings: this.options,
|
||||
defaultTemperature: BEDROCK_DEFAULT_TEMPERATURE,
|
||||
})
|
||||
return { ...this.costModelConfig, ...params }
|
||||
return { ...this.costModelConfig, info, ...params }
|
||||
}
|
||||
|
||||
let modelConfig = undefined
|
||||
|
|
@ -1158,6 +1160,9 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH
|
|||
}
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
modelConfig.info = this.applyModelDefaults(modelConfig.id, modelConfig.info)
|
||||
|
||||
// Don't override maxTokens/contextWindow here; handled in getModelById (and includes user overrides)
|
||||
return { ...modelConfig, ...params } as {
|
||||
id: BedrockModelId | string
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import { type CerebrasModelId, cerebrasDefaultModelId, cerebrasModels } from "@roo-code/types"
|
||||
import { type CerebrasModelId, type ModelInfo, cerebrasDefaultModelId, cerebrasModels } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { calculateApiCostOpenAI } from "../../shared/cost"
|
||||
|
|
@ -38,13 +38,18 @@ export class CerebrasHandler extends BaseProvider implements SingleCompletionHan
|
|||
}
|
||||
}
|
||||
|
||||
getModel(): { id: CerebrasModelId; info: (typeof cerebrasModels)[CerebrasModelId] } {
|
||||
getModel(): { id: CerebrasModelId; info: ModelInfo } {
|
||||
const modelId = this.options.apiModelId as CerebrasModelId
|
||||
const validModelId = modelId && this.providerModels[modelId] ? modelId : this.defaultProviderModelId
|
||||
|
||||
let info: ModelInfo = { ...this.providerModels[validModelId] }
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(validModelId, info)
|
||||
|
||||
return {
|
||||
id: validModelId,
|
||||
info: this.providerModels[validModelId],
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
type ModelInfo,
|
||||
} from "@roo-code/types"
|
||||
import { type ApiHandler, ApiHandlerCreateMessageMetadata, type SingleCompletionHandler } from ".."
|
||||
import { applyModelFamilyDefaults } from "./utils/model-family-defaults"
|
||||
import { ApiStreamUsageChunk, type ApiStream } from "../transform/stream"
|
||||
import { claudeCodeOAuthManager, generateUserId } from "../../integrations/claude-code/oauth"
|
||||
import {
|
||||
|
|
@ -275,12 +276,18 @@ export class ClaudeCodeHandler implements ApiHandler, SingleCompletionHandler {
|
|||
const modelId = this.options.apiModelId
|
||||
if (modelId && Object.hasOwn(claudeCodeModels, modelId)) {
|
||||
const id = modelId as ClaudeCodeModelId
|
||||
return { id, info: { ...claudeCodeModels[id] } }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = applyModelFamilyDefaults(id, { ...claudeCodeModels[id] })
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = applyModelFamilyDefaults(claudeCodeDefaultModelId, {
|
||||
...claudeCodeModels[claudeCodeDefaultModelId],
|
||||
})
|
||||
return {
|
||||
id: claudeCodeDefaultModelId,
|
||||
info: { ...claudeCodeModels[claudeCodeDefaultModelId] },
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,10 @@ export class DeepInfraHandler extends RouterProvider implements SingleCompletion
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.deepInfraModelId ?? deepInfraDefaultModelId
|
||||
const info = this.models[id] ?? deepInfraDefaultModelInfo
|
||||
let info = this.models[id] ?? deepInfraDefaultModelInfo
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
|||
import OpenAI from "openai"
|
||||
|
||||
import {
|
||||
type ModelInfo,
|
||||
deepSeekModels,
|
||||
deepSeekDefaultModelId,
|
||||
DEEP_SEEK_DEFAULT_TEMPERATURE,
|
||||
|
|
@ -36,7 +37,13 @@ export class DeepSeekHandler extends OpenAiHandler {
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? deepSeekDefaultModelId
|
||||
const info = deepSeekModels[id as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId]
|
||||
let info: ModelInfo = {
|
||||
...(deepSeekModels[id as keyof typeof deepSeekModels] || deepSeekModels[deepSeekDefaultModelId]),
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { OpenAiHandler } from "./openai"
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { DOUBAO_API_BASE_URL, doubaoDefaultModelId, doubaoModels } from "@roo-code/types"
|
||||
import { type ModelInfo, DOUBAO_API_BASE_URL, doubaoDefaultModelId, doubaoModels } from "@roo-code/types"
|
||||
import { getModelParams } from "../transform/model-params"
|
||||
import { ApiStreamUsageChunk } from "../transform/stream"
|
||||
|
||||
|
|
@ -63,7 +63,13 @@ export class DoubaoHandler extends OpenAiHandler {
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? doubaoDefaultModelId
|
||||
const info = doubaoModels[id as keyof typeof doubaoModels] || doubaoModels[doubaoDefaultModelId]
|
||||
let info: ModelInfo = {
|
||||
...(doubaoModels[id as keyof typeof doubaoModels] || doubaoModels[doubaoDefaultModelId]),
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -348,6 +348,9 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
let id = modelId && modelId in geminiModels ? (modelId as GeminiModelId) : geminiDefaultModelId
|
||||
let info: ModelInfo = geminiModels[id]
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "gemini",
|
||||
modelId: id,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import OpenAI from "openai"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
|
||||
import type { ModelInfo } from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions, ModelRecord } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { convertToOpenAiMessages } from "../transform/openai-format"
|
||||
|
|
@ -112,9 +114,11 @@ export class HuggingFaceHandler extends BaseProvider implements SingleCompletion
|
|||
const modelId = this.options.huggingFaceModelId || "meta-llama/Llama-3.3-70B-Instruct"
|
||||
|
||||
// Try to get model info from cache
|
||||
const modelInfo = this.modelCache?.[modelId]
|
||||
let modelInfo = this.modelCache?.[modelId]
|
||||
|
||||
if (modelInfo) {
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
modelInfo = this.applyModelDefaults(modelId, modelInfo)
|
||||
return {
|
||||
id: modelId,
|
||||
info: modelInfo,
|
||||
|
|
@ -122,14 +126,19 @@ export class HuggingFaceHandler extends BaseProvider implements SingleCompletion
|
|||
}
|
||||
|
||||
// Fallback to default values if model not found in cache
|
||||
let defaultInfo: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
defaultInfo = this.applyModelDefaults(modelId, defaultInfo)
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
info: {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 131072,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
info: defaultInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { getCommand } from "../../utils/commands"
|
|||
import { ApiStream } from "../transform/stream"
|
||||
|
||||
import type { ApiHandler, SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { applyModelFamilyDefaults } from "./utils/model-family-defaults"
|
||||
|
||||
/**
|
||||
* Human Relay API processor
|
||||
|
|
@ -62,18 +63,22 @@ export class HumanRelayHandler implements ApiHandler, SingleCompletionHandler {
|
|||
* Get model information
|
||||
*/
|
||||
getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = "human-relay"
|
||||
// Human relay does not depend on a specific model, here is a default configuration
|
||||
const baseInfo: ModelInfo = {
|
||||
maxTokens: 16384,
|
||||
contextWindow: 100000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Calling web-side AI model through human relay",
|
||||
}
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = applyModelFamilyDefaults(modelId, baseInfo)
|
||||
return {
|
||||
id: "human-relay",
|
||||
info: {
|
||||
maxTokens: 16384,
|
||||
contextWindow: 100000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0,
|
||||
outputPrice: 0,
|
||||
description: "Calling web-side AI model through human relay",
|
||||
},
|
||||
id: modelId,
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import { ioIntelligenceDefaultModelId, ioIntelligenceModels, type IOIntelligenceModelId } from "@roo-code/types"
|
||||
import {
|
||||
type ModelInfo,
|
||||
ioIntelligenceDefaultModelId,
|
||||
ioIntelligenceModels,
|
||||
type IOIntelligenceModelId,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import type { ApiHandlerOptions } from "../../shared/api"
|
||||
import { BaseOpenAiCompatibleProvider } from "./base-openai-compatible-provider"
|
||||
|
|
@ -27,18 +32,25 @@ export class IOIntelligenceHandler extends BaseOpenAiCompatibleProvider<IOIntell
|
|||
this.providerModels[modelId as IOIntelligenceModelId] ?? this.providerModels[ioIntelligenceDefaultModelId]
|
||||
|
||||
if (modelInfo) {
|
||||
return { id: modelId as IOIntelligenceModelId, info: modelInfo }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, modelInfo)
|
||||
return { id: modelId as IOIntelligenceModelId, info }
|
||||
}
|
||||
|
||||
// Return the requested model ID even if not found, with fallback info.
|
||||
let defaultInfo: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
defaultInfo = this.applyModelDefaults(modelId, defaultInfo)
|
||||
|
||||
return {
|
||||
id: modelId as IOIntelligenceModelId,
|
||||
info: {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
},
|
||||
info: defaultInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,16 +174,21 @@ export class LmStudioHandler extends BaseProvider implements SingleCompletionHan
|
|||
}
|
||||
|
||||
override getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.lmStudioModelId || ""
|
||||
const models = getModelsFromCache("lmstudio")
|
||||
if (models && this.options.lmStudioModelId && models[this.options.lmStudioModelId]) {
|
||||
if (models && modelId && models[modelId]) {
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, models[modelId])
|
||||
return {
|
||||
id: this.options.lmStudioModelId,
|
||||
info: models[this.options.lmStudioModelId],
|
||||
id: modelId,
|
||||
info,
|
||||
}
|
||||
} else {
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, openAiModelInfoSaneDefaults)
|
||||
return {
|
||||
id: this.options.lmStudioModelId || "",
|
||||
info: openAiModelInfoSaneDefaults,
|
||||
id: modelId,
|
||||
info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,7 +272,9 @@ export class MiniMaxHandler extends BaseProvider implements SingleCompletionHand
|
|||
getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
const id = modelId && modelId in minimaxModels ? (modelId as MinimaxModelId) : minimaxDefaultModelId
|
||||
const info = minimaxModels[id]
|
||||
const baseInfo = minimaxModels[id]
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, baseInfo)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "anthropic",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import OpenAI from "openai"
|
|||
|
||||
import {
|
||||
type MistralModelId,
|
||||
type ModelInfo,
|
||||
mistralDefaultModelId,
|
||||
mistralModels,
|
||||
MISTRAL_DEFAULT_TEMPERATURE,
|
||||
|
|
@ -89,7 +90,7 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
|
|||
} = {
|
||||
model,
|
||||
messages: [{ role: "system", content: systemPrompt }, ...convertToMistralMessages(messages)],
|
||||
maxTokens: maxTokens ?? info.maxTokens,
|
||||
maxTokens: maxTokens ?? info.maxTokens ?? 8192,
|
||||
temperature,
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +188,10 @@ export class MistralHandler extends BaseProvider implements SingleCompletionHand
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? mistralDefaultModelId
|
||||
const info = mistralModels[id as MistralModelId] ?? mistralModels[mistralDefaultModelId]
|
||||
let info: ModelInfo = { ...(mistralModels[id as MistralModelId] ?? mistralModels[mistralDefaultModelId]) }
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
// @TODO: Move this to the `getModelParams` function.
|
||||
const maxTokens = this.options.includeMaxTokens ? info.maxTokens : undefined
|
||||
|
|
|
|||
|
|
@ -22,7 +22,13 @@ export class MoonshotHandler extends OpenAiHandler {
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.apiModelId ?? moonshotDefaultModelId
|
||||
const info = moonshotModels[id as keyof typeof moonshotModels] || moonshotModels[moonshotDefaultModelId]
|
||||
let info: ModelInfo = {
|
||||
...(moonshotModels[id as keyof typeof moonshotModels] || moonshotModels[moonshotDefaultModelId]),
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -334,9 +334,12 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
|
|||
|
||||
override getModel(): { id: string; info: ModelInfo } {
|
||||
const modelId = this.options.ollamaModelId || ""
|
||||
const baseInfo = this.models[modelId] || openAiModelInfoSaneDefaults
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, baseInfo)
|
||||
return {
|
||||
id: modelId,
|
||||
info: this.models[modelId] || openAiModelInfoSaneDefaults,
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1220,7 +1220,10 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
|
|||
let id =
|
||||
modelId && modelId in openAiNativeModels ? (modelId as OpenAiNativeModelId) : openAiNativeDefaultModelId
|
||||
|
||||
const info: ModelInfo = openAiNativeModels[id]
|
||||
let info: ModelInfo = openAiNativeModels[id]
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
|
|
|
|||
|
|
@ -299,7 +299,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
|
||||
override getModel() {
|
||||
const id = this.options.openAiModelId ?? ""
|
||||
const info = this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults
|
||||
let info = this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import { BaseProvider } from "./base-provider"
|
|||
import type { ApiHandlerCreateMessageMetadata, SingleCompletionHandler } from "../index"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { generateImageWithProvider, ImageGenerationResult } from "./utils/image-generation"
|
||||
import { applyRouterToolPreferences } from "./utils/router-tool-preferences"
|
||||
|
||||
// Add custom interface for OpenRouter params.
|
||||
type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
|
||||
|
|
@ -530,8 +529,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
|
|||
info = this.endpoints[this.options.openRouterSpecificProvider]
|
||||
}
|
||||
|
||||
// Apply tool preferences for models accessed through routers (OpenAI, Gemini)
|
||||
info = applyRouterToolPreferences(id, info)
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const isDeepSeekR1 = id.startsWith("deepseek/deepseek-r1") || id === "perplexity/sonar-reasoning"
|
||||
|
||||
|
|
|
|||
|
|
@ -322,7 +322,9 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan
|
|||
|
||||
override getModel(): { id: string; info: ModelInfo } {
|
||||
const id = this.options.apiModelId ?? qwenCodeDefaultModelId
|
||||
const info = qwenCodeModels[id as keyof typeof qwenCodeModels] || qwenCodeModels[qwenCodeDefaultModelId]
|
||||
const baseInfo = qwenCodeModels[id as keyof typeof qwenCodeModels] || qwenCodeModels[qwenCodeDefaultModelId]
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, baseInfo)
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import { BaseProvider } from "./base-provider"
|
|||
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
|
||||
import { toRequestyServiceUrl } from "../../shared/utils/requesty"
|
||||
import { handleOpenAIError } from "./utils/openai-error-handler"
|
||||
import { applyRouterToolPreferences } from "./utils/router-tool-preferences"
|
||||
|
||||
// Requesty usage includes an extra field for Anthropic use cases.
|
||||
// Safely cast the prompt token details section to the appropriate structure.
|
||||
|
|
@ -81,8 +80,8 @@ export class RequestyHandler extends BaseProvider implements SingleCompletionHan
|
|||
const id = this.options.requestyModelId ?? requestyDefaultModelId
|
||||
let info = this.models[id] ?? requestyDefaultModelInfo
|
||||
|
||||
// Apply tool preferences for models accessed through routers (OpenAI, Gemini)
|
||||
info = applyRouterToolPreferences(id, info)
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "anthropic",
|
||||
|
|
|
|||
|
|
@ -349,11 +349,13 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
const modelInfo = models[modelId]
|
||||
|
||||
if (modelInfo) {
|
||||
return { id: modelId, info: modelInfo }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, modelInfo)
|
||||
return { id: modelId, info }
|
||||
}
|
||||
|
||||
// Return the requested model ID even if not found, with fallback info.
|
||||
const fallbackInfo = {
|
||||
let fallbackInfo = {
|
||||
maxTokens: 16_384,
|
||||
contextWindow: 262_144,
|
||||
supportsImages: false,
|
||||
|
|
@ -365,6 +367,9 @@ export class RooHandler extends BaseOpenAiCompatibleProvider<string> {
|
|||
isFree: false,
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
fallbackInfo = this.applyModelDefaults(modelId, fallbackInfo) as typeof fallbackInfo
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
info: fallbackInfo,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ export abstract class RouterProvider extends BaseProvider {
|
|||
|
||||
// First check instance models (populated by fetchModel)
|
||||
if (this.models[id]) {
|
||||
return { id, info: this.models[id] }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, this.models[id])
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
// Fall back to global cache (synchronous disk/memory cache)
|
||||
|
|
@ -74,11 +76,15 @@ export abstract class RouterProvider extends BaseProvider {
|
|||
if (cachedModels?.[id]) {
|
||||
// Also populate instance models for future calls
|
||||
this.models = cachedModels
|
||||
return { id, info: cachedModels[id] }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, cachedModels[id])
|
||||
return { id, info }
|
||||
}
|
||||
|
||||
// Last resort: return default model
|
||||
return { id: this.defaultModelId, info: this.defaultModelInfo }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(this.defaultModelId, this.defaultModelInfo)
|
||||
return { id: this.defaultModelId, info }
|
||||
}
|
||||
|
||||
protected supportsTemperature(modelId: string): boolean {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,9 @@ export class UnboundHandler extends RouterProvider implements SingleCompletionHa
|
|||
const requestedId = this.options.unboundModelId ?? unboundDefaultModelId
|
||||
const modelExists = this.models[requestedId]
|
||||
const id = modelExists ? requestedId : unboundDefaultModelId
|
||||
const info = modelExists ? this.models[requestedId] : unboundDefaultModelInfo
|
||||
const baseInfo = modelExists ? this.models[requestedId] : unboundDefaultModelInfo
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(id, baseInfo)
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
|
|
|
|||
224
src/api/providers/utils/__tests__/model-family-defaults.spec.ts
Normal file
224
src/api/providers/utils/__tests__/model-family-defaults.spec.ts
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
import { ModelInfo } from "@roo-code/types"
|
||||
import { applyModelFamilyDefaults, MODEL_FAMILY_REGISTRY } from "../model-family-defaults"
|
||||
|
||||
describe("model-family-defaults", () => {
|
||||
describe("MODEL_FAMILY_REGISTRY", () => {
|
||||
it("should have Gemini 3 pattern as first entry (most specific)", () => {
|
||||
expect(MODEL_FAMILY_REGISTRY[0].pattern.toString()).toMatch(/gemini-3|gemini\/gemini-3/i)
|
||||
})
|
||||
|
||||
it("should have general Gemini pattern after Gemini 3", () => {
|
||||
const geminiIndex = MODEL_FAMILY_REGISTRY.findIndex(
|
||||
(config) =>
|
||||
config.pattern.toString().includes("gemini") && !config.pattern.toString().includes("gemini-3"),
|
||||
)
|
||||
expect(geminiIndex).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it("should have OpenAI/GPT pattern", () => {
|
||||
const openaiConfig = MODEL_FAMILY_REGISTRY.find((config) => config.pattern.toString().includes("gpt"))
|
||||
expect(openaiConfig).toBeDefined()
|
||||
expect(openaiConfig!.defaults.includedTools).toContain("apply_patch")
|
||||
expect(openaiConfig!.defaults.excludedTools).toContain("apply_diff")
|
||||
})
|
||||
})
|
||||
|
||||
describe("applyModelFamilyDefaults", () => {
|
||||
const baseInfo: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
|
||||
describe("Gemini 3 models", () => {
|
||||
it("should apply Gemini 3 defaults for gemini-3 model", () => {
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
expect(result.defaultTemperature).toBe(1)
|
||||
})
|
||||
|
||||
it("should apply Gemini 3 defaults for google/gemini-3 model", () => {
|
||||
const result = applyModelFamilyDefaults("google/gemini-3-pro", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
expect(result.defaultTemperature).toBe(1)
|
||||
})
|
||||
|
||||
it("should NOT override explicit includedTools", () => {
|
||||
const infoWithTools: ModelInfo = {
|
||||
...baseInfo,
|
||||
includedTools: ["custom_tool"],
|
||||
}
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", infoWithTools)
|
||||
expect(result.includedTools).toEqual(["custom_tool"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"]) // This wasn't set, so default applies
|
||||
})
|
||||
|
||||
it("should NOT override explicit excludedTools", () => {
|
||||
const infoWithTools: ModelInfo = {
|
||||
...baseInfo,
|
||||
excludedTools: ["other_tool"],
|
||||
}
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", infoWithTools)
|
||||
expect(result.excludedTools).toEqual(["other_tool"])
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"]) // This wasn't set, so default applies
|
||||
})
|
||||
|
||||
it("should NOT override explicit defaultTemperature", () => {
|
||||
const infoWithTemp: ModelInfo = {
|
||||
...baseInfo,
|
||||
defaultTemperature: 0.5,
|
||||
}
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", infoWithTemp)
|
||||
expect(result.defaultTemperature).toBe(0.5)
|
||||
})
|
||||
})
|
||||
|
||||
describe("General Gemini models", () => {
|
||||
it("should apply Gemini defaults for gemini model", () => {
|
||||
const result = applyModelFamilyDefaults("gemini-1.5-pro", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
expect(result.defaultTemperature).toBeUndefined() // General Gemini doesn't set temperature
|
||||
})
|
||||
|
||||
it("should apply Gemini defaults for google/gemini model", () => {
|
||||
const result = applyModelFamilyDefaults("google/gemini-1.5-flash", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
})
|
||||
|
||||
it("should apply Gemini defaults for models with gemini in the name", () => {
|
||||
const result = applyModelFamilyDefaults("openrouter/google/gemini-pro", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("OpenAI/GPT models", () => {
|
||||
it("should apply OpenAI defaults for gpt-4 model", () => {
|
||||
const result = applyModelFamilyDefaults("gpt-4-turbo", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
})
|
||||
|
||||
it("should apply OpenAI defaults for openai/ prefixed model", () => {
|
||||
const result = applyModelFamilyDefaults("openai/gpt-4o", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
})
|
||||
|
||||
it("should apply OpenAI defaults for o1 model", () => {
|
||||
const result = applyModelFamilyDefaults("o1-preview", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
})
|
||||
|
||||
it("should apply OpenAI defaults for o3-mini model", () => {
|
||||
const result = applyModelFamilyDefaults("o3-mini", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
})
|
||||
|
||||
it("should apply OpenAI defaults for o4 model", () => {
|
||||
const result = applyModelFamilyDefaults("o4", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff", "write_to_file"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("Non-matching models", () => {
|
||||
it("should return unchanged info for non-matching model", () => {
|
||||
const result = applyModelFamilyDefaults("claude-3-opus", baseInfo)
|
||||
expect(result).toEqual(baseInfo)
|
||||
})
|
||||
|
||||
it("should return unchanged info for anthropic models", () => {
|
||||
const result = applyModelFamilyDefaults("anthropic/claude-3.5-sonnet", baseInfo)
|
||||
expect(result).toEqual(baseInfo)
|
||||
})
|
||||
|
||||
it("should return unchanged info for deepseek models", () => {
|
||||
const result = applyModelFamilyDefaults("deepseek-r1", baseInfo)
|
||||
expect(result).toEqual(baseInfo)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Preserving existing properties", () => {
|
||||
it("should preserve all original info properties", () => {
|
||||
const fullInfo: ModelInfo = {
|
||||
maxTokens: 16384,
|
||||
contextWindow: 256000,
|
||||
supportsImages: true,
|
||||
supportsPromptCache: true,
|
||||
supportsNativeTools: true,
|
||||
inputPrice: 0.01,
|
||||
outputPrice: 0.03,
|
||||
cacheReadsPrice: 0.005,
|
||||
cacheWritesPrice: 0.015,
|
||||
description: "Test model",
|
||||
}
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", fullInfo)
|
||||
|
||||
// Original properties should be preserved
|
||||
expect(result.maxTokens).toBe(16384)
|
||||
expect(result.contextWindow).toBe(256000)
|
||||
expect(result.supportsImages).toBe(true)
|
||||
expect(result.supportsPromptCache).toBe(true)
|
||||
expect(result.supportsNativeTools).toBe(true)
|
||||
expect(result.inputPrice).toBe(0.01)
|
||||
expect(result.outputPrice).toBe(0.03)
|
||||
expect(result.cacheReadsPrice).toBe(0.005)
|
||||
expect(result.cacheWritesPrice).toBe(0.015)
|
||||
expect(result.description).toBe("Test model")
|
||||
|
||||
// Family defaults should be applied
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
expect(result.excludedTools).toEqual(["apply_diff"])
|
||||
expect(result.defaultTemperature).toBe(1)
|
||||
})
|
||||
|
||||
it("should not mutate original info object", () => {
|
||||
const originalInfo: ModelInfo = {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
}
|
||||
const originalCopy = { ...originalInfo }
|
||||
|
||||
applyModelFamilyDefaults("gemini-3-flash", originalInfo)
|
||||
|
||||
expect(originalInfo).toEqual(originalCopy)
|
||||
})
|
||||
})
|
||||
|
||||
describe("First-match-wins behavior", () => {
|
||||
it("should use Gemini 3 defaults for gemini-3 (not general Gemini)", () => {
|
||||
const result = applyModelFamilyDefaults("gemini-3-flash", baseInfo)
|
||||
// Gemini 3 has defaultTemperature: 1, general Gemini doesn't
|
||||
expect(result.defaultTemperature).toBe(1)
|
||||
})
|
||||
|
||||
it("should use general Gemini defaults for gemini-1.5 (not Gemini 3)", () => {
|
||||
const result = applyModelFamilyDefaults("gemini-1.5-pro", baseInfo)
|
||||
// General Gemini doesn't have defaultTemperature, so it should be undefined
|
||||
expect(result.defaultTemperature).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("Case insensitivity", () => {
|
||||
it("should match Gemini case-insensitively", () => {
|
||||
const result = applyModelFamilyDefaults("GEMINI-1.5-PRO", baseInfo)
|
||||
expect(result.includedTools).toEqual(["write_file", "edit_file"])
|
||||
})
|
||||
|
||||
it("should match GPT case-insensitively", () => {
|
||||
const result = applyModelFamilyDefaults("GPT-4-TURBO", baseInfo)
|
||||
expect(result.includedTools).toEqual(["apply_patch"])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
122
src/api/providers/utils/model-family-defaults.ts
Normal file
122
src/api/providers/utils/model-family-defaults.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import type { ModelInfo } from "@roo-code/types"
|
||||
|
||||
/**
|
||||
* Model family default configuration.
|
||||
* Each entry defines a pattern to match against model IDs and default ModelInfo properties
|
||||
* to apply when the pattern matches.
|
||||
*/
|
||||
interface ModelFamilyConfig {
|
||||
/**
|
||||
* Regular expression pattern to match against model IDs.
|
||||
* More specific patterns should come first in the registry.
|
||||
*/
|
||||
pattern: RegExp
|
||||
|
||||
/**
|
||||
* Description of this model family for documentation purposes.
|
||||
*/
|
||||
description: string
|
||||
|
||||
/**
|
||||
* Default ModelInfo properties to apply when this pattern matches.
|
||||
* These will only be applied if the corresponding property is not already set.
|
||||
*/
|
||||
defaults: Partial<ModelInfo>
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry of model family configurations.
|
||||
*
|
||||
* IMPORTANT: Order matters! Patterns are matched first-match-wins,
|
||||
* so more specific patterns should come before more general ones.
|
||||
*
|
||||
* For example, "gemini-3" should come before "gemini" to ensure
|
||||
* Gemini 3 models get their specific defaults before falling back
|
||||
* to general Gemini defaults.
|
||||
*/
|
||||
export const MODEL_FAMILY_REGISTRY: ModelFamilyConfig[] = [
|
||||
// Gemini 3 models (most specific - must come before general gemini)
|
||||
{
|
||||
pattern: /gemini-3|gemini\/gemini-3/i,
|
||||
description: "Google Gemini 3 models with enhanced tool support and temperature defaults",
|
||||
defaults: {
|
||||
defaultTemperature: 1,
|
||||
includedTools: ["write_file", "edit_file"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
},
|
||||
|
||||
// All Gemini models (general fallback for non-Gemini-3)
|
||||
{
|
||||
pattern: /gemini|google\/gemini/i,
|
||||
description: "Google Gemini models with file-based tool preferences",
|
||||
defaults: {
|
||||
includedTools: ["write_file", "edit_file"],
|
||||
excludedTools: ["apply_diff"],
|
||||
},
|
||||
},
|
||||
|
||||
// OpenAI GPT models (includes models with "gpt" or "openai" in the ID)
|
||||
{
|
||||
pattern: /gpt|openai\/|^o[134]-|^o[134]$/i,
|
||||
description: "OpenAI GPT and O-series models with apply_patch preference",
|
||||
defaults: {
|
||||
includedTools: ["apply_patch"],
|
||||
excludedTools: ["apply_diff", "write_to_file"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Apply model family defaults to a ModelInfo object.
|
||||
*
|
||||
* This function matches the model ID against patterns in the MODEL_FAMILY_REGISTRY
|
||||
* and applies the first matching family's defaults. Defaults are only applied for
|
||||
* properties that are not already explicitly set on the input ModelInfo.
|
||||
*
|
||||
* @param modelId - The model identifier (e.g., "openai/gpt-4", "google/gemini-2.5-pro")
|
||||
* @param info - The original ModelInfo object
|
||||
* @returns A new ModelInfo object with family defaults applied (if any match)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Model accessed through OpenRouter
|
||||
* const info = applyModelFamilyDefaults("openai/gpt-4o", { maxTokens: 16384, contextWindow: 128000 })
|
||||
* // Result: { maxTokens: 16384, contextWindow: 128000, includedTools: ["apply_patch"], excludedTools: ["apply_diff", "write_to_file"] }
|
||||
*
|
||||
* // Model with explicitly set tools (not overridden)
|
||||
* const info2 = applyModelFamilyDefaults("openai/gpt-4o", { includedTools: ["custom_tool"], contextWindow: 128000 })
|
||||
* // Result: { includedTools: ["custom_tool"], contextWindow: 128000, excludedTools: ["apply_diff", "write_to_file"] }
|
||||
* ```
|
||||
*/
|
||||
export function applyModelFamilyDefaults(modelId: string, info: ModelInfo): ModelInfo {
|
||||
// Find the first matching family configuration
|
||||
const matchingFamily = MODEL_FAMILY_REGISTRY.find((family) => family.pattern.test(modelId))
|
||||
|
||||
// If no match found, return the original info unchanged
|
||||
if (!matchingFamily) {
|
||||
return info
|
||||
}
|
||||
|
||||
// Apply defaults only for properties that are not already set
|
||||
const result = { ...info }
|
||||
const defaults = matchingFamily.defaults
|
||||
|
||||
// Apply defaultTemperature if not already set
|
||||
if (defaults.defaultTemperature !== undefined && result.defaultTemperature === undefined) {
|
||||
result.defaultTemperature = defaults.defaultTemperature
|
||||
}
|
||||
|
||||
// Apply includedTools if not already set
|
||||
// Note: We check for undefined specifically, as an empty array is a valid explicit value
|
||||
if (defaults.includedTools !== undefined && result.includedTools === undefined) {
|
||||
result.includedTools = [...defaults.includedTools]
|
||||
}
|
||||
|
||||
// Apply excludedTools if not already set
|
||||
if (defaults.excludedTools !== undefined && result.excludedTools === undefined) {
|
||||
result.excludedTools = [...defaults.excludedTools]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import type { ModelInfo } from "@roo-code/types"
|
||||
|
||||
/**
|
||||
* Apply tool preferences for models accessed through dynamic routers (OpenRouter, Requesty).
|
||||
*
|
||||
* Different model families perform better with specific tools:
|
||||
* - OpenAI models: Better results with apply_patch instead of apply_diff/write_to_file
|
||||
* - Gemini models: Higher quality results with write_file and edit_file
|
||||
*
|
||||
* This function modifies the model info to apply these preferences consistently
|
||||
* across all dynamic router providers.
|
||||
*
|
||||
* @param modelId The model identifier (e.g., "openai/gpt-4", "google/gemini-2.5-pro")
|
||||
* @param info The original model info object
|
||||
* @returns A new model info object with tool preferences applied
|
||||
*/
|
||||
export function applyRouterToolPreferences(modelId: string, info: ModelInfo): ModelInfo {
|
||||
let result = info
|
||||
|
||||
// For OpenAI models via routers, exclude write_to_file and apply_diff, and include apply_patch
|
||||
// This matches the behavior of the native OpenAI provider
|
||||
if (modelId.includes("openai")) {
|
||||
result = {
|
||||
...result,
|
||||
excludedTools: [...new Set([...(result.excludedTools || []), "apply_diff", "write_to_file"])],
|
||||
includedTools: [...new Set([...(result.includedTools || []), "apply_patch"])],
|
||||
}
|
||||
}
|
||||
|
||||
// For Gemini models via routers, include write_file and edit_file
|
||||
// This matches the behavior of the native Gemini provider
|
||||
if (modelId.includes("gemini")) {
|
||||
result = {
|
||||
...result,
|
||||
excludedTools: [...new Set([...(result.excludedTools || []), "apply_diff"])],
|
||||
includedTools: [...new Set([...(result.includedTools || []), "write_file", "edit_file"])],
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -15,7 +15,11 @@ export class VertexHandler extends GeminiHandler implements SingleCompletionHand
|
|||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
|
||||
const info: ModelInfo = vertexModels[id]
|
||||
let info: ModelInfo = vertexModels[id]
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "gemini", modelId: id, model: info, settings: this.options })
|
||||
|
||||
// The `:thinking` suffix indicates that the model is a "Hybrid"
|
||||
|
|
|
|||
|
|
@ -537,7 +537,7 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
const modelId = this.client.id || modelParts.join(SELECTOR_SEPARATOR)
|
||||
|
||||
// Build model info with conservative defaults for missing values
|
||||
const modelInfo: ModelInfo = {
|
||||
const baseInfo: ModelInfo = {
|
||||
maxTokens: -1, // Unlimited tokens by default
|
||||
contextWindow:
|
||||
typeof this.client.maxInputTokens === "number"
|
||||
|
|
@ -552,7 +552,10 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
description: `VSCode Language Model: ${modelId}`,
|
||||
}
|
||||
|
||||
return { id: modelId, info: modelInfo }
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(modelId, baseInfo)
|
||||
|
||||
return { id: modelId, info }
|
||||
}
|
||||
|
||||
// Fallback when no client is available
|
||||
|
|
@ -562,14 +565,19 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
|
||||
console.debug("Roo Code <Language Model API>: No client available, using fallback model info")
|
||||
|
||||
const baseInfo: ModelInfo = {
|
||||
...openAiModelInfoSaneDefaults,
|
||||
supportsNativeTools: true, // VSCode Language Model API supports native tool calling
|
||||
defaultToolProtocol: "native", // Use native tool protocol by default
|
||||
description: `VSCode Language Model (Fallback): ${fallbackId}`,
|
||||
}
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
const info = this.applyModelDefaults(fallbackId, baseInfo)
|
||||
|
||||
return {
|
||||
id: fallbackId,
|
||||
info: {
|
||||
...openAiModelInfoSaneDefaults,
|
||||
supportsNativeTools: true, // VSCode Language Model API supports native tool calling
|
||||
defaultToolProtocol: "native", // Use native tool protocol by default
|
||||
description: `VSCode Language Model (Fallback): ${fallbackId}`,
|
||||
},
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
import { type XAIModelId, xaiDefaultModelId, xaiModels, ApiProviderError } from "@roo-code/types"
|
||||
import { type XAIModelId, type ModelInfo, xaiDefaultModelId, xaiModels, ApiProviderError } from "@roo-code/types"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { NativeToolCallParser } from "../../core/assistant-message/NativeToolCallParser"
|
||||
|
|
@ -42,7 +42,11 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
|
|||
? (this.options.apiModelId as XAIModelId)
|
||||
: xaiDefaultModelId
|
||||
|
||||
const info = xaiModels[id]
|
||||
let info: ModelInfo = { ...xaiModels[id] }
|
||||
|
||||
// Apply model family defaults for consistent behavior across providers
|
||||
info = this.applyModelDefaults(id, info)
|
||||
|
||||
const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options })
|
||||
return { id, info, ...params }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue