diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index bc2e6042c..ebd94ca9b 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -9,7 +9,7 @@ const { getResponseSender, validateVisionModel, } = require('librechat-data-provider'); -const { SplitStreamHandler: _Handler, GraphEvents } = require('@librechat/agents'); +const { SplitStreamHandler: _Handler } = require('@librechat/agents'); const { truncateText, formatMessage, @@ -26,10 +26,11 @@ const { const { getModelMaxTokens, getModelMaxOutputTokens, matchModelName } = require('~/utils'); const { spendTokens, spendStructuredTokens } = require('~/models/spendTokens'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); +const { createFetch, createStreamEventHandlers } = require('./generators'); const Tokenizer = require('~/server/services/Tokenizer'); -const { logger, sendEvent } = require('~/config'); const { sleep } = require('~/server/utils'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); const HUMAN_PROMPT = '\n\nHuman:'; const AI_PROMPT = '\n\nAssistant:'; @@ -184,7 +185,10 @@ class AnthropicClient extends BaseClient { getClient(requestOptions) { /** @type {Anthropic.ClientOptions} */ const options = { - fetch: this.fetch, + fetch: createFetch({ + directEndpoint: this.options.directEndpoint, + reverseProxyUrl: this.options.reverseProxyUrl, + }), apiKey: this.apiKey, }; @@ -795,14 +799,11 @@ class AnthropicClient extends BaseClient { } logger.debug('[AnthropicClient]', { ...requestOptions }); + const handlers = createStreamEventHandlers(this.options.res); this.streamHandler = new SplitStreamHandler({ accumulate: true, runId: this.responseMessageId, - handlers: { - [GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event), - }, + handlers, }); let intermediateReply = this.streamHandler.tokens; diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index f89f1b3a8..0c9a6e4d7 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -28,15 +28,10 @@ class BaseClient { month: 'long', day: 'numeric', }); - this.fetch = this.fetch.bind(this); /** @type {boolean} */ this.skipSaveConvo = false; /** @type {boolean} */ this.skipSaveUserMessage = false; - /** @type {ClientDatabaseSavePromise} */ - this.userMessagePromise; - /** @type {ClientDatabaseSavePromise} */ - this.responsePromise; /** @type {string} */ this.user; /** @type {string} */ @@ -564,6 +559,8 @@ class BaseClient { } async sendMessage(message, opts = {}) { + /** @type {Promise} */ + let userMessagePromise; const { user, head, isEdited, conversationId, responseMessageId, saveOptions, userMessage } = await this.handleStartMethods(message, opts); @@ -625,11 +622,11 @@ class BaseClient { } if (!isEdited && !this.skipSaveUserMessage) { - this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); + userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); this.savedMessageIds.add(userMessage.messageId); if (typeof opts?.getReqData === 'function') { opts.getReqData({ - userMessagePromise: this.userMessagePromise, + userMessagePromise, }); } } @@ -655,7 +652,9 @@ class BaseClient { /** @type {string|string[]|undefined} */ const completion = await this.sendCompletion(payload, opts); - this.abortController.requestCompleted = true; + if (this.abortController) { + this.abortController.requestCompleted = true; + } /** @type {TMessage} */ const responseMessage = { @@ -703,7 +702,13 @@ class BaseClient { if (usage != null && Number(usage[this.outputTokensKey]) > 0) { responseMessage.tokenCount = usage[this.outputTokensKey]; completionTokens = responseMessage.tokenCount; - await this.updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts }); + await this.updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }); } else { responseMessage.tokenCount = this.getTokenCountForResponse(responseMessage); completionTokens = responseMessage.tokenCount; @@ -712,8 +717,8 @@ class BaseClient { await this.recordTokenUsage({ promptTokens, completionTokens, usage }); } - if (this.userMessagePromise) { - await this.userMessagePromise; + if (userMessagePromise) { + await userMessagePromise; } if (this.artifactPromises) { @@ -728,7 +733,11 @@ class BaseClient { } } - this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); + responseMessage.databasePromise = this.saveMessageToDatabase( + responseMessage, + saveOptions, + user, + ); this.savedMessageIds.add(responseMessage.messageId); delete responseMessage.tokenCount; return responseMessage; @@ -749,9 +758,16 @@ class BaseClient { * @param {StreamUsage} params.usage * @param {Record} params.tokenCountMap * @param {TMessage} params.userMessage + * @param {Promise} params.userMessagePromise * @param {object} params.opts */ - async updateUserMessageTokenCount({ usage, tokenCountMap, userMessage, opts }) { + async updateUserMessageTokenCount({ + usage, + tokenCountMap, + userMessage, + userMessagePromise, + opts, + }) { /** @type {boolean} */ const shouldUpdateCount = this.calculateCurrentTokenCount != null && @@ -787,7 +803,7 @@ class BaseClient { Note: we update the user message to be sure it gets the calculated token count; though `AskController` saves the user message, EditController does not */ - await this.userMessagePromise; + await userMessagePromise; await this.updateMessageInDatabase({ messageId: userMessage.messageId, tokenCount: userMessageTokenCount, @@ -853,7 +869,7 @@ class BaseClient { } const savedMessage = await saveMessage( - this.options.req, + this.options?.req, { ...message, endpoint: this.options.endpoint, @@ -877,7 +893,7 @@ class BaseClient { const existingConvo = this.fetchedConvo === true ? null - : await getConvo(this.options.req?.user?.id, message.conversationId); + : await getConvo(this.options?.req?.user?.id, message.conversationId); const unsetFields = {}; const exceptions = new Set(['spec', 'iconURL']); @@ -897,7 +913,7 @@ class BaseClient { } } - const conversation = await saveConvo(this.options.req, fieldsToKeep, { + const conversation = await saveConvo(this.options?.req, fieldsToKeep, { context: 'api/app/clients/BaseClient.js - saveMessageToDatabase #saveConvo', unsetFields, }); diff --git a/api/app/clients/ChatGPTClient.js b/api/app/clients/ChatGPTClient.js index 5450300a1..07b2fa97b 100644 --- a/api/app/clients/ChatGPTClient.js +++ b/api/app/clients/ChatGPTClient.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const crypto = require('crypto'); const { CohereClient } = require('cohere-ai'); const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); @@ -339,7 +339,7 @@ class ChatGPTClient extends BaseClient { opts.body = JSON.stringify(modelOptions); if (modelOptions.stream) { - // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { try { let done = false; diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 179f5c986..8c58d70f7 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -1,7 +1,7 @@ const OpenAI = require('openai'); const { OllamaClient } = require('./OllamaClient'); const { HttpsProxyAgent } = require('https-proxy-agent'); -const { SplitStreamHandler, GraphEvents } = require('@librechat/agents'); +const { SplitStreamHandler } = require('@librechat/agents'); const { Constants, ImageDetail, @@ -32,17 +32,18 @@ const { createContextHandlers, } = require('./prompts'); const { encodeAndFormat } = require('~/server/services/Files/images/encode'); +const { createFetch, createStreamEventHandlers } = require('./generators'); const { addSpaceIfNeeded, isEnabled, sleep } = require('~/server/utils'); const Tokenizer = require('~/server/services/Tokenizer'); const { spendTokens } = require('~/models/spendTokens'); const { handleOpenAIErrors } = require('./tools/util'); const { createLLM, RunManager } = require('./llm'); -const { logger, sendEvent } = require('~/config'); const ChatGPTClient = require('./ChatGPTClient'); const { summaryBuffer } = require('./memory'); const { runTitleChain } = require('./chains'); const { tokenSplit } = require('./document'); const BaseClient = require('./BaseClient'); +const { logger } = require('~/config'); class OpenAIClient extends BaseClient { constructor(apiKey, options = {}) { @@ -609,7 +610,7 @@ class OpenAIClient extends BaseClient { return result.trim(); } - logger.debug('[OpenAIClient] sendCompletion: result', result); + logger.debug('[OpenAIClient] sendCompletion: result', { ...result }); if (this.isChatCompletion) { reply = result.choices[0].message.content; @@ -818,7 +819,7 @@ ${convo} const completionTokens = this.getTokenCount(title); - this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' }); + await this.recordTokenUsage({ promptTokens, completionTokens, context: 'title' }); } catch (e) { logger.error( '[OpenAIClient] There was an issue generating the title with the completion method', @@ -1245,7 +1246,10 @@ ${convo} let chatCompletion; /** @type {OpenAI} */ const openai = new OpenAI({ - fetch: this.fetch, + fetch: createFetch({ + directEndpoint: this.options.directEndpoint, + reverseProxyUrl: this.options.reverseProxyUrl, + }), apiKey: this.apiKey, ...opts, }); @@ -1275,12 +1279,13 @@ ${convo} } if (this.options.addParams && typeof this.options.addParams === 'object') { + const addParams = { ...this.options.addParams }; modelOptions = { ...modelOptions, - ...this.options.addParams, + ...addParams, }; logger.debug('[OpenAIClient] chatCompletion: added params', { - addParams: this.options.addParams, + addParams: addParams, modelOptions, }); } @@ -1309,11 +1314,12 @@ ${convo} } if (this.options.dropParams && Array.isArray(this.options.dropParams)) { - this.options.dropParams.forEach((param) => { + const dropParams = [...this.options.dropParams]; + dropParams.forEach((param) => { delete modelOptions[param]; }); logger.debug('[OpenAIClient] chatCompletion: dropped params', { - dropParams: this.options.dropParams, + dropParams: dropParams, modelOptions, }); } @@ -1355,15 +1361,12 @@ ${convo} delete modelOptions.reasoning_effort; } + const handlers = createStreamEventHandlers(this.options.res); this.streamHandler = new SplitStreamHandler({ reasoningKey, accumulate: true, runId: this.responseMessageId, - handlers: { - [GraphEvents.ON_RUN_STEP]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_MESSAGE_DELTA]: (event) => sendEvent(this.options.res, event), - [GraphEvents.ON_REASONING_DELTA]: (event) => sendEvent(this.options.res, event), - }, + handlers, }); intermediateReply = this.streamHandler.tokens; diff --git a/api/app/clients/PluginsClient.js b/api/app/clients/PluginsClient.js index 60f8703e0..d0ffe2ef7 100644 --- a/api/app/clients/PluginsClient.js +++ b/api/app/clients/PluginsClient.js @@ -252,12 +252,14 @@ class PluginsClient extends OpenAIClient { await this.recordTokenUsage(responseMessage); } - this.responsePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); + const databasePromise = this.saveMessageToDatabase(responseMessage, saveOptions, user); delete responseMessage.tokenCount; - return { ...responseMessage, ...result }; + return { ...responseMessage, ...result, databasePromise }; } async sendMessage(message, opts = {}) { + /** @type {Promise} */ + let userMessagePromise; /** @type {{ filteredTools: string[], includedTools: string[] }} */ const { filteredTools = [], includedTools = [] } = this.options.req.app.locals; @@ -327,10 +329,10 @@ class PluginsClient extends OpenAIClient { } if (!this.skipSaveUserMessage) { - this.userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); + userMessagePromise = this.saveMessageToDatabase(userMessage, saveOptions, user); if (typeof opts?.getReqData === 'function') { opts.getReqData({ - userMessagePromise: this.userMessagePromise, + userMessagePromise, }); } } diff --git a/api/app/clients/generators.js b/api/app/clients/generators.js new file mode 100644 index 000000000..4d3988bf3 --- /dev/null +++ b/api/app/clients/generators.js @@ -0,0 +1,60 @@ +const { GraphEvents } = require('@librechat/agents'); +const { logger, sendEvent } = require('~/config'); + +/** + * Makes a function to make HTTP request and logs the process. + * @param {Object} params + * @param {boolean} [params.directEndpoint] - Whether to use a direct endpoint. + * @param {string} [params.reverseProxyUrl] - The reverse proxy URL to use for the request. + * @returns {Promise} - A promise that resolves to the response of the fetch request. + */ +function createFetch({ directEndpoint = false, reverseProxyUrl = '' }) { + /** + * Makes an HTTP request and logs the process. + * @param {RequestInfo} url - The URL to make the request to. Can be a string or a Request object. + * @param {RequestInit} [init] - Optional init options for the request. + * @returns {Promise} - A promise that resolves to the response of the fetch request. + */ + return async (_url, init) => { + let url = _url; + if (directEndpoint) { + url = reverseProxyUrl; + } + logger.debug(`Making request to ${url}`); + if (typeof Bun !== 'undefined') { + return await fetch(url, init); + } + return await fetch(url, init); + }; +} + +// Add this at the module level outside the class +/** + * Creates event handlers for stream events that don't capture client references + * @param {Object} res - The response object to send events to + * @returns {Object} Object containing handler functions + */ +function createStreamEventHandlers(res) { + return { + [GraphEvents.ON_RUN_STEP]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + [GraphEvents.ON_MESSAGE_DELTA]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + [GraphEvents.ON_REASONING_DELTA]: (event) => { + if (res) { + sendEvent(res, event); + } + }, + }; +} + +module.exports = { + createFetch, + createStreamEventHandlers, +}; diff --git a/api/app/clients/tools/util/handleTools.js b/api/app/clients/tools/util/handleTools.js index 063d6e032..8ce9d7bc7 100644 --- a/api/app/clients/tools/util/handleTools.js +++ b/api/app/clients/tools/util/handleTools.js @@ -123,7 +123,7 @@ const getAuthFields = (toolKey) => { * * @param {object} object * @param {string} object.user - * @param {Agent} [object.agent] + * @param {Pick} [object.agent] * @param {string} [object.model] * @param {EModelEndpoint} [object.endpoint] * @param {LoadToolOptions} [object.options] diff --git a/api/cache/getLogStores.js b/api/cache/getLogStores.js index 6d5ea15a7..e652cfdee 100644 --- a/api/cache/getLogStores.js +++ b/api/cache/getLogStores.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const { CacheKeys, ViolationTypes, Time } = require('librechat-data-provider'); const { logFile, violationFile } = require('./keyvFiles'); const { math, isEnabled } = require('~/server/utils'); diff --git a/api/cache/ioredisClient.js b/api/cache/ioredisClient.js new file mode 100644 index 000000000..cd48459ab --- /dev/null +++ b/api/cache/ioredisClient.js @@ -0,0 +1,92 @@ +const fs = require('fs'); +const Redis = require('ioredis'); +const { isEnabled } = require('~/server/utils'); +const logger = require('~/config/winston'); + +const { REDIS_URI, USE_REDIS, USE_REDIS_CLUSTER, REDIS_CA, REDIS_MAX_LISTENERS } = process.env; + +/** @type {import('ioredis').Redis | import('ioredis').Cluster} */ +let ioredisClient; +const redis_max_listeners = Number(REDIS_MAX_LISTENERS) || 40; + +function mapURI(uri) { + const regex = + /^(?:(?\w+):\/\/)?(?:(?[^:@]+)(?::(?[^@]+))?@)?(?[\w.-]+)(?::(?\d{1,5}))?$/; + const match = uri.match(regex); + + if (match) { + const { scheme, user, password, host, port } = match.groups; + + return { + scheme: scheme || 'none', + user: user || null, + password: password || null, + host: host || null, + port: port || null, + }; + } else { + const parts = uri.split(':'); + if (parts.length === 2) { + return { + scheme: 'none', + user: null, + password: null, + host: parts[0], + port: parts[1], + }; + } + + return { + scheme: 'none', + user: null, + password: null, + host: uri, + port: null, + }; + } +} + +if (REDIS_URI && isEnabled(USE_REDIS)) { + let redisOptions = null; + + if (REDIS_CA) { + const ca = fs.readFileSync(REDIS_CA); + redisOptions = { tls: { ca } }; + } + + if (isEnabled(USE_REDIS_CLUSTER)) { + const hosts = REDIS_URI.split(',').map((item) => { + var value = mapURI(item); + + return { + host: value.host, + port: value.port, + }; + }); + ioredisClient = new Redis.Cluster(hosts, { redisOptions }); + } else { + ioredisClient = new Redis(REDIS_URI, redisOptions); + } + + ioredisClient.on('ready', () => { + logger.info('IoRedis connection ready'); + }); + ioredisClient.on('reconnecting', () => { + logger.info('IoRedis connection reconnecting'); + }); + ioredisClient.on('end', () => { + logger.info('IoRedis connection ended'); + }); + ioredisClient.on('close', () => { + logger.info('IoRedis connection closed'); + }); + ioredisClient.on('error', (err) => logger.error('IoRedis connection error:', err)); + ioredisClient.setMaxListeners(redis_max_listeners); + logger.info( + '[Optional] IoRedis initialized for rate limiters. If you have issues, disable Redis or restart the server.', + ); +} else { + logger.info('[Optional] IoRedis not initialized for rate limiters.'); +} + +module.exports = ioredisClient; diff --git a/api/cache/keyvFiles.js b/api/cache/keyvFiles.js index f969174b7..1476b60cb 100644 --- a/api/cache/keyvFiles.js +++ b/api/cache/keyvFiles.js @@ -1,11 +1,9 @@ const { KeyvFile } = require('keyv-file'); -const logFile = new KeyvFile({ filename: './data/logs.json' }); -const pendingReqFile = new KeyvFile({ filename: './data/pendingReqCache.json' }); -const violationFile = new KeyvFile({ filename: './data/violations.json' }); +const logFile = new KeyvFile({ filename: './data/logs.json' }).setMaxListeners(20); +const violationFile = new KeyvFile({ filename: './data/violations.json' }).setMaxListeners(20); module.exports = { logFile, - pendingReqFile, violationFile, }; diff --git a/api/cache/keyvMongo.js b/api/cache/keyvMongo.js index 8f5b9fd8d..3321be56f 100644 --- a/api/cache/keyvMongo.js +++ b/api/cache/keyvMongo.js @@ -1,4 +1,4 @@ -const KeyvMongo = require('@keyv/mongo'); +const { KeyvMongo } = require('@keyv/mongo'); const { logger } = require('~/config'); const { MONGO_URI } = process.env ?? {}; diff --git a/api/cache/keyvRedis.js b/api/cache/keyvRedis.js index 992e789ae..0c830422e 100644 --- a/api/cache/keyvRedis.js +++ b/api/cache/keyvRedis.js @@ -1,6 +1,6 @@ const fs = require('fs'); const ioredis = require('ioredis'); -const KeyvRedis = require('@keyv/redis'); +const KeyvRedis = require('@keyv/redis').default; const { isEnabled } = require('~/server/utils'); const logger = require('~/config/winston'); @@ -50,6 +50,7 @@ function mapURI(uri) { if (REDIS_URI && isEnabled(USE_REDIS)) { let redisOptions = null; + /** @type {import('@keyv/redis').KeyvRedisOptions} */ let keyvOpts = { useRedisSets: false, keyPrefix: redis_prefix, @@ -74,6 +75,18 @@ if (REDIS_URI && isEnabled(USE_REDIS)) { } else { keyvRedis = new KeyvRedis(REDIS_URI, keyvOpts); } + keyvRedis.on('ready', () => { + logger.info('KeyvRedis connection ready'); + }); + keyvRedis.on('reconnecting', () => { + logger.info('KeyvRedis connection reconnecting'); + }); + keyvRedis.on('end', () => { + logger.info('KeyvRedis connection ended'); + }); + keyvRedis.on('close', () => { + logger.info('KeyvRedis connection closed'); + }); keyvRedis.on('error', (err) => logger.error('KeyvRedis connection error:', err)); keyvRedis.setMaxListeners(redis_max_listeners); logger.info( diff --git a/api/cache/redis.js b/api/cache/redis.js deleted file mode 100644 index adf291d02..000000000 --- a/api/cache/redis.js +++ /dev/null @@ -1,4 +0,0 @@ -const Redis = require('ioredis'); -const { REDIS_URI } = process.env ?? {}; -const redis = new Redis.Cluster(REDIS_URI); -module.exports = redis; diff --git a/api/config/index.js b/api/config/index.js index 919919b55..57bc45fe1 100644 --- a/api/config/index.js +++ b/api/config/index.js @@ -6,15 +6,19 @@ const logger = require('./winston'); global.EventSource = EventSource; +/** @type {MCPManager} */ let mcpManager = null; let flowManager = null; /** + * @param {string} [userId] - Optional user ID, to avoid disconnecting the current user. * @returns {MCPManager} */ -function getMCPManager() { +function getMCPManager(userId) { if (!mcpManager) { mcpManager = MCPManager.getInstance(logger); + } else { + mcpManager.checkIdleConnections(userId); } return mcpManager; } diff --git a/api/models/tx.js b/api/models/tx.js index 41003e665..5099e37f4 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -123,6 +123,10 @@ const tokenValues = Object.assign( 'grok-2-1212': { prompt: 2.0, completion: 10.0 }, 'grok-2-latest': { prompt: 2.0, completion: 10.0 }, 'grok-2': { prompt: 2.0, completion: 10.0 }, + 'grok-3-mini-fast': { prompt: 0.4, completion: 4 }, + 'grok-3-mini': { prompt: 0.3, completion: 0.5 }, + 'grok-3-fast': { prompt: 5.0, completion: 25.0 }, + 'grok-3': { prompt: 3.0, completion: 15.0 }, 'grok-beta': { prompt: 5.0, completion: 15.0 }, 'mistral-large': { prompt: 2.0, completion: 6.0 }, 'pixtral-large': { prompt: 2.0, completion: 6.0 }, diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index f612e222b..f759e658f 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -507,5 +507,27 @@ describe('Grok Model Tests - Pricing', () => { expect(getMultiplier({ model: 'grok-beta', tokenType: 'prompt' })).toBe(5.0); expect(getMultiplier({ model: 'grok-beta', tokenType: 'completion' })).toBe(15.0); }); + + test('should return correct prompt and completion rates for Grok 3 models', () => { + expect(getMultiplier({ model: 'grok-3', tokenType: 'prompt' })).toBe(3.0); + expect(getMultiplier({ model: 'grok-3', tokenType: 'completion' })).toBe(15.0); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'prompt' })).toBe(5.0); + expect(getMultiplier({ model: 'grok-3-fast', tokenType: 'completion' })).toBe(25.0); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'prompt' })).toBe(0.3); + expect(getMultiplier({ model: 'grok-3-mini', tokenType: 'completion' })).toBe(0.5); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); + expect(getMultiplier({ model: 'grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + }); + + test('should return correct prompt and completion rates for Grok 3 models with prefixes', () => { + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'prompt' })).toBe(3.0); + expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'completion' })).toBe(15.0); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'prompt' })).toBe(5.0); + expect(getMultiplier({ model: 'xai/grok-3-fast', tokenType: 'completion' })).toBe(25.0); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'prompt' })).toBe(0.3); + expect(getMultiplier({ model: 'xai/grok-3-mini', tokenType: 'completion' })).toBe(0.5); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'prompt' })).toBe(0.4); + expect(getMultiplier({ model: 'xai/grok-3-mini-fast', tokenType: 'completion' })).toBe(4.0); + }); }); }); diff --git a/api/package.json b/api/package.json index 19bad78cf..73b415ad7 100644 --- a/api/package.json +++ b/api/package.json @@ -42,14 +42,14 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.1", + "@keyv/mongo": "^3.0.1", + "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.12", + "@librechat/agents": "^2.4.14", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -76,8 +76,8 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", - "keyv": "^4.5.4", - "keyv-file": "^0.2.0", + "keyv": "^5.3.2", + "keyv-file": "^5.1.2", "klona": "^2.0.6", "librechat-data-provider": "*", "librechat-mcp": "*", diff --git a/api/server/cleanup.js b/api/server/cleanup.js new file mode 100644 index 000000000..508d2e812 --- /dev/null +++ b/api/server/cleanup.js @@ -0,0 +1,240 @@ +const { logger } = require('~/config'); + +// WeakMap to hold temporary data associated with requests +const requestDataMap = new WeakMap(); + +const FinalizationRegistry = global.FinalizationRegistry || null; + +/** + * FinalizationRegistry to clean up client objects when they are garbage collected. + * This is used to prevent memory leaks and ensure that client objects are + * properly disposed of when they are no longer needed. + * The registry holds a weak reference to the client object and a cleanup + * callback that is called when the client object is garbage collected. + * The callback can be used to perform any necessary cleanup operations, + * such as removing event listeners or freeing up resources. + */ +const clientRegistry = FinalizationRegistry + ? new FinalizationRegistry((heldValue) => { + try { + // This will run when the client is garbage collected + if (heldValue && heldValue.userId) { + logger.debug(`[FinalizationRegistry] Cleaning up client for user ${heldValue.userId}`); + } else { + logger.debug('[FinalizationRegistry] Cleaning up client'); + } + } catch (e) { + // Ignore errors + } + }) + : null; + +/** + * Cleans up the client object by removing references to its properties. + * This is useful for preventing memory leaks and ensuring that the client + * and its properties can be garbage collected when it is no longer needed. + */ +function disposeClient(client) { + if (!client) { + return; + } + + try { + if (client.user) { + client.user = null; + } + if (client.apiKey) { + client.apiKey = null; + } + if (client.azure) { + client.azure = null; + } + if (client.conversationId) { + client.conversationId = null; + } + if (client.responseMessageId) { + client.responseMessageId = null; + } + if (client.clientName) { + client.clientName = null; + } + if (client.sender) { + client.sender = null; + } + if (client.model) { + client.model = null; + } + if (client.maxContextTokens) { + client.maxContextTokens = null; + } + if (client.contextStrategy) { + client.contextStrategy = null; + } + if (client.currentDateString) { + client.currentDateString = null; + } + if (client.inputTokensKey) { + client.inputTokensKey = null; + } + if (client.outputTokensKey) { + client.outputTokensKey = null; + } + if (client.run) { + // Break circular references in run + if (client.run.Graph) { + client.run.Graph.resetValues(); + client.run.Graph.handlerRegistry = null; + client.run.Graph.runId = null; + client.run.Graph.tools = null; + client.run.Graph.signal = null; + client.run.Graph.config = null; + client.run.Graph.toolEnd = null; + client.run.Graph.toolMap = null; + client.run.Graph.provider = null; + client.run.Graph.streamBuffer = null; + client.run.Graph.clientOptions = null; + client.run.Graph.graphState = null; + client.run.Graph.boundModel = null; + client.run.Graph.systemMessage = null; + client.run.Graph.reasoningKey = null; + client.run.Graph.messages = null; + client.run.Graph.contentData = null; + client.run.Graph.stepKeyIds = null; + client.run.Graph.contentIndexMap = null; + client.run.Graph.toolCallStepIds = null; + client.run.Graph.messageIdsByStepKey = null; + client.run.Graph.messageStepHasToolCalls = null; + client.run.Graph.prelimMessageIdsByStepKey = null; + client.run.Graph.currentTokenType = null; + client.run.Graph.lastToken = null; + client.run.Graph.tokenTypeSwitch = null; + client.run.Graph.indexTokenCountMap = null; + client.run.Graph.currentUsage = null; + client.run.Graph.tokenCounter = null; + client.run.Graph.maxContextTokens = null; + client.run.Graph.pruneMessages = null; + client.run.Graph.lastStreamCall = null; + client.run.Graph.startIndex = null; + client.run.Graph = null; + } + if (client.run.handlerRegistry) { + client.run.handlerRegistry = null; + } + if (client.run.graphRunnable) { + if (client.run.graphRunnable.channels) { + client.run.graphRunnable.channels = null; + } + if (client.run.graphRunnable.nodes) { + client.run.graphRunnable.nodes = null; + } + if (client.run.graphRunnable.lc_kwargs) { + client.run.graphRunnable.lc_kwargs = null; + } + if (client.run.graphRunnable.builder?.nodes) { + client.run.graphRunnable.builder.nodes = null; + client.run.graphRunnable.builder = null; + } + client.run.graphRunnable = null; + } + client.run = null; + } + if (client.sendMessage) { + client.sendMessage = null; + } + if (client.savedMessageIds) { + client.savedMessageIds.clear(); + client.savedMessageIds = null; + } + if (client.currentMessages) { + client.currentMessages = null; + } + if (client.streamHandler) { + client.streamHandler = null; + } + if (client.contentParts) { + client.contentParts = null; + } + if (client.abortController) { + client.abortController = null; + } + if (client.collectedUsage) { + client.collectedUsage = null; + } + if (client.indexTokenCountMap) { + client.indexTokenCountMap = null; + } + if (client.agentConfigs) { + client.agentConfigs = null; + } + if (client.artifactPromises) { + client.artifactPromises = null; + } + if (client.usage) { + client.usage = null; + } + if (typeof client.dispose === 'function') { + client.dispose(); + } + if (client.options) { + if (client.options.req) { + client.options.req = null; + } + if (client.options.res) { + client.options.res = null; + } + if (client.options.attachments) { + client.options.attachments = null; + } + if (client.options.agent) { + client.options.agent = null; + } + } + client.options = null; + } catch (e) { + // Ignore errors during disposal + } +} + +function processReqData(data = {}, context) { + let { + abortKey, + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, + } = context; + for (const key in data) { + if (key === 'userMessage') { + userMessage = data[key]; + userMessageId = data[key].messageId; + } else if (key === 'userMessagePromise') { + userMessagePromise = data[key]; + } else if (key === 'responseMessageId') { + responseMessageId = data[key]; + } else if (key === 'promptTokens') { + promptTokens = data[key]; + } else if (key === 'abortKey') { + abortKey = data[key]; + } else if (!conversationId && key === 'conversationId') { + conversationId = data[key]; + } + } + return { + abortKey, + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, + }; +} + +module.exports = { + disposeClient, + requestDataMap, + clientRegistry, + processReqData, +}; diff --git a/api/server/controllers/AskController.js b/api/server/controllers/AskController.js index 2df6f34ed..8904a5722 100644 --- a/api/server/controllers/AskController.js +++ b/api/server/controllers/AskController.js @@ -1,5 +1,15 @@ const { getResponseSender, Constants } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { + disposeClient, + processReqData, + clientRegistry, + requestDataMap, +} = require('~/server/cleanup'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -14,90 +24,162 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { overrideParentMessageId = null, } = req.body; + let client = null; + let abortKey = null; + let cleanupHandlers = []; + let clientRef = null; + logger.debug('[AskController]', { text, conversationId, ...endpointOption, - modelsConfig: endpointOption.modelsConfig ? 'exists' : '', + modelsConfig: endpointOption?.modelsConfig ? 'exists' : '', }); - let userMessage; - let userMessagePromise; - let promptTokens; - let userMessageId; - let responseMessageId; + let userMessage = null; + let userMessagePromise = null; + let promptTokens = null; + let userMessageId = null; + let responseMessageId = null; + let getAbortData = null; + const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model, modelDisplayLabel, }); - const newConvo = !conversationId; - const user = req.user.id; + const initialConversationId = conversationId; + const newConvo = !initialConversationId; + const userId = req.user.id; - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - userMessageId = data[key].messageId; - } else if (key === 'userMessagePromise') { - userMessagePromise = data[key]; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } else if (!conversationId && key === 'conversationId') { - conversationId = data[key]; - } - } + let reqDataContext = { + userMessage, + userMessagePromise, + responseMessageId, + promptTokens, + conversationId, + userMessageId, }; - let getText; + const updateReqData = (data = {}) => { + reqDataContext = processReqData(data, reqDataContext); + abortKey = reqDataContext.abortKey; + userMessage = reqDataContext.userMessage; + userMessagePromise = reqDataContext.userMessagePromise; + responseMessageId = reqDataContext.responseMessageId; + promptTokens = reqDataContext.promptTokens; + conversationId = reqDataContext.conversationId; + userMessageId = reqDataContext.userMessageId; + }; + + let { onProgress: progressCallback, getPartialText } = createOnProgress(); + + const performCleanup = () => { + logger.debug('[AskController] Performing cleanup'); + if (Array.isArray(cleanupHandlers)) { + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore + } + } + } + + if (abortKey) { + logger.debug('[AskController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + abortKey = null; + } + + if (client) { + disposeClient(client); + client = null; + } + + reqDataContext = null; + userMessage = null; + userMessagePromise = null; + promptTokens = null; + getAbortData = null; + progressCallback = null; + endpointOption = null; + cleanupHandlers = null; + addTitle = null; + + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[AskController] Cleanup completed'); + }; try { - const { client } = await initializeClient({ req, res, endpointOption }); - const { onProgress: progressCallback, getPartialText } = createOnProgress(); + ({ client } = await initializeClient({ req, res, endpointOption })); + if (clientRegistry && client) { + clientRegistry.register(client, { userId }, client); + } - getText = client.getStreamText != null ? client.getStreamText.bind(client) : getPartialText; + if (client) { + requestDataMap.set(req, { client }); + } - const getAbortData = () => ({ - sender, - conversationId, - userMessagePromise, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getText(), - userMessage, - promptTokens, - }); + clientRef = new WeakRef(client); - const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); + getAbortData = () => { + const currentClient = clientRef.deref(); + const currentText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); - res.on('close', () => { + return { + sender, + conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: currentText, + userMessage: userMessage, + userMessagePromise: userMessagePromise, + promptTokens: reqDataContext.promptTokens, + }; + }; + + const { onStart, abortController } = createAbortController( + req, + res, + getAbortData, + updateReqData, + ); + + const closeHandler = () => { logger.debug('[AskController] Request closed'); - if (!abortController) { - return; - } else if (abortController.signal.aborted) { - return; - } else if (abortController.requestCompleted) { + if (!abortController || abortController.signal.aborted || abortController.requestCompleted) { return; } - abortController.abort(); logger.debug('[AskController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); const messageOptions = { - user, + user: userId, parentMessageId, - conversationId, + conversationId: reqDataContext.conversationId, overrideParentMessageId, - getReqData, + getReqData: updateReqData, onStart, abortController, progressCallback, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }; @@ -105,59 +187,94 @@ const AskController = async (req, res, next, initializeClient, addTitle) => { let response = await client.sendMessage(text, messageOptions); response.endpoint = endpointOption.endpoint; - const { conversation = {} } = await client.responsePromise; + const databasePromise = response.databasePromise; + delete response.databasePromise; + + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (client.options.attachments) { - userMessage.files = client.options.attachments; - conversation.model = endpointOption.modelOptions.model; - delete userMessage.image_urls; + const latestUserMessage = reqDataContext.userMessage; + + if (client?.options?.attachments && latestUserMessage) { + latestUserMessage.files = client.options.attachments; + if (endpointOption?.modelOptions?.model) { + conversation.model = endpointOption.modelOptions.model; + } + delete latestUserMessage.image_urls; } if (!abortController.signal.aborted) { + const finalResponseMessage = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, - requestMessage: userMessage, - responseMessage: response, + requestMessage: latestUserMessage, + responseMessage: finalResponseMessage, }); res.end(); - if (!client.savedMessageIds.has(response.messageId)) { + if (client?.savedMessageIds && !client.savedMessageIds.has(response.messageId)) { await saveMessage( req, - { ...response, user }, + { ...finalResponseMessage, user: userId }, { context: 'api/server/controllers/AskController.js - response end' }, ); } } - if (!client.skipSaveUserMessage) { - await saveMessage(req, userMessage, { + if (!client?.skipSaveUserMessage && latestUserMessage) { + await saveMessage(req, latestUserMessage, { context: 'api/server/controllers/AskController.js - don\'t skip saving user message', }); } - if (addTitle && parentMessageId === Constants.NO_PARENT && newConvo) { + if (typeof addTitle === 'function' && parentMessageId === Constants.NO_PARENT && newConvo) { addTitle(req, { text, - response, + response: { ...response }, client, - }); + }) + .then(() => { + logger.debug('[AskController] Title generation started'); + }) + .catch((err) => { + logger.error('[AskController] Error in title generation', err); + }) + .finally(() => { + logger.debug('[AskController] Title generation completed'); + performCleanup(); + }); + } else { + performCleanup(); } } catch (error) { - const partialText = getText && getText(); + logger.error('[AskController] Error handling request', error); + let partialText = ''; + try { + const currentClient = clientRef.deref(); + partialText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + } catch (getTextError) { + logger.error('[AskController] Error calling getText() during error handling', getTextError); + } + handleAbortError(res, req, error, { sender, partialText, - conversationId, - messageId: responseMessageId, - parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[AskController] Error in `handleAbortError`', err); - }); + conversationId: reqDataContext.conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? reqDataContext.userMessageId ?? parentMessageId, + }) + .catch((err) => { + logger.error('[AskController] Error in `handleAbortError` during catch block', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/controllers/EditController.js b/api/server/controllers/EditController.js index 1de972572..e7ebc0785 100644 --- a/api/server/controllers/EditController.js +++ b/api/server/controllers/EditController.js @@ -1,5 +1,15 @@ const { getResponseSender } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { + disposeClient, + processReqData, + clientRegistry, + requestDataMap, +} = require('~/server/cleanup'); const { sendMessage, createOnProgress } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -17,6 +27,11 @@ const EditController = async (req, res, next, initializeClient) => { overrideParentMessageId = null, } = req.body; + let client = null; + let abortKey = null; + let cleanupHandlers = []; + let clientRef = null; // Declare clientRef here + logger.debug('[EditController]', { text, generation, @@ -26,123 +41,204 @@ const EditController = async (req, res, next, initializeClient) => { modelsConfig: endpointOption.modelsConfig ? 'exists' : '', }); - let userMessage; - let userMessagePromise; - let promptTokens; + let userMessage = null; + let userMessagePromise = null; + let promptTokens = null; + let getAbortData = null; + const sender = getResponseSender({ ...endpointOption, model: endpointOption.modelOptions.model, modelDisplayLabel, }); const userMessageId = parentMessageId; - const user = req.user.id; + const userId = req.user.id; - const getReqData = (data = {}) => { - for (let key in data) { - if (key === 'userMessage') { - userMessage = data[key]; - } else if (key === 'userMessagePromise') { - userMessagePromise = data[key]; - } else if (key === 'responseMessageId') { - responseMessageId = data[key]; - } else if (key === 'promptTokens') { - promptTokens = data[key]; - } - } + let reqDataContext = { userMessage, userMessagePromise, responseMessageId, promptTokens }; + + const updateReqData = (data = {}) => { + reqDataContext = processReqData(data, reqDataContext); + abortKey = reqDataContext.abortKey; + userMessage = reqDataContext.userMessage; + userMessagePromise = reqDataContext.userMessagePromise; + responseMessageId = reqDataContext.responseMessageId; + promptTokens = reqDataContext.promptTokens; }; - const { onProgress: progressCallback, getPartialText } = createOnProgress({ + let { onProgress: progressCallback, getPartialText } = createOnProgress({ generation, }); - let getText; + const performCleanup = () => { + logger.debug('[EditController] Performing cleanup'); + if (Array.isArray(cleanupHandlers)) { + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore + } + } + } + + if (abortKey) { + logger.debug('[AskController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + abortKey = null; + } + + if (client) { + disposeClient(client); + client = null; + } + + reqDataContext = null; + userMessage = null; + userMessagePromise = null; + promptTokens = null; + getAbortData = null; + progressCallback = null; + endpointOption = null; + cleanupHandlers = null; + + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[EditController] Cleanup completed'); + }; try { - const { client } = await initializeClient({ req, res, endpointOption }); + ({ client } = await initializeClient({ req, res, endpointOption })); - getText = client.getStreamText != null ? client.getStreamText.bind(client) : getPartialText; + if (clientRegistry && client) { + clientRegistry.register(client, { userId }, client); + } - const getAbortData = () => ({ - conversationId, - userMessagePromise, - messageId: responseMessageId, - sender, - parentMessageId: overrideParentMessageId ?? userMessageId, - text: getText(), - userMessage, - promptTokens, - }); + if (client) { + requestDataMap.set(req, { client }); + } - const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); + clientRef = new WeakRef(client); - res.on('close', () => { + getAbortData = () => { + const currentClient = clientRef.deref(); + const currentText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + + return { + sender, + conversationId, + messageId: reqDataContext.responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + text: currentText, + userMessage: userMessage, + userMessagePromise: userMessagePromise, + promptTokens: reqDataContext.promptTokens, + }; + }; + + const { onStart, abortController } = createAbortController( + req, + res, + getAbortData, + updateReqData, + ); + + const closeHandler = () => { logger.debug('[EditController] Request closed'); - if (!abortController) { - return; - } else if (abortController.signal.aborted) { - return; - } else if (abortController.requestCompleted) { + if (!abortController || abortController.signal.aborted || abortController.requestCompleted) { return; } - abortController.abort(); logger.debug('[EditController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); let response = await client.sendMessage(text, { - user, + user: userId, generation, isContinued, isEdited: true, conversationId, parentMessageId, - responseMessageId, + responseMessageId: reqDataContext.responseMessageId, overrideParentMessageId, - getReqData, + getReqData: updateReqData, onStart, abortController, progressCallback, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }); - const { conversation = {} } = await client.responsePromise; + const databasePromise = response.databasePromise; + delete response.databasePromise; + + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (client.options.attachments) { + if (client?.options?.attachments && endpointOption?.modelOptions?.model) { conversation.model = endpointOption.modelOptions.model; } if (!abortController.signal.aborted) { + const finalUserMessage = reqDataContext.userMessage; + const finalResponseMessage = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, - requestMessage: userMessage, - responseMessage: response, + requestMessage: finalUserMessage, + responseMessage: finalResponseMessage, }); res.end(); await saveMessage( req, - { ...response, user }, + { ...finalResponseMessage, user: userId }, { context: 'api/server/controllers/EditController.js - response end' }, ); } + + performCleanup(); } catch (error) { - const partialText = getText(); + logger.error('[EditController] Error handling request', error); + let partialText = ''; + try { + const currentClient = clientRef.deref(); + partialText = + currentClient?.getStreamText != null ? currentClient.getStreamText() : getPartialText(); + } catch (getTextError) { + logger.error('[EditController] Error calling getText() during error handling', getTextError); + } + handleAbortError(res, req, error, { sender, partialText, conversationId, - messageId: responseMessageId, + messageId: reqDataContext.responseMessageId, parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[EditController] Error in `handleAbortError`', err); - }); + }) + .catch((err) => { + logger.error('[EditController] Error in `handleAbortError` during catch block', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index ff98d80a1..a0de98c5f 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -63,6 +63,21 @@ const noSystemModelRegex = [/\bo1\b/gi]; // const { getFormattedMemories } = require('~/models/Memory'); // const { getCurrentDateTime } = require('~/utils'); +function createTokenCounter(encoding) { + return (message) => { + const countTokens = (text) => Tokenizer.getTokenCount(text, encoding); + return getTokenCountForMessage(message, countTokens); + }; +} + +function logToolError(graph, error, toolId) { + logger.error( + '[api/server/controllers/agents/client.js #chatCompletion] Tool Error', + error, + toolId, + ); +} + class AgentClient extends BaseClient { constructor(options = {}) { super(null, options); @@ -535,6 +550,10 @@ class AgentClient extends BaseClient { } async chatCompletion({ payload, abortController = null }) { + /** @type {Partial & { version: 'v1' | 'v2'; run_id?: string; streamMode: string }} */ + let config; + /** @type {ReturnType} */ + let run; try { if (!abortController) { abortController = new AbortController(); @@ -632,11 +651,11 @@ class AgentClient extends BaseClient { /** @type {TCustomConfig['endpoints']['agents']} */ const agentsEConfig = this.options.req.app.locals[EModelEndpoint.agents]; - /** @type {Partial & { version: 'v1' | 'v2'; run_id?: string; streamMode: string }} */ - const config = { + config = { configurable: { thread_id: this.conversationId, last_agent_index: this.agentConfigs?.size ?? 0, + user_id: this.user ?? this.options.req.user?.id, hide_sequential_outputs: this.options.agent.hide_sequential_outputs, }, recursionLimit: agentsEConfig?.recursionLimit, @@ -655,15 +674,6 @@ class AgentClient extends BaseClient { initialMessages = formatContentStrings(initialMessages); } - /** @type {ReturnType} */ - let run; - const countTokens = ((text) => this.getTokenCount(text)).bind(this); - - /** @type {(message: BaseMessage) => number} */ - const tokenCounter = (message) => { - return getTokenCountForMessage(message, countTokens); - }; - /** * * @param {Agent} agent @@ -767,19 +777,14 @@ class AgentClient extends BaseClient { run.Graph.contentData = contentData; } + const encoding = this.getEncoding(); await run.processStream({ messages }, config, { keepContent: i !== 0, - tokenCounter, + tokenCounter: createTokenCounter(encoding), indexTokenCountMap: currentIndexCountMap, maxContextTokens: agent.maxContextTokens, callbacks: { - [Callback.TOOL_ERROR]: (graph, error, toolId) => { - logger.error( - '[api/server/controllers/agents/client.js #chatCompletion] Tool Error', - error, - toolId, - ); - }, + [Callback.TOOL_ERROR]: logToolError, }, }); }; @@ -809,6 +814,8 @@ class AgentClient extends BaseClient { break; } } + const encoding = this.getEncoding(); + const tokenCounter = createTokenCounter(encoding); for (const [agentId, agent] of this.agentConfigs) { if (abortController.signal.aborted === true) { break; @@ -917,7 +924,7 @@ class AgentClient extends BaseClient { * @param {string} params.text * @param {string} params.conversationId */ - async titleConvo({ text }) { + async titleConvo({ text, abortController }) { if (!this.run) { throw new Error('Run not initialized'); } @@ -950,6 +957,7 @@ class AgentClient extends BaseClient { contentParts: this.contentParts, clientOptions, chainOptions: { + signal: abortController.signal, callbacks: [ { handleLLMEnd, @@ -975,7 +983,7 @@ class AgentClient extends BaseClient { }; }); - this.recordCollectedUsage({ + await this.recordCollectedUsage({ model: clientOptions.model, context: 'title', collectedUsage, diff --git a/api/server/controllers/agents/request.js b/api/server/controllers/agents/request.js index 91277d5bc..80b70a745 100644 --- a/api/server/controllers/agents/request.js +++ b/api/server/controllers/agents/request.js @@ -1,5 +1,10 @@ const { Constants } = require('librechat-data-provider'); -const { createAbortController, handleAbortError } = require('~/server/middleware'); +const { + handleAbortError, + createAbortController, + cleanupAbortController, +} = require('~/server/middleware'); +const { disposeClient, clientRegistry, requestDataMap } = require('~/server/cleanup'); const { sendMessage } = require('~/server/utils'); const { saveMessage } = require('~/models'); const { logger } = require('~/config'); @@ -14,16 +19,22 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { } = req.body; let sender; + let abortKey; let userMessage; let promptTokens; let userMessageId; let responseMessageId; let userMessagePromise; + let getAbortData; + let client = null; + // Initialize as an array + let cleanupHandlers = []; const newConvo = !conversationId; - const user = req.user.id; + const userId = req.user.id; - const getReqData = (data = {}) => { + // Create handler to avoid capturing the entire parent scope + let getReqData = (data = {}) => { for (let key in data) { if (key === 'userMessage') { userMessage = data[key]; @@ -36,30 +47,96 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { promptTokens = data[key]; } else if (key === 'sender') { sender = data[key]; + } else if (key === 'abortKey') { + abortKey = data[key]; } else if (!conversationId && key === 'conversationId') { conversationId = data[key]; } } }; + // Create a function to handle final cleanup + const performCleanup = () => { + logger.debug('[AgentController] Performing cleanup'); + // Make sure cleanupHandlers is an array before iterating + if (Array.isArray(cleanupHandlers)) { + // Execute all cleanup handlers + for (const handler of cleanupHandlers) { + try { + if (typeof handler === 'function') { + handler(); + } + } catch (e) { + // Ignore cleanup errors + } + } + } + + // Clean up abort controller + if (abortKey) { + logger.debug('[AgentController] Cleaning up abort controller'); + cleanupAbortController(abortKey); + } + + // Dispose client properly + if (client) { + disposeClient(client); + } + + // Clear all references + client = null; + getReqData = null; + userMessage = null; + getAbortData = null; + endpointOption.agent = null; + endpointOption = null; + cleanupHandlers = null; + userMessagePromise = null; + + // Clear request data map + if (requestDataMap.has(req)) { + requestDataMap.delete(req); + } + logger.debug('[AgentController] Cleanup completed'); + }; + try { /** @type {{ client: TAgentClient }} */ - const { client } = await initializeClient({ req, res, endpointOption }); + const result = await initializeClient({ req, res, endpointOption }); + client = result.client; - const getAbortData = () => ({ - sender, - userMessage, - promptTokens, - conversationId, - userMessagePromise, - messageId: responseMessageId, - content: client.getContentParts(), - parentMessageId: overrideParentMessageId ?? userMessageId, - }); + // Register client with finalization registry if available + if (clientRegistry) { + clientRegistry.register(client, { userId }, client); + } + + // Store request data in WeakMap keyed by req object + requestDataMap.set(req, { client }); + + // Use WeakRef to allow GC but still access content if it exists + const contentRef = new WeakRef(client.contentParts || []); + + // Minimize closure scope - only capture small primitives and WeakRef + getAbortData = () => { + // Dereference WeakRef each time + const content = contentRef.deref(); + + return { + sender, + content: content || [], + userMessage, + promptTokens, + conversationId, + userMessagePromise, + messageId: responseMessageId, + parentMessageId: overrideParentMessageId ?? userMessageId, + }; + }; const { abortController, onStart } = createAbortController(req, res, getAbortData, getReqData); - res.on('close', () => { + // Simple handler to avoid capturing scope + const closeHandler = () => { logger.debug('[AgentController] Request closed'); if (!abortController) { return; @@ -71,10 +148,19 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { abortController.abort(); logger.debug('[AgentController] Request aborted on close'); + }; + + res.on('close', closeHandler); + cleanupHandlers.push(() => { + try { + res.removeListener('close', closeHandler); + } catch (e) { + // Ignore + } }); const messageOptions = { - user, + user: userId, onStart, getReqData, conversationId, @@ -83,69 +169,103 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { overrideParentMessageId, progressOptions: { res, - // parentMessageId: overrideParentMessageId || userMessageId, }, }; let response = await client.sendMessage(text, messageOptions); - response.endpoint = endpointOption.endpoint; - const { conversation = {} } = await client.responsePromise; + // Extract what we need and immediately break reference + const messageId = response.messageId; + const endpoint = endpointOption.endpoint; + response.endpoint = endpoint; + + // Store database promise locally + const databasePromise = response.databasePromise; + delete response.databasePromise; + + // Resolve database-related data + const { conversation: convoData = {} } = await databasePromise; + const conversation = { ...convoData }; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; - if (req.body.files && client.options.attachments) { + // Process files if needed + if (req.body.files && client.options?.attachments) { userMessage.files = []; const messageFiles = new Set(req.body.files.map((file) => file.file_id)); for (let attachment of client.options.attachments) { if (messageFiles.has(attachment.file_id)) { - userMessage.files.push(attachment); + userMessage.files.push({ ...attachment }); } } delete userMessage.image_urls; } + // Only send if not aborted if (!abortController.signal.aborted) { + // Create a new response object with minimal copies + const finalResponse = { ...response }; + sendMessage(res, { final: true, conversation, title: conversation.title, requestMessage: userMessage, - responseMessage: response, + responseMessage: finalResponse, }); res.end(); - if (!client.savedMessageIds.has(response.messageId)) { + // Save the message if needed + if (client.savedMessageIds && !client.savedMessageIds.has(messageId)) { await saveMessage( req, - { ...response, user }, + { ...finalResponse, user: userId }, { context: 'api/server/controllers/agents/request.js - response end' }, ); } } + // Save user message if needed if (!client.skipSaveUserMessage) { await saveMessage(req, userMessage, { context: 'api/server/controllers/agents/request.js - don\'t skip saving user message', }); } + // Add title if needed - extract minimal data if (addTitle && parentMessageId === Constants.NO_PARENT && newConvo) { addTitle(req, { text, - response, + response: { ...response }, client, - }); + }) + .then(() => { + logger.debug('[AgentController] Title generation started'); + }) + .catch((err) => { + logger.error('[AgentController] Error in title generation', err); + }) + .finally(() => { + logger.debug('[AgentController] Title generation completed'); + performCleanup(); + }); + } else { + performCleanup(); } } catch (error) { + // Handle error without capturing much scope handleAbortError(res, req, error, { conversationId, sender, messageId: responseMessageId, parentMessageId: overrideParentMessageId ?? userMessageId ?? parentMessageId, - }).catch((err) => { - logger.error('[api/server/controllers/agents/request] Error in `handleAbortError`', err); - }); + }) + .catch((err) => { + logger.error('[api/server/controllers/agents/request] Error in `handleAbortError`', err); + }) + .finally(() => { + performCleanup(); + }); } }; diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index ccc4ed043..b10f6586f 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -1,3 +1,4 @@ +// abortMiddleware.js const { isAssistantsEndpoint, ErrorTypes } = require('librechat-data-provider'); const { sendMessage, sendError, countTokens, isEnabled } = require('~/server/utils'); const { truncateText, smartTruncateText } = require('~/app/clients/prompts'); @@ -8,6 +9,68 @@ const { saveMessage, getConvo } = require('~/models'); const { abortRun } = require('./abortRun'); const { logger } = require('~/config'); +const abortDataMap = new WeakMap(); + +function cleanupAbortController(abortKey) { + if (!abortControllers.has(abortKey)) { + return false; + } + + const { abortController } = abortControllers.get(abortKey); + + if (!abortController) { + abortControllers.delete(abortKey); + return true; + } + + // 1. Check if this controller has any composed signals and clean them up + try { + // This creates a temporary composed signal to use for cleanup + const composedSignal = AbortSignal.any([abortController.signal]); + + // Get all event types - in practice, AbortSignal typically only uses 'abort' + const eventTypes = ['abort']; + + // First, execute a dummy listener removal to handle potential composed signals + for (const eventType of eventTypes) { + const dummyHandler = () => {}; + composedSignal.addEventListener(eventType, dummyHandler); + composedSignal.removeEventListener(eventType, dummyHandler); + + const listeners = composedSignal.listeners?.(eventType) || []; + for (const listener of listeners) { + composedSignal.removeEventListener(eventType, listener); + } + } + } catch (e) { + logger.debug(`Error cleaning up composed signals: ${e}`); + } + + // 2. Abort the controller if not already aborted + if (!abortController.signal.aborted) { + abortController.abort(); + } + + // 3. Remove from registry + abortControllers.delete(abortKey); + + // 4. Clean up any data stored in the WeakMap + if (abortDataMap.has(abortController)) { + abortDataMap.delete(abortController); + } + + // 5. Clean up function references on the controller + if (abortController.getAbortData) { + abortController.getAbortData = null; + } + + if (abortController.abortCompletion) { + abortController.abortCompletion = null; + } + + return true; +} + async function abortMessage(req, res) { let { abortKey, endpoint } = req.body; @@ -29,19 +92,19 @@ async function abortMessage(req, res) { if (!abortController) { return res.status(204).send({ message: 'Request not found' }); } - const finalEvent = await abortController.abortCompletion(); + + const finalEvent = await abortController.abortCompletion?.(); logger.debug( `[abortMessage] ID: ${req.user.id} | ${req.user.email} | Aborted request: ` + JSON.stringify({ abortKey }), ); - abortControllers.delete(abortKey); + cleanupAbortController(abortKey); if (res.headersSent && finalEvent) { return sendMessage(res, finalEvent); } res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(finalEvent)); } @@ -62,8 +125,48 @@ const createAbortController = (req, res, getAbortData, getReqData) => { const abortController = new AbortController(); const { endpointOption } = req.body; + // Store minimal data in WeakMap to avoid circular references + abortDataMap.set(abortController, { + getAbortDataFn: getAbortData, + userId: req.user.id, + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }); + + // Replace the direct function reference with a wrapper that uses WeakMap abortController.getAbortData = function () { - return getAbortData(); + const data = abortDataMap.get(this); + if (!data || typeof data.getAbortDataFn !== 'function') { + return {}; + } + + try { + const result = data.getAbortDataFn(); + + // Create a copy without circular references + const cleanResult = { ...result }; + + // If userMessagePromise exists, break its reference to client + if ( + cleanResult.userMessagePromise && + typeof cleanResult.userMessagePromise.then === 'function' + ) { + // Create a new promise that fulfills with the same result but doesn't reference the original + const originalPromise = cleanResult.userMessagePromise; + cleanResult.userMessagePromise = new Promise((resolve, reject) => { + originalPromise.then( + (result) => resolve({ ...result }), + (error) => reject(error), + ); + }); + } + + return cleanResult; + } catch (err) { + logger.error('[abortController.getAbortData] Error:', err); + return {}; + } }; /** @@ -74,6 +177,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => { sendMessage(res, { message: userMessage, created: true }); const abortKey = userMessage?.conversationId ?? req.user.id; + getReqData({ abortKey }); const prevRequest = abortControllers.get(abortKey); const { overrideUserMessageId } = req?.body ?? {}; @@ -81,34 +185,74 @@ const createAbortController = (req, res, getAbortData, getReqData) => { const data = prevRequest.abortController.getAbortData(); getReqData({ userMessage: data?.userMessage }); const addedAbortKey = `${abortKey}:${responseMessageId}`; - abortControllers.set(addedAbortKey, { abortController, ...endpointOption }); - res.on('finish', function () { - abortControllers.delete(addedAbortKey); - }); + + // Store minimal options + const minimalOptions = { + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }; + + abortControllers.set(addedAbortKey, { abortController, ...minimalOptions }); + + // Use a simple function for cleanup to avoid capturing context + const cleanupHandler = () => { + try { + cleanupAbortController(addedAbortKey); + } catch (e) { + // Ignore cleanup errors + } + }; + + res.on('finish', cleanupHandler); return; } - abortControllers.set(abortKey, { abortController, ...endpointOption }); + // Store minimal options + const minimalOptions = { + endpoint: endpointOption.endpoint, + iconURL: endpointOption.iconURL, + model: endpointOption.modelOptions?.model || endpointOption.model_parameters?.model, + }; - res.on('finish', function () { - abortControllers.delete(abortKey); - }); + abortControllers.set(abortKey, { abortController, ...minimalOptions }); + + // Use a simple function for cleanup to avoid capturing context + const cleanupHandler = () => { + try { + cleanupAbortController(abortKey); + } catch (e) { + // Ignore cleanup errors + } + }; + + res.on('finish', cleanupHandler); }; + // Define abortCompletion without capturing the entire parent scope abortController.abortCompletion = async function () { - abortController.abort(); + this.abort(); + + // Get data from WeakMap + const ctrlData = abortDataMap.get(this); + if (!ctrlData || !ctrlData.getAbortDataFn) { + return { final: true, conversation: {}, title: 'New Chat' }; + } + + // Get abort data using stored function const { conversationId, userMessage, userMessagePromise, promptTokens, ...responseData } = - getAbortData(); + ctrlData.getAbortDataFn(); + const completionTokens = await countTokens(responseData?.text ?? ''); - const user = req.user.id; + const user = ctrlData.userId; const responseMessage = { ...responseData, conversationId, finish_reason: 'incomplete', - endpoint: endpointOption.endpoint, - iconURL: endpointOption.iconURL, - model: endpointOption.modelOptions?.model ?? endpointOption.model_parameters?.model, + endpoint: ctrlData.endpoint, + iconURL: ctrlData.iconURL, + model: ctrlData.modelOptions?.model ?? ctrlData.model_parameters?.model, unfinished: false, error: false, isCreatedByUser: false, @@ -130,10 +274,12 @@ const createAbortController = (req, res, getAbortData, getReqData) => { if (userMessagePromise) { const resolved = await userMessagePromise; conversation = resolved?.conversation; + // Break reference to promise + resolved.conversation = null; } if (!conversation) { - conversation = await getConvo(req.user.id, conversationId); + conversation = await getConvo(user, conversationId); } return { @@ -218,11 +364,12 @@ const handleAbortError = async (res, req, error, data) => { }; } + // Create a simple callback without capturing parent scope const callback = async () => { - if (abortControllers.has(conversationId)) { - const { abortController } = abortControllers.get(conversationId); - abortController.abort(); - abortControllers.delete(conversationId); + try { + cleanupAbortController(conversationId); + } catch (e) { + // Ignore cleanup errors } }; @@ -243,6 +390,7 @@ const handleAbortError = async (res, req, error, data) => { module.exports = { handleAbort, - createAbortController, handleAbortError, + createAbortController, + cleanupAbortController, }; diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js index 67540bb00..4e0593192 100644 --- a/api/server/middleware/checkBan.js +++ b/api/server/middleware/checkBan.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const uap = require('ua-parser-js'); const { ViolationTypes } = require('librechat-data-provider'); const { isEnabled, removePorts } = require('~/server/utils'); diff --git a/api/server/middleware/limiters/importLimiters.js b/api/server/middleware/limiters/importLimiters.js index 5e50046a3..f353f5e99 100644 --- a/api/server/middleware/limiters/importLimiters.js +++ b/api/server/middleware/limiters/importLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createImportLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for import rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'import_ip_limiter:', diff --git a/api/server/middleware/limiters/loginLimiter.js b/api/server/middleware/limiters/loginLimiter.js index 8cf10ccb1..d57af2941 100644 --- a/api/server/middleware/limiters/loginLimiter.js +++ b/api/server/middleware/limiters/loginLimiter.js @@ -1,8 +1,7 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -31,13 +30,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for login rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'login_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/messageLimiters.js b/api/server/middleware/limiters/messageLimiters.js index fe4f75a9c..4191c9fe7 100644 --- a/api/server/middleware/limiters/messageLimiters.js +++ b/api/server/middleware/limiters/messageLimiters.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const denyRequest = require('~/server/middleware/denyRequest'); +const ioredisClient = require('~/cache/ioredisClient'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -63,11 +62,9 @@ const userLimiterOptions = { }, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for message rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'message_ip_limiter:', diff --git a/api/server/middleware/limiters/registerLimiter.js b/api/server/middleware/limiters/registerLimiter.js index f9bf1215c..7d38b3044 100644 --- a/api/server/middleware/limiters/registerLimiter.js +++ b/api/server/middleware/limiters/registerLimiter.js @@ -1,8 +1,7 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -31,13 +30,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for register rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'register_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/resetPasswordLimiter.js b/api/server/middleware/limiters/resetPasswordLimiter.js index 9f56bd794..673b23e8e 100644 --- a/api/server/middleware/limiters/resetPasswordLimiter.js +++ b/api/server/middleware/limiters/resetPasswordLimiter.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -36,13 +35,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for reset password rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'reset_password_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/sttLimiters.js b/api/server/middleware/limiters/sttLimiters.js index f9304637c..72ed3af6a 100644 --- a/api/server/middleware/limiters/sttLimiters.js +++ b/api/server/middleware/limiters/sttLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createSTTLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for STT rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'stt_ip_limiter:', diff --git a/api/server/middleware/limiters/toolCallLimiter.js b/api/server/middleware/limiters/toolCallLimiter.js index 7a867b5bc..482744a3e 100644 --- a/api/server/middleware/limiters/toolCallLimiter.js +++ b/api/server/middleware/limiters/toolCallLimiter.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const handler = async (req, res) => { @@ -29,13 +28,10 @@ const limiterOptions = { }, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for tool call rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'tool_call_limiter:', }); limiterOptions.store = store; diff --git a/api/server/middleware/limiters/ttsLimiters.js b/api/server/middleware/limiters/ttsLimiters.js index e13aaf48c..9054a6beb 100644 --- a/api/server/middleware/limiters/ttsLimiters.js +++ b/api/server/middleware/limiters/ttsLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -67,11 +66,9 @@ const createTTSLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for TTS rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'tts_ip_limiter:', diff --git a/api/server/middleware/limiters/uploadLimiters.js b/api/server/middleware/limiters/uploadLimiters.js index 9fffface6..d9049f898 100644 --- a/api/server/middleware/limiters/uploadLimiters.js +++ b/api/server/middleware/limiters/uploadLimiters.js @@ -1,10 +1,9 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); +const ioredisClient = require('~/cache/ioredisClient'); const logViolation = require('~/cache/logViolation'); const { isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); const { logger } = require('~/config'); const getEnvironmentVariables = () => { @@ -72,11 +71,9 @@ const createFileLimiters = () => { }, }; - if (isEnabled(process.env.USE_REDIS)) { + if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for file upload rate limiters.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); + const sendCommand = (...args) => ioredisClient.call(...args); const ipStore = new RedisStore({ sendCommand, prefix: 'file_upload_ip_limiter:', diff --git a/api/server/middleware/limiters/verifyEmailLimiter.js b/api/server/middleware/limiters/verifyEmailLimiter.js index 0b245afbd..73bfa2daf 100644 --- a/api/server/middleware/limiters/verifyEmailLimiter.js +++ b/api/server/middleware/limiters/verifyEmailLimiter.js @@ -1,9 +1,8 @@ -const Keyv = require('keyv'); const rateLimit = require('express-rate-limit'); const { RedisStore } = require('rate-limit-redis'); const { ViolationTypes } = require('librechat-data-provider'); const { removePorts, isEnabled } = require('~/server/utils'); -const keyvRedis = require('~/cache/keyvRedis'); +const ioredisClient = require('~/cache/ioredisClient'); const { logViolation } = require('~/cache'); const { logger } = require('~/config'); @@ -36,13 +35,10 @@ const limiterOptions = { keyGenerator: removePorts, }; -if (isEnabled(process.env.USE_REDIS)) { +if (isEnabled(process.env.USE_REDIS) && ioredisClient) { logger.debug('Using Redis for verify email rate limiter.'); - const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; - const sendCommand = (...args) => client.call(...args); const store = new RedisStore({ - sendCommand, + sendCommand: (...args) => ioredisClient.call(...args), prefix: 'verify_email_limiter:', }); limiterOptions.store = store; diff --git a/api/server/routes/agents/actions.js b/api/server/routes/agents/actions.js index 786f44dd8..5413bc1d6 100644 --- a/api/server/routes/agents/actions.js +++ b/api/server/routes/agents/actions.js @@ -58,7 +58,7 @@ router.post('/:agent_id', async (req, res) => { } let { domain } = metadata; - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); @@ -164,7 +164,7 @@ router.delete('/:agent_id/:action_id', async (req, res) => { return true; }); - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); diff --git a/api/server/routes/agents/chat.js b/api/server/routes/agents/chat.js index fe50fdc76..ef66ef789 100644 --- a/api/server/routes/agents/chat.js +++ b/api/server/routes/agents/chat.js @@ -2,7 +2,6 @@ const express = require('express'); const { PermissionTypes, Permissions } = require('librechat-data-provider'); const { setHeaders, - handleAbort, moderateText, // validateModel, generateCheckAccess, @@ -16,7 +15,6 @@ const addTitle = require('~/server/services/Endpoints/agents/title'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]); diff --git a/api/server/routes/ask/addToCache.js b/api/server/routes/ask/addToCache.js index 6e21edd2b..a2f427098 100644 --- a/api/server/routes/ask/addToCache.js +++ b/api/server/routes/ask/addToCache.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const { KeyvFile } = require('keyv-file'); const { logger } = require('~/config'); diff --git a/api/server/routes/ask/anthropic.js b/api/server/routes/ask/anthropic.js index a08d1d257..afe1720d8 100644 --- a/api/server/routes/ask/anthropic.js +++ b/api/server/routes/ask/anthropic.js @@ -11,8 +11,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/custom.js b/api/server/routes/ask/custom.js index 668a9902c..8fc343cf1 100644 --- a/api/server/routes/ask/custom.js +++ b/api/server/routes/ask/custom.js @@ -3,7 +3,6 @@ const AskController = require('~/server/controllers/AskController'); const { initializeClient } = require('~/server/services/Endpoints/custom'); const { addTitle } = require('~/server/services/Endpoints/openAI'); const { - handleAbort, setHeaders, validateModel, validateEndpoint, @@ -12,8 +11,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/google.js b/api/server/routes/ask/google.js index 2b3378bf6..16c7e265f 100644 --- a/api/server/routes/ask/google.js +++ b/api/server/routes/ask/google.js @@ -3,7 +3,6 @@ const AskController = require('~/server/controllers/AskController'); const { initializeClient, addTitle } = require('~/server/services/Endpoints/google'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/ask/gptPlugins.js b/api/server/routes/ask/gptPlugins.js index 036654f84..a40022848 100644 --- a/api/server/routes/ask/gptPlugins.js +++ b/api/server/routes/ask/gptPlugins.js @@ -20,7 +20,6 @@ const { logger } = require('~/config'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', @@ -196,7 +195,8 @@ router.post( logger.debug('[/ask/gptPlugins]', response); - const { conversation = {} } = await client.responsePromise; + const { conversation = {} } = await response.databasePromise; + delete response.databasePromise; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; diff --git a/api/server/routes/ask/openAI.js b/api/server/routes/ask/openAI.js index 5083a08b1..dadf00def 100644 --- a/api/server/routes/ask/openAI.js +++ b/api/server/routes/ask/openAI.js @@ -12,7 +12,6 @@ const { const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', diff --git a/api/server/routes/assistants/actions.js b/api/server/routes/assistants/actions.js index 9f4db5d6b..3dc392350 100644 --- a/api/server/routes/assistants/actions.js +++ b/api/server/routes/assistants/actions.js @@ -36,7 +36,7 @@ router.post('/:assistant_id', async (req, res) => { } let { domain } = metadata; - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); @@ -172,7 +172,7 @@ router.delete('/:assistant_id/:action_id/:model', async (req, res) => { return true; }); - domain = await domainParser(req, domain, true); + domain = await domainParser(domain, true); if (!domain) { return res.status(400).json({ message: 'No domain provided' }); diff --git a/api/server/routes/bedrock/chat.js b/api/server/routes/bedrock/chat.js index 11db89f07..263ca9600 100644 --- a/api/server/routes/bedrock/chat.js +++ b/api/server/routes/bedrock/chat.js @@ -14,7 +14,6 @@ const AgentController = require('~/server/controllers/agents/request'); const addTitle = require('~/server/services/Endpoints/agents/title'); router.use(moderateText); -router.post('/abort', handleAbort()); /** * @route POST / diff --git a/api/server/routes/edit/anthropic.js b/api/server/routes/edit/anthropic.js index c7bf128d7..704a9f4ea 100644 --- a/api/server/routes/edit/anthropic.js +++ b/api/server/routes/edit/anthropic.js @@ -3,7 +3,6 @@ const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/anthropic'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/custom.js b/api/server/routes/edit/custom.js index 0bf97ba18..a6fd80476 100644 --- a/api/server/routes/edit/custom.js +++ b/api/server/routes/edit/custom.js @@ -12,8 +12,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/google.js b/api/server/routes/edit/google.js index 7482f11b4..187f4f615 100644 --- a/api/server/routes/edit/google.js +++ b/api/server/routes/edit/google.js @@ -3,7 +3,6 @@ const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/google'); const { setHeaders, - handleAbort, validateModel, validateEndpoint, buildEndpointOption, @@ -11,8 +10,6 @@ const { const router = express.Router(); -router.post('/abort', handleAbort()); - router.post( '/', validateEndpoint, diff --git a/api/server/routes/edit/gptPlugins.js b/api/server/routes/edit/gptPlugins.js index 5547a1fcd..94d9b91d0 100644 --- a/api/server/routes/edit/gptPlugins.js +++ b/api/server/routes/edit/gptPlugins.js @@ -2,7 +2,6 @@ const express = require('express'); const { getResponseSender } = require('librechat-data-provider'); const { setHeaders, - handleAbort, moderateText, validateModel, handleAbortError, @@ -19,7 +18,6 @@ const { logger } = require('~/config'); const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', @@ -173,7 +171,8 @@ router.post( logger.debug('[/edit/gptPlugins] CLIENT RESPONSE', response); - const { conversation = {} } = await client.responsePromise; + const { conversation = {} } = await response.databasePromise; + delete response.databasePromise; conversation.title = conversation && !conversation.title ? null : conversation?.title || 'New Chat'; diff --git a/api/server/routes/edit/openAI.js b/api/server/routes/edit/openAI.js index ae26b235c..ee25a42ee 100644 --- a/api/server/routes/edit/openAI.js +++ b/api/server/routes/edit/openAI.js @@ -2,7 +2,6 @@ const express = require('express'); const EditController = require('~/server/controllers/EditController'); const { initializeClient } = require('~/server/services/Endpoints/openAI'); const { - handleAbort, setHeaders, validateModel, validateEndpoint, @@ -12,7 +11,6 @@ const { const router = express.Router(); router.use(moderateText); -router.post('/abort', handleAbort()); router.post( '/', diff --git a/api/server/routes/search.js b/api/server/routes/search.js index 68cff7532..0341a241d 100644 --- a/api/server/routes/search.js +++ b/api/server/routes/search.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const express = require('express'); const { MeiliSearch } = require('meilisearch'); const { Conversation, getConvosQueried } = require('~/models/Conversation'); diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 12e30fd8b..8b13e2609 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -50,7 +50,7 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { return null; } - const parsedDomain = await domainParser(req, domain, true); + const parsedDomain = await domainParser(domain, true); if (!parsedDomain) { return null; @@ -66,12 +66,11 @@ const validateAndUpdateTool = async ({ req, tool, assistant_id }) => { * * Necessary due to `[a-zA-Z0-9_-]*` Regex Validation, limited to a 64-character maximum. * - * @param {Express.Request} req - The Express Request object. * @param {string} domain - The domain name to encode/decode. * @param {boolean} inverse - False to decode from base64, true to encode to base64. * @returns {Promise} Encoded or decoded domain string. */ -async function domainParser(req, domain, inverse = false) { +async function domainParser(domain, inverse = false) { if (!domain) { return; } @@ -122,7 +121,7 @@ async function loadActionSets(searchParams) { * Creates a general tool for an entire action set. * * @param {Object} params - The parameters for loading action sets. - * @param {ServerRequest} params.req + * @param {string} params.userId * @param {ServerResponse} params.res * @param {Action} params.action - The action set. Necessary for decrypting authentication values. * @param {ActionRequest} params.requestBuilder - The ActionRequest builder class to execute the API call. @@ -133,7 +132,7 @@ async function loadActionSets(searchParams) { * @returns { Promise unknown}> } An object with `_call` method to execute the tool input. */ async function createActionTool({ - req, + userId, res, action, requestBuilder, @@ -154,7 +153,7 @@ async function createActionTool({ try { if (metadata.auth.type === AuthTypeEnum.OAuth && metadata.auth.authorization_url) { const action_id = action.action_id; - const identifier = `${req.user.id}:${action.action_id}`; + const identifier = `${userId}:${action.action_id}`; const requestLogin = async () => { const { args: _args, stepId, ...toolCall } = config.toolCall ?? {}; if (!stepId) { @@ -162,7 +161,7 @@ async function createActionTool({ } const statePayload = { nonce: nanoid(), - user: req.user.id, + user: userId, action_id, }; @@ -206,7 +205,7 @@ async function createActionTool({ 'oauth', { state: stateToken, - userId: req.user.id, + userId: userId, client_url: metadata.auth.client_url, redirect_uri: `${process.env.DOMAIN_CLIENT}/api/actions/${action_id}/oauth/callback`, /** Encrypted values */ @@ -232,10 +231,10 @@ async function createActionTool({ }; const tokenPromises = []; - tokenPromises.push(findToken({ userId: req.user.id, type: 'oauth', identifier })); + tokenPromises.push(findToken({ userId, type: 'oauth', identifier })); tokenPromises.push( findToken({ - userId: req.user.id, + userId, type: 'oauth_refresh', identifier: `${identifier}:refresh`, }), @@ -258,9 +257,9 @@ async function createActionTool({ const refresh_token = await decryptV2(refreshTokenData.token); const refreshTokens = async () => await refreshAccessToken({ + userId, identifier, refresh_token, - userId: req.user.id, client_url: metadata.auth.client_url, encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_secret: encrypted.oauth_client_secret, diff --git a/api/server/services/ActionService.spec.js b/api/server/services/ActionService.spec.js index 8f9d67a9d..f3b442319 100644 --- a/api/server/services/ActionService.spec.js +++ b/api/server/services/ActionService.spec.js @@ -78,20 +78,20 @@ describe('domainParser', () => { // Non-azure request it('does not return domain as is if not azure', async () => { const domain = `example.com${actionDomainSeparator}test${actionDomainSeparator}`; - const result1 = await domainParser(reqNoAzure, domain, false); - const result2 = await domainParser(reqNoAzure, domain, true); + const result1 = await domainParser(domain, false); + const result2 = await domainParser(domain, true); expect(result1).not.toEqual(domain); expect(result2).not.toEqual(domain); }); // Test for Empty or Null Inputs it('returns undefined for null domain input', async () => { - const result = await domainParser(req, null, true); + const result = await domainParser(null, true); expect(result).toBeUndefined(); }); it('returns undefined for empty domain input', async () => { - const result = await domainParser(req, '', true); + const result = await domainParser('', true); expect(result).toBeUndefined(); }); @@ -102,7 +102,7 @@ describe('domainParser', () => { .toString('base64') .substring(0, Constants.ENCODED_DOMAIN_LENGTH); - await domainParser(req, domain, true); + await domainParser(domain, true); const cachedValue = await globalCache[encodedDomain]; expect(cachedValue).toEqual(Buffer.from(domain).toString('base64')); @@ -112,14 +112,14 @@ describe('domainParser', () => { it('encodes domain exactly at threshold without modification', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - TLD.length) + TLD; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); it('encodes domain just below threshold without modification', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH - 1 - TLD.length) + TLD; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); @@ -129,7 +129,7 @@ describe('domainParser', () => { const encodedDomain = Buffer.from(unicodeDomain) .toString('base64') .substring(0, Constants.ENCODED_DOMAIN_LENGTH); - const result = await domainParser(req, unicodeDomain, true); + const result = await domainParser(unicodeDomain, true); expect(result).toEqual(encodedDomain); }); @@ -139,7 +139,6 @@ describe('domainParser', () => { globalCache[encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH)] = encodedDomain; // Simulate caching const result = await domainParser( - req, encodedDomain.substring(0, Constants.ENCODED_DOMAIN_LENGTH), false, ); @@ -150,27 +149,27 @@ describe('domainParser', () => { it('returns domain with replaced separators if no cached domain exists', async () => { const domain = 'example.com'; const withSeparator = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, withSeparator, false); + const result = await domainParser(withSeparator, false); expect(result).toEqual(domain); }); it('returns domain with replaced separators when inverse is false and under encoding length', async () => { const domain = 'examp.com'; const withSeparator = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, withSeparator, false); + const result = await domainParser(withSeparator, false); expect(result).toEqual(domain); }); it('replaces periods with actionDomainSeparator when inverse is true and under encoding length', async () => { const domain = 'examp.com'; const expected = domain.replace(/\./g, actionDomainSeparator); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).toEqual(expected); }); it('encodes domain when length is above threshold and inverse is true', async () => { const domain = 'a'.repeat(Constants.ENCODED_DOMAIN_LENGTH + 1).concat('.com'); - const result = await domainParser(req, domain, true); + const result = await domainParser(domain, true); expect(result).not.toEqual(domain); expect(result.length).toBeLessThanOrEqual(Constants.ENCODED_DOMAIN_LENGTH); }); @@ -180,20 +179,20 @@ describe('domainParser', () => { const encodedDomain = Buffer.from( originalDomain.replace(/\./g, actionDomainSeparator), ).toString('base64'); - const result = await domainParser(req, encodedDomain, false); + const result = await domainParser(encodedDomain, false); expect(result).toEqual(encodedDomain); }); it('decodes encoded value if cached and encoded value is provided, and inverse is false', async () => { const originalDomain = 'example.com'; - const encodedDomain = await domainParser(req, originalDomain, true); - const result = await domainParser(req, encodedDomain, false); + const encodedDomain = await domainParser(originalDomain, true); + const result = await domainParser(encodedDomain, false); expect(result).toEqual(originalDomain); }); it('handles invalid base64 encoded values gracefully', async () => { const invalidBase64Domain = 'not_base64_encoded'; - const result = await domainParser(req, invalidBase64Domain, false); + const result = await domainParser(invalidBase64Domain, false); expect(result).toEqual(invalidBase64Domain); }); }); diff --git a/api/server/services/Endpoints/agents/initialize.js b/api/server/services/Endpoints/agents/initialize.js index 018654175..eaff058bf 100644 --- a/api/server/services/Endpoints/agents/initialize.js +++ b/api/server/services/Endpoints/agents/initialize.js @@ -159,14 +159,20 @@ const initializeAgentOptions = async ({ currentFiles, agent.tool_resources, ); + + const provider = agent.provider; const { tools, toolContextMap } = await loadAgentTools({ req, res, - agent, + agent: { + id: agent.id, + tools: agent.tools, + provider, + model: agent.model, + }, tool_resources, }); - const provider = agent.provider; agent.endpoint = provider; let getOptions = providerConfigMap[provider]; if (!getOptions && providerConfigMap[provider.toLowerCase()] != null) { diff --git a/api/server/services/Endpoints/agents/title.js b/api/server/services/Endpoints/agents/title.js index f25746582..ab171bc79 100644 --- a/api/server/services/Endpoints/agents/title.js +++ b/api/server/services/Endpoints/agents/title.js @@ -2,7 +2,11 @@ const { CacheKeys } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); const { isEnabled } = require('~/server/utils'); const { saveConvo } = require('~/models'); +const { logger } = require('~/config'); +/** + * Add title to conversation in a way that avoids memory retention + */ const addTitle = async (req, { text, response, client }) => { const { TITLE_CONVO = true } = process.env ?? {}; if (!isEnabled(TITLE_CONVO)) { @@ -13,37 +17,55 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted, don't generate the title. - if (client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; - const responseText = - response?.content && Array.isArray(response?.content) - ? response.content.reduce((acc, block) => { - if (block?.type === 'text') { - return acc + block.text; - } - return acc; - }, '') - : (response?.content ?? response?.text ?? ''); + /** @type {NodeJS.Timeout} */ + let timeoutId; + try { + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error('Title generation timeout')), 25000); + }).catch((error) => { + logger.error('Title error:', error); + }); - const title = await client.titleConvo({ - text, - responseText, - conversationId: response.conversationId, - }); - await titleCache.set(key, title, 120000); - await saveConvo( - req, - { - conversationId: response.conversationId, - title, - }, - { context: 'api/server/services/Endpoints/agents/title.js' }, - ); + let titlePromise; + let abortController = new AbortController(); + if (client && typeof client.titleConvo === 'function') { + titlePromise = Promise.race([ + client + .titleConvo({ + text, + abortController, + }) + .catch((error) => { + logger.error('Client title error:', error); + }), + timeoutPromise, + ]); + } else { + return; + } + + const title = await titlePromise; + if (!abortController.signal.aborted) { + abortController.abort(); + } + if (timeoutId) { + clearTimeout(timeoutId); + } + + await titleCache.set(key, title, 120000); + await saveConvo( + req, + { + conversationId: response.conversationId, + title, + }, + { context: 'api/server/services/Endpoints/agents/title.js' }, + ); + } catch (error) { + logger.error('Error generating title:', error); + } }; module.exports = addTitle; diff --git a/api/server/services/Endpoints/anthropic/initialize.js b/api/server/services/Endpoints/anthropic/initialize.js index 6c89eff46..d4c6dd179 100644 --- a/api/server/services/Endpoints/anthropic/initialize.js +++ b/api/server/services/Endpoints/anthropic/initialize.js @@ -1,7 +1,7 @@ const { EModelEndpoint } = require('librechat-data-provider'); const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/anthropic/llm'); -const { AnthropicClient } = require('~/app'); +const AnthropicClient = require('~/app/clients/AnthropicClient'); const initializeClient = async ({ req, res, endpointOption, overrideModel, optionsOnly }) => { const { ANTHROPIC_API_KEY, ANTHROPIC_REVERSE_PROXY, PROXY } = process.env; diff --git a/api/server/services/Endpoints/anthropic/title.js b/api/server/services/Endpoints/anthropic/title.js index 5c477632d..0f9a5e97d 100644 --- a/api/server/services/Endpoints/anthropic/title.js +++ b/api/server/services/Endpoints/anthropic/title.js @@ -13,11 +13,6 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted, don't generate the title. - if (client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; diff --git a/api/server/services/Endpoints/custom/initialize.js b/api/server/services/Endpoints/custom/initialize.js index e98ec7198..c44b9e0e3 100644 --- a/api/server/services/Endpoints/custom/initialize.js +++ b/api/server/services/Endpoints/custom/initialize.js @@ -11,8 +11,8 @@ const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); const { getCustomEndpointConfig } = require('~/server/services/Config'); const { fetchModels } = require('~/server/services/ModelService'); const { isUserProvided, sleep } = require('~/server/utils'); +const OpenAIClient = require('~/app/clients/OpenAIClient'); const getLogStores = require('~/cache/getLogStores'); -const { OpenAIClient } = require('~/app'); const { PROXY } = process.env; diff --git a/api/server/services/Endpoints/openAI/initialize.js b/api/server/services/Endpoints/openAI/initialize.js index 4d358cef1..1661f9e6c 100644 --- a/api/server/services/Endpoints/openAI/initialize.js +++ b/api/server/services/Endpoints/openAI/initialize.js @@ -7,8 +7,14 @@ const { const { getUserKeyValues, checkUserKeyExpiry } = require('~/server/services/UserService'); const { getLLMConfig } = require('~/server/services/Endpoints/openAI/llm'); const { isEnabled, isUserProvided, sleep } = require('~/server/utils'); +const OpenAIClient = require('~/app/clients/OpenAIClient'); const { getAzureCredentials } = require('~/utils'); -const { OpenAIClient } = require('~/app'); + +function createHandleNewToken(streamRate) { + async () => { + await sleep(streamRate); + }; +} const initializeClient = async ({ req, @@ -140,14 +146,13 @@ const initializeClient = async ({ clientOptions = Object.assign({ modelOptions }, clientOptions); clientOptions.modelOptions.user = req.user.id; const options = getLLMConfig(apiKey, clientOptions); - if (!clientOptions.streamRate) { + const streamRate = clientOptions.streamRate; + if (!streamRate) { return options; } options.llmConfig.callbacks = [ { - handleLLMNewToken: async () => { - await sleep(clientOptions.streamRate); - }, + handleLLMNewToken: createHandleNewToken(streamRate), }, ]; return options; diff --git a/api/server/services/Endpoints/openAI/title.js b/api/server/services/Endpoints/openAI/title.js index 35291c5e3..3b9e9c82b 100644 --- a/api/server/services/Endpoints/openAI/title.js +++ b/api/server/services/Endpoints/openAI/title.js @@ -13,11 +13,6 @@ const addTitle = async (req, { text, response, client }) => { return; } - // If the request was aborted and is not azure, don't generate the title. - if (!client.azure && client.abortController.signal.aborted) { - return; - } - const titleCache = getLogStores(CacheKeys.GEN_TITLE); const key = `${req.user.id}-${response.conversationId}`; diff --git a/api/server/services/MCP.js b/api/server/services/MCP.js index 0a2711c67..5c5bd9677 100644 --- a/api/server/services/MCP.js +++ b/api/server/services/MCP.js @@ -37,9 +37,8 @@ async function createMCPTool({ req, toolKey, provider }) { } const [toolName, serverName] = toolKey.split(Constants.mcp_delimiter); - const userId = req.user?.id; - if (!userId) { + if (!req.user?.id) { logger.error( `[MCP][${serverName}][${toolName}] User ID not found on request. Cannot create tool.`, ); @@ -49,15 +48,16 @@ async function createMCPTool({ req, toolKey, provider }) { /** @type {(toolArguments: Object | string, config?: GraphRunnableConfig) => Promise} */ const _call = async (toolArguments, config) => { try { - const mcpManager = getMCPManager(); + const derivedSignal = config?.signal ? AbortSignal.any([config.signal]) : undefined; + const mcpManager = getMCPManager(config?.userId); const result = await mcpManager.callTool({ serverName, toolName, provider, toolArguments, options: { - userId, - signal: config?.signal, + userId: config?.configurable?.user_id, + signal: derivedSignal, }, }); @@ -70,7 +70,7 @@ async function createMCPTool({ req, toolKey, provider }) { return result; } catch (error) { logger.error( - `[MCP][User: ${userId}][${serverName}] Error calling "${toolName}" MCP tool:`, + `[MCP][User: ${config?.userId}][${serverName}] Error calling "${toolName}" MCP tool:`, error, ); throw new Error( diff --git a/api/server/services/ToolService.js b/api/server/services/ToolService.js index fca26ffcf..046d4e9bf 100644 --- a/api/server/services/ToolService.js +++ b/api/server/services/ToolService.js @@ -334,7 +334,7 @@ async function processRequiredActions(client, requiredActions) { const domainMap = new Map(); for (const action of actionSets) { - const domain = await domainParser(client.req, action.metadata.domain, true); + const domain = await domainParser(action.metadata.domain, true); domainMap.set(domain, action); // Check if domain is allowed @@ -404,7 +404,7 @@ async function processRequiredActions(client, requiredActions) { // We've already decrypted the metadata, so we can pass it directly tool = await createActionTool({ - req: client.req, + userId: client.req.user.id, res: client.res, action, requestBuilder, @@ -458,7 +458,7 @@ async function processRequiredActions(client, requiredActions) { * @param {Object} params - Run params containing user and request information. * @param {ServerRequest} params.req - The request object. * @param {ServerResponse} params.res - The request object. - * @param {Agent} params.agent - The agent to load tools for. + * @param {Pick} The agent tools. */ @@ -570,7 +570,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey }) const domainMap = new Map(); for (const action of actionSets) { - const domain = await domainParser(req, action.metadata.domain, true); + const domain = await domainParser(action.metadata.domain, true); domainMap.set(domain, action); // Check if domain is allowed (do this once per action set) @@ -639,7 +639,7 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey }) if (requestBuilder) { const tool = await createActionTool({ - req, + userId: req.user.id, res, action, requestBuilder, diff --git a/api/server/socialLogins.js b/api/server/socialLogins.js index af80a3b88..0eb44514d 100644 --- a/api/server/socialLogins.js +++ b/api/server/socialLogins.js @@ -1,4 +1,4 @@ -const Keyv = require('keyv'); +const { Keyv } = require('keyv'); const passport = require('passport'); const session = require('express-session'); const MemoryStore = require('memorystore')(session); @@ -53,7 +53,7 @@ const configureSocialLogins = (app) => { if (isEnabled(process.env.USE_REDIS)) { logger.debug('Using Redis for session storage in OpenID...'); const keyv = new Keyv({ store: keyvRedis }); - const client = keyv.opts.store.redis; + const client = keyv.opts.store.client; sessionOptions.store = new RedisStore({ client, prefix: 'openid_session' }); } else { sessionOptions.store = new MemoryStore({ diff --git a/api/test/__mocks__/KeyvMongo.js b/api/test/__mocks__/KeyvMongo.js index f88bc144b..f990ae389 100644 --- a/api/test/__mocks__/KeyvMongo.js +++ b/api/test/__mocks__/KeyvMongo.js @@ -1,6 +1,3 @@ -const mockGet = jest.fn(); -const mockSet = jest.fn(); - jest.mock('@keyv/mongo', () => { const EventEmitter = require('events'); class KeyvMongo extends EventEmitter { @@ -20,11 +17,32 @@ jest.mock('@keyv/mongo', () => { ...url, ...options, }; + + // In-memory store for tests + this.store = new Map(); } - get = mockGet; - set = mockSet; + async get(key) { + return this.store.get(key); + } + + async set(key, value, ttl) { + this.store.set(key, value); + return true; + } + + async delete(key) { + return this.store.delete(key); + } + + async clear() { + this.store.clear(); + return true; + } } - return KeyvMongo; + // Create a store factory function for the test suite + const store = () => new KeyvMongo(); + + return { KeyvMongo }; }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index 2982aedcb..1f648b34f 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -196,6 +196,7 @@ const bedrockModels = { }; const xAIModels = { + grok: 131072, 'grok-beta': 131072, 'grok-vision-beta': 8192, 'grok-2': 131072, @@ -204,6 +205,10 @@ const xAIModels = { 'grok-2-vision': 32768, 'grok-2-vision-latest': 32768, 'grok-2-vision-1212': 32768, + 'grok-3': 131072, + 'grok-3-fast': 131072, + 'grok-3-mini': 131072, + 'grok-3-mini-fast': 131072, }; const aggregateModels = { ...openAIModels, ...googleModels, ...bedrockModels, ...xAIModels }; diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index e5ae21b64..9960b8f5f 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -517,18 +517,30 @@ describe('Grok Model Tests - Tokens', () => { expect(getModelMaxTokens('grok-2-latest')).toBe(131072); }); + test('should return correct tokens for Grok 3 series models', () => { + expect(getModelMaxTokens('grok-3')).toBe(131072); + expect(getModelMaxTokens('grok-3-fast')).toBe(131072); + expect(getModelMaxTokens('grok-3-mini')).toBe(131072); + expect(getModelMaxTokens('grok-3-mini-fast')).toBe(131072); + }); + test('should handle partial matches for Grok models with prefixes', () => { // Vision models should match before general models - expect(getModelMaxTokens('openai/grok-2-vision-1212')).toBe(32768); - expect(getModelMaxTokens('openai/grok-2-vision')).toBe(32768); - expect(getModelMaxTokens('openai/grok-2-vision-latest')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision-1212')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision')).toBe(32768); + expect(getModelMaxTokens('xai/grok-2-vision-latest')).toBe(32768); // Beta models - expect(getModelMaxTokens('openai/grok-vision-beta')).toBe(8192); - expect(getModelMaxTokens('openai/grok-beta')).toBe(131072); + expect(getModelMaxTokens('xai/grok-vision-beta')).toBe(8192); + expect(getModelMaxTokens('xai/grok-beta')).toBe(131072); // Text models - expect(getModelMaxTokens('openai/grok-2-1212')).toBe(131072); - expect(getModelMaxTokens('openai/grok-2')).toBe(131072); - expect(getModelMaxTokens('openai/grok-2-latest')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2-1212')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2')).toBe(131072); + expect(getModelMaxTokens('xai/grok-2-latest')).toBe(131072); + // Grok 3 models + expect(getModelMaxTokens('xai/grok-3')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-fast')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-mini')).toBe(131072); + expect(getModelMaxTokens('xai/grok-3-mini-fast')).toBe(131072); }); }); @@ -545,20 +557,30 @@ describe('Grok Model Tests - Tokens', () => { expect(matchModelName('grok-2-1212')).toBe('grok-2-1212'); expect(matchModelName('grok-2')).toBe('grok-2'); expect(matchModelName('grok-2-latest')).toBe('grok-2-latest'); + // Grok 3 models + expect(matchModelName('grok-3')).toBe('grok-3'); + expect(matchModelName('grok-3-fast')).toBe('grok-3-fast'); + expect(matchModelName('grok-3-mini')).toBe('grok-3-mini'); + expect(matchModelName('grok-3-mini-fast')).toBe('grok-3-mini-fast'); }); test('should match Grok model variations with prefixes', () => { // Vision models should match before general models - expect(matchModelName('openai/grok-2-vision-1212')).toBe('grok-2-vision-1212'); - expect(matchModelName('openai/grok-2-vision')).toBe('grok-2-vision'); - expect(matchModelName('openai/grok-2-vision-latest')).toBe('grok-2-vision-latest'); + expect(matchModelName('xai/grok-2-vision-1212')).toBe('grok-2-vision-1212'); + expect(matchModelName('xai/grok-2-vision')).toBe('grok-2-vision'); + expect(matchModelName('xai/grok-2-vision-latest')).toBe('grok-2-vision-latest'); // Beta models - expect(matchModelName('openai/grok-vision-beta')).toBe('grok-vision-beta'); - expect(matchModelName('openai/grok-beta')).toBe('grok-beta'); + expect(matchModelName('xai/grok-vision-beta')).toBe('grok-vision-beta'); + expect(matchModelName('xai/grok-beta')).toBe('grok-beta'); // Text models - expect(matchModelName('openai/grok-2-1212')).toBe('grok-2-1212'); - expect(matchModelName('openai/grok-2')).toBe('grok-2'); - expect(matchModelName('openai/grok-2-latest')).toBe('grok-2-latest'); + expect(matchModelName('xai/grok-2-1212')).toBe('grok-2-1212'); + expect(matchModelName('xai/grok-2')).toBe('grok-2'); + expect(matchModelName('xai/grok-2-latest')).toBe('grok-2-latest'); + // Grok 3 models + expect(matchModelName('xai/grok-3')).toBe('grok-3'); + expect(matchModelName('xai/grok-3-fast')).toBe('grok-3-fast'); + expect(matchModelName('xai/grok-3-mini')).toBe('grok-3-mini'); + expect(matchModelName('xai/grok-3-mini-fast')).toBe('grok-3-mini-fast'); }); }); }); diff --git a/client/src/hooks/SSE/useEventHandlers.ts b/client/src/hooks/SSE/useEventHandlers.ts index 6eb6941c8..75a918fb5 100644 --- a/client/src/hooks/SSE/useEventHandlers.ts +++ b/client/src/hooks/SSE/useEventHandlers.ts @@ -11,6 +11,7 @@ import { tMessageSchema, tConvoUpdateSchema, ContentTypes, + isAssistantsEndpoint, } from 'librechat-data-provider'; import type { TMessage, @@ -622,6 +623,17 @@ export default function useEventHandlers({ const { endpoint: _endpoint, endpointType } = (submission.conversation as TConversation | null) ?? {}; const endpoint = endpointType ?? _endpoint; + if (!isAssistantsEndpoint(endpoint)) { + if (newConversation) { + newConversation({ + template: { conversationId: conversationId || v4() }, + preset: tPresetSchema.parse(submission.conversation), + }); + } + setIsSubmitting(false); + return; + } + try { const response = await fetch(`${EndpointURLs[endpoint ?? '']}/abort`, { method: 'POST', diff --git a/package-lock.json b/package-lock.json index ec692aaa1..1e98d035c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,14 +58,14 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@keyv/mongo": "^2.1.8", - "@keyv/redis": "^2.8.1", + "@keyv/mongo": "^3.0.1", + "@keyv/redis": "^4.3.3", "@langchain/community": "^0.3.39", "@langchain/core": "^0.3.43", "@langchain/google-genai": "^0.2.2", "@langchain/google-vertexai": "^0.2.3", "@langchain/textsplitters": "^0.1.0", - "@librechat/agents": "^2.4.12", + "@librechat/agents": "^2.4.14", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -92,8 +92,8 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", - "keyv": "^4.5.4", - "keyv-file": "^0.2.0", + "keyv": "^5.3.2", + "keyv-file": "^5.1.2", "klona": "^2.0.6", "librechat-data-provider": "*", "librechat-mcp": "*", @@ -158,6 +158,27 @@ "node": ">=18.0.0" } }, + "api/node_modules/@keyv/mongo": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-3.0.1.tgz", + "integrity": "sha512-dNVIhm68mh/CAySN6q7o1vyBRdBRt41nrJXIDlqLhnXOo4l8IAY1Vj5BlTkUfw1BDLuZ9zjb+g1lr/BMRdzNdg==", + "dependencies": { + "mongodb": "^6.8.0" + } + }, + "api/node_modules/@keyv/redis": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.3.3.tgz", + "integrity": "sha512-J/uhvKu/Qfh11yMUs+9KdcGCLmWFd3vMxtDVQh2j9cOcnrpnM5jE1xU+K1/kI89czSVEdeMyqTC9gGNtwi3JEQ==", + "dependencies": { + "cluster-key-slot": "^1.1.2", + "keyv": "^5.3.2", + "redis": "^4.7.0" + }, + "engines": { + "node": ">= 18" + } + }, "api/node_modules/@langchain/community": { "version": "0.3.39", "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", @@ -825,6 +846,29 @@ "node": ">= 14" } }, + "api/node_modules/keyv": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", + "dependencies": { + "@keyv/serialize": "^1.0.3" + } + }, + "api/node_modules/keyv-file": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.1.2.tgz", + "integrity": "sha512-Sx5W55HeSbmsX4BfanJwaJZd3xePoQKxfuysvIhZ3JTPoSeZjApFO1QnuXGVy9hDXpmztS5mm39wlBFOUalVgw==", + "dependencies": { + "@keyv/serialize": "^1.0.1", + "fs-extra": "^4.0.1", + "tslib": "^1.14.1" + } + }, + "api/node_modules/keyv-file/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "api/node_modules/mongodb": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", @@ -3369,12 +3413,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" @@ -5384,6 +5428,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.623.0.tgz", "integrity": "sha512-kGYnTzXTMGdjko5+GZ1PvWvfXA7quiOp5iMo5gbh5b55pzIdc918MHN0pvaqplVGWYlaFJF4YzxUT5Nbxd7Xeg==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -5436,6 +5481,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -5448,6 +5494,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -5461,6 +5508,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -5473,6 +5521,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -5486,6 +5535,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -5499,6 +5549,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7395,6 +7446,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.623.0.tgz", "integrity": "sha512-oEACriysQMnHIVcNp7TD6D1nzgiHfYK0tmMBMbUxgoFuCBkW9g9QYvspHN+S9KgoePfMEXHuPUe9mtG9AH9XeA==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7444,6 +7496,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.623.0.tgz", "integrity": "sha512-lMFEXCa6ES/FGV7hpyrppT1PiAkqQb51AbG0zVU3TIgI2IO4XX02uzMUXImRSRqRpGymRCbJCaCs9LtKvS/37Q==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7497,6 +7550,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7509,6 +7563,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7522,6 +7577,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7534,6 +7590,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7547,6 +7604,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7560,6 +7618,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7573,6 +7632,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7585,6 +7645,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7598,6 +7659,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7610,6 +7672,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7623,6 +7686,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7636,6 +7700,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7649,6 +7714,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.623.0.tgz", "integrity": "sha512-iJNdx76SOw0YjHAUv8aj3HXzSu3TKI7qSGuR+OGATwA/kpJZDd+4+WYBdGtr8YK+hPrGGqhfecuCkEg805O5iA==", "optional": true, + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -7700,6 +7766,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7712,6 +7779,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7725,6 +7793,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7737,6 +7806,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7750,6 +7820,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7763,6 +7834,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7776,6 +7848,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.623.0.tgz", "integrity": "sha512-8Toq3X6trX/67obSdh4K0MFQY4f132bEbr1i0YPDWk/O3KdBt12mLC/sW3aVRnlIs110XMuX9yrWWqJ8fDW10g==", "optional": true, + "peer": true, "dependencies": { "@smithy/core": "^2.3.2", "@smithy/node-config-provider": "^3.1.4", @@ -7796,6 +7869,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7808,6 +7882,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7821,6 +7896,7 @@ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "@smithy/protocol-http": "^4.1.0", @@ -7840,6 +7916,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7852,6 +7929,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -7865,6 +7943,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7877,6 +7956,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -7890,6 +7970,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7902,6 +7983,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -7915,6 +7997,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.623.0.tgz", "integrity": "sha512-sXU2KtWpFzIzE4iffSIUbl4mgbeN1Rta6BnuKtS3rrVrryku9akAxY//pulbsIsYfXRzOwZzULsa+cxQN00lrw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/types": "3.609.0", @@ -7931,6 +8014,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7943,6 +8027,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -7958,6 +8043,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -7970,6 +8056,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.622.0.tgz", "integrity": "sha512-VUHbr24Oll1RK3WR8XLUugLpgK9ZuxEm/NVeVqyFts1Ck9gsKpRg1x4eH7L7tW3SJ4TDEQNMbD7/7J+eoL2svg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/fetch-http-handler": "^3.2.4", @@ -7990,6 +8077,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8003,6 +8091,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8015,6 +8104,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.623.0.tgz", "integrity": "sha512-kvXA1SwGneqGzFwRZNpESitnmaENHGFFuuTvgGwtMe7mzXWuA/LkXdbiHmdyAzOo0iByKTCD8uetuwh3CXy4Pw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8040,6 +8130,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8052,6 +8143,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.623.0.tgz", "integrity": "sha512-qDwCOkhbu5PfaQHyuQ+h57HEx3+eFhKdtIw7aISziWkGdFrMe07yIBd7TJqGe4nxXnRF1pfkg05xeOlMId997g==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-http": "3.622.0", @@ -8075,6 +8167,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8087,6 +8180,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8103,6 +8197,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8115,6 +8210,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.623.0.tgz", "integrity": "sha512-70LZhUb3l7cttEsg4A0S4Jq3qrCT/v5Jfyl8F7w1YZJt5zr3oPPcvDJxo/UYckFz4G4/5BhGa99jK8wMlNE9QA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-sso": "3.623.0", "@aws-sdk/token-providers": "3.614.0", @@ -8133,6 +8229,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8145,6 +8242,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -8163,6 +8261,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8175,6 +8274,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.623.0.tgz", "integrity": "sha512-abtlH1hkVWAkzuOX79Q47l0ztWOV2Q7l7J4JwQgzEQm7+zCk5iUAiwqKyDzr+ByCyo4I3IWFjy+e1gBdL7rXQQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/client-cognito-identity": "3.623.0", "@aws-sdk/client-sso": "3.623.0", @@ -8202,6 +8302,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8822,6 +8923,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -8837,6 +8939,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8850,6 +8953,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8889,6 +8993,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -8903,6 +9008,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -8915,6 +9021,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/protocol-http": "^4.1.0", @@ -8930,6 +9037,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -8943,6 +9051,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -9402,6 +9511,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", "integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@aws-sdk/util-endpoints": "3.614.0", @@ -9418,6 +9528,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -9431,6 +9542,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10149,6 +10261,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -10166,6 +10279,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10178,6 +10292,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -10676,6 +10791,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/property-provider": "^3.1.3", @@ -10695,6 +10811,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10742,6 +10859,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", "integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -10757,6 +10875,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10834,6 +10953,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/types": "^3.3.0", @@ -10846,6 +10966,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -10858,6 +10979,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "optional": true, + "peer": true, "dependencies": { "@aws-sdk/types": "3.609.0", "@smithy/node-config-provider": "^3.1.4", @@ -10881,6 +11003,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -16440,9 +16563,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -16453,75 +16576,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@keyv/mongo": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/@keyv/mongo/-/mongo-2.2.8.tgz", - "integrity": "sha512-2y8RXQDzCUzvhkzjH0bj4+Ur9Ce+x9PjNrV6KnGGpRocexFKVgOYexIegnEc/DBy6HhNyqUlgWOpuFfnhpmF+A==", + "node_modules/@keyv/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", "dependencies": { - "mongodb": "^4.5.0", - "pify": "^5.0.0" - } - }, - "node_modules/@keyv/mongo/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@keyv/mongo/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/@keyv/mongo/node_modules/mongodb": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.17.2.tgz", - "integrity": "sha512-mLV7SEiov2LHleRJPMPrK2PMyhXFZt2UQLC4VD4pnth3jMjYKHhtqfwwkkvS/NXuo/Fp3vbhaNcXrIDaLRb9Tg==", - "dependencies": { - "bson": "^4.7.2", - "mongodb-connection-string-url": "^2.6.0", - "socks": "^2.7.1" - }, - "engines": { - "node": ">=12.9.0" - }, - "optionalDependencies": { - "@aws-sdk/credential-providers": "^3.186.0", - "@mongodb-js/saslprep": "^1.1.0" - } - }, - "node_modules/@keyv/redis": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-2.8.4.tgz", - "integrity": "sha512-osO4C+i+Gi844wHjvXuHwhl+sDx3289Of309ZlLcj6SJReTLmPXzNiVR81N88wOu5aC+lVFdmx9FUQkkjdbPRQ==", - "dependencies": { - "ioredis": "^5.3.2" - }, - "engines": { - "node": ">= 14" + "buffer": "^6.0.3" } }, "node_modules/@langchain/anthropic": { @@ -17629,9 +17689,9 @@ } }, "node_modules/@langchain/langgraph-sdk": { - "version": "0.0.65", - "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.65.tgz", - "integrity": "sha512-Zn1FhiKr/mYa1+W5NcuCPWmdTtJS4UZYu+YVEjxgESd0aMX19FTkqjaSV6tFDcRqHHwlIgHloCSqHLkYWF/Zug==", + "version": "0.0.66", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-sdk/-/langgraph-sdk-0.0.66.tgz", + "integrity": "sha512-l0V4yfKXhHaTRK/1bKMfZ14k3wWZu27DWTlCUnbYJvdo7os5srhONgPCOqQgpazhi5EhXbW2EVgeu/wLW2zH6Q==", "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", @@ -17819,9 +17879,9 @@ } }, "node_modules/@librechat/agents": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.12.tgz", - "integrity": "sha512-m8CEVCjVeQDKXMS0ISG4h4YXU1x51yWGPLCuiNbQI3k+fmDvEFqVuzFi15OhVfoGGQxbKfzGiiF8fsKK2xIKEw==", + "version": "2.4.14", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.14.tgz", + "integrity": "sha512-EYtjjl7/Xb94UZh+6S8XHcbyuqAYrVi41aFk9UFV+8TRTN16ZK/K0zhPix1SGeA8J60Xs3Y1HkZBJdbGMi9X2Q==", "dependencies": { "@langchain/anthropic": "^0.3.16", "@langchain/aws": "^0.1.7", @@ -17844,9 +17904,9 @@ } }, "node_modules/@librechat/agents/node_modules/@langchain/community": { - "version": "0.3.39", - "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.39.tgz", - "integrity": "sha512-8sMJD22bUNlQVoh0b8PgRSpO2Hz5zO6BuZeNY3ElgW3cNVXCoywHW7FBmqbwrygGocJQDee76RqiIN0XQunhTg==", + "version": "0.3.40", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.3.40.tgz", + "integrity": "sha512-UvpEebdFKJsjFBKeUOvvYHOEFsUcjZnyU1qNirtDajwjzTJlszXtv+Mq8F6w5mJsznpI9x7ZMNzAqydVxMG5hA==", "dependencies": { "@langchain/openai": ">=0.2.0 <0.6.0", "binary-extensions": "^2.2.0", @@ -18368,9 +18428,9 @@ } }, "node_modules/@librechat/agents/node_modules/@langchain/openai": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.4.tgz", - "integrity": "sha512-fBIEgaAMs1OHHjSuOwtonhtFTlRyFETAS9y/4SZxlXV5lLdwEU5OAbfaBTcCR0A58rrlmgt8iPnm2IWn4Onjjw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.5.tgz", + "integrity": "sha512-QwdZrWcx6FB+UMKQ6+a0M9ZXzeUnZCwXP7ltqCCycPzdfiwxg3TQ6WkSefdEyiPpJcVVq/9HZSxrzGmf18QGyw==", "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^4.87.3", @@ -20752,6 +20812,64 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.0.tgz", @@ -21279,6 +21397,7 @@ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21292,6 +21411,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21381,6 +21501,7 @@ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -21397,6 +21518,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21409,6 +21531,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21422,6 +21545,7 @@ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-retry": "^3.0.15", @@ -21443,6 +21567,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21455,6 +21580,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21468,6 +21594,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21480,6 +21607,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -21493,6 +21621,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21506,6 +21635,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -21519,6 +21649,7 @@ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/property-provider": "^3.1.3", @@ -21535,6 +21666,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21672,6 +21804,7 @@ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "optional": true, + "peer": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/querystring-builder": "^3.0.3", @@ -21685,6 +21818,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21698,6 +21832,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21725,6 +21860,7 @@ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-buffer-from": "^3.0.0", @@ -21740,6 +21876,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21752,6 +21889,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21764,6 +21902,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -21777,6 +21916,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -21842,6 +21982,7 @@ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21852,6 +21993,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21927,6 +22069,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "optional": true, + "peer": true, "dependencies": { "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", @@ -21941,6 +22084,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -21954,6 +22098,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21966,6 +22111,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-serde": "^3.0.3", "@smithy/node-config-provider": "^3.1.4", @@ -21984,6 +22130,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -21996,6 +22143,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22009,6 +22157,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/protocol-http": "^4.1.0", @@ -22029,6 +22178,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22042,6 +22192,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22054,6 +22205,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22067,6 +22219,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22080,6 +22233,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22092,6 +22246,7 @@ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22105,6 +22260,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22117,6 +22273,7 @@ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/shared-ini-file-loader": "^3.1.4", @@ -22132,6 +22289,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22144,6 +22302,7 @@ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "optional": true, + "peer": true, "dependencies": { "@smithy/abort-controller": "^3.1.1", "@smithy/protocol-http": "^4.1.0", @@ -22160,6 +22319,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22173,6 +22333,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22185,6 +22346,7 @@ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22198,6 +22360,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22237,6 +22400,7 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "@smithy/util-uri-escape": "^3.0.0", @@ -22251,6 +22415,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22263,6 +22428,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22275,6 +22441,7 @@ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22288,6 +22455,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22300,6 +22468,7 @@ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0" }, @@ -22312,6 +22481,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22324,6 +22494,7 @@ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22337,6 +22508,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22395,6 +22567,7 @@ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "optional": true, + "peer": true, "dependencies": { "@smithy/middleware-endpoint": "^3.1.0", "@smithy/middleware-stack": "^3.0.3", @@ -22412,6 +22585,7 @@ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "optional": true, + "peer": true, "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -22425,6 +22599,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22448,6 +22623,7 @@ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "optional": true, + "peer": true, "dependencies": { "@smithy/querystring-parser": "^3.0.3", "@smithy/types": "^3.3.0", @@ -22459,6 +22635,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22471,6 +22648,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", @@ -22485,6 +22663,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22497,6 +22676,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22510,6 +22690,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -22523,6 +22704,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" } @@ -22532,6 +22714,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22556,6 +22739,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22568,6 +22752,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "optional": true, + "peer": true, "dependencies": { "@smithy/property-provider": "^3.1.3", "@smithy/smithy-client": "^3.2.0", @@ -22584,6 +22769,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22596,6 +22782,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "optional": true, + "peer": true, "dependencies": { "@smithy/config-resolver": "^3.0.5", "@smithy/credential-provider-imds": "^3.2.0", @@ -22614,6 +22801,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22626,6 +22814,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", "optional": true, + "peer": true, "dependencies": { "@smithy/node-config-provider": "^3.1.4", "@smithy/types": "^3.3.0", @@ -22640,6 +22829,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22677,6 +22867,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "optional": true, + "peer": true, "dependencies": { "@smithy/service-error-classification": "^3.0.3", "@smithy/types": "^3.3.0", @@ -22691,6 +22882,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22703,6 +22895,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "optional": true, + "peer": true, "dependencies": { "@smithy/fetch-http-handler": "^3.2.4", "@smithy/node-http-handler": "^3.1.4", @@ -22722,6 +22915,7 @@ "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22734,6 +22928,7 @@ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22746,6 +22941,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "optional": true, + "peer": true, "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -22759,6 +22955,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "optional": true, + "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -22771,6 +22968,7 @@ "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "optional": true, + "peer": true, "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -23555,15 +23753,6 @@ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" }, - "node_modules/@types/whatwg-url": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", - "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", - "dependencies": { - "@types/node": "*", - "@types/webidl-conversions": "*" - } - }, "node_modules/@types/winston": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", @@ -24859,9 +25048,9 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz", + "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==", "engines": { "node": "*" } @@ -26896,6 +27085,29 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -28907,6 +29119,14 @@ "node": ">=12" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -30156,6 +30376,8 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "optional": true, + "peer": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -30167,7 +30389,9 @@ "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "optional": true, + "peer": true }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -30901,9 +31125,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "dependencies": { "html-escaper": "^2.0.0", @@ -31729,7 +31953,9 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "optional": true, + "peer": true }, "node_modules/jschardet": { "version": "3.1.4", @@ -31976,21 +32202,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/keyv-file": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-0.2.0.tgz", - "integrity": "sha512-zUQ11eZRmilEUpV1gJSj8mBAHjyXpleQo1iCS0khb+GFRhiPfwavWgn4eDUKNlOyMZzmExnISl8HE1hNbim0gw==", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^4.0.1", - "tslib": "^1.9.3" - } - }, - "node_modules/keyv-file/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -32997,15 +33208,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -34489,15 +34697,6 @@ "node": "*" } }, - "node_modules/mongodb-connection-string-url": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", - "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", - "dependencies": { - "@types/whatwg-url": "^8.2.1", - "whatwg-url": "^11.0.0" - } - }, "node_modules/moo-color": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", @@ -35754,17 +35953,6 @@ "node": ">=0.10" } }, - "node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -38078,6 +38266,22 @@ "node": ">=8" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -39713,6 +39917,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "peer": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -39728,6 +39934,8 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "optional": true, + "peer": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -40919,6 +41127,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "devOptional": true, "dependencies": { "punycode": "^2.1.1" }, @@ -42316,6 +42525,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "devOptional": true, "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" diff --git a/packages/mcp/src/manager.ts b/packages/mcp/src/manager.ts index 83a5f8fe9..d5c5a3138 100644 --- a/packages/mcp/src/manager.ts +++ b/packages/mcp/src/manager.ts @@ -151,11 +151,14 @@ export class MCPManager { } /** Check for and disconnect idle connections */ - private checkIdleConnections(): void { + private checkIdleConnections(currentUserId?: string): void { const now = Date.now(); // Iterate through all users to check for idle ones for (const [userId, lastActivity] of this.userLastActivity.entries()) { + if (currentUserId && currentUserId === userId) { + continue; + } if (now - lastActivity > this.USER_CONNECTION_IDLE_TIMEOUT) { this.logger.info( `[MCP][User: ${userId}] User idle for too long. Disconnecting all connections...`,