From 64bd373bc87659d561dbfed6e02e8f0607a5c5f1 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 13 Apr 2025 23:01:55 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20fix:=20Keyv=20and=20Proxy=20Issu?= =?UTF-8?q?es,=20and=20More=20Memory=20Optimizations=20(#6867)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: update @librechat/agents dependency to version 2.4.15 * refactor: Prevent memory leaks by nullifying boundModel.client in disposeClient function * fix: use of proxy, use undici * chore: update @librechat/agents dependency to version 2.4.16 * Revert "fix: use of proxy, use undici" This reverts commit 83153cd58268e8018ae960d232bd17413983d667. * fix: ensure fetch is imported for HTTP requests * fix: replace direct OpenAI import with CustomOpenAIClient from @librechat/agents * fix: update keyv peer dependency to version 5.3.2 * fix: update keyv dependency to version 5.3.2 * refactor: replace KeyvMongo with custom implementation and update flow state manager usage * fix: update @librechat/agents dependency to version 2.4.17 * ci: update OpenAIClient tests to use CustomOpenAIClient from @librechat/agents * refactor: remove KeyvMongo mock and related dependencies --- api/app/clients/OpenAIClient.js | 3 +- api/app/clients/generators.js | 1 + api/app/clients/specs/BaseClient.test.js | 2 +- api/app/clients/specs/OpenAIClient.test.js | 73 +-- api/cache/keyvMongo.js | 269 +++++++- api/config/index.js | 6 +- api/jest.config.js | 1 - api/package.json | 3 +- api/server/cleanup.js | 3 + api/server/controllers/agents/client.js | 2 + api/server/routes/actions.js | 5 +- api/server/services/ActionService.js | 7 +- api/test/__mocks__/KeyvMongo.js | 48 -- package-lock.json | 684 ++------------------- packages/data-schemas/package.json | 2 +- packages/mcp/package.json | 2 +- packages/mcp/src/flow/manager.spec.ts | 2 +- packages/mcp/src/flow/manager.ts | 5 +- 18 files changed, 375 insertions(+), 743 deletions(-) delete mode 100644 api/test/__mocks__/KeyvMongo.js diff --git a/api/app/clients/OpenAIClient.js b/api/app/clients/OpenAIClient.js index 8c58d70f7..6cebdff14 100644 --- a/api/app/clients/OpenAIClient.js +++ b/api/app/clients/OpenAIClient.js @@ -1,7 +1,6 @@ -const OpenAI = require('openai'); const { OllamaClient } = require('./OllamaClient'); const { HttpsProxyAgent } = require('https-proxy-agent'); -const { SplitStreamHandler } = require('@librechat/agents'); +const { SplitStreamHandler, CustomOpenAIClient: OpenAI } = require('@librechat/agents'); const { Constants, ImageDetail, diff --git a/api/app/clients/generators.js b/api/app/clients/generators.js index 4d3988bf3..971ac16da 100644 --- a/api/app/clients/generators.js +++ b/api/app/clients/generators.js @@ -1,3 +1,4 @@ +const fetch = require('node-fetch'); const { GraphEvents } = require('@librechat/agents'); const { logger, sendEvent } = require('~/config'); diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index c9be50d3d..d620d5f64 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -32,7 +32,7 @@ jest.mock('~/models', () => ({ const { getConvo, saveConvo } = require('~/models'); -jest.mock('@langchain/openai', () => { +jest.mock('@librechat/agents', () => { return { ChatOpenAI: jest.fn().mockImplementation(() => { return {}; diff --git a/api/app/clients/specs/OpenAIClient.test.js b/api/app/clients/specs/OpenAIClient.test.js index adc290486..579f636ee 100644 --- a/api/app/clients/specs/OpenAIClient.test.js +++ b/api/app/clients/specs/OpenAIClient.test.js @@ -1,9 +1,7 @@ jest.mock('~/cache/getLogStores'); require('dotenv').config(); -const OpenAI = require('openai'); -const getLogStores = require('~/cache/getLogStores'); const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); -const { genAzureChatCompletion } = require('~/utils/azureUtils'); +const getLogStores = require('~/cache/getLogStores'); const OpenAIClient = require('../OpenAIClient'); jest.mock('meilisearch'); @@ -36,19 +34,21 @@ jest.mock('~/models', () => ({ updateFileUsage: jest.fn(), })); -jest.mock('@langchain/openai', () => { - return { - ChatOpenAI: jest.fn().mockImplementation(() => { - return {}; - }), - }; +// Import the actual module but mock specific parts +const agents = jest.requireActual('@librechat/agents'); +const { CustomOpenAIClient } = agents; + +// Also mock ChatOpenAI to prevent real API calls +agents.ChatOpenAI = jest.fn().mockImplementation(() => { + return {}; +}); +agents.AzureChatOpenAI = jest.fn().mockImplementation(() => { + return {}; }); -jest.mock('openai'); - -jest.spyOn(OpenAI, 'constructor').mockImplementation(function (...options) { - // We can add additional logic here if needed - return new OpenAI(...options); +// Mock only the CustomOpenAIClient constructor +jest.spyOn(CustomOpenAIClient, 'constructor').mockImplementation(function (...options) { + return new CustomOpenAIClient(...options); }); const finalChatCompletion = jest.fn().mockResolvedValue({ @@ -120,7 +120,13 @@ const create = jest.fn().mockResolvedValue({ ], }); -OpenAI.mockImplementation(() => ({ +// Mock the implementation of CustomOpenAIClient instances +jest.spyOn(CustomOpenAIClient.prototype, 'constructor').mockImplementation(function () { + return this; +}); + +// Create a mock for the CustomOpenAIClient class +const mockCustomOpenAIClient = jest.fn().mockImplementation(() => ({ beta: { chat: { completions: { @@ -135,6 +141,8 @@ OpenAI.mockImplementation(() => ({ }, })); +CustomOpenAIClient.mockImplementation = mockCustomOpenAIClient; + describe('OpenAIClient', () => { beforeEach(() => { const mockCache = { @@ -559,41 +567,6 @@ describe('OpenAIClient', () => { expect(requestBody).toHaveProperty('model'); expect(requestBody.model).toBe(model); }); - - it('[Azure OpenAI] should call chatCompletion and OpenAI.stream with correct args', async () => { - // Set a default model - process.env.AZURE_OPENAI_DEFAULT_MODEL = 'gpt4-turbo'; - - const onProgress = jest.fn().mockImplementation(() => ({})); - client.azure = defaultAzureOptions; - const chatCompletion = jest.spyOn(client, 'chatCompletion'); - await client.sendMessage('Hi mom!', { - replaceOptions: true, - ...defaultOptions, - modelOptions: { model: 'gpt4-turbo', stream: true }, - onProgress, - azure: defaultAzureOptions, - }); - - expect(chatCompletion).toHaveBeenCalled(); - expect(chatCompletion.mock.calls.length).toBe(1); - - const chatCompletionArgs = chatCompletion.mock.calls[0][0]; - const { payload } = chatCompletionArgs; - - expect(payload[0].role).toBe('user'); - expect(payload[0].content).toBe('Hi mom!'); - - // Azure OpenAI does not use the model property, and will error if it's passed - // This check ensures the model property is not present - const streamArgs = stream.mock.calls[0][0]; - expect(streamArgs).not.toHaveProperty('model'); - - // Check if the baseURL is correct - const constructorArgs = OpenAI.mock.calls[0][0]; - const expectedURL = genAzureChatCompletion(defaultAzureOptions).split('/chat')[0]; - expect(constructorArgs.baseURL).toBe(expectedURL); - }); }); describe('checkVisionRequest functionality', () => { diff --git a/api/cache/keyvMongo.js b/api/cache/keyvMongo.js index 3321be56f..1606e98eb 100644 --- a/api/cache/keyvMongo.js +++ b/api/cache/keyvMongo.js @@ -1,9 +1,272 @@ -const { KeyvMongo } = require('@keyv/mongo'); +// api/cache/keyvMongo.js +const mongoose = require('mongoose'); +const EventEmitter = require('events'); +const { GridFSBucket } = require('mongodb'); const { logger } = require('~/config'); -const { MONGO_URI } = process.env ?? {}; +const storeMap = new Map(); + +class KeyvMongoCustom extends EventEmitter { + constructor(url, options = {}) { + super(); + + url = url || {}; + if (typeof url === 'string') { + url = { url }; + } + if (url.uri) { + url = { url: url.uri, ...url }; + } + + this.opts = { + url: 'mongodb://127.0.0.1:27017', + collection: 'keyv', + ...url, + ...options, + }; + + this.ttlSupport = false; + + // Filter valid options + const keyvMongoKeys = new Set([ + 'url', + 'collection', + 'namespace', + 'serialize', + 'deserialize', + 'uri', + 'useGridFS', + 'dialect', + ]); + this.opts = Object.fromEntries(Object.entries(this.opts).filter(([k]) => keyvMongoKeys.has(k))); + } + + // Helper to access the store WITHOUT storing a promise on the instance + _getClient() { + const storeKey = `${this.opts.collection}:${this.opts.useGridFS ? 'gridfs' : 'collection'}`; + + // If we already have the store initialized, return it directly + if (storeMap.has(storeKey)) { + return Promise.resolve(storeMap.get(storeKey)); + } + + // Check mongoose connection state + if (mongoose.connection.readyState !== 1) { + return Promise.reject( + new Error('Mongoose connection not ready. Ensure connectDb() is called first.'), + ); + } + + try { + const db = mongoose.connection.db; + let client; + + if (this.opts.useGridFS) { + const bucket = new GridFSBucket(db, { + readPreference: this.opts.readPreference, + bucketName: this.opts.collection, + }); + const store = db.collection(`${this.opts.collection}.files`); + client = { bucket, store, db }; + } else { + const collection = this.opts.collection || 'keyv'; + const store = db.collection(collection); + client = { store, db }; + } + + storeMap.set(storeKey, client); + return Promise.resolve(client); + } catch (error) { + this.emit('error', error); + return Promise.reject(error); + } + } + + async get(key) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + await client.store.updateOne( + { + filename: key, + }, + { + $set: { + 'metadata.lastAccessed': new Date(), + }, + }, + ); + + const stream = client.bucket.openDownloadStreamByName(key); + + return new Promise((resolve) => { + const resp = []; + stream.on('error', () => { + resolve(undefined); + }); + + stream.on('end', () => { + const data = Buffer.concat(resp).toString('utf8'); + resolve(data); + }); + + stream.on('data', (chunk) => { + resp.push(chunk); + }); + }); + } + + const document = await client.store.findOne({ key: { $eq: key } }); + + if (!document) { + return undefined; + } + + return document.value; + } + + async getMany(keys) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + const promises = []; + for (const key of keys) { + promises.push(this.get(key)); + } + + const values = await Promise.allSettled(promises); + const data = []; + for (const value of values) { + data.push(value.value); + } + + return data; + } + + const values = await client.store + .find({ key: { $in: keys } }) + .project({ _id: 0, value: 1, key: 1 }) + .toArray(); + + const results = [...keys]; + let i = 0; + for (const key of keys) { + const rowIndex = values.findIndex((row) => row.key === key); + results[i] = rowIndex > -1 ? values[rowIndex].value : undefined; + i++; + } + + return results; + } + + async set(key, value, ttl) { + const client = await this._getClient(); + const expiresAt = typeof ttl === 'number' ? new Date(Date.now() + ttl) : null; + + if (this.opts.useGridFS) { + const stream = client.bucket.openUploadStream(key, { + metadata: { + expiresAt, + lastAccessed: new Date(), + }, + }); + + return new Promise((resolve) => { + stream.on('finish', () => { + resolve(stream); + }); + stream.end(value); + }); + } + + await client.store.updateOne( + { key: { $eq: key } }, + { $set: { key, value, expiresAt } }, + { upsert: true }, + ); + } + + async delete(key) { + if (typeof key !== 'string') { + return false; + } + + const client = await this._getClient(); + + if (this.opts.useGridFS) { + try { + const bucket = new GridFSBucket(client.db, { + bucketName: this.opts.collection, + }); + const files = await bucket.find({ filename: key }).toArray(); + await client.bucket.delete(files[0]._id); + return true; + } catch { + return false; + } + } + + const object = await client.store.deleteOne({ key: { $eq: key } }); + return object.deletedCount > 0; + } + + async deleteMany(keys) { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + const bucket = new GridFSBucket(client.db, { + bucketName: this.opts.collection, + }); + const files = await bucket.find({ filename: { $in: keys } }).toArray(); + if (files.length === 0) { + return false; + } + + await Promise.all(files.map(async (file) => client.bucket.delete(file._id))); + return true; + } + + const object = await client.store.deleteMany({ key: { $in: keys } }); + return object.deletedCount > 0; + } + + async clear() { + const client = await this._getClient(); + + if (this.opts.useGridFS) { + try { + await client.bucket.drop(); + } catch (error) { + // Throw error if not "namespace not found" error + if (!(error.code === 26)) { + throw error; + } + } + } + + await client.store.deleteMany({ + key: { $regex: this.namespace ? `^${this.namespace}:*` : '' }, + }); + } + + async has(key) { + const client = await this._getClient(); + const filter = { [this.opts.useGridFS ? 'filename' : 'key']: { $eq: key } }; + const document = await client.store.countDocuments(filter, { limit: 1 }); + return document !== 0; + } + + // No-op disconnect + async disconnect() { + // This is a no-op since we don't want to close the shared mongoose connection + return true; + } +} + +const keyvMongo = new KeyvMongoCustom({ + collection: 'logs', +}); -const keyvMongo = new KeyvMongo(MONGO_URI, { collection: 'logs' }); keyvMongo.on('error', (err) => logger.error('KeyvMongo connection error:', err)); module.exports = keyvMongo; diff --git a/api/config/index.js b/api/config/index.js index 57bc45fe1..e238f700b 100644 --- a/api/config/index.js +++ b/api/config/index.js @@ -24,12 +24,12 @@ function getMCPManager(userId) { } /** - * @param {(key: string) => Keyv} getLogStores + * @param {Keyv} flowsCache * @returns {FlowStateManager} */ -function getFlowStateManager(getLogStores) { +function getFlowStateManager(flowsCache) { if (!flowManager) { - flowManager = new FlowStateManager(getLogStores(CacheKeys.FLOWS), { + flowManager = new FlowStateManager(flowsCache, { ttl: Time.ONE_MINUTE * 3, logger, }); diff --git a/api/jest.config.js b/api/jest.config.js index ec44bd7f5..2df7790b7 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -5,7 +5,6 @@ module.exports = { coverageDirectory: 'coverage', setupFiles: [ './test/jestSetup.js', - './test/__mocks__/KeyvMongo.js', './test/__mocks__/logger.js', './test/__mocks__/fetchEventSource.js', ], diff --git a/api/package.json b/api/package.json index 73b415ad7..33fb73412 100644 --- a/api/package.json +++ b/api/package.json @@ -42,14 +42,13 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@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.14", + "@librechat/agents": "^2.4.17", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", diff --git a/api/server/cleanup.js b/api/server/cleanup.js index 6a9abb818..6d5b77196 100644 --- a/api/server/cleanup.js +++ b/api/server/cleanup.js @@ -238,6 +238,9 @@ function disposeClient(client) { client.run.Graph.streamBuffer = null; client.run.Graph.clientOptions = null; client.run.Graph.graphState = null; + if (client.run.Graph.boundModel?.client) { + client.run.Graph.boundModel.client = null; + } client.run.Graph.boundModel = null; client.run.Graph.systemMessage = null; client.run.Graph.reasoningKey = null; diff --git a/api/server/controllers/agents/client.js b/api/server/controllers/agents/client.js index a0de98c5f..8a128bcdb 100644 --- a/api/server/controllers/agents/client.js +++ b/api/server/controllers/agents/client.js @@ -787,6 +787,8 @@ class AgentClient extends BaseClient { [Callback.TOOL_ERROR]: logToolError, }, }); + + config.signal = null; }; await runAgent(this.options.agent, initialMessages); diff --git a/api/server/routes/actions.js b/api/server/routes/actions.js index 28845e3f1..dc474d1a6 100644 --- a/api/server/routes/actions.js +++ b/api/server/routes/actions.js @@ -1,5 +1,6 @@ const express = require('express'); const jwt = require('jsonwebtoken'); +const { CacheKeys } = require('librechat-data-provider'); const { getAccessToken } = require('~/server/services/TokenService'); const { logger, getFlowStateManager } = require('~/config'); const { getLogStores } = require('~/cache'); @@ -19,8 +20,8 @@ const JWT_SECRET = process.env.JWT_SECRET; router.get('/:action_id/oauth/callback', async (req, res) => { const { action_id } = req.params; const { code, state } = req.query; - - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); let identifier = action_id; try { let decodedState; diff --git a/api/server/services/ActionService.js b/api/server/services/ActionService.js index 8b13e2609..1255b3db4 100644 --- a/api/server/services/ActionService.js +++ b/api/server/services/ActionService.js @@ -74,7 +74,6 @@ async function domainParser(domain, inverse = false) { if (!domain) { return; } - const domainsCache = getLogStores(CacheKeys.ENCODED_DOMAINS); const cachedDomain = await domainsCache.get(domain); if (inverse && cachedDomain) { @@ -188,7 +187,8 @@ async function createActionTool({ expires_at: Date.now() + Time.TWO_MINUTES, }, }; - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); await flowManager.createFlowWithHandler( `${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`, 'oauth_login', @@ -264,7 +264,8 @@ async function createActionTool({ encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_secret: encrypted.oauth_client_secret, }); - const flowManager = getFlowStateManager(getLogStores); + const flowsCache = getLogStores(CacheKeys.FLOWS); + const flowManager = getFlowStateManager(flowsCache); const refreshData = await flowManager.createFlowWithHandler( `${identifier}:refresh`, 'oauth_refresh', diff --git a/api/test/__mocks__/KeyvMongo.js b/api/test/__mocks__/KeyvMongo.js deleted file mode 100644 index f990ae389..000000000 --- a/api/test/__mocks__/KeyvMongo.js +++ /dev/null @@ -1,48 +0,0 @@ -jest.mock('@keyv/mongo', () => { - const EventEmitter = require('events'); - class KeyvMongo extends EventEmitter { - constructor(url = 'mongodb://127.0.0.1:27017', options) { - super(); - this.ttlSupport = false; - url = url ?? {}; - if (typeof url === 'string') { - url = { url }; - } - if (url.uri) { - url = { url: url.uri, ...url }; - } - this.opts = { - url, - collection: 'keyv', - ...url, - ...options, - }; - - // In-memory store for tests - this.store = new Map(); - } - - 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; - } - } - - // Create a store factory function for the test suite - const store = () => new KeyvMongo(); - - return { KeyvMongo }; -}); diff --git a/package-lock.json b/package-lock.json index 1e98d035c..b60145153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,14 +58,13 @@ "@azure/storage-blob": "^12.26.0", "@google/generative-ai": "^0.23.0", "@googleapis/youtube": "^20.0.0", - "@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.14", + "@librechat/agents": "^2.4.17", "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "^1.8.2", @@ -158,14 +157,6 @@ "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", @@ -720,6 +711,31 @@ "@langchain/core": ">=0.3.39 <0.4.0" } }, + "api/node_modules/@librechat/agents": { + "version": "2.4.17", + "resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-2.4.17.tgz", + "integrity": "sha512-lGgOqovIqzaFtO3wUe8LShckJYmFGxa/RAn1edUxmQgK76F4QK53POFivzQhYUxso9z4SNvu1b8q/+vq7lWYaw==", + "dependencies": { + "@langchain/anthropic": "^0.3.16", + "@langchain/aws": "^0.1.7", + "@langchain/community": "^0.3.39", + "@langchain/core": "^0.3.43", + "@langchain/deepseek": "^0.0.1", + "@langchain/google-genai": "^0.2.2", + "@langchain/google-vertexai": "^0.2.3", + "@langchain/langgraph": "^0.2.62", + "@langchain/mistralai": "^0.2.0", + "@langchain/ollama": "^0.2.0", + "@langchain/openai": "^0.5.4", + "@langchain/xai": "^0.0.2", + "dotenv": "^16.4.7", + "https-proxy-agent": "^7.0.6", + "nanoid": "^3.3.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, "api/node_modules/@types/node": { "version": "18.19.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", @@ -846,14 +862,6 @@ "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", @@ -17878,604 +17886,6 @@ "@lezer/common": "^1.0.0" } }, - "node_modules/@librechat/agents": { - "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", - "@langchain/community": "^0.3.39", - "@langchain/core": "^0.3.43", - "@langchain/deepseek": "^0.0.1", - "@langchain/google-genai": "^0.2.2", - "@langchain/google-vertexai": "^0.2.3", - "@langchain/langgraph": "^0.2.62", - "@langchain/mistralai": "^0.2.0", - "@langchain/ollama": "^0.2.0", - "@langchain/openai": "^0.5.4", - "@langchain/xai": "^0.0.2", - "dotenv": "^16.4.7", - "https-proxy-agent": "^7.0.6", - "nanoid": "^3.3.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@librechat/agents/node_modules/@langchain/community": { - "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", - "expr-eval": "^2.0.2", - "flat": "^5.0.2", - "js-yaml": "^4.1.0", - "langchain": ">=0.2.3 <0.3.0 || >=0.3.4 <0.4.0", - "langsmith": ">=0.2.8 <0.4.0", - "uuid": "^10.0.0", - "zod": "^3.22.3", - "zod-to-json-schema": "^3.22.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@arcjet/redact": "^v1.0.0-alpha.23", - "@aws-crypto/sha256-js": "^5.0.0", - "@aws-sdk/client-bedrock-agent-runtime": "^3.749.0", - "@aws-sdk/client-bedrock-runtime": "^3.749.0", - "@aws-sdk/client-dynamodb": "^3.749.0", - "@aws-sdk/client-kendra": "^3.749.0", - "@aws-sdk/client-lambda": "^3.749.0", - "@aws-sdk/client-s3": "^3.749.0", - "@aws-sdk/client-sagemaker-runtime": "^3.749.0", - "@aws-sdk/client-sfn": "^3.749.0", - "@aws-sdk/credential-provider-node": "^3.388.0", - "@azure/search-documents": "^12.0.0", - "@azure/storage-blob": "^12.15.0", - "@browserbasehq/sdk": "*", - "@browserbasehq/stagehand": "^1.0.0", - "@clickhouse/client": "^0.2.5", - "@cloudflare/ai": "*", - "@datastax/astra-db-ts": "^1.0.0", - "@elastic/elasticsearch": "^8.4.0", - "@getmetal/metal-sdk": "*", - "@getzep/zep-cloud": "^1.0.6", - "@getzep/zep-js": "^0.9.0", - "@gomomento/sdk": "^1.51.1", - "@gomomento/sdk-core": "^1.51.1", - "@google-ai/generativelanguage": "*", - "@google-cloud/storage": "^6.10.1 || ^7.7.0", - "@gradientai/nodejs-sdk": "^1.2.0", - "@huggingface/inference": "^2.6.4", - "@huggingface/transformers": "^3.2.3", - "@ibm-cloud/watsonx-ai": "*", - "@lancedb/lancedb": "^0.12.0", - "@langchain/core": ">=0.2.21 <0.4.0", - "@layerup/layerup-security": "^1.5.12", - "@libsql/client": "^0.14.0", - "@mendable/firecrawl-js": "^1.4.3", - "@mlc-ai/web-llm": "*", - "@mozilla/readability": "*", - "@neondatabase/serverless": "*", - "@notionhq/client": "^2.2.10", - "@opensearch-project/opensearch": "*", - "@pinecone-database/pinecone": "*", - "@planetscale/database": "^1.8.0", - "@premai/prem-sdk": "^0.3.25", - "@qdrant/js-client-rest": "^1.8.2", - "@raycast/api": "^1.55.2", - "@rockset/client": "^0.9.1", - "@smithy/eventstream-codec": "^2.0.5", - "@smithy/protocol-http": "^3.0.6", - "@smithy/signature-v4": "^2.0.10", - "@smithy/util-utf8": "^2.0.0", - "@spider-cloud/spider-client": "^0.0.21", - "@supabase/supabase-js": "^2.45.0", - "@tensorflow-models/universal-sentence-encoder": "*", - "@tensorflow/tfjs-converter": "*", - "@tensorflow/tfjs-core": "*", - "@upstash/ratelimit": "^1.1.3 || ^2.0.3", - "@upstash/redis": "^1.20.6", - "@upstash/vector": "^1.1.1", - "@vercel/kv": "*", - "@vercel/postgres": "*", - "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.28.0", - "@zilliz/milvus2-sdk-node": ">=2.3.5", - "apify-client": "^2.7.1", - "assemblyai": "^4.6.0", - "azion": "^1.11.1", - "better-sqlite3": ">=9.4.0 <12.0.0", - "cassandra-driver": "^4.7.2", - "cborg": "^4.1.1", - "cheerio": "^1.0.0-rc.12", - "chromadb": "*", - "closevector-common": "0.1.3", - "closevector-node": "0.1.6", - "closevector-web": "0.1.6", - "cohere-ai": "*", - "convex": "^1.3.1", - "crypto-js": "^4.2.0", - "d3-dsv": "^2.0.0", - "discord.js": "^14.14.1", - "dria": "^0.0.3", - "duck-duck-scrape": "^2.2.5", - "epub2": "^3.0.1", - "fast-xml-parser": "*", - "firebase-admin": "^11.9.0 || ^12.0.0", - "google-auth-library": "*", - "googleapis": "*", - "hnswlib-node": "^3.0.0", - "html-to-text": "^9.0.5", - "ibm-cloud-sdk-core": "*", - "ignore": "^5.2.0", - "interface-datastore": "^8.2.11", - "ioredis": "^5.3.2", - "it-all": "^3.0.4", - "jsdom": "*", - "jsonwebtoken": "^9.0.2", - "llmonitor": "^0.5.9", - "lodash": "^4.17.21", - "lunary": "^0.7.10", - "mammoth": "^1.6.0", - "mariadb": "^3.4.0", - "mem0ai": "^2.1.8", - "mongodb": ">=5.2.0", - "mysql2": "^3.9.8", - "neo4j-driver": "*", - "notion-to-md": "^3.1.0", - "officeparser": "^4.0.4", - "openai": "*", - "pdf-parse": "1.1.1", - "pg": "^8.11.0", - "pg-copy-streams": "^6.0.5", - "pickleparser": "^0.2.1", - "playwright": "^1.32.1", - "portkey-ai": "^0.1.11", - "puppeteer": "*", - "pyodide": ">=0.24.1 <0.27.0", - "redis": "*", - "replicate": "*", - "sonix-speech-recognition": "^2.1.1", - "srt-parser-2": "^1.2.3", - "typeorm": "^0.3.20", - "typesense": "^1.5.3", - "usearch": "^1.1.1", - "voy-search": "0.6.2", - "weaviate-ts-client": "*", - "web-auth-library": "^1.0.3", - "word-extractor": "*", - "ws": "^8.14.2", - "youtubei.js": "*" - }, - "peerDependenciesMeta": { - "@arcjet/redact": { - "optional": true - }, - "@aws-crypto/sha256-js": { - "optional": true - }, - "@aws-sdk/client-bedrock-agent-runtime": { - "optional": true - }, - "@aws-sdk/client-bedrock-runtime": { - "optional": true - }, - "@aws-sdk/client-dynamodb": { - "optional": true - }, - "@aws-sdk/client-kendra": { - "optional": true - }, - "@aws-sdk/client-lambda": { - "optional": true - }, - "@aws-sdk/client-s3": { - "optional": true - }, - "@aws-sdk/client-sagemaker-runtime": { - "optional": true - }, - "@aws-sdk/client-sfn": { - "optional": true - }, - "@aws-sdk/credential-provider-node": { - "optional": true - }, - "@aws-sdk/dsql-signer": { - "optional": true - }, - "@azure/search-documents": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@browserbasehq/sdk": { - "optional": true - }, - "@clickhouse/client": { - "optional": true - }, - "@cloudflare/ai": { - "optional": true - }, - "@datastax/astra-db-ts": { - "optional": true - }, - "@elastic/elasticsearch": { - "optional": true - }, - "@getmetal/metal-sdk": { - "optional": true - }, - "@getzep/zep-cloud": { - "optional": true - }, - "@getzep/zep-js": { - "optional": true - }, - "@gomomento/sdk": { - "optional": true - }, - "@gomomento/sdk-core": { - "optional": true - }, - "@google-ai/generativelanguage": { - "optional": true - }, - "@google-cloud/storage": { - "optional": true - }, - "@gradientai/nodejs-sdk": { - "optional": true - }, - "@huggingface/inference": { - "optional": true - }, - "@huggingface/transformers": { - "optional": true - }, - "@lancedb/lancedb": { - "optional": true - }, - "@layerup/layerup-security": { - "optional": true - }, - "@libsql/client": { - "optional": true - }, - "@mendable/firecrawl-js": { - "optional": true - }, - "@mlc-ai/web-llm": { - "optional": true - }, - "@mozilla/readability": { - "optional": true - }, - "@neondatabase/serverless": { - "optional": true - }, - "@notionhq/client": { - "optional": true - }, - "@opensearch-project/opensearch": { - "optional": true - }, - "@pinecone-database/pinecone": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@premai/prem-sdk": { - "optional": true - }, - "@qdrant/js-client-rest": { - "optional": true - }, - "@raycast/api": { - "optional": true - }, - "@rockset/client": { - "optional": true - }, - "@smithy/eventstream-codec": { - "optional": true - }, - "@smithy/protocol-http": { - "optional": true - }, - "@smithy/signature-v4": { - "optional": true - }, - "@smithy/util-utf8": { - "optional": true - }, - "@spider-cloud/spider-client": { - "optional": true - }, - "@supabase/supabase-js": { - "optional": true - }, - "@tensorflow-models/universal-sentence-encoder": { - "optional": true - }, - "@tensorflow/tfjs-converter": { - "optional": true - }, - "@tensorflow/tfjs-core": { - "optional": true - }, - "@upstash/ratelimit": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@upstash/vector": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "@vercel/postgres": { - "optional": true - }, - "@writerai/writer-sdk": { - "optional": true - }, - "@xata.io/client": { - "optional": true - }, - "@zilliz/milvus2-sdk-node": { - "optional": true - }, - "apify-client": { - "optional": true - }, - "assemblyai": { - "optional": true - }, - "azion": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "cassandra-driver": { - "optional": true - }, - "cborg": { - "optional": true - }, - "cheerio": { - "optional": true - }, - "chromadb": { - "optional": true - }, - "closevector-common": { - "optional": true - }, - "closevector-node": { - "optional": true - }, - "closevector-web": { - "optional": true - }, - "cohere-ai": { - "optional": true - }, - "convex": { - "optional": true - }, - "crypto-js": { - "optional": true - }, - "d3-dsv": { - "optional": true - }, - "discord.js": { - "optional": true - }, - "dria": { - "optional": true - }, - "duck-duck-scrape": { - "optional": true - }, - "epub2": { - "optional": true - }, - "fast-xml-parser": { - "optional": true - }, - "firebase-admin": { - "optional": true - }, - "google-auth-library": { - "optional": true - }, - "googleapis": { - "optional": true - }, - "hnswlib-node": { - "optional": true - }, - "html-to-text": { - "optional": true - }, - "ignore": { - "optional": true - }, - "interface-datastore": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "it-all": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "jsonwebtoken": { - "optional": true - }, - "llmonitor": { - "optional": true - }, - "lodash": { - "optional": true - }, - "lunary": { - "optional": true - }, - "mammoth": { - "optional": true - }, - "mariadb": { - "optional": true - }, - "mem0ai": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "neo4j-driver": { - "optional": true - }, - "notion-to-md": { - "optional": true - }, - "officeparser": { - "optional": true - }, - "pdf-parse": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-copy-streams": { - "optional": true - }, - "pickleparser": { - "optional": true - }, - "playwright": { - "optional": true - }, - "portkey-ai": { - "optional": true - }, - "puppeteer": { - "optional": true - }, - "pyodide": { - "optional": true - }, - "redis": { - "optional": true - }, - "replicate": { - "optional": true - }, - "sonix-speech-recognition": { - "optional": true - }, - "srt-parser-2": { - "optional": true - }, - "typeorm": { - "optional": true - }, - "typesense": { - "optional": true - }, - "usearch": { - "optional": true - }, - "voy-search": { - "optional": true - }, - "weaviate-ts-client": { - "optional": true - }, - "web-auth-library": { - "optional": true - }, - "word-extractor": { - "optional": true - }, - "ws": { - "optional": true - }, - "youtubei.js": { - "optional": true - } - } - }, - "node_modules/@librechat/agents/node_modules/@langchain/openai": { - "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", - "zod": "^3.22.4", - "zod-to-json-schema": "^3.22.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@langchain/core": ">=0.3.39 <0.4.0" - } - }, - "node_modules/@librechat/agents/node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/@librechat/agents/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@librechat/agents/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@librechat/backend": { "resolved": "api", "link": true @@ -18698,6 +18108,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@microsoft/eslint-formatter-sarif/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/@mistralai/mistralai": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.2.tgz", @@ -28807,6 +28226,15 @@ "node": ">=16" } }, + "node_modules/flat-cache/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/flatted": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", @@ -32035,7 +31463,8 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -32195,11 +31624,11 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz", + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.0.3" } }, "node_modules/kleur": { @@ -37234,6 +36663,15 @@ "dev": true, "license": "MIT" }, + "node_modules/prettier-eslint/node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/prettier-eslint/node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -43684,7 +43122,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } }, "packages/data-schemas/node_modules/@types/whatwg-url": { @@ -43918,7 +43356,7 @@ "typescript": "^5.0.4" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } }, "packages/mcp/node_modules/@modelcontextprotocol/sdk": { diff --git a/packages/data-schemas/package.json b/packages/data-schemas/package.json index 51f77aa9f..2f35c69c9 100644 --- a/packages/data-schemas/package.json +++ b/packages/data-schemas/package.json @@ -62,7 +62,7 @@ "mongoose": "^8.12.1" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" }, "publishConfig": { "registry": "https://registry.npmjs.org/", diff --git a/packages/mcp/package.json b/packages/mcp/package.json index fcb17f7e6..6e084dd81 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -73,6 +73,6 @@ "express": "^4.21.2" }, "peerDependencies": { - "keyv": "^4.5.4" + "keyv": "^5.3.2" } } diff --git a/packages/mcp/src/flow/manager.spec.ts b/packages/mcp/src/flow/manager.spec.ts index 2d8897747..1f1714509 100644 --- a/packages/mcp/src/flow/manager.spec.ts +++ b/packages/mcp/src/flow/manager.spec.ts @@ -1,5 +1,5 @@ import { FlowStateManager } from './manager'; -import Keyv from 'keyv'; +import { Keyv } from 'keyv'; import type { FlowState } from './types'; // Create a mock class without extending Keyv diff --git a/packages/mcp/src/flow/manager.ts b/packages/mcp/src/flow/manager.ts index 0e629cc86..6a421b8d1 100644 --- a/packages/mcp/src/flow/manager.ts +++ b/packages/mcp/src/flow/manager.ts @@ -1,4 +1,5 @@ -import Keyv from 'keyv'; +import { Keyv } from 'keyv'; +import type { StoredDataNoRaw } from 'keyv'; import type { Logger } from 'winston'; import type { FlowState, FlowMetadata, FlowManagerOptions } from './types'; @@ -202,7 +203,7 @@ export class FlowStateManager { /** * Gets current flow state */ - async getFlowState(flowId: string, type: string): Promise | null> { + async getFlowState(flowId: string, type: string): Promise> | null> { const flowKey = this.getFlowKey(flowId, type); return this.keyv.get(flowKey); }