🔧 fix: Keyv and Proxy Issues, and More Memory Optimizations (#6867)

* 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 83153cd582.

* 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
This commit is contained in:
Danny Avila 2025-04-13 23:01:55 -04:00 committed by GitHub
parent 339882eea4
commit 64bd373bc8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 375 additions and 743 deletions

View file

@ -1,7 +1,6 @@
const OpenAI = require('openai');
const { OllamaClient } = require('./OllamaClient'); const { OllamaClient } = require('./OllamaClient');
const { HttpsProxyAgent } = require('https-proxy-agent'); const { HttpsProxyAgent } = require('https-proxy-agent');
const { SplitStreamHandler } = require('@librechat/agents'); const { SplitStreamHandler, CustomOpenAIClient: OpenAI } = require('@librechat/agents');
const { const {
Constants, Constants,
ImageDetail, ImageDetail,

View file

@ -1,3 +1,4 @@
const fetch = require('node-fetch');
const { GraphEvents } = require('@librechat/agents'); const { GraphEvents } = require('@librechat/agents');
const { logger, sendEvent } = require('~/config'); const { logger, sendEvent } = require('~/config');

View file

@ -32,7 +32,7 @@ jest.mock('~/models', () => ({
const { getConvo, saveConvo } = require('~/models'); const { getConvo, saveConvo } = require('~/models');
jest.mock('@langchain/openai', () => { jest.mock('@librechat/agents', () => {
return { return {
ChatOpenAI: jest.fn().mockImplementation(() => { ChatOpenAI: jest.fn().mockImplementation(() => {
return {}; return {};

View file

@ -1,9 +1,7 @@
jest.mock('~/cache/getLogStores'); jest.mock('~/cache/getLogStores');
require('dotenv').config(); require('dotenv').config();
const OpenAI = require('openai');
const getLogStores = require('~/cache/getLogStores');
const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source'); const { fetchEventSource } = require('@waylaidwanderer/fetch-event-source');
const { genAzureChatCompletion } = require('~/utils/azureUtils'); const getLogStores = require('~/cache/getLogStores');
const OpenAIClient = require('../OpenAIClient'); const OpenAIClient = require('../OpenAIClient');
jest.mock('meilisearch'); jest.mock('meilisearch');
@ -36,19 +34,21 @@ jest.mock('~/models', () => ({
updateFileUsage: jest.fn(), updateFileUsage: jest.fn(),
})); }));
jest.mock('@langchain/openai', () => { // Import the actual module but mock specific parts
return { const agents = jest.requireActual('@librechat/agents');
ChatOpenAI: jest.fn().mockImplementation(() => { const { CustomOpenAIClient } = agents;
// Also mock ChatOpenAI to prevent real API calls
agents.ChatOpenAI = jest.fn().mockImplementation(() => {
return {};
});
agents.AzureChatOpenAI = jest.fn().mockImplementation(() => {
return {}; return {};
}),
};
}); });
jest.mock('openai'); // Mock only the CustomOpenAIClient constructor
jest.spyOn(CustomOpenAIClient, 'constructor').mockImplementation(function (...options) {
jest.spyOn(OpenAI, 'constructor').mockImplementation(function (...options) { return new CustomOpenAIClient(...options);
// We can add additional logic here if needed
return new OpenAI(...options);
}); });
const finalChatCompletion = jest.fn().mockResolvedValue({ 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: { beta: {
chat: { chat: {
completions: { completions: {
@ -135,6 +141,8 @@ OpenAI.mockImplementation(() => ({
}, },
})); }));
CustomOpenAIClient.mockImplementation = mockCustomOpenAIClient;
describe('OpenAIClient', () => { describe('OpenAIClient', () => {
beforeEach(() => { beforeEach(() => {
const mockCache = { const mockCache = {
@ -559,41 +567,6 @@ describe('OpenAIClient', () => {
expect(requestBody).toHaveProperty('model'); expect(requestBody).toHaveProperty('model');
expect(requestBody.model).toBe(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', () => { describe('checkVisionRequest functionality', () => {

269
api/cache/keyvMongo.js vendored
View file

@ -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 { 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)); keyvMongo.on('error', (err) => logger.error('KeyvMongo connection error:', err));
module.exports = keyvMongo; module.exports = keyvMongo;

View file

@ -24,12 +24,12 @@ function getMCPManager(userId) {
} }
/** /**
* @param {(key: string) => Keyv} getLogStores * @param {Keyv} flowsCache
* @returns {FlowStateManager} * @returns {FlowStateManager}
*/ */
function getFlowStateManager(getLogStores) { function getFlowStateManager(flowsCache) {
if (!flowManager) { if (!flowManager) {
flowManager = new FlowStateManager(getLogStores(CacheKeys.FLOWS), { flowManager = new FlowStateManager(flowsCache, {
ttl: Time.ONE_MINUTE * 3, ttl: Time.ONE_MINUTE * 3,
logger, logger,
}); });

View file

@ -5,7 +5,6 @@ module.exports = {
coverageDirectory: 'coverage', coverageDirectory: 'coverage',
setupFiles: [ setupFiles: [
'./test/jestSetup.js', './test/jestSetup.js',
'./test/__mocks__/KeyvMongo.js',
'./test/__mocks__/logger.js', './test/__mocks__/logger.js',
'./test/__mocks__/fetchEventSource.js', './test/__mocks__/fetchEventSource.js',
], ],

View file

@ -42,14 +42,13 @@
"@azure/storage-blob": "^12.26.0", "@azure/storage-blob": "^12.26.0",
"@google/generative-ai": "^0.23.0", "@google/generative-ai": "^0.23.0",
"@googleapis/youtube": "^20.0.0", "@googleapis/youtube": "^20.0.0",
"@keyv/mongo": "^3.0.1",
"@keyv/redis": "^4.3.3", "@keyv/redis": "^4.3.3",
"@langchain/community": "^0.3.39", "@langchain/community": "^0.3.39",
"@langchain/core": "^0.3.43", "@langchain/core": "^0.3.43",
"@langchain/google-genai": "^0.2.2", "@langchain/google-genai": "^0.2.2",
"@langchain/google-vertexai": "^0.2.3", "@langchain/google-vertexai": "^0.2.3",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.4.14", "@librechat/agents": "^2.4.17",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@waylaidwanderer/fetch-event-source": "^3.0.1", "@waylaidwanderer/fetch-event-source": "^3.0.1",
"axios": "^1.8.2", "axios": "^1.8.2",

View file

@ -238,6 +238,9 @@ function disposeClient(client) {
client.run.Graph.streamBuffer = null; client.run.Graph.streamBuffer = null;
client.run.Graph.clientOptions = null; client.run.Graph.clientOptions = null;
client.run.Graph.graphState = 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.boundModel = null;
client.run.Graph.systemMessage = null; client.run.Graph.systemMessage = null;
client.run.Graph.reasoningKey = null; client.run.Graph.reasoningKey = null;

View file

@ -787,6 +787,8 @@ class AgentClient extends BaseClient {
[Callback.TOOL_ERROR]: logToolError, [Callback.TOOL_ERROR]: logToolError,
}, },
}); });
config.signal = null;
}; };
await runAgent(this.options.agent, initialMessages); await runAgent(this.options.agent, initialMessages);

View file

@ -1,5 +1,6 @@
const express = require('express'); const express = require('express');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { CacheKeys } = require('librechat-data-provider');
const { getAccessToken } = require('~/server/services/TokenService'); const { getAccessToken } = require('~/server/services/TokenService');
const { logger, getFlowStateManager } = require('~/config'); const { logger, getFlowStateManager } = require('~/config');
const { getLogStores } = require('~/cache'); const { getLogStores } = require('~/cache');
@ -19,8 +20,8 @@ const JWT_SECRET = process.env.JWT_SECRET;
router.get('/:action_id/oauth/callback', async (req, res) => { router.get('/:action_id/oauth/callback', async (req, res) => {
const { action_id } = req.params; const { action_id } = req.params;
const { code, state } = req.query; const { code, state } = req.query;
const flowsCache = getLogStores(CacheKeys.FLOWS);
const flowManager = getFlowStateManager(getLogStores); const flowManager = getFlowStateManager(flowsCache);
let identifier = action_id; let identifier = action_id;
try { try {
let decodedState; let decodedState;

View file

@ -74,7 +74,6 @@ async function domainParser(domain, inverse = false) {
if (!domain) { if (!domain) {
return; return;
} }
const domainsCache = getLogStores(CacheKeys.ENCODED_DOMAINS); const domainsCache = getLogStores(CacheKeys.ENCODED_DOMAINS);
const cachedDomain = await domainsCache.get(domain); const cachedDomain = await domainsCache.get(domain);
if (inverse && cachedDomain) { if (inverse && cachedDomain) {
@ -188,7 +187,8 @@ async function createActionTool({
expires_at: Date.now() + Time.TWO_MINUTES, expires_at: Date.now() + Time.TWO_MINUTES,
}, },
}; };
const flowManager = getFlowStateManager(getLogStores); const flowsCache = getLogStores(CacheKeys.FLOWS);
const flowManager = getFlowStateManager(flowsCache);
await flowManager.createFlowWithHandler( await flowManager.createFlowWithHandler(
`${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`, `${identifier}:oauth_login:${config.metadata.thread_id}:${config.metadata.run_id}`,
'oauth_login', 'oauth_login',
@ -264,7 +264,8 @@ async function createActionTool({
encrypted_oauth_client_id: encrypted.oauth_client_id, encrypted_oauth_client_id: encrypted.oauth_client_id,
encrypted_oauth_client_secret: encrypted.oauth_client_secret, 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( const refreshData = await flowManager.createFlowWithHandler(
`${identifier}:refresh`, `${identifier}:refresh`,
'oauth_refresh', 'oauth_refresh',

View file

@ -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 };
});

684
package-lock.json generated
View file

@ -58,14 +58,13 @@
"@azure/storage-blob": "^12.26.0", "@azure/storage-blob": "^12.26.0",
"@google/generative-ai": "^0.23.0", "@google/generative-ai": "^0.23.0",
"@googleapis/youtube": "^20.0.0", "@googleapis/youtube": "^20.0.0",
"@keyv/mongo": "^3.0.1",
"@keyv/redis": "^4.3.3", "@keyv/redis": "^4.3.3",
"@langchain/community": "^0.3.39", "@langchain/community": "^0.3.39",
"@langchain/core": "^0.3.43", "@langchain/core": "^0.3.43",
"@langchain/google-genai": "^0.2.2", "@langchain/google-genai": "^0.2.2",
"@langchain/google-vertexai": "^0.2.3", "@langchain/google-vertexai": "^0.2.3",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.4.14", "@librechat/agents": "^2.4.17",
"@librechat/data-schemas": "*", "@librechat/data-schemas": "*",
"@waylaidwanderer/fetch-event-source": "^3.0.1", "@waylaidwanderer/fetch-event-source": "^3.0.1",
"axios": "^1.8.2", "axios": "^1.8.2",
@ -158,14 +157,6 @@
"node": ">=18.0.0" "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": { "api/node_modules/@keyv/redis": {
"version": "4.3.3", "version": "4.3.3",
"resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.3.3.tgz", "resolved": "https://registry.npmjs.org/@keyv/redis/-/redis-4.3.3.tgz",
@ -720,6 +711,31 @@
"@langchain/core": ">=0.3.39 <0.4.0" "@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": { "api/node_modules/@types/node": {
"version": "18.19.14", "version": "18.19.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz",
@ -846,14 +862,6 @@
"node": ">= 14" "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": { "api/node_modules/keyv-file": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.1.2.tgz", "resolved": "https://registry.npmjs.org/keyv-file/-/keyv-file-5.1.2.tgz",
@ -17878,604 +17886,6 @@
"@lezer/common": "^1.0.0" "@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": { "node_modules/@librechat/backend": {
"resolved": "api", "resolved": "api",
"link": true "link": true
@ -18698,6 +18108,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@mistralai/mistralai": {
"version": "1.5.2", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.2.tgz", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.5.2.tgz",
@ -28807,6 +28226,15 @@
"node": ">=16" "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": { "node_modules/flatted": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
@ -32035,7 +31463,8 @@
"node_modules/json-buffer": { "node_modules/json-buffer": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "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": { "node_modules/json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
@ -32195,11 +31624,11 @@
} }
}, },
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.2.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==",
"dependencies": { "dependencies": {
"json-buffer": "3.0.1" "@keyv/serialize": "^1.0.3"
} }
}, },
"node_modules/kleur": { "node_modules/kleur": {
@ -37234,6 +36663,15 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/prettier-eslint/node_modules/ts-api-utils": {
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
@ -43684,7 +43122,7 @@
"typescript": "^5.0.4" "typescript": "^5.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"keyv": "^4.5.4" "keyv": "^5.3.2"
} }
}, },
"packages/data-schemas/node_modules/@types/whatwg-url": { "packages/data-schemas/node_modules/@types/whatwg-url": {
@ -43918,7 +43356,7 @@
"typescript": "^5.0.4" "typescript": "^5.0.4"
}, },
"peerDependencies": { "peerDependencies": {
"keyv": "^4.5.4" "keyv": "^5.3.2"
} }
}, },
"packages/mcp/node_modules/@modelcontextprotocol/sdk": { "packages/mcp/node_modules/@modelcontextprotocol/sdk": {

View file

@ -62,7 +62,7 @@
"mongoose": "^8.12.1" "mongoose": "^8.12.1"
}, },
"peerDependencies": { "peerDependencies": {
"keyv": "^4.5.4" "keyv": "^5.3.2"
}, },
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org/", "registry": "https://registry.npmjs.org/",

View file

@ -73,6 +73,6 @@
"express": "^4.21.2" "express": "^4.21.2"
}, },
"peerDependencies": { "peerDependencies": {
"keyv": "^4.5.4" "keyv": "^5.3.2"
} }
} }

View file

@ -1,5 +1,5 @@
import { FlowStateManager } from './manager'; import { FlowStateManager } from './manager';
import Keyv from 'keyv'; import { Keyv } from 'keyv';
import type { FlowState } from './types'; import type { FlowState } from './types';
// Create a mock class without extending Keyv // Create a mock class without extending Keyv

View file

@ -1,4 +1,5 @@
import Keyv from 'keyv'; import { Keyv } from 'keyv';
import type { StoredDataNoRaw } from 'keyv';
import type { Logger } from 'winston'; import type { Logger } from 'winston';
import type { FlowState, FlowMetadata, FlowManagerOptions } from './types'; import type { FlowState, FlowMetadata, FlowManagerOptions } from './types';
@ -202,7 +203,7 @@ export class FlowStateManager<T = unknown> {
/** /**
* Gets current flow state * Gets current flow state
*/ */
async getFlowState(flowId: string, type: string): Promise<FlowState<T> | null> { async getFlowState(flowId: string, type: string): Promise<StoredDataNoRaw<FlowState<T>> | null> {
const flowKey = this.getFlowKey(flowId, type); const flowKey = this.getFlowKey(flowId, type);
return this.keyv.get(flowKey); return this.keyv.get(flowKey);
} }