You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

641 lines
22 KiB

11 months ago
  1. process.env.NODE_ENV === "development"
  2. ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
  3. : require("dotenv").config();
  4. const { default: slugify } = require("slugify");
  5. const { isValidUrl, safeJsonParse } = require("../utils/http");
  6. const prisma = require("../utils/prisma");
  7. const { v4 } = require("uuid");
  8. const { MetaGenerator } = require("../utils/boot/MetaGenerator");
  9. function isNullOrNaN(value) {
  10. if (value === null) return true;
  11. return isNaN(value);
  12. }
  13. const SystemSettings = {
  14. protectedFields: ["multi_user_mode", "hub_api_key"],
  15. publicFields: [
  16. "footer_data",
  17. "support_email",
  18. "text_splitter_chunk_size",
  19. "text_splitter_chunk_overlap",
  20. "max_embed_chunk_size",
  21. "agent_search_provider",
  22. "agent_sql_connections",
  23. "default_agent_skills",
  24. "disabled_agent_skills",
  25. "imported_agent_skills",
  26. "custom_app_name",
  27. "feature_flags",
  28. "meta_page_title",
  29. "meta_page_favicon",
  30. ],
  31. supportedFields: [
  32. "logo_filename",
  33. "telemetry_id",
  34. "footer_data",
  35. "support_email",
  36. "text_splitter_chunk_size",
  37. "text_splitter_chunk_overlap",
  38. "agent_search_provider",
  39. "default_agent_skills",
  40. "disabled_agent_skills",
  41. "agent_sql_connections",
  42. "custom_app_name",
  43. // Meta page customization
  44. "meta_page_title",
  45. "meta_page_favicon",
  46. // beta feature flags
  47. "experimental_live_file_sync",
  48. // Hub settings
  49. "hub_api_key",
  50. ],
  51. validations: {
  52. footer_data: (updates) => {
  53. try {
  54. const array = JSON.parse(updates)
  55. .filter((setting) => isValidUrl(setting.url))
  56. .slice(0, 3); // max of 3 items in footer.
  57. return JSON.stringify(array);
  58. } catch (e) {
  59. console.error(`Failed to run validation function on footer_data`);
  60. return JSON.stringify([]);
  61. }
  62. },
  63. text_splitter_chunk_size: (update) => {
  64. try {
  65. if (isNullOrNaN(update)) throw new Error("Value is not a number.");
  66. if (Number(update) <= 0) throw new Error("Value must be non-zero.");
  67. return Number(update);
  68. } catch (e) {
  69. console.error(
  70. `Failed to run validation function on text_splitter_chunk_size`,
  71. e.message
  72. );
  73. return 1000;
  74. }
  75. },
  76. text_splitter_chunk_overlap: (update) => {
  77. try {
  78. if (isNullOrNaN(update)) throw new Error("Value is not a number");
  79. if (Number(update) < 0) throw new Error("Value cannot be less than 0.");
  80. return Number(update);
  81. } catch (e) {
  82. console.error(
  83. `Failed to run validation function on text_splitter_chunk_overlap`,
  84. e.message
  85. );
  86. return 20;
  87. }
  88. },
  89. agent_search_provider: (update) => {
  90. try {
  91. if (update === "none") return null;
  92. if (
  93. ![
  94. "google-search-engine",
  95. "searchapi",
  96. "serper-dot-dev",
  97. "bing-search",
  98. "serply-engine",
  99. "searxng-engine",
  100. "tavily-search",
  101. "duckduckgo-engine",
  102. ].includes(update)
  103. )
  104. throw new Error("Invalid SERP provider.");
  105. return String(update);
  106. } catch (e) {
  107. console.error(
  108. `Failed to run validation function on agent_search_provider`,
  109. e.message
  110. );
  111. return null;
  112. }
  113. },
  114. default_agent_skills: (updates) => {
  115. try {
  116. const skills = updates.split(",").filter((skill) => !!skill);
  117. return JSON.stringify(skills);
  118. } catch (e) {
  119. console.error(`Could not validate agent skills.`);
  120. return JSON.stringify([]);
  121. }
  122. },
  123. disabled_agent_skills: (updates) => {
  124. try {
  125. const skills = updates.split(",").filter((skill) => !!skill);
  126. return JSON.stringify(skills);
  127. } catch (e) {
  128. console.error(`Could not validate disabled agent skills.`);
  129. return JSON.stringify([]);
  130. }
  131. },
  132. agent_sql_connections: async (updates) => {
  133. const existingConnections = safeJsonParse(
  134. (await SystemSettings.get({ label: "agent_sql_connections" }))?.value,
  135. []
  136. );
  137. try {
  138. const updatedConnections = mergeConnections(
  139. existingConnections,
  140. safeJsonParse(updates, [])
  141. );
  142. return JSON.stringify(updatedConnections);
  143. } catch (e) {
  144. console.error(`Failed to merge connections`);
  145. return JSON.stringify(existingConnections ?? []);
  146. }
  147. },
  148. experimental_live_file_sync: (update) => {
  149. if (typeof update === "boolean")
  150. return update === true ? "enabled" : "disabled";
  151. if (!["enabled", "disabled"].includes(update)) return "disabled";
  152. return String(update);
  153. },
  154. meta_page_title: (newTitle) => {
  155. try {
  156. if (typeof newTitle !== "string" || !newTitle) return null;
  157. return String(newTitle);
  158. } catch {
  159. return null;
  160. } finally {
  161. new MetaGenerator().clearConfig();
  162. }
  163. },
  164. meta_page_favicon: (faviconUrl) => {
  165. if (!faviconUrl) return null;
  166. try {
  167. const url = new URL(faviconUrl);
  168. return url.toString();
  169. } catch {
  170. return null;
  171. } finally {
  172. new MetaGenerator().clearConfig();
  173. }
  174. },
  175. hub_api_key: (apiKey) => {
  176. if (!apiKey) return null;
  177. return String(apiKey);
  178. },
  179. },
  180. currentSettings: async function () {
  181. const { hasVectorCachedFiles } = require("../utils/files");
  182. const llmProvider = process.env.LLM_PROVIDER;
  183. const vectorDB = process.env.VECTOR_DB;
  184. return {
  185. // --------------------------------------------------------
  186. // General Settings
  187. // --------------------------------------------------------
  188. RequiresAuth: !!process.env.AUTH_TOKEN,
  189. AuthToken: !!process.env.AUTH_TOKEN,
  190. JWTSecret: !!process.env.JWT_SECRET,
  191. StorageDir: process.env.STORAGE_DIR,
  192. MultiUserMode: await this.isMultiUserMode(),
  193. DisableTelemetry: process.env.DISABLE_TELEMETRY || "false",
  194. // --------------------------------------------------------
  195. // Embedder Provider Selection Settings & Configs
  196. // --------------------------------------------------------
  197. EmbeddingEngine: process.env.EMBEDDING_ENGINE,
  198. HasExistingEmbeddings: await this.hasEmbeddings(), // check if they have any currently embedded documents active in workspaces.
  199. HasCachedEmbeddings: hasVectorCachedFiles(), // check if they any currently cached embedded docs.
  200. EmbeddingBasePath: process.env.EMBEDDING_BASE_PATH,
  201. EmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
  202. EmbeddingModelMaxChunkLength:
  203. process.env.EMBEDDING_MODEL_MAX_CHUNK_LENGTH,
  204. VoyageAiApiKey: !!process.env.VOYAGEAI_API_KEY,
  205. GenericOpenAiEmbeddingApiKey:
  206. !!process.env.GENERIC_OPEN_AI_EMBEDDING_API_KEY,
  207. GenericOpenAiEmbeddingMaxConcurrentChunks:
  208. process.env.GENERIC_OPEN_AI_EMBEDDING_MAX_CONCURRENT_CHUNKS || 500,
  209. GeminiEmbeddingApiKey: !!process.env.GEMINI_EMBEDDING_API_KEY,
  210. // --------------------------------------------------------
  211. // VectorDB Provider Selection Settings & Configs
  212. // --------------------------------------------------------
  213. VectorDB: vectorDB,
  214. ...this.vectorDBPreferenceKeys(),
  215. // --------------------------------------------------------
  216. // LLM Provider Selection Settings & Configs
  217. // --------------------------------------------------------
  218. LLMProvider: llmProvider,
  219. ...this.llmPreferenceKeys(),
  220. // --------------------------------------------------------
  221. // Whisper (Audio transcription) Selection Settings & Configs
  222. // - Currently the only 3rd party is OpenAI, so is OPEN_AI_KEY is set
  223. // - then it can be shared.
  224. // --------------------------------------------------------
  225. WhisperProvider: process.env.WHISPER_PROVIDER || "local",
  226. WhisperModelPref:
  227. process.env.WHISPER_MODEL_PREF || "Xenova/whisper-small",
  228. // --------------------------------------------------------
  229. // TTS/STT Selection Settings & Configs
  230. // - Currently the only 3rd party is OpenAI or the native browser-built in
  231. // --------------------------------------------------------
  232. TextToSpeechProvider: process.env.TTS_PROVIDER || "native",
  233. TTSOpenAIKey: !!process.env.TTS_OPEN_AI_KEY,
  234. TTSOpenAIVoiceModel: process.env.TTS_OPEN_AI_VOICE_MODEL,
  235. // Eleven Labs TTS
  236. TTSElevenLabsKey: !!process.env.TTS_ELEVEN_LABS_KEY,
  237. TTSElevenLabsVoiceModel: process.env.TTS_ELEVEN_LABS_VOICE_MODEL,
  238. // Piper TTS
  239. TTSPiperTTSVoiceModel:
  240. process.env.TTS_PIPER_VOICE_MODEL ?? "en_US-hfc_female-medium",
  241. // OpenAI Generic TTS
  242. TTSOpenAICompatibleKey: !!process.env.TTS_OPEN_AI_COMPATIBLE_KEY,
  243. TTSOpenAICompatibleVoiceModel:
  244. process.env.TTS_OPEN_AI_COMPATIBLE_VOICE_MODEL,
  245. TTSOpenAICompatibleEndpoint: process.env.TTS_OPEN_AI_COMPATIBLE_ENDPOINT,
  246. // --------------------------------------------------------
  247. // Agent Settings & Configs
  248. // --------------------------------------------------------
  249. AgentGoogleSearchEngineId: process.env.AGENT_GSE_CTX || null,
  250. AgentGoogleSearchEngineKey: !!process.env.AGENT_GSE_KEY || null,
  251. AgentSearchApiKey: !!process.env.AGENT_SEARCHAPI_API_KEY || null,
  252. AgentSearchApiEngine: process.env.AGENT_SEARCHAPI_ENGINE || "google",
  253. AgentSerperApiKey: !!process.env.AGENT_SERPER_DEV_KEY || null,
  254. AgentBingSearchApiKey: !!process.env.AGENT_BING_SEARCH_API_KEY || null,
  255. AgentSerplyApiKey: !!process.env.AGENT_SERPLY_API_KEY || null,
  256. AgentSearXNGApiUrl: process.env.AGENT_SEARXNG_API_URL || null,
  257. AgentTavilyApiKey: !!process.env.AGENT_TAVILY_API_KEY || null,
  258. // --------------------------------------------------------
  259. // Compliance Settings
  260. // --------------------------------------------------------
  261. // Disable View Chat History for the whole instance.
  262. DisableViewChatHistory:
  263. "DISABLE_VIEW_CHAT_HISTORY" in process.env || false,
  264. };
  265. },
  266. get: async function (clause = {}) {
  267. try {
  268. const setting = await prisma.system_settings.findFirst({ where: clause });
  269. return setting || null;
  270. } catch (error) {
  271. console.error(error.message);
  272. return null;
  273. }
  274. },
  275. getValueOrFallback: async function (clause = {}, fallback = null) {
  276. try {
  277. return (await this.get(clause))?.value ?? fallback;
  278. } catch (error) {
  279. console.error(error.message);
  280. return fallback;
  281. }
  282. },
  283. where: async function (clause = {}, limit) {
  284. try {
  285. const settings = await prisma.system_settings.findMany({
  286. where: clause,
  287. take: limit || undefined,
  288. });
  289. return settings;
  290. } catch (error) {
  291. console.error(error.message);
  292. return [];
  293. }
  294. },
  295. // Can take generic keys and will pre-filter invalid keys
  296. // from the set before sending to the explicit update function
  297. // that will then enforce validations as well.
  298. updateSettings: async function (updates = {}) {
  299. const validFields = Object.keys(updates).filter((key) =>
  300. this.supportedFields.includes(key)
  301. );
  302. Object.entries(updates).forEach(([key]) => {
  303. if (validFields.includes(key)) return;
  304. delete updates[key];
  305. });
  306. return this._updateSettings(updates);
  307. },
  308. // Explicit update of settings + key validations.
  309. // Only use this method when directly setting a key value
  310. // that takes no user input for the keys being modified.
  311. _updateSettings: async function (updates = {}) {
  312. try {
  313. const updatePromises = [];
  314. for (const key of Object.keys(updates)) {
  315. let validatedValue = updates[key];
  316. if (this.validations.hasOwnProperty(key)) {
  317. if (this.validations[key].constructor.name === "AsyncFunction") {
  318. validatedValue = await this.validations[key](updates[key]);
  319. } else {
  320. validatedValue = this.validations[key](updates[key]);
  321. }
  322. }
  323. updatePromises.push(
  324. prisma.system_settings.upsert({
  325. where: { label: key },
  326. update: {
  327. value: validatedValue === null ? null : String(validatedValue),
  328. },
  329. create: {
  330. label: key,
  331. value: validatedValue === null ? null : String(validatedValue),
  332. },
  333. })
  334. );
  335. }
  336. await Promise.all(updatePromises);
  337. return { success: true, error: null };
  338. } catch (error) {
  339. console.error("FAILED TO UPDATE SYSTEM SETTINGS", error.message);
  340. return { success: false, error: error.message };
  341. }
  342. },
  343. isMultiUserMode: async function () {
  344. try {
  345. const setting = await this.get({ label: "multi_user_mode" });
  346. return setting?.value === "true";
  347. } catch (error) {
  348. console.error(error.message);
  349. return false;
  350. }
  351. },
  352. currentLogoFilename: async function () {
  353. try {
  354. const setting = await this.get({ label: "logo_filename" });
  355. return setting?.value || null;
  356. } catch (error) {
  357. console.error(error.message);
  358. return null;
  359. }
  360. },
  361. hasEmbeddings: async function () {
  362. try {
  363. const { Document } = require("./documents");
  364. const count = await Document.count({}, 1);
  365. return count > 0;
  366. } catch (error) {
  367. console.error(error.message);
  368. return false;
  369. }
  370. },
  371. vectorDBPreferenceKeys: function () {
  372. return {
  373. // Pinecone DB Keys
  374. PineConeKey: !!process.env.PINECONE_API_KEY,
  375. PineConeIndex: process.env.PINECONE_INDEX,
  376. // Chroma DB Keys
  377. ChromaEndpoint: process.env.CHROMA_ENDPOINT,
  378. ChromaApiHeader: process.env.CHROMA_API_HEADER,
  379. ChromaApiKey: !!process.env.CHROMA_API_KEY,
  380. // Weaviate DB Keys
  381. WeaviateEndpoint: process.env.WEAVIATE_ENDPOINT,
  382. WeaviateApiKey: process.env.WEAVIATE_API_KEY,
  383. // QDrant DB Keys
  384. QdrantEndpoint: process.env.QDRANT_ENDPOINT,
  385. QdrantApiKey: process.env.QDRANT_API_KEY,
  386. // Milvus DB Keys
  387. MilvusAddress: process.env.MILVUS_ADDRESS,
  388. MilvusUsername: process.env.MILVUS_USERNAME,
  389. MilvusPassword: !!process.env.MILVUS_PASSWORD,
  390. // Zilliz DB Keys
  391. ZillizEndpoint: process.env.ZILLIZ_ENDPOINT,
  392. ZillizApiToken: process.env.ZILLIZ_API_TOKEN,
  393. // AstraDB Keys
  394. AstraDBApplicationToken: process?.env?.ASTRA_DB_APPLICATION_TOKEN,
  395. AstraDBEndpoint: process?.env?.ASTRA_DB_ENDPOINT,
  396. };
  397. },
  398. llmPreferenceKeys: function () {
  399. return {
  400. // OpenAI Keys
  401. OpenAiKey: !!process.env.OPEN_AI_KEY,
  402. OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-4o",
  403. // Azure + OpenAI Keys
  404. AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
  405. AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY,
  406. AzureOpenAiModelPref: process.env.OPEN_MODEL_PREF,
  407. AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF,
  408. AzureOpenAiTokenLimit: process.env.AZURE_OPENAI_TOKEN_LIMIT || 4096,
  409. AzureOpenAiModelType: process.env.AZURE_OPENAI_MODEL_TYPE || "default",
  410. // Anthropic Keys
  411. AnthropicApiKey: !!process.env.ANTHROPIC_API_KEY,
  412. AnthropicModelPref: process.env.ANTHROPIC_MODEL_PREF || "claude-2",
  413. // Gemini Keys
  414. GeminiLLMApiKey: !!process.env.GEMINI_API_KEY,
  415. GeminiLLMModelPref: process.env.GEMINI_LLM_MODEL_PREF || "gemini-pro",
  416. GeminiSafetySetting:
  417. process.env.GEMINI_SAFETY_SETTING || "BLOCK_MEDIUM_AND_ABOVE",
  418. // LMStudio Keys
  419. LMStudioBasePath: process.env.LMSTUDIO_BASE_PATH,
  420. LMStudioTokenLimit: process.env.LMSTUDIO_MODEL_TOKEN_LIMIT,
  421. LMStudioModelPref: process.env.LMSTUDIO_MODEL_PREF,
  422. // LocalAI Keys
  423. LocalAiApiKey: !!process.env.LOCAL_AI_API_KEY,
  424. LocalAiBasePath: process.env.LOCAL_AI_BASE_PATH,
  425. LocalAiModelPref: process.env.LOCAL_AI_MODEL_PREF,
  426. LocalAiTokenLimit: process.env.LOCAL_AI_MODEL_TOKEN_LIMIT,
  427. // Ollama LLM Keys
  428. OllamaLLMBasePath: process.env.OLLAMA_BASE_PATH,
  429. OllamaLLMModelPref: process.env.OLLAMA_MODEL_PREF,
  430. OllamaLLMTokenLimit: process.env.OLLAMA_MODEL_TOKEN_LIMIT,
  431. OllamaLLMKeepAliveSeconds: process.env.OLLAMA_KEEP_ALIVE_TIMEOUT ?? 300,
  432. OllamaLLMPerformanceMode: process.env.OLLAMA_PERFORMANCE_MODE ?? "base",
  433. // Novita LLM Keys
  434. NovitaLLMApiKey: !!process.env.NOVITA_LLM_API_KEY,
  435. NovitaLLMModelPref: process.env.NOVITA_LLM_MODEL_PREF,
  436. NovitaLLMTimeout: process.env.NOVITA_LLM_TIMEOUT_MS,
  437. // TogetherAI Keys
  438. TogetherAiApiKey: !!process.env.TOGETHER_AI_API_KEY,
  439. TogetherAiModelPref: process.env.TOGETHER_AI_MODEL_PREF,
  440. // Fireworks AI API Keys
  441. FireworksAiLLMApiKey: !!process.env.FIREWORKS_AI_LLM_API_KEY,
  442. FireworksAiLLMModelPref: process.env.FIREWORKS_AI_LLM_MODEL_PREF,
  443. // Perplexity AI Keys
  444. PerplexityApiKey: !!process.env.PERPLEXITY_API_KEY,
  445. PerplexityModelPref: process.env.PERPLEXITY_MODEL_PREF,
  446. // OpenRouter Keys
  447. OpenRouterApiKey: !!process.env.OPENROUTER_API_KEY,
  448. OpenRouterModelPref: process.env.OPENROUTER_MODEL_PREF,
  449. OpenRouterTimeout: process.env.OPENROUTER_TIMEOUT_MS,
  450. // Mistral AI (API) Keys
  451. MistralApiKey: !!process.env.MISTRAL_API_KEY,
  452. MistralModelPref: process.env.MISTRAL_MODEL_PREF,
  453. // Groq AI API Keys
  454. GroqApiKey: !!process.env.GROQ_API_KEY,
  455. GroqModelPref: process.env.GROQ_MODEL_PREF,
  456. // HuggingFace Dedicated Inference
  457. HuggingFaceLLMEndpoint: process.env.HUGGING_FACE_LLM_ENDPOINT,
  458. HuggingFaceLLMAccessToken: !!process.env.HUGGING_FACE_LLM_API_KEY,
  459. HuggingFaceLLMTokenLimit: process.env.HUGGING_FACE_LLM_TOKEN_LIMIT,
  460. // KoboldCPP Keys
  461. KoboldCPPModelPref: process.env.KOBOLD_CPP_MODEL_PREF,
  462. KoboldCPPBasePath: process.env.KOBOLD_CPP_BASE_PATH,
  463. KoboldCPPTokenLimit: process.env.KOBOLD_CPP_MODEL_TOKEN_LIMIT,
  464. // Text Generation Web UI Keys
  465. TextGenWebUIBasePath: process.env.TEXT_GEN_WEB_UI_BASE_PATH,
  466. TextGenWebUITokenLimit: process.env.TEXT_GEN_WEB_UI_MODEL_TOKEN_LIMIT,
  467. TextGenWebUIAPIKey: !!process.env.TEXT_GEN_WEB_UI_API_KEY,
  468. // LiteLLM Keys
  469. LiteLLMModelPref: process.env.LITE_LLM_MODEL_PREF,
  470. LiteLLMTokenLimit: process.env.LITE_LLM_MODEL_TOKEN_LIMIT,
  471. LiteLLMBasePath: process.env.LITE_LLM_BASE_PATH,
  472. LiteLLMApiKey: !!process.env.LITE_LLM_API_KEY,
  473. // Generic OpenAI Keys
  474. GenericOpenAiBasePath: process.env.GENERIC_OPEN_AI_BASE_PATH,
  475. GenericOpenAiModelPref: process.env.GENERIC_OPEN_AI_MODEL_PREF,
  476. GenericOpenAiTokenLimit: process.env.GENERIC_OPEN_AI_MODEL_TOKEN_LIMIT,
  477. GenericOpenAiKey: !!process.env.GENERIC_OPEN_AI_API_KEY,
  478. GenericOpenAiMaxTokens: process.env.GENERIC_OPEN_AI_MAX_TOKENS,
  479. AwsBedrockLLMConnectionMethod:
  480. process.env.AWS_BEDROCK_LLM_CONNECTION_METHOD || "iam",
  481. AwsBedrockLLMAccessKeyId: !!process.env.AWS_BEDROCK_LLM_ACCESS_KEY_ID,
  482. AwsBedrockLLMAccessKey: !!process.env.AWS_BEDROCK_LLM_ACCESS_KEY,
  483. AwsBedrockLLMSessionToken: !!process.env.AWS_BEDROCK_LLM_SESSION_TOKEN,
  484. AwsBedrockLLMRegion: process.env.AWS_BEDROCK_LLM_REGION,
  485. AwsBedrockLLMModel: process.env.AWS_BEDROCK_LLM_MODEL_PREFERENCE,
  486. AwsBedrockLLMTokenLimit: process.env.AWS_BEDROCK_LLM_MODEL_TOKEN_LIMIT,
  487. // Cohere API Keys
  488. CohereApiKey: !!process.env.COHERE_API_KEY,
  489. CohereModelPref: process.env.COHERE_MODEL_PREF,
  490. // DeepSeek API Keys
  491. DeepSeekApiKey: !!process.env.DEEPSEEK_API_KEY,
  492. DeepSeekModelPref: process.env.DEEPSEEK_MODEL_PREF,
  493. // APIPie LLM API Keys
  494. ApipieLLMApiKey: !!process.env.APIPIE_LLM_API_KEY,
  495. ApipieLLMModelPref: process.env.APIPIE_LLM_MODEL_PREF,
  496. // xAI LLM API Keys
  497. XAIApiKey: !!process.env.XAI_LLM_API_KEY,
  498. XAIModelPref: process.env.XAI_LLM_MODEL_PREF,
  499. // NVIDIA NIM Keys
  500. NvidiaNimLLMBasePath: process.env.NVIDIA_NIM_LLM_BASE_PATH,
  501. NvidiaNimLLMModelPref: process.env.NVIDIA_NIM_LLM_MODEL_PREF,
  502. NvidiaNimLLMTokenLimit: process.env.NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT,
  503. };
  504. },
  505. // For special retrieval of a key setting that does not expose any credential information
  506. brief: {
  507. agent_sql_connections: async function () {
  508. const setting = await SystemSettings.get({
  509. label: "agent_sql_connections",
  510. });
  511. if (!setting) return [];
  512. return safeJsonParse(setting.value, []).map((dbConfig) => {
  513. const { connectionString, ...rest } = dbConfig;
  514. return rest;
  515. });
  516. },
  517. },
  518. getFeatureFlags: async function () {
  519. return {
  520. experimental_live_file_sync:
  521. (await SystemSettings.get({ label: "experimental_live_file_sync" }))
  522. ?.value === "enabled",
  523. };
  524. },
  525. /**
  526. * Get user configured Community Hub Settings
  527. * Connection key is used to authenticate with the Community Hub API
  528. * for your account.
  529. * @returns {Promise<{connectionKey: string}>}
  530. */
  531. hubSettings: async function () {
  532. try {
  533. const hubKey = await this.get({ label: "hub_api_key" });
  534. return { connectionKey: hubKey?.value || null };
  535. } catch (error) {
  536. console.error(error.message);
  537. return { connectionKey: null };
  538. }
  539. },
  540. };
  541. function mergeConnections(existingConnections = [], updates = []) {
  542. let updatedConnections = [...existingConnections];
  543. const existingDbIds = existingConnections.map((conn) => conn.database_id);
  544. // First remove all 'action:remove' candidates from existing connections.
  545. const toRemove = updates
  546. .filter((conn) => conn.action === "remove")
  547. .map((conn) => conn.database_id);
  548. updatedConnections = updatedConnections.filter(
  549. (conn) => !toRemove.includes(conn.database_id)
  550. );
  551. // Next add all 'action:add' candidates into the updatedConnections; We DO NOT validate the connection strings.
  552. // but we do validate their database_id is unique.
  553. updates
  554. .filter((conn) => conn.action === "add")
  555. .forEach((update) => {
  556. if (!update.connectionString) return; // invalid connection string
  557. // Remap name to be unique to entire set.
  558. if (existingDbIds.includes(update.database_id)) {
  559. update.database_id = slugify(
  560. `${update.database_id}-${v4().slice(0, 4)}`
  561. );
  562. } else {
  563. update.database_id = slugify(update.database_id);
  564. }
  565. updatedConnections.push({
  566. engine: update.engine,
  567. database_id: update.database_id,
  568. connectionString: update.connectionString,
  569. });
  570. });
  571. return updatedConnections;
  572. }
  573. module.exports.SystemSettings = SystemSettings;