mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
- Add ENV-configurable model capabilities (tools, reasoning, vision, imageGeneration) via PROVIDER_SUPPORTS_* environment variables - Add optional stream usage reporting via GENERIC_OPEN_AI_REPORT_USAGE - Fix streaming tool calls for providers that send null tool_call.id (e.g., mlx-server) by generating fallback UUIDs - Refactor supportsNativeToolCalling() to use centralized capabilities API
1378 lines
35 KiB
JavaScript
1378 lines
35 KiB
JavaScript
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: "AZURE_OPENAI_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],
|
|
},
|
|
AnthropicCacheControl: {
|
|
envKey: "ANTHROPIC_CACHE_CONTROL",
|
|
checks: [
|
|
(input) =>
|
|
["none", "5m", "1h"].includes(input)
|
|
? null
|
|
: "Invalid cache control. Must be one of: 5m, 1h.",
|
|
],
|
|
},
|
|
|
|
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: [],
|
|
},
|
|
LMStudioAuthToken: {
|
|
envKey: "LMSTUDIO_AUTH_TOKEN",
|
|
checks: [],
|
|
},
|
|
|
|
// 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: [],
|
|
},
|
|
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: [],
|
|
},
|
|
AwsBedrockLLMAccessKey: {
|
|
envKey: "AWS_BEDROCK_LLM_ACCESS_KEY",
|
|
checks: [],
|
|
},
|
|
AwsBedrockLLMSessionToken: {
|
|
envKey: "AWS_BEDROCK_LLM_SESSION_TOKEN",
|
|
checks: [],
|
|
},
|
|
AwsBedrockLLMAPIKey: {
|
|
envKey: "AWS_BEDROCK_LLM_API_KEY",
|
|
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],
|
|
},
|
|
EmbeddingOutputDimensions: {
|
|
envKey: "EMBEDDING_OUTPUT_DIMENSIONS",
|
|
checks: [],
|
|
},
|
|
OllamaEmbeddingBatchSize: {
|
|
envKey: "OLLAMA_EMBEDDING_BATCH_SIZE",
|
|
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
|
|
AgentSerpApiKey: {
|
|
envKey: "AGENT_SERPAPI_API_KEY",
|
|
checks: [],
|
|
},
|
|
AgentSerpApiEngine: {
|
|
envKey: "AGENT_SERPAPI_ENGINE",
|
|
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: [],
|
|
},
|
|
AgentPerplexityApiKey: {
|
|
envKey: "AGENT_PERPLEXITY_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],
|
|
},
|
|
|
|
// Foundry Options
|
|
FoundryBasePath: {
|
|
envKey: "FOUNDRY_BASE_PATH",
|
|
checks: [isNotEmpty],
|
|
},
|
|
FoundryModelPref: {
|
|
envKey: "FOUNDRY_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
postUpdate: [
|
|
// On new model selection, re-cache the context windows
|
|
async (_, prevValue, __) => {
|
|
const { FoundryLLM } = require("../AiProviders/foundry");
|
|
await FoundryLLM.unloadModelFromEngine(prevValue);
|
|
await FoundryLLM.cacheContextWindows(true);
|
|
},
|
|
],
|
|
},
|
|
FoundryModelTokenLimit: {
|
|
envKey: "FOUNDRY_MODEL_TOKEN_LIMIT",
|
|
checks: [],
|
|
},
|
|
|
|
// 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: [],
|
|
},
|
|
|
|
// Z.AI Options
|
|
ZAiApiKey: {
|
|
envKey: "ZAI_API_KEY",
|
|
checks: [isNotEmpty],
|
|
},
|
|
ZAiModelPref: {
|
|
envKey: "ZAI_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
|
|
// GiteeAI Options
|
|
GiteeAIApiKey: {
|
|
envKey: "GITEE_AI_API_KEY",
|
|
checks: [isNotEmpty],
|
|
},
|
|
GiteeAIModelPref: {
|
|
envKey: "GITEE_AI_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
GiteeAITokenLimit: {
|
|
envKey: "GITEE_AI_MODEL_TOKEN_LIMIT",
|
|
checks: [nonZero],
|
|
},
|
|
|
|
// Docker Model Runner Options
|
|
DockerModelRunnerBasePath: {
|
|
envKey: "DOCKER_MODEL_RUNNER_BASE_PATH",
|
|
checks: [isValidURL],
|
|
},
|
|
DockerModelRunnerModelPref: {
|
|
envKey: "DOCKER_MODEL_RUNNER_LLM_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
DockerModelRunnerModelTokenLimit: {
|
|
envKey: "DOCKER_MODEL_RUNNER_LLM_MODEL_TOKEN_LIMIT",
|
|
checks: [nonZero],
|
|
},
|
|
|
|
// Privatemode Options
|
|
PrivateModeBasePath: {
|
|
envKey: "PRIVATEMODE_LLM_BASE_PATH",
|
|
checks: [isValidURL],
|
|
},
|
|
PrivateModeModelPref: {
|
|
envKey: "PRIVATEMODE_LLM_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
|
|
// SambaNova Options
|
|
SambaNovaLLMApiKey: {
|
|
envKey: "SAMBANOVA_LLM_API_KEY",
|
|
checks: [isNotEmpty],
|
|
},
|
|
SambaNovaLLMModelPref: {
|
|
envKey: "SAMBANOVA_LLM_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
|
|
// Lemonade Options
|
|
LemonadeLLMBasePath: {
|
|
envKey: "LEMONADE_LLM_BASE_PATH",
|
|
checks: [isValidURL],
|
|
},
|
|
LemonadeLLMApiKey: {
|
|
envKey: "LEMONADE_LLM_API_KEY",
|
|
checks: [],
|
|
},
|
|
LemonadeLLMModelPref: {
|
|
envKey: "LEMONADE_LLM_MODEL_PREF",
|
|
checks: [isNotEmpty],
|
|
},
|
|
LemonadeLLMModelTokenLimit: {
|
|
envKey: "LEMONADE_LLM_MODEL_TOKEN_LIMIT",
|
|
checks: [nonZero],
|
|
},
|
|
|
|
// Agent Skill Settings
|
|
AgentSkillMaxToolCalls: {
|
|
envKey: "AGENT_MAX_TOOL_CALLS",
|
|
checks: [nonZero],
|
|
},
|
|
AgentSkillRerankerEnabled: {
|
|
envKey: "AGENT_SKILL_RERANKER_ENABLED",
|
|
checks: [],
|
|
},
|
|
AgentSkillRerankerTopN: {
|
|
envKey: "AGENT_SKILL_RERANKER_TOP_N",
|
|
checks: [nonZero],
|
|
},
|
|
};
|
|
|
|
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 {
|
|
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",
|
|
"foundry",
|
|
"zai",
|
|
"giteeai",
|
|
"docker-model-runner",
|
|
"privatemode",
|
|
"sambanova",
|
|
"lemonade",
|
|
].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",
|
|
"openrouter",
|
|
"lemonade",
|
|
];
|
|
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 runAfterAll = [];
|
|
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 = [], // Functions to run before updating a specific ENV variable
|
|
postUpdate = [], // Functions to run after updating a specific ENV variable
|
|
postSettled = [], // Functions to run after all ENV variables have been updated
|
|
} = KEY_MAPPING[key];
|
|
runAfterAll.push(...postSettled);
|
|
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);
|
|
}
|
|
|
|
for (const runAfterAllFunc of runAfterAll)
|
|
await runAfterAllFunc(newValues, userId);
|
|
|
|
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",
|
|
// Custom headers for Generic OpenAI
|
|
"GENERIC_OPEN_AI_CUSTOM_HEADERS",
|
|
|
|
// Specify Chromium args for collector
|
|
"ANYTHINGLLM_CHROMIUM_ARGS",
|
|
|
|
// Allow setting a custom response timeout for Ollama
|
|
"OLLAMA_RESPONSE_TIMEOUT",
|
|
|
|
// Allow disabling of MCP tool cooldown
|
|
"MCP_NO_COOLDOWN",
|
|
|
|
// Allow disabling of streaming for AWS Bedrock
|
|
"AWS_BEDROCK_STREAMING_DISABLED",
|
|
|
|
// Allow capabilities for specific providers.
|
|
"PROVIDER_SUPPORTS_NATIVE_TOOL_CALLING",
|
|
"PROVIDER_SUPPORTS_REASONING",
|
|
"PROVIDER_SUPPORTS_IMAGE_GENERATION",
|
|
"PROVIDER_SUPPORTS_VISION",
|
|
"GENERIC_OPEN_AI_REPORT_USAGE",
|
|
|
|
// Allow auto-approval of skills
|
|
"AGENT_AUTO_APPROVED_SKILLS",
|
|
];
|
|
|
|
// 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,
|
|
};
|