mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-03 14:27:20 +02:00
* refactor: Remove deprecated and unused fields from endpoint schemas - Remove summarize, summaryModel from endpointSchema and azureEndpointSchema - Remove plugins from azureEndpointSchema - Remove customOrder from endpointSchema and azureEndpointSchema - Remove baseURL from all and agents endpoint schemas - Type paramDefinitions with full SettingDefinition-based schema - Clean up summarize/summaryModel references in initialize.ts and config.spec.ts * refactor: Improve MCP transport schema typing - Add defaults to transport type discriminators (stdio, websocket, sse) - Type stderr field as IOType union instead of z.any() * refactor: Add narrowed preset schema for model specs - Create tModelSpecPresetSchema omitting system/DB/deprecated fields - Update tModelSpecSchema to use the narrowed preset schema * test: Add explicit type field to MCP test fixtures Add transport type discriminator to test objects that construct MCPOptions/ParsedServerConfig directly, required after type field changed from optional to default in schema definitions. * chore: Bump librechat-data-provider to 0.8.404 * refactor: Tighten z.record(z.any()) fields to precise value types - Type headers fields as z.record(z.string()) in endpoint, assistant, and azure schemas - Type addParams as z.record(z.union([z.string(), z.number(), z.boolean(), z.null()])) - Type azure additionalHeaders as z.record(z.string()) - Type memory model_parameters as z.record(z.union([z.string(), z.number(), z.boolean()])) - Type firecrawl changeTrackingOptions.schema as z.record(z.string()) * refactor: Type supportedMimeTypes schema as z.array(z.string()) Replace z.array(z.any()).refine() with z.array(z.string()) since config input is always strings that get converted to RegExp via convertStringsToRegex() after parsing. Destructure supportedMimeTypes from spreads to avoid string[]/RegExp[] type mismatch. * refactor: Tighten enum, role, and numeric constraint schemas - Type engineSTT as enum ['openai', 'azureOpenAI'] - Type engineTTS as enum ['openai', 'azureOpenAI', 'elevenlabs', 'localai'] - Constrain playbackRate to 0.25–4 range - Type titleMessageRole as enum ['system', 'user', 'assistant'] - Add int().nonnegative() to MCP timeout and firecrawl timeout * chore: Bump librechat-data-provider to 0.8.405 * fix: Accept both string and RegExp in supportedMimeTypes schema The schema must accept both string[] (config input) and RegExp[] (post-merge runtime) since tests validate merged output against the schema. Use z.union([z.string(), z.instanceof(RegExp)]) to handle both. * refactor: Address review findings for schema tightening PR - Revert changeTrackingOptions.schema to z.record(z.unknown()) (JSON Schema is nested, not flat strings) - Remove dead contextStrategy code from BaseClient.js and cleanup.js - Extract paramDefinitionSchema to named exported constant - Add .int() constraint to columnSpan and columns - Apply consistent .int().nonnegative() to initTimeout, sseReadTimeout, scraperTimeout - Update stale stderr JSDoc to match actual accepted types - Add comprehensive tests for paramDefinitionSchema, tModelSpecPresetSchema, endpointSchema deprecated field stripping, and azureEndpointSchema * fix: Address second review pass findings - Revert supportedMimeTypesSchema to z.array(z.string()) and remove as string[] casts — fix tests to not validate merged RegExp[] output against the config input schema - Remove unused tModelSpecSchema import from test file - Consolidate duplicate '../src/schemas' imports - Add expiredAt coverage to tModelSpecPresetSchema test - Assert plugins is absent in azureEndpointSchema test - Add sync comments for engineSTT/engineTTS enum literals * refactor: Omit preset-management fields from tModelSpecPresetSchema Omit conversationId, presetId, title, defaultPreset, and order from the model spec preset schema — these are preset-management fields that don't belong in model spec configuration.
431 lines
11 KiB
JavaScript
431 lines
11 KiB
JavaScript
const { logger } = require('@librechat/data-schemas');
|
|
|
|
/** 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 {
|
|
// Ignore errors
|
|
}
|
|
})
|
|
: null;
|
|
|
|
const graphPropsToClean = [
|
|
'handlerRegistry',
|
|
'runId',
|
|
'tools',
|
|
'signal',
|
|
'config',
|
|
'messages',
|
|
'contentData',
|
|
'stepKeyIds',
|
|
'contentIndexMap',
|
|
'toolCallStepIds',
|
|
'messageIdsByStepKey',
|
|
'messageStepHasToolCalls',
|
|
'prelimMessageIdsByStepKey',
|
|
'startIndex',
|
|
'defaultAgentId',
|
|
'dispatchReasoningDelta',
|
|
'compileOptions',
|
|
'invokedToolIds',
|
|
'overrideModel',
|
|
];
|
|
|
|
const graphRunnablePropsToClean = [
|
|
'lc_serializable',
|
|
'lc_kwargs',
|
|
'lc_runnable',
|
|
'name',
|
|
'lc_namespace',
|
|
'lg_is_pregel',
|
|
'nodes',
|
|
'channels',
|
|
'inputChannels',
|
|
'outputChannels',
|
|
'autoValidate',
|
|
'streamMode',
|
|
'streamChannels',
|
|
'interruptAfter',
|
|
'interruptBefore',
|
|
'stepTimeout',
|
|
'debug',
|
|
'checkpointer',
|
|
'retryPolicy',
|
|
'config',
|
|
'store',
|
|
'triggerToNodes',
|
|
'cache',
|
|
'description',
|
|
'metaRegistry',
|
|
];
|
|
|
|
/**
|
|
* Cleans up the client object by removing potential circular 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.parentMessageId) {
|
|
client.parentMessageId = null;
|
|
}
|
|
if (client.message_file_map) {
|
|
client.message_file_map = 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.currentDateString) {
|
|
client.currentDateString = null;
|
|
}
|
|
if (client.inputTokensKey) {
|
|
client.inputTokensKey = null;
|
|
}
|
|
if (client.outputTokensKey) {
|
|
client.outputTokensKey = null;
|
|
}
|
|
if (client.skipSaveUserMessage !== undefined) {
|
|
client.skipSaveUserMessage = null;
|
|
}
|
|
if (client.visionMode) {
|
|
client.visionMode = null;
|
|
}
|
|
if (client.continued !== undefined) {
|
|
client.continued = null;
|
|
}
|
|
if (client.fetchedConvo !== undefined) {
|
|
client.fetchedConvo = null;
|
|
}
|
|
if (client.previous_summary) {
|
|
client.previous_summary = null;
|
|
}
|
|
if (client.metadata) {
|
|
client.metadata = null;
|
|
}
|
|
if (client.isVisionModel) {
|
|
client.isVisionModel = null;
|
|
}
|
|
if (client.isChatCompletion !== undefined) {
|
|
client.isChatCompletion = null;
|
|
}
|
|
if (client.contextHandlers) {
|
|
client.contextHandlers = null;
|
|
}
|
|
if (client.augmentedPrompt) {
|
|
client.augmentedPrompt = null;
|
|
}
|
|
if (client.systemMessage) {
|
|
client.systemMessage = null;
|
|
}
|
|
if (client.azureEndpoint) {
|
|
client.azureEndpoint = null;
|
|
}
|
|
if (client.langchainProxy) {
|
|
client.langchainProxy = null;
|
|
}
|
|
if (client.isOmni !== undefined) {
|
|
client.isOmni = null;
|
|
}
|
|
if (client.runManager) {
|
|
client.runManager = null;
|
|
}
|
|
// Properties specific to AnthropicClient
|
|
if (client.message_start) {
|
|
client.message_start = null;
|
|
}
|
|
if (client.message_delta) {
|
|
client.message_delta = null;
|
|
}
|
|
if (client.isClaudeLatest !== undefined) {
|
|
client.isClaudeLatest = null;
|
|
}
|
|
if (client.useMessages !== undefined) {
|
|
client.useMessages = null;
|
|
}
|
|
if (client.supportsCacheControl !== undefined) {
|
|
client.supportsCacheControl = null;
|
|
}
|
|
// Properties specific to GoogleClient
|
|
if (client.serviceKey) {
|
|
client.serviceKey = null;
|
|
}
|
|
if (client.project_id) {
|
|
client.project_id = null;
|
|
}
|
|
if (client.client_email) {
|
|
client.client_email = null;
|
|
}
|
|
if (client.private_key) {
|
|
client.private_key = null;
|
|
}
|
|
if (client.access_token) {
|
|
client.access_token = null;
|
|
}
|
|
if (client.reverseProxyUrl) {
|
|
client.reverseProxyUrl = null;
|
|
}
|
|
if (client.authHeader) {
|
|
client.authHeader = null;
|
|
}
|
|
if (client.isGenerativeModel !== undefined) {
|
|
client.isGenerativeModel = null;
|
|
}
|
|
// Properties specific to OpenAIClient
|
|
if (client.completionsUrl) {
|
|
client.completionsUrl = null;
|
|
}
|
|
if (client.shouldSummarize !== undefined) {
|
|
client.shouldSummarize = null;
|
|
}
|
|
if (client.isOllama !== undefined) {
|
|
client.isOllama = null;
|
|
}
|
|
if (client.FORCE_PROMPT !== undefined) {
|
|
client.FORCE_PROMPT = null;
|
|
}
|
|
if (client.isChatGptModel !== undefined) {
|
|
client.isChatGptModel = null;
|
|
}
|
|
if (client.isUnofficialChatGptModel !== undefined) {
|
|
client.isUnofficialChatGptModel = null;
|
|
}
|
|
if (client.useOpenRouter !== undefined) {
|
|
client.useOpenRouter = null;
|
|
}
|
|
if (client.startToken) {
|
|
client.startToken = null;
|
|
}
|
|
if (client.endToken) {
|
|
client.endToken = null;
|
|
}
|
|
if (client.userLabel) {
|
|
client.userLabel = null;
|
|
}
|
|
if (client.chatGptLabel) {
|
|
client.chatGptLabel = null;
|
|
}
|
|
if (client.modelLabel) {
|
|
client.modelLabel = null;
|
|
}
|
|
if (client.modelOptions) {
|
|
client.modelOptions = null;
|
|
}
|
|
if (client.defaultVisionModel) {
|
|
client.defaultVisionModel = null;
|
|
}
|
|
if (client.maxPromptTokens) {
|
|
client.maxPromptTokens = null;
|
|
}
|
|
if (client.maxResponseTokens) {
|
|
client.maxResponseTokens = null;
|
|
}
|
|
if (client.processMemory) {
|
|
client.processMemory = null;
|
|
}
|
|
|
|
if (client.run) {
|
|
if (client.run.Graph) {
|
|
if (typeof client.run.Graph.clearHeavyState === 'function') {
|
|
client.run.Graph.clearHeavyState();
|
|
} else {
|
|
client.run.Graph.resetValues();
|
|
}
|
|
|
|
if (client.run.Graph.agentContexts) {
|
|
client.run.Graph.agentContexts.clear();
|
|
client.run.Graph.agentContexts = null;
|
|
}
|
|
|
|
graphPropsToClean.forEach((prop) => {
|
|
if (client.run.Graph[prop] !== undefined) {
|
|
client.run.Graph[prop] = null;
|
|
}
|
|
});
|
|
|
|
client.run.Graph = null;
|
|
}
|
|
|
|
if (client.run.graphRunnable) {
|
|
graphRunnablePropsToClean.forEach((prop) => {
|
|
if (client.run.graphRunnable[prop] !== undefined) {
|
|
client.run.graphRunnable[prop] = null;
|
|
}
|
|
});
|
|
|
|
if (client.run.graphRunnable.builder) {
|
|
if (client.run.graphRunnable.builder.nodes !== undefined) {
|
|
client.run.graphRunnable.builder.nodes = null;
|
|
}
|
|
client.run.graphRunnable.builder = null;
|
|
}
|
|
|
|
client.run.graphRunnable = null;
|
|
}
|
|
|
|
const runPropsToClean = [
|
|
'handlerRegistry',
|
|
'id',
|
|
'indexTokenCountMap',
|
|
'returnContent',
|
|
'tokenCounter',
|
|
];
|
|
|
|
runPropsToClean.forEach((prop) => {
|
|
if (client.run[prop] !== undefined) {
|
|
client.run[prop] = 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 {
|
|
// Ignore errors during disposal
|
|
} finally {
|
|
logger.debug('[disposeClient] Client disposed');
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|