mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: preserve custom model IDs across all providers instead of silently replacing them
Previously, when a user provided a custom model ID not in the predefined list, many providers would silently replace it with the default model ID. This affected: Anthropic, Anthropic Vertex, Gemini, MiniMax, OpenAI Codex, OpenAI Native, Vertex, xAI, and BaseOpenAiCompatibleProvider (which also affects SambaNova, Baseten, Fireworks, Z.ai). Now, all providers preserve the user's custom model ID and only fall back to the default model's *info* (capabilities/pricing metadata) when the model is not in the predefined list. This matches the pattern already used by DeepSeek, Moonshot, Mistral, and QwenCode. Closes #11964
This commit is contained in:
parent
137d3f4fd8
commit
1cd51f6689
18 changed files with 139 additions and 39 deletions
|
|
@ -841,6 +841,18 @@ describe("VertexHandler", () => {
|
|||
expect(modelInfo.info.contextWindow).toBe(200_000)
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new AnthropicVertexHandler({
|
||||
apiModelId: "claude-custom-vertex-model",
|
||||
vertexProjectId: "test-project",
|
||||
vertexRegion: "us-central1",
|
||||
})
|
||||
const modelInfo = customHandler.getModel()
|
||||
expect(modelInfo.id).toBe("claude-custom-vertex-model")
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
expect(modelInfo.info.contextWindow).toBeDefined()
|
||||
})
|
||||
|
||||
it("honors custom maxTokens for thinking models", () => {
|
||||
const handler = new AnthropicVertexHandler({
|
||||
apiKey: "test-api-key",
|
||||
|
|
|
|||
|
|
@ -268,6 +268,17 @@ describe("AnthropicHandler", () => {
|
|||
expect(model.info.supportsPromptCache).toBe(true)
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
apiModelId: "claude-custom-model-v1",
|
||||
})
|
||||
const model = customHandler.getModel()
|
||||
expect(model.id).toBe("claude-custom-model-v1")
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info.contextWindow).toBeDefined()
|
||||
})
|
||||
|
||||
it("honors custom maxTokens for thinking models", () => {
|
||||
const handler = new AnthropicHandler({
|
||||
apiKey: "test-api-key",
|
||||
|
|
|
|||
|
|
@ -545,4 +545,43 @@ describe("BaseOpenAiCompatibleProvider", () => {
|
|||
expect(endChunks).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getModel", () => {
|
||||
it("should return the default model when no apiModelId is set", () => {
|
||||
const model = handler.getModel()
|
||||
expect(model.id).toBe("test-model")
|
||||
expect(model.info).toBeDefined()
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new (class extends BaseOpenAiCompatibleProvider<"test-model"> {
|
||||
constructor() {
|
||||
const testModels: Record<"test-model", ModelInfo> = {
|
||||
"test-model": {
|
||||
maxTokens: 4096,
|
||||
contextWindow: 128000,
|
||||
supportsImages: false,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 0.5,
|
||||
outputPrice: 1.5,
|
||||
},
|
||||
}
|
||||
|
||||
super({
|
||||
providerName: "TestProvider",
|
||||
baseURL: "https://test.example.com/v1",
|
||||
defaultProviderModelId: "test-model",
|
||||
providerModels: testModels,
|
||||
apiKey: "test-key",
|
||||
apiModelId: "custom-unknown-model",
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
const model = customHandler.getModel()
|
||||
expect(model.id).toBe("custom-unknown-model")
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info.contextWindow).toBe(128000)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -165,13 +165,17 @@ describe("GeminiHandler", () => {
|
|||
expect(modelInfo.info).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return default model if invalid model specified", () => {
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const invalidHandler = new GeminiHandler({
|
||||
apiModelId: "invalid-model",
|
||||
apiModelId: "gemini-custom-model",
|
||||
geminiApiKey: "test-key",
|
||||
})
|
||||
const modelInfo = invalidHandler.getModel()
|
||||
expect(modelInfo.id).toBe(geminiDefaultModelId) // Default model
|
||||
expect(modelInfo.id).toBe("gemini-custom-model")
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
// Should fall back to default model's info
|
||||
const defaultHandler = new GeminiHandler({ geminiApiKey: "test-key" })
|
||||
expect(modelInfo.info.contextWindow).toBe(defaultHandler.getModel().info.contextWindow)
|
||||
})
|
||||
|
||||
it("should exclude apply_diff and include edit in tool preferences", () => {
|
||||
|
|
|
|||
|
|
@ -134,6 +134,17 @@ describe("MiniMaxHandler", () => {
|
|||
expect(model.info.cacheWritesPrice).toBe(0.375)
|
||||
expect(model.info.cacheReadsPrice).toBe(0.03)
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const handlerWithCustom = new MiniMaxHandler({
|
||||
apiModelId: "MiniMax-M2.7-custom",
|
||||
minimaxApiKey: "test-minimax-api-key",
|
||||
})
|
||||
const model = handlerWithCustom.getModel()
|
||||
expect(model.id).toBe("MiniMax-M2.7-custom")
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info).toEqual(minimaxModels[minimaxDefaultModelId])
|
||||
})
|
||||
})
|
||||
|
||||
describe("China MiniMax", () => {
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@ describe("OpenAiCodexHandler.getModel", () => {
|
|||
},
|
||||
)
|
||||
|
||||
it("should fall back to default model when an invalid model id is provided", () => {
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const handler = new OpenAiCodexHandler({ apiModelId: "not-a-real-model" })
|
||||
const model = handler.getModel()
|
||||
|
||||
expect(model.id).toBe("gpt-5.3-codex")
|
||||
expect(model.id).toBe("not-a-real-model")
|
||||
expect(model.info).toBeDefined()
|
||||
// Should fall back to default model's info
|
||||
const defaultHandler = new OpenAiCodexHandler({})
|
||||
expect(model.info.contextWindow).toBe(defaultHandler.getModel().info.contextWindow)
|
||||
})
|
||||
|
||||
it("should use Spark-specific limits and capabilities", () => {
|
||||
|
|
|
|||
|
|
@ -324,6 +324,17 @@ describe("OpenAiNativeHandler", () => {
|
|||
expect(modelInfo.id).toBe("gpt-5.1-codex-max") // Default model
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new OpenAiNativeHandler({
|
||||
...mockOptions,
|
||||
apiModelId: "gpt-custom-model",
|
||||
})
|
||||
const modelInfo = customHandler.getModel()
|
||||
expect(modelInfo.id).toBe("gpt-custom-model")
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
expect(modelInfo.info.contextWindow).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("GPT-5 models", () => {
|
||||
|
|
|
|||
|
|
@ -138,6 +138,18 @@ describe("VertexHandler", () => {
|
|||
expect(modelInfo.info.contextWindow).toBe(1048576)
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new VertexHandler({
|
||||
apiModelId: "gemini-custom-vertex-model",
|
||||
vertexProjectId: "test-project",
|
||||
vertexRegion: "us-central1",
|
||||
})
|
||||
const modelInfo = customHandler.getModel()
|
||||
expect(modelInfo.id).toBe("gemini-custom-vertex-model")
|
||||
expect(modelInfo.info).toBeDefined()
|
||||
expect(modelInfo.info.contextWindow).toBeDefined()
|
||||
})
|
||||
|
||||
it("should exclude apply_diff and include edit in tool preferences", () => {
|
||||
const testHandler = new VertexHandler({
|
||||
apiModelId: "gemini-2.0-flash-001",
|
||||
|
|
|
|||
|
|
@ -80,6 +80,14 @@ describe("XAIHandler", () => {
|
|||
expect(model.info).toEqual(xaiModels[testModelId])
|
||||
})
|
||||
|
||||
it("should preserve custom model ID and use default model info for unknown models", () => {
|
||||
const customHandler = new XAIHandler({ apiModelId: "grok-custom-model" })
|
||||
const model = customHandler.getModel()
|
||||
expect(model.id).toBe("grok-custom-model")
|
||||
expect(model.info).toBeDefined()
|
||||
expect(model.info).toEqual(xaiModels[xaiDefaultModelId])
|
||||
})
|
||||
|
||||
it("should include reasoning_effort parameter for mini models", async () => {
|
||||
const miniModelHandler = new XAIHandler({
|
||||
apiModelId: "grok-3-mini",
|
||||
|
|
|
|||
|
|
@ -206,9 +206,8 @@ export class AnthropicVertexHandler extends BaseProvider implements SingleComple
|
|||
}
|
||||
|
||||
getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
|
||||
let info: ModelInfo = vertexModels[id]
|
||||
const id = this.options.apiModelId ?? vertexDefaultModelId
|
||||
let info: ModelInfo = vertexModels[id as VertexModelId] || vertexModels[vertexDefaultModelId]
|
||||
|
||||
// Check if 1M context beta should be enabled for supported models
|
||||
const supports1MContext = VERTEX_1M_CONTEXT_MODEL_IDS.includes(
|
||||
|
|
|
|||
|
|
@ -333,9 +333,8 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
|
|||
}
|
||||
|
||||
getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
let id = modelId && modelId in anthropicModels ? (modelId as AnthropicModelId) : anthropicDefaultModelId
|
||||
let info: ModelInfo = anthropicModels[id]
|
||||
const id = this.options.apiModelId ?? anthropicDefaultModelId
|
||||
let info: ModelInfo = anthropicModels[id as AnthropicModelId] || anthropicModels[anthropicDefaultModelId]
|
||||
|
||||
// If 1M context beta is enabled for supported models, update the model info
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -250,11 +250,11 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
|
|||
}
|
||||
|
||||
override getModel() {
|
||||
const id =
|
||||
this.options.apiModelId && this.options.apiModelId in this.providerModels
|
||||
? (this.options.apiModelId as ModelName)
|
||||
: this.defaultProviderModelId
|
||||
const id = this.options.apiModelId ?? this.defaultProviderModelId
|
||||
|
||||
return { id, info: this.providerModels[id] }
|
||||
return {
|
||||
id,
|
||||
info: this.providerModels[id as ModelName] || this.providerModels[this.defaultProviderModelId as ModelName],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -347,9 +347,8 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
|
|||
}
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
let id = modelId && modelId in geminiModels ? (modelId as GeminiModelId) : geminiDefaultModelId
|
||||
let info: ModelInfo = geminiModels[id]
|
||||
const id = this.options.apiModelId ?? geminiDefaultModelId
|
||||
let info: ModelInfo = geminiModels[id as GeminiModelId] || geminiModels[geminiDefaultModelId]
|
||||
|
||||
const params = getModelParams({
|
||||
format: "gemini",
|
||||
|
|
|
|||
|
|
@ -270,9 +270,8 @@ 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 id = this.options.apiModelId ?? minimaxDefaultModelId
|
||||
const info = minimaxModels[id as MinimaxModelId] || minimaxModels[minimaxDefaultModelId]
|
||||
|
||||
const params = getModelParams({
|
||||
format: "anthropic",
|
||||
|
|
|
|||
|
|
@ -1115,11 +1115,10 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
|
|||
}
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
const id = this.options.apiModelId ?? openAiCodexDefaultModelId
|
||||
|
||||
let id = modelId && modelId in openAiCodexModels ? (modelId as OpenAiCodexModelId) : openAiCodexDefaultModelId
|
||||
|
||||
const info: ModelInfo = openAiCodexModels[id]
|
||||
const info: ModelInfo =
|
||||
openAiCodexModels[id as OpenAiCodexModelId] || openAiCodexModels[openAiCodexDefaultModelId]
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
|
|
|
|||
|
|
@ -1433,12 +1433,10 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio
|
|||
// Removed isResponsesApiModel method as ALL models now use the Responses API
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
const id = this.options.apiModelId ?? openAiNativeDefaultModelId
|
||||
|
||||
let id =
|
||||
modelId && modelId in openAiNativeModels ? (modelId as OpenAiNativeModelId) : openAiNativeDefaultModelId
|
||||
|
||||
const info: ModelInfo = openAiNativeModels[id]
|
||||
const info: ModelInfo =
|
||||
openAiNativeModels[id as OpenAiNativeModelId] || openAiNativeModels[openAiNativeDefaultModelId]
|
||||
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
|
|
|
|||
|
|
@ -13,9 +13,8 @@ export class VertexHandler extends GeminiHandler implements SingleCompletionHand
|
|||
}
|
||||
|
||||
override getModel() {
|
||||
const modelId = this.options.apiModelId
|
||||
let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
|
||||
let info: ModelInfo = vertexModels[id]
|
||||
const id = this.options.apiModelId ?? vertexDefaultModelId
|
||||
let info: ModelInfo = vertexModels[id as VertexModelId] || vertexModels[vertexDefaultModelId]
|
||||
const params = getModelParams({
|
||||
format: "gemini",
|
||||
modelId: id,
|
||||
|
|
|
|||
|
|
@ -37,12 +37,9 @@ export class XAIHandler extends BaseProvider implements SingleCompletionHandler
|
|||
}
|
||||
|
||||
override getModel() {
|
||||
const id =
|
||||
this.options.apiModelId && this.options.apiModelId in xaiModels
|
||||
? (this.options.apiModelId as XAIModelId)
|
||||
: xaiDefaultModelId
|
||||
const id = this.options.apiModelId ?? xaiDefaultModelId
|
||||
|
||||
const info = xaiModels[id]
|
||||
const info = xaiModels[id as XAIModelId] || xaiModels[xaiDefaultModelId]
|
||||
const params = getModelParams({
|
||||
format: "openai",
|
||||
modelId: id,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue