mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
304 lines
8.7 KiB
TypeScript
304 lines
8.7 KiB
TypeScript
import i18next from "i18next"
|
|
|
|
import {
|
|
type ProviderSettings,
|
|
type OrganizationAllowList,
|
|
type ProviderName,
|
|
type RouterModels,
|
|
modelIdKeysByProvider,
|
|
isProviderName,
|
|
isRetiredProvider,
|
|
isDynamicProvider,
|
|
isFauxProvider,
|
|
isCustomProvider,
|
|
} from "@roo-code/types"
|
|
|
|
export function validateApiConfiguration(
|
|
apiConfiguration: ProviderSettings,
|
|
routerModels?: RouterModels,
|
|
organizationAllowList?: OrganizationAllowList,
|
|
): string | undefined {
|
|
const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration)
|
|
|
|
if (keysAndIdsPresentErrorMessage) {
|
|
return keysAndIdsPresentErrorMessage
|
|
}
|
|
|
|
const organizationAllowListError = validateProviderAgainstOrganizationSettings(
|
|
apiConfiguration,
|
|
organizationAllowList,
|
|
)
|
|
|
|
if (organizationAllowListError) {
|
|
return organizationAllowListError.message
|
|
}
|
|
|
|
return validateDynamicProviderModelId(apiConfiguration, routerModels)
|
|
}
|
|
|
|
function validateModelsAndKeysProvided(apiConfiguration: ProviderSettings): string | undefined {
|
|
switch (apiConfiguration.apiProvider) {
|
|
case "openrouter":
|
|
if (!apiConfiguration.openRouterApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "requesty":
|
|
if (!apiConfiguration.requestyApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "unbound":
|
|
if (!apiConfiguration.unboundApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "litellm":
|
|
if (!apiConfiguration.litellmApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "anthropic":
|
|
if (!apiConfiguration.apiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "bedrock":
|
|
if (!apiConfiguration.awsRegion) {
|
|
return i18next.t("settings:validation.awsRegion")
|
|
}
|
|
break
|
|
case "vertex":
|
|
if (!apiConfiguration.vertexProjectId || !apiConfiguration.vertexRegion) {
|
|
return i18next.t("settings:validation.googleCloud")
|
|
}
|
|
break
|
|
case "gemini":
|
|
if (!apiConfiguration.geminiApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "openai-native":
|
|
if (!apiConfiguration.openAiNativeApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "mistral":
|
|
if (!apiConfiguration.mistralApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "openai":
|
|
if (!apiConfiguration.openAiBaseUrl || !apiConfiguration.openAiApiKey || !apiConfiguration.openAiModelId) {
|
|
return i18next.t("settings:validation.openAi")
|
|
}
|
|
break
|
|
case "ollama":
|
|
if (!apiConfiguration.ollamaModelId) {
|
|
return i18next.t("settings:validation.modelId")
|
|
}
|
|
break
|
|
case "lmstudio":
|
|
if (!apiConfiguration.lmStudioModelId) {
|
|
return i18next.t("settings:validation.modelId")
|
|
}
|
|
break
|
|
case "vscode-lm":
|
|
if (!apiConfiguration.vsCodeLmModelSelector) {
|
|
return i18next.t("settings:validation.modelSelector")
|
|
}
|
|
break
|
|
case "fireworks":
|
|
if (!apiConfiguration.fireworksApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "qwen-code":
|
|
if (!apiConfiguration.qwenCodeOauthPath) {
|
|
return i18next.t("settings:validation.qwenCodeOauthPath")
|
|
}
|
|
break
|
|
case "vercel-ai-gateway":
|
|
if (!apiConfiguration.vercelAiGatewayApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
case "baseten":
|
|
if (!apiConfiguration.basetenApiKey) {
|
|
return i18next.t("settings:validation.apiKey")
|
|
}
|
|
break
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
type ValidationError = {
|
|
message: string
|
|
code: "PROVIDER_NOT_ALLOWED" | "MODEL_NOT_ALLOWED"
|
|
}
|
|
|
|
function validateProviderAgainstOrganizationSettings(
|
|
apiConfiguration: ProviderSettings,
|
|
organizationAllowList?: OrganizationAllowList,
|
|
): ValidationError | undefined {
|
|
if (organizationAllowList && !organizationAllowList.allowAll) {
|
|
const provider = apiConfiguration.apiProvider
|
|
|
|
if (!provider) {
|
|
return undefined
|
|
}
|
|
|
|
const providerConfig = organizationAllowList.providers[provider]
|
|
|
|
if (!providerConfig) {
|
|
return {
|
|
message: i18next.t("settings:validation.providerNotAllowed", { provider }),
|
|
code: "PROVIDER_NOT_ALLOWED",
|
|
}
|
|
}
|
|
|
|
if (!providerConfig.allowAll) {
|
|
const activeProvider = isRetiredProvider(provider) ? undefined : provider
|
|
const modelId = activeProvider ? getModelIdForProvider(apiConfiguration, activeProvider) : undefined
|
|
const allowedModels = providerConfig.models || []
|
|
|
|
if (modelId && !allowedModels.includes(modelId)) {
|
|
return {
|
|
message: i18next.t("settings:validation.modelNotAllowed", {
|
|
model: modelId,
|
|
provider,
|
|
}),
|
|
code: "MODEL_NOT_ALLOWED",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function getModelIdForProvider(apiConfiguration: ProviderSettings, provider: ProviderName): string | undefined {
|
|
if (provider === "vscode-lm") {
|
|
return apiConfiguration.vsCodeLmModelSelector?.id
|
|
}
|
|
|
|
if (isCustomProvider(provider) || isFauxProvider(provider)) {
|
|
return apiConfiguration.apiModelId
|
|
}
|
|
|
|
return apiConfiguration[modelIdKeysByProvider[provider]]
|
|
}
|
|
|
|
/**
|
|
* Validates an Amazon Bedrock ARN and optionally checks if the region in
|
|
* the ARN matches the provided region.
|
|
*
|
|
* Note: This function does not perform strict format validation on the ARN.
|
|
* Users entering custom ARNs are advanced users who should be trusted to
|
|
* provide valid ARNs without restriction. See issue #10108.
|
|
*
|
|
* @param arn The ARN string to validate
|
|
* @param region Optional region to check against the ARN's region
|
|
* @returns An object with validation results: { isValid, arnRegion, errorMessage }
|
|
*/
|
|
export function validateBedrockArn(arn: string, region?: string) {
|
|
// Try to extract region from ARN for region mismatch warning.
|
|
// This is a permissive regex that attempts to find the region component
|
|
// without enforcing strict ARN format validation.
|
|
const regionMatch = arn.match(/^arn:[^:]+:[^:]+:([^:]+):/)
|
|
const arnRegion = regionMatch?.[1]
|
|
|
|
// Check if region in ARN matches provided region (if specified).
|
|
if (region && arnRegion && arnRegion !== region) {
|
|
return {
|
|
isValid: true,
|
|
arnRegion,
|
|
errorMessage: i18next.t("settings:validation.arn.regionMismatch", { arnRegion, region }),
|
|
}
|
|
}
|
|
|
|
// ARN is always considered valid - trust the user to enter valid ARNs.
|
|
return { isValid: true, arnRegion, errorMessage: undefined }
|
|
}
|
|
|
|
function validateDynamicProviderModelId(
|
|
apiConfiguration: ProviderSettings,
|
|
routerModels?: RouterModels,
|
|
): string | undefined {
|
|
const provider = apiConfiguration.apiProvider ?? ""
|
|
|
|
// We only validate model ids from dynamic providers.
|
|
if (!isDynamicProvider(provider)) {
|
|
return undefined
|
|
}
|
|
|
|
const modelId = getModelIdForProvider(apiConfiguration, provider)
|
|
|
|
if (!modelId) {
|
|
return i18next.t("settings:validation.modelId")
|
|
}
|
|
|
|
const models = routerModels?.[provider]
|
|
|
|
if (models && Object.keys(models).length > 1 && !Object.keys(models).includes(modelId)) {
|
|
return i18next.t("settings:validation.modelAvailability", { modelId })
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Extracts model-specific validation errors from the API configuration.
|
|
* This is used to show model errors specifically in the model selector components.
|
|
*/
|
|
export function getModelValidationError(
|
|
apiConfiguration: ProviderSettings,
|
|
routerModels?: RouterModels,
|
|
organizationAllowList?: OrganizationAllowList,
|
|
): string | undefined {
|
|
const modelId = isProviderName(apiConfiguration.apiProvider)
|
|
? getModelIdForProvider(apiConfiguration, apiConfiguration.apiProvider)
|
|
: apiConfiguration.apiModelId
|
|
|
|
const configWithModelId = {
|
|
...apiConfiguration,
|
|
apiModelId: modelId || "",
|
|
}
|
|
|
|
const orgError = validateProviderAgainstOrganizationSettings(configWithModelId, organizationAllowList)
|
|
|
|
if (orgError && orgError.code === "MODEL_NOT_ALLOWED") {
|
|
return orgError.message
|
|
}
|
|
|
|
return validateDynamicProviderModelId(configWithModelId, routerModels)
|
|
}
|
|
|
|
/**
|
|
* Validates API configuration but excludes model-specific errors.
|
|
* This is used for the general API error display to prevent duplication
|
|
* when model errors are shown in the model selector.
|
|
*/
|
|
export function validateApiConfigurationExcludingModelErrors(
|
|
apiConfiguration: ProviderSettings,
|
|
_routerModels?: RouterModels, // Keeping this for compatibility with the old function.
|
|
organizationAllowList?: OrganizationAllowList,
|
|
): string | undefined {
|
|
const keysAndIdsPresentErrorMessage = validateModelsAndKeysProvided(apiConfiguration)
|
|
|
|
if (keysAndIdsPresentErrorMessage) {
|
|
return keysAndIdsPresentErrorMessage
|
|
}
|
|
|
|
const organizationAllowListError = validateProviderAgainstOrganizationSettings(
|
|
apiConfiguration,
|
|
organizationAllowList,
|
|
)
|
|
|
|
// Only return organization errors if they're not model-specific.
|
|
if (organizationAllowListError && organizationAllowListError.code === "PROVIDER_NOT_ALLOWED") {
|
|
return organizationAllowListError.message
|
|
}
|
|
|
|
// Skip model validation errors as they'll be shown in the model selector.
|
|
return undefined
|
|
}
|