const { Telemetry } = require("../../models/telemetry"); const { SUPPORTED_CONNECTION_METHODS, } = require("../AiProviders/bedrock/utils"); const { resetAllVectorStores } = require("../vectorStore/resetAllVectorStores"); const KEY_MAPPING = { LLMProvider: { envKey: "LLM_PROVIDER", checks: [isNotEmpty, supportedLLM], }, // OpenAI Settings OpenAiKey: { envKey: "OPEN_AI_KEY", checks: [isNotEmpty, validOpenAIKey], }, OpenAiModelPref: { envKey: "OPEN_MODEL_PREF", checks: [isNotEmpty], }, // Azure OpenAI Settings AzureOpenAiEndpoint: { envKey: "AZURE_OPENAI_ENDPOINT", checks: [isNotEmpty], }, AzureOpenAiTokenLimit: { envKey: "AZURE_OPENAI_TOKEN_LIMIT", checks: [validOpenAiTokenLimit], }, AzureOpenAiKey: { envKey: "AZURE_OPENAI_KEY", checks: [isNotEmpty], }, AzureOpenAiModelPref: { envKey: "OPEN_MODEL_PREF", checks: [isNotEmpty], }, AzureOpenAiEmbeddingModelPref: { envKey: "EMBEDDING_MODEL_PREF", checks: [isNotEmpty], }, AzureOpenAiModelType: { envKey: "AZURE_OPENAI_MODEL_TYPE", checks: [ (input) => ["default", "reasoning"].includes(input) ? null : "Invalid model type. Must be one of: default, reasoning.", ], }, // Anthropic Settings AnthropicApiKey: { envKey: "ANTHROPIC_API_KEY", checks: [isNotEmpty, validAnthropicApiKey], }, AnthropicModelPref: { envKey: "ANTHROPIC_MODEL_PREF", checks: [isNotEmpty], }, GeminiLLMApiKey: { envKey: "GEMINI_API_KEY", checks: [isNotEmpty], }, GeminiLLMModelPref: { envKey: "GEMINI_LLM_MODEL_PREF", checks: [isNotEmpty], }, GeminiSafetySetting: { envKey: "GEMINI_SAFETY_SETTING", checks: [validGeminiSafetySetting], }, // LMStudio Settings LMStudioBasePath: { envKey: "LMSTUDIO_BASE_PATH", checks: [isNotEmpty, validLLMExternalBasePath, validDockerizedUrl], }, LMStudioModelPref: { envKey: "LMSTUDIO_MODEL_PREF", checks: [], }, LMStudioTokenLimit: { envKey: "LMSTUDIO_MODEL_TOKEN_LIMIT", checks: [nonZero], }, // LocalAI Settings LocalAiBasePath: { envKey: "LOCAL_AI_BASE_PATH", checks: [isNotEmpty, validLLMExternalBasePath, validDockerizedUrl], }, LocalAiModelPref: { envKey: "LOCAL_AI_MODEL_PREF", checks: [], }, LocalAiTokenLimit: { envKey: "LOCAL_AI_MODEL_TOKEN_LIMIT", checks: [nonZero], }, LocalAiApiKey: { envKey: "LOCAL_AI_API_KEY", checks: [], }, OllamaLLMBasePath: { envKey: "OLLAMA_BASE_PATH", checks: [isNotEmpty, validOllamaLLMBasePath, validDockerizedUrl], }, OllamaLLMModelPref: { envKey: "OLLAMA_MODEL_PREF", checks: [], }, OllamaLLMTokenLimit: { envKey: "OLLAMA_MODEL_TOKEN_LIMIT", checks: [nonZero], }, OllamaLLMPerformanceMode: { envKey: "OLLAMA_PERFORMANCE_MODE", checks: [], }, OllamaLLMKeepAliveSeconds: { envKey: "OLLAMA_KEEP_ALIVE_TIMEOUT", checks: [isInteger], }, OllamaLLMAuthToken: { envKey: "OLLAMA_AUTH_TOKEN", checks: [], }, // Mistral AI API Settings MistralApiKey: { envKey: "MISTRAL_API_KEY", checks: [isNotEmpty], }, MistralModelPref: { envKey: "MISTRAL_MODEL_PREF", checks: [isNotEmpty], }, // Hugging Face LLM Inference Settings HuggingFaceLLMEndpoint: { envKey: "HUGGING_FACE_LLM_ENDPOINT", checks: [isNotEmpty, isValidURL, validHuggingFaceEndpoint], }, HuggingFaceLLMAccessToken: { envKey: "HUGGING_FACE_LLM_API_KEY", checks: [isNotEmpty], }, HuggingFaceLLMTokenLimit: { envKey: "HUGGING_FACE_LLM_TOKEN_LIMIT", checks: [nonZero], }, // KoboldCPP Settings KoboldCPPBasePath: { envKey: "KOBOLD_CPP_BASE_PATH", checks: [isNotEmpty, isValidURL], }, KoboldCPPModelPref: { envKey: "KOBOLD_CPP_MODEL_PREF", checks: [isNotEmpty], }, KoboldCPPTokenLimit: { envKey: "KOBOLD_CPP_MODEL_TOKEN_LIMIT", checks: [nonZero], }, KoboldCPPMaxTokens: { envKey: "KOBOLD_CPP_MAX_TOKENS", checks: [nonZero], }, // Text Generation Web UI Settings TextGenWebUIBasePath: { envKey: "TEXT_GEN_WEB_UI_BASE_PATH", checks: [isValidURL], }, TextGenWebUITokenLimit: { envKey: "TEXT_GEN_WEB_UI_MODEL_TOKEN_LIMIT", checks: [nonZero], }, TextGenWebUIAPIKey: { envKey: "TEXT_GEN_WEB_UI_API_KEY", checks: [], }, // LiteLLM Settings LiteLLMModelPref: { envKey: "LITE_LLM_MODEL_PREF", checks: [isNotEmpty], }, LiteLLMTokenLimit: { envKey: "LITE_LLM_MODEL_TOKEN_LIMIT", checks: [nonZero], }, LiteLLMBasePath: { envKey: "LITE_LLM_BASE_PATH", checks: [isValidURL], }, LiteLLMApiKey: { envKey: "LITE_LLM_API_KEY", checks: [], }, // Generic OpenAI InferenceSettings GenericOpenAiBasePath: { envKey: "GENERIC_OPEN_AI_BASE_PATH", checks: [isValidURL], }, GenericOpenAiModelPref: { envKey: "GENERIC_OPEN_AI_MODEL_PREF", checks: [isNotEmpty], }, GenericOpenAiTokenLimit: { envKey: "GENERIC_OPEN_AI_MODEL_TOKEN_LIMIT", checks: [nonZero], }, GenericOpenAiKey: { envKey: "GENERIC_OPEN_AI_API_KEY", checks: [], }, GenericOpenAiMaxTokens: { envKey: "GENERIC_OPEN_AI_MAX_TOKENS", checks: [nonZero], }, // AWS Bedrock LLM InferenceSettings AwsBedrockLLMConnectionMethod: { envKey: "AWS_BEDROCK_LLM_CONNECTION_METHOD", checks: [ (input) => SUPPORTED_CONNECTION_METHODS.includes(input) ? null : "invalid Value", ], }, AwsBedrockLLMAccessKeyId: { envKey: "AWS_BEDROCK_LLM_ACCESS_KEY_ID", checks: [isNotEmpty], }, AwsBedrockLLMAccessKey: { envKey: "AWS_BEDROCK_LLM_ACCESS_KEY", checks: [isNotEmpty], }, AwsBedrockLLMSessionToken: { envKey: "AWS_BEDROCK_LLM_SESSION_TOKEN", checks: [], }, AwsBedrockLLMRegion: { envKey: "AWS_BEDROCK_LLM_REGION", checks: [isNotEmpty], }, AwsBedrockLLMModel: { envKey: "AWS_BEDROCK_LLM_MODEL_PREFERENCE", checks: [isNotEmpty], }, AwsBedrockLLMTokenLimit: { envKey: "AWS_BEDROCK_LLM_MODEL_TOKEN_LIMIT", checks: [nonZero], }, AwsBedrockLLMMaxOutputTokens: { envKey: "AWS_BEDROCK_LLM_MAX_OUTPUT_TOKENS", checks: [nonZero], }, // Dell Pro AI Studio Settings DellProAiStudioBasePath: { envKey: "DPAIS_LLM_BASE_PATH", checks: [isNotEmpty, validDockerizedUrl], }, DellProAiStudioModelPref: { envKey: "DPAIS_LLM_MODEL_PREF", checks: [isNotEmpty], }, DellProAiStudioTokenLimit: { envKey: "DPAIS_LLM_MODEL_TOKEN_LIMIT", checks: [nonZero], }, EmbeddingEngine: { envKey: "EMBEDDING_ENGINE", checks: [supportedEmbeddingModel], postUpdate: [handleVectorStoreReset], }, EmbeddingBasePath: { envKey: "EMBEDDING_BASE_PATH", checks: [isNotEmpty, validDockerizedUrl], }, EmbeddingModelPref: { envKey: "EMBEDDING_MODEL_PREF", checks: [isNotEmpty], postUpdate: [handleVectorStoreReset, downloadEmbeddingModelIfRequired], }, EmbeddingModelMaxChunkLength: { envKey: "EMBEDDING_MODEL_MAX_CHUNK_LENGTH", checks: [nonZero], }, // Gemini Embedding Settings GeminiEmbeddingApiKey: { envKey: "GEMINI_EMBEDDING_API_KEY", checks: [isNotEmpty], }, // Generic OpenAI Embedding Settings GenericOpenAiEmbeddingApiKey: { envKey: "GENERIC_OPEN_AI_EMBEDDING_API_KEY", checks: [], }, GenericOpenAiEmbeddingMaxConcurrentChunks: { envKey: "GENERIC_OPEN_AI_EMBEDDING_MAX_CONCURRENT_CHUNKS", checks: [nonZero], }, // Vector Database Selection Settings VectorDB: { envKey: "VECTOR_DB", checks: [isNotEmpty, supportedVectorDB], postUpdate: [handleVectorStoreReset], }, // Chroma Options ChromaEndpoint: { envKey: "CHROMA_ENDPOINT", checks: [isValidURL, validChromaURL, validDockerizedUrl], }, ChromaApiHeader: { envKey: "CHROMA_API_HEADER", checks: [], }, ChromaApiKey: { envKey: "CHROMA_API_KEY", checks: [], }, // ChromaCloud Options ChromaCloudApiKey: { envKey: "CHROMACLOUD_API_KEY", checks: [isNotEmpty], }, ChromaCloudTenant: { envKey: "CHROMACLOUD_TENANT", checks: [isNotEmpty], }, ChromaCloudDatabase: { envKey: "CHROMACLOUD_DATABASE", checks: [isNotEmpty], }, // Weaviate Options WeaviateEndpoint: { envKey: "WEAVIATE_ENDPOINT", checks: [isValidURL, validDockerizedUrl], }, WeaviateApiKey: { envKey: "WEAVIATE_API_KEY", checks: [], }, // QDrant Options QdrantEndpoint: { envKey: "QDRANT_ENDPOINT", checks: [isValidURL, validDockerizedUrl], }, QdrantApiKey: { envKey: "QDRANT_API_KEY", checks: [], }, PineConeKey: { envKey: "PINECONE_API_KEY", checks: [], }, PineConeIndex: { envKey: "PINECONE_INDEX", checks: [], }, // Milvus Options MilvusAddress: { envKey: "MILVUS_ADDRESS", checks: [isValidURL, validDockerizedUrl], }, MilvusUsername: { envKey: "MILVUS_USERNAME", checks: [isNotEmpty], }, MilvusPassword: { envKey: "MILVUS_PASSWORD", checks: [isNotEmpty], }, // Zilliz Cloud Options ZillizEndpoint: { envKey: "ZILLIZ_ENDPOINT", checks: [isValidURL], }, ZillizApiToken: { envKey: "ZILLIZ_API_TOKEN", checks: [isNotEmpty], }, // Astra DB Options AstraDBApplicationToken: { envKey: "ASTRA_DB_APPLICATION_TOKEN", checks: [isNotEmpty], }, AstraDBEndpoint: { envKey: "ASTRA_DB_ENDPOINT", checks: [isNotEmpty], }, /* PGVector Options - Does very simple validations - we should expand this in the future - to ensure the connection string is valid and the table name is valid - via direct query */ PGVectorConnectionString: { envKey: "PGVECTOR_CONNECTION_STRING", checks: [isNotEmpty, looksLikePostgresConnectionString], preUpdate: [validatePGVectorConnectionString], }, PGVectorTableName: { envKey: "PGVECTOR_TABLE_NAME", checks: [isNotEmpty], preUpdate: [validatePGVectorTableName], }, // Together Ai Options TogetherAiApiKey: { envKey: "TOGETHER_AI_API_KEY", checks: [isNotEmpty], }, TogetherAiModelPref: { envKey: "TOGETHER_AI_MODEL_PREF", checks: [isNotEmpty], }, // Fireworks AI Options FireworksAiLLMApiKey: { envKey: "FIREWORKS_AI_LLM_API_KEY", checks: [isNotEmpty], }, FireworksAiLLMModelPref: { envKey: "FIREWORKS_AI_LLM_MODEL_PREF", checks: [isNotEmpty], }, // Perplexity Options PerplexityApiKey: { envKey: "PERPLEXITY_API_KEY", checks: [isNotEmpty], }, PerplexityModelPref: { envKey: "PERPLEXITY_MODEL_PREF", checks: [isNotEmpty], }, // OpenRouter Options OpenRouterApiKey: { envKey: "OPENROUTER_API_KEY", checks: [isNotEmpty], }, OpenRouterModelPref: { envKey: "OPENROUTER_MODEL_PREF", checks: [isNotEmpty], }, OpenRouterTimeout: { envKey: "OPENROUTER_TIMEOUT_MS", checks: [], }, // Novita Options NovitaLLMApiKey: { envKey: "NOVITA_LLM_API_KEY", checks: [isNotEmpty], }, NovitaLLMModelPref: { envKey: "NOVITA_LLM_MODEL_PREF", checks: [isNotEmpty], }, NovitaLLMTimeout: { envKey: "NOVITA_LLM_TIMEOUT_MS", checks: [], }, // Groq Options GroqApiKey: { envKey: "GROQ_API_KEY", checks: [isNotEmpty], }, GroqModelPref: { envKey: "GROQ_MODEL_PREF", checks: [isNotEmpty], }, // Cohere Options CohereApiKey: { envKey: "COHERE_API_KEY", checks: [isNotEmpty], }, CohereModelPref: { envKey: "COHERE_MODEL_PREF", checks: [isNotEmpty], }, // VoyageAi Options VoyageAiApiKey: { envKey: "VOYAGEAI_API_KEY", checks: [isNotEmpty], }, // Whisper (transcription) providers WhisperProvider: { envKey: "WHISPER_PROVIDER", checks: [isNotEmpty, supportedTranscriptionProvider], postUpdate: [], }, WhisperModelPref: { envKey: "WHISPER_MODEL_PREF", checks: [validLocalWhisper], postUpdate: [], }, // System Settings AuthToken: { envKey: "AUTH_TOKEN", checks: [requiresForceMode, noRestrictedChars], }, JWTSecret: { envKey: "JWT_SECRET", checks: [requiresForceMode], }, DisableTelemetry: { envKey: "DISABLE_TELEMETRY", checks: [], preUpdate: [ (_, __, nextValue) => { if (nextValue === "true") Telemetry.sendTelemetry("telemetry_disabled"); }, ], }, // Agent Integration ENVs AgentGoogleSearchEngineId: { envKey: "AGENT_GSE_CTX", checks: [], }, AgentGoogleSearchEngineKey: { envKey: "AGENT_GSE_KEY", checks: [], }, AgentSearchApiKey: { envKey: "AGENT_SEARCHAPI_API_KEY", checks: [], }, AgentSearchApiEngine: { envKey: "AGENT_SEARCHAPI_ENGINE", checks: [], }, AgentSerperApiKey: { envKey: "AGENT_SERPER_DEV_KEY", checks: [], }, AgentBingSearchApiKey: { envKey: "AGENT_BING_SEARCH_API_KEY", checks: [], }, AgentSerplyApiKey: { envKey: "AGENT_SERPLY_API_KEY", checks: [], }, AgentSearXNGApiUrl: { envKey: "AGENT_SEARXNG_API_URL", checks: [], }, AgentTavilyApiKey: { envKey: "AGENT_TAVILY_API_KEY", checks: [], }, AgentExaApiKey: { envKey: "AGENT_EXA_API_KEY", checks: [], }, // TTS/STT Integration ENVS TextToSpeechProvider: { envKey: "TTS_PROVIDER", checks: [supportedTTSProvider], }, // TTS OpenAI TTSOpenAIKey: { envKey: "TTS_OPEN_AI_KEY", checks: [validOpenAIKey], }, TTSOpenAIVoiceModel: { envKey: "TTS_OPEN_AI_VOICE_MODEL", checks: [], }, // TTS ElevenLabs TTSElevenLabsKey: { envKey: "TTS_ELEVEN_LABS_KEY", checks: [isNotEmpty], }, TTSElevenLabsVoiceModel: { envKey: "TTS_ELEVEN_LABS_VOICE_MODEL", checks: [], }, // PiperTTS Local TTSPiperTTSVoiceModel: { envKey: "TTS_PIPER_VOICE_MODEL", checks: [], }, // OpenAI Generic TTS TTSOpenAICompatibleKey: { envKey: "TTS_OPEN_AI_COMPATIBLE_KEY", checks: [], }, TTSOpenAICompatibleModel: { envKey: "TTS_OPEN_AI_COMPATIBLE_MODEL", checks: [], }, TTSOpenAICompatibleVoiceModel: { envKey: "TTS_OPEN_AI_COMPATIBLE_VOICE_MODEL", checks: [isNotEmpty], }, TTSOpenAICompatibleEndpoint: { envKey: "TTS_OPEN_AI_COMPATIBLE_ENDPOINT", checks: [isValidURL], }, // DeepSeek Options DeepSeekApiKey: { envKey: "DEEPSEEK_API_KEY", checks: [isNotEmpty], }, DeepSeekModelPref: { envKey: "DEEPSEEK_MODEL_PREF", checks: [isNotEmpty], }, // APIPie Options ApipieLLMApiKey: { envKey: "APIPIE_LLM_API_KEY", checks: [isNotEmpty], }, ApipieLLMModelPref: { envKey: "APIPIE_LLM_MODEL_PREF", checks: [isNotEmpty], }, // xAI Options XAIApiKey: { envKey: "XAI_LLM_API_KEY", checks: [isNotEmpty], }, XAIModelPref: { envKey: "XAI_LLM_MODEL_PREF", checks: [isNotEmpty], }, // Nvidia NIM Options NvidiaNimLLMBasePath: { envKey: "NVIDIA_NIM_LLM_BASE_PATH", checks: [isValidURL], postUpdate: [ (_, __, nextValue) => { const { parseNvidiaNimBasePath } = require("../AiProviders/nvidiaNim"); process.env.NVIDIA_NIM_LLM_BASE_PATH = parseNvidiaNimBasePath(nextValue); }, ], }, NvidiaNimLLMModelPref: { envKey: "NVIDIA_NIM_LLM_MODEL_PREF", checks: [], postUpdate: [ async (_, __, nextValue) => { const { NvidiaNimLLM } = require("../AiProviders/nvidiaNim"); await NvidiaNimLLM.setModelTokenLimit(nextValue); }, ], }, // PPIO Options PPIOApiKey: { envKey: "PPIO_API_KEY", checks: [isNotEmpty], }, PPIOModelPref: { envKey: "PPIO_MODEL_PREF", checks: [isNotEmpty], }, // Moonshot AI Options MoonshotAiApiKey: { envKey: "MOONSHOT_AI_API_KEY", checks: [isNotEmpty], }, MoonshotAiModelPref: { envKey: "MOONSHOT_AI_MODEL_PREF", checks: [isNotEmpty], }, // CometAPI Options CometApiLLMApiKey: { envKey: "COMETAPI_LLM_API_KEY", checks: [isNotEmpty], }, CometApiLLMModelPref: { envKey: "COMETAPI_LLM_MODEL_PREF", checks: [isNotEmpty], }, CometApiLLMTimeout: { envKey: "COMETAPI_LLM_TIMEOUT_MS", checks: [], }, }; function isNotEmpty(input = "") { return !input || input.length === 0 ? "Value cannot be empty" : null; } function nonZero(input = "") { if (isNaN(Number(input))) return "Value must be a number"; return Number(input) <= 0 ? "Value must be greater than zero" : null; } function isInteger(input = "") { if (isNaN(Number(input))) return "Value must be a number"; return Number(input); } function isValidURL(input = "") { try { new URL(input); return null; } catch (e) { return "URL is not a valid URL."; } } function validOpenAIKey(input = "") { return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-"; } function validAnthropicApiKey(input = "") { return input.startsWith("sk-ant-") ? null : "Anthropic Key must start with sk-ant-"; } function validLLMExternalBasePath(input = "") { try { new URL(input); if (!input.includes("v1")) return "URL must include /v1"; if (input.split("").slice(-1)?.[0] === "/") return "URL cannot end with a slash"; return null; } catch { return "Not a valid URL"; } } function validOllamaLLMBasePath(input = "") { try { new URL(input); if (input.split("").slice(-1)?.[0] === "/") return "URL cannot end with a slash"; return null; } catch { return "Not a valid URL"; } } function supportedTTSProvider(input = "") { const validSelection = [ "native", "openai", "elevenlabs", "piper_local", "generic-openai", ].includes(input); return validSelection ? null : `${input} is not a valid TTS provider.`; } function validLocalWhisper(input = "") { const validSelection = [ "Xenova/whisper-small", "Xenova/whisper-large", ].includes(input); return validSelection ? null : `${input} is not a valid Whisper model selection.`; } function supportedLLM(input = "") { const validSelection = [ "openai", "azure", "anthropic", "gemini", "lmstudio", "localai", "ollama", "togetherai", "fireworksai", "mistral", "huggingface", "perplexity", "openrouter", "novita", "groq", "koboldcpp", "textgenwebui", "cohere", "litellm", "generic-openai", "bedrock", "deepseek", "apipie", "xai", "nvidia-nim", "ppio", "dpais", "moonshotai", "cometapi", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; } function supportedTranscriptionProvider(input = "") { const validSelection = ["openai", "local"].includes(input); return validSelection ? null : `${input} is not a valid transcription model provider.`; } function validGeminiSafetySetting(input = "") { const validModes = [ "BLOCK_NONE", "BLOCK_ONLY_HIGH", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_LOW_AND_ABOVE", ]; return validModes.includes(input) ? null : `Invalid Safety setting. Must be one of ${validModes.join(", ")}.`; } function supportedEmbeddingModel(input = "") { const supported = [ "openai", "azure", "gemini", "localai", "native", "ollama", "lmstudio", "cohere", "voyageai", "litellm", "generic-openai", "mistral", ]; return supported.includes(input) ? null : `Invalid Embedding model type. Must be one of ${supported.join(", ")}.`; } function supportedVectorDB(input = "") { const supported = [ "chroma", "chromacloud", "pinecone", "lancedb", "weaviate", "qdrant", "milvus", "zilliz", "astra", "pgvector", ]; return supported.includes(input) ? null : `Invalid VectorDB type. Must be one of ${supported.join(", ")}.`; } function validChromaURL(input = "") { return input.slice(-1) === "/" ? `Chroma Instance URL should not end in a trailing slash.` : null; } function validOpenAiTokenLimit(input = "") { const tokenLimit = Number(input); if (isNaN(tokenLimit)) return "Token limit is not a number"; return null; } function requiresForceMode(_, forceModeEnabled = false) { return forceModeEnabled === true ? null : "Cannot set this setting."; } async function validDockerizedUrl(input = "") { if (process.env.ANYTHING_LLM_RUNTIME !== "docker") return null; try { const { isPortInUse, getLocalHosts } = require("./portAvailabilityChecker"); const localInterfaces = getLocalHosts(); const url = new URL(input); const hostname = url.hostname.toLowerCase(); const port = parseInt(url.port, 10); // If not a loopback, skip this check. if (!localInterfaces.includes(hostname)) return null; if (isNaN(port)) return "Invalid URL: Port is not specified or invalid"; const isPortAvailableFromDocker = await isPortInUse(port, hostname); if (isPortAvailableFromDocker) return "Port is not running a reachable service on loopback address from inside the AnythingLLM container. Please use host.docker.internal (for linux use 172.17.0.1), a real machine ip, or domain to connect to your service."; } catch (error) { console.error(error.message); return "An error occurred while validating the URL"; } return null; } function validHuggingFaceEndpoint(input = "") { return input.slice(-6) !== ".cloud" ? `Your HF Endpoint should end in ".cloud"` : null; } function noRestrictedChars(input = "") { const regExp = new RegExp(/^[a-zA-Z0-9_\-!@$%^&*();]+$/); return !regExp.test(input) ? `Your password has restricted characters in it. Allowed symbols are _,-,!,@,$,%,^,&,*,(,),;` : null; } async function handleVectorStoreReset(key, prevValue, nextValue) { if (prevValue === nextValue) return; if (key === "VectorDB") { console.log( `Vector configuration changed from ${prevValue} to ${nextValue} - resetting ${prevValue} namespaces` ); return await resetAllVectorStores({ vectorDbKey: prevValue }); } if (key === "EmbeddingEngine" || key === "EmbeddingModelPref") { console.log( `${key} changed from ${prevValue} to ${nextValue} - resetting ${process.env.VECTOR_DB} namespaces` ); return await resetAllVectorStores({ vectorDbKey: process.env.VECTOR_DB }); } return false; } /** * Downloads the embedding model in background if the user has selected a different model * - Only supported for the native embedder * - Must have the native embedder selected prior (otherwise will download on embed) */ async function downloadEmbeddingModelIfRequired(key, prevValue, nextValue) { if (prevValue === nextValue) return; if (key !== "EmbeddingModelPref" || process.env.EMBEDDING_ENGINE !== "native") return; const { NativeEmbedder } = require("../EmbeddingEngines/native"); if (!NativeEmbedder.supportedModels[nextValue]) return; // if the model is not supported, don't download it new NativeEmbedder().embedderClient(); return false; } /** * Validates the Postgres connection string for the PGVector options. * @param {string} input - The Postgres connection string to validate. * @returns {string} - An error message if the connection string is invalid, otherwise null. */ async function looksLikePostgresConnectionString(connectionString = null) { if (!connectionString || !connectionString.startsWith("postgresql://")) return "Invalid Postgres connection string. Must start with postgresql://"; if (connectionString.includes(" ")) return "Invalid Postgres connection string. Must not contain spaces."; return null; } /** * Validates the Postgres connection string for the PGVector options. * @param {string} key - The ENV key we are validating. * @param {string} prevValue - The previous value of the key. * @param {string} nextValue - The next value of the key. * @returns {string} - An error message if the connection string is invalid, otherwise null. */ async function validatePGVectorConnectionString(key, prevValue, nextValue) { const envKey = KEY_MAPPING[key].envKey; if (prevValue === nextValue) return; // If the value is the same as the previous value, don't validate it. if (!nextValue) return; // If the value is not set, don't validate it. if (nextValue === process.env[envKey]) return; // If the value is the same as the current connection string, don't validate it. const { PGVector } = require("../vectorDbProviders/pgvector"); const { error, success } = await PGVector.validateConnection({ connectionString: nextValue, }); if (!success) return error; // Set the ENV variable for the PGVector connection string early so we can use it in the table check. process.env[envKey] = nextValue; return null; } /** * Validates the Postgres table name for the PGVector options. * - Table should not already exist in the database. * @param {string} key - The ENV key we are validating. * @param {string} prevValue - The previous value of the key. * @param {string} nextValue - The next value of the key. * @returns {string} - An error message if the table name is invalid, otherwise null. */ async function validatePGVectorTableName(key, prevValue, nextValue) { const envKey = KEY_MAPPING[key].envKey; if (prevValue === nextValue) return; // If the value is the same as the previous value, don't validate it. if (!nextValue) return; // If the value is not set, don't validate it. if (nextValue === process.env[envKey]) return; // If the value is the same as the current table name, don't validate it. if (!process.env.PGVECTOR_CONNECTION_STRING) return; // if connection string is not set, don't validate it since it will fail. const { PGVector } = require("../vectorDbProviders/pgvector"); const { error, success } = await PGVector.validateConnection({ connectionString: process.env.PGVECTOR_CONNECTION_STRING, tableName: nextValue, }); if (!success) return error; return null; } // This will force update .env variables which for any which reason were not able to be parsed or // read from an ENV file as this seems to be a complicating step for many so allowing people to write // to the process will at least alleviate that issue. It does not perform comprehensive validity checks or sanity checks // and is simply for debugging when the .env not found issue many come across. async function updateENV(newENVs = {}, force = false, userId = null) { let error = ""; const validKeys = Object.keys(KEY_MAPPING); const ENV_KEYS = Object.keys(newENVs).filter( (key) => validKeys.includes(key) && !newENVs[key].includes("******") // strip out answers where the value is all asterisks ); const newValues = {}; for (const key of ENV_KEYS) { const { envKey, checks, preUpdate = [], postUpdate = [], } = KEY_MAPPING[key]; const prevValue = process.env[envKey]; const nextValue = newENVs[key]; let errors = await executeValidationChecks(checks, nextValue, force); // If there are any errors from regular simple validation checks // exit early. if (errors.length > 0) { error += errors.join("\n"); break; } // Accumulate errors from preUpdate functions errors = []; for (const preUpdateFunc of preUpdate) { const errorMsg = await preUpdateFunc(key, prevValue, nextValue); if (!!errorMsg && typeof errorMsg === "string") errors.push(errorMsg); } // If there are any errors from preUpdate functions // exit early. if (errors.length > 0) { error += errors.join("\n"); break; } newValues[key] = nextValue; process.env[envKey] = nextValue; for (const postUpdateFunc of postUpdate) await postUpdateFunc(key, prevValue, nextValue); } await logChangesToEventLog(newValues, userId); if (process.env.NODE_ENV === "production") dumpENV(); return { newValues, error: error?.length > 0 ? error : false }; } async function executeValidationChecks(checks, value, force) { const results = await Promise.all( checks.map((validator) => validator(value, force)) ); return results.filter((err) => typeof err === "string"); } async function logChangesToEventLog(newValues = {}, userId = null) { const { EventLogs } = require("../../models/eventLogs"); const eventMapping = { LLMProvider: "update_llm_provider", EmbeddingEngine: "update_embedding_engine", VectorDB: "update_vector_db", }; for (const [key, eventName] of Object.entries(eventMapping)) { if (!newValues.hasOwnProperty(key)) continue; await EventLogs.logEvent(eventName, {}, userId); } return; } function dumpENV() { const fs = require("fs"); const path = require("path"); const frozenEnvs = {}; const protectedKeys = [ ...Object.values(KEY_MAPPING).map((values) => values.envKey), // Manually Add Keys here which are not already defined in KEY_MAPPING // and are either managed or manually set ENV key:values. "JWT_EXPIRY", "STORAGE_DIR", "SERVER_PORT", // For persistent data encryption "SIG_KEY", "SIG_SALT", // Password Schema Keys if present. "PASSWORDMINCHAR", "PASSWORDMAXCHAR", "PASSWORDLOWERCASE", "PASSWORDUPPERCASE", "PASSWORDNUMERIC", "PASSWORDSYMBOL", "PASSWORDREQUIREMENTS", // HTTPS SETUP KEYS "ENABLE_HTTPS", "HTTPS_CERT_PATH", "HTTPS_KEY_PATH", // Other Configuration Keys "DISABLE_VIEW_CHAT_HISTORY", // Simple SSO "SIMPLE_SSO_ENABLED", "SIMPLE_SSO_NO_LOGIN", "SIMPLE_SSO_NO_LOGIN_REDIRECT", // Community Hub "COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED", // Nvidia NIM Keys that are automatically managed "NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT", // OCR Language Support "TARGET_OCR_LANG", // Collector API common ENV - allows bypassing URL validation checks "COLLECTOR_ALLOW_ANY_IP", // Allow disabling of streaming for generic openai "GENERIC_OPENAI_STREAMING_DISABLED", // Specify Chromium args for collector "ANYTHINGLLM_CHROMIUM_ARGS", ]; // Simple sanitization of each value to prevent ENV injection via newline or quote escaping. function sanitizeValue(value) { const offendingChars = /[\n\r\t\v\f\u0085\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000"'`#]/; const firstOffendingCharIndex = value.search(offendingChars); if (firstOffendingCharIndex === -1) return value; return value.substring(0, firstOffendingCharIndex); } for (const key of protectedKeys) { const envValue = process.env?.[key] || null; if (!envValue) continue; frozenEnvs[key] = process.env?.[key] || null; } var envResult = `# Auto-dump ENV from system call on ${new Date().toTimeString()}\n`; envResult += Object.entries(frozenEnvs) .map(([key, value]) => `${key}='${sanitizeValue(value)}'`) .join("\n"); const envPath = path.join(__dirname, "../../.env"); fs.writeFileSync(envPath, envResult, { encoding: "utf8", flag: "w" }); return true; } module.exports = { dumpENV, updateENV, };