mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: improve OpenRouter model validation to support newer models
- Changed validation threshold from >1 to >10 models to avoid false negatives - Added pattern matching for known model providers (OpenAI, Google, X-AI, etc.) - Allows newer models like gpt-5.1, gemini-3-pro-preview, and grok-4.1-fast - Added comprehensive tests for the new validation logic Fixes #9597
This commit is contained in:
parent
099ea6dc6a
commit
e3c1bc0280
2 changed files with 143 additions and 8 deletions
|
|
@ -4,6 +4,20 @@ import { RouterModels } from "@roo/api"
|
|||
|
||||
import { getModelValidationError, validateApiConfigurationExcludingModelErrors } from "../validate"
|
||||
|
||||
// Mock i18next
|
||||
vi.mock("i18next", () => ({
|
||||
default: {
|
||||
t: vi.fn((key: string, options?: any) => {
|
||||
// Return the key as-is for testing (this matches what the actual code expects)
|
||||
if (options?.modelId) {
|
||||
return key // For validation.modelAvailability, just return the key
|
||||
}
|
||||
return key
|
||||
}),
|
||||
language: "en",
|
||||
},
|
||||
}))
|
||||
|
||||
describe("Model Validation Functions", () => {
|
||||
const mockRouterModels: RouterModels = {
|
||||
openrouter: {
|
||||
|
|
@ -47,6 +61,25 @@ describe("Model Validation Functions", () => {
|
|||
chutes: {},
|
||||
}
|
||||
|
||||
// Mock router models with many models (>10) to trigger validation
|
||||
const mockRouterModelsWithManyModels: RouterModels = {
|
||||
...mockRouterModels,
|
||||
openrouter: {
|
||||
...mockRouterModels.openrouter,
|
||||
"model-1": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-2": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-3": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-4": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-5": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-6": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-7": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-8": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-9": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-10": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
"model-11": { maxTokens: 1000, contextWindow: 10000, supportsImages: false, supportsPromptCache: false },
|
||||
},
|
||||
}
|
||||
|
||||
const allowAllOrganization: OrganizationAllowList = {
|
||||
allowAll: true,
|
||||
providers: {},
|
||||
|
|
@ -73,14 +106,92 @@ describe("Model Validation Functions", () => {
|
|||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns error for invalid OpenRouter model", () => {
|
||||
it("returns error for invalid OpenRouter model when model list has many models", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "invalid-model",
|
||||
}
|
||||
|
||||
// Should return error when we have many models (>10) in cache
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBe("settings:validation.modelAvailability")
|
||||
})
|
||||
|
||||
it("allows newer OpenRouter models like gpt-5.1 even when not in cache", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "openai/gpt-5.1",
|
||||
}
|
||||
|
||||
// Should allow gpt-5.1 even when not in the model list
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("allows newer OpenRouter models like gemini-3-pro-preview even when not in cache", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "google/gemini-3-pro-preview",
|
||||
}
|
||||
|
||||
// Should allow gemini-3 models even when not in the model list
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("allows newer OpenRouter models like grok-4.1-fast:free even when not in cache", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "x-ai/grok-4.1-fast:free",
|
||||
}
|
||||
|
||||
// Should allow grok-4 models even when not in the model list
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("allows known pattern models for OpenRouter even when not in cache", () => {
|
||||
const testCases = [
|
||||
"openai/gpt-5",
|
||||
"openai/gpt-5.2",
|
||||
"google/gemini-4",
|
||||
"x-ai/grok-5",
|
||||
"anthropic/claude-4-opus",
|
||||
"meta-llama/llama-4",
|
||||
"mistralai/mistral-large-2",
|
||||
]
|
||||
|
||||
testCases.forEach((modelId) => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: modelId,
|
||||
}
|
||||
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
it("still validates unknown pattern models for OpenRouter", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "unknown-provider/unknown-model",
|
||||
}
|
||||
|
||||
// Should still validate models that don't match known patterns
|
||||
const result = getModelValidationError(config, mockRouterModelsWithManyModels, allowAllOrganization)
|
||||
expect(result).toBe("settings:validation.modelAvailability")
|
||||
})
|
||||
|
||||
it("does not validate when model list is small (<=10 models)", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "openrouter",
|
||||
openRouterModelId: "any-model-id",
|
||||
}
|
||||
|
||||
// Should not validate when we have few models in cache (initial state)
|
||||
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
||||
expect(result).toBe("validation.modelAvailability")
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("returns error for model not allowed by organization", () => {
|
||||
|
|
@ -130,7 +241,7 @@ describe("Model Validation Functions", () => {
|
|||
}
|
||||
|
||||
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
||||
expect(result).toBe("validation.modelId")
|
||||
expect(result).toBe("settings:validation.modelId")
|
||||
})
|
||||
|
||||
it("handles undefined model IDs gracefully", () => {
|
||||
|
|
@ -140,7 +251,7 @@ describe("Model Validation Functions", () => {
|
|||
}
|
||||
|
||||
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
||||
expect(result).toBe("validation.modelId")
|
||||
expect(result).toBe("settings:validation.modelId")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -164,7 +275,7 @@ describe("Model Validation Functions", () => {
|
|||
}
|
||||
|
||||
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
||||
expect(result).toBe("validation.apiKey")
|
||||
expect(result).toBe("settings:validation.apiKey")
|
||||
})
|
||||
|
||||
it("excludes model-specific errors", () => {
|
||||
|
|
@ -196,7 +307,7 @@ describe("Model Validation Functions", () => {
|
|||
it("returns undefined for valid IO Intelligence model", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "io-intelligence",
|
||||
glamaModelId: "valid-model",
|
||||
ioIntelligenceModelId: "valid-model",
|
||||
}
|
||||
|
||||
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
||||
|
|
@ -206,7 +317,7 @@ describe("Model Validation Functions", () => {
|
|||
it("returns error for invalid IO Intelligence model", () => {
|
||||
const config: ProviderSettings = {
|
||||
apiProvider: "io-intelligence",
|
||||
glamaModelId: "invalid-model",
|
||||
ioIntelligenceModelId: "invalid-model",
|
||||
}
|
||||
|
||||
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
||||
|
|
@ -276,7 +276,31 @@ function validateDynamicProviderModelId(
|
|||
|
||||
const models = routerModels?.[provider]
|
||||
|
||||
if (models && Object.keys(models).length > 1 && !Object.keys(models).includes(modelId)) {
|
||||
// Only validate if we have a substantial model list (more than 10 models)
|
||||
// This prevents false negatives for newer models that haven't been added to the cache yet
|
||||
// OpenRouter frequently adds new models that are immediately usable but may not appear
|
||||
// in the /models endpoint right away
|
||||
if (models && Object.keys(models).length > 10 && !Object.keys(models).includes(modelId)) {
|
||||
// For OpenRouter specifically, we'll be more lenient with model validation
|
||||
// to support newer models like gpt-5.1, gemini-3-pro-preview, grok-4.1-fast:free
|
||||
if (provider === "openrouter") {
|
||||
// Only show a warning for models that don't match any known pattern
|
||||
// Allow models that follow common naming patterns even if not in cache
|
||||
const knownPatterns = [
|
||||
/^openai\/gpt-\d+(\.\d+)?/, // Matches gpt-5, gpt-5.1, etc.
|
||||
/^google\/gemini-\d+/, // Matches gemini-3, gemini-3-pro, etc.
|
||||
/^x-ai\/grok-\d+/, // Matches grok-4, grok-4.1, etc.
|
||||
/^anthropic\/claude-/, // Claude models
|
||||
/^meta-llama\//, // Meta Llama models
|
||||
/^mistralai\//, // Mistral models
|
||||
]
|
||||
|
||||
// If the model matches a known pattern, allow it
|
||||
if (knownPatterns.some((pattern) => pattern.test(modelId))) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return i18next.t("settings:validation.modelAvailability", { modelId })
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue