mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00

* feat: Add conversation ID support to custom endpoint headers - Add LIBRECHAT_CONVERSATION_ID to customUserVars when provided - Pass conversation ID to header resolution for dynamic headers - Add comprehensive test coverage Enables custom endpoints to access conversation context using {{LIBRECHAT_CONVERSATION_ID}} placeholder. * fix: filter out unresolved placeholders from headers (thanks @MrunmayS) * feat: add support for request body placeholders in custom endpoint headers - Add {{LIBRECHAT_BODY_*}} placeholders for conversationId, parentMessageId, messageId - Update tests to reflect new body placeholder functionality * refactor resolveHeaders * style: minor styling cleanup * fix: type error in unit test * feat: add body to other endpoints * feat: add body for mcp tool calls * chore: remove changes that unnecessarily increase scope after clarification of requirements * refactor: move http.ts to packages/api and have RequestBody intersect with Express request body * refactor: processMCPEnv now uses single object argument pattern * refactor: update processMCPEnv to use 'options' parameter and align types across MCP connection classes * feat: enhance MCP connection handling with dynamic request headers to pass request body fields --------- Co-authored-by: Gopal Sharma <gopalsharma@gopal.sharma1> Co-authored-by: s10gopal <36487439+s10gopal@users.noreply.github.com> Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
387 lines
11 KiB
JavaScript
387 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;
|
|
|
|
/**
|
|
* Cleans up the client object by removing references to its properties.
|
|
* This is useful for preventing memory leaks and ensuring that the client
|
|
* and its properties can be garbage collected when it is no longer needed.
|
|
*/
|
|
function disposeClient(client) {
|
|
if (!client) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (client.user) {
|
|
client.user = null;
|
|
}
|
|
if (client.apiKey) {
|
|
client.apiKey = null;
|
|
}
|
|
if (client.azure) {
|
|
client.azure = null;
|
|
}
|
|
if (client.conversationId) {
|
|
client.conversationId = null;
|
|
}
|
|
if (client.responseMessageId) {
|
|
client.responseMessageId = null;
|
|
}
|
|
if (client.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.contextStrategy) {
|
|
client.contextStrategy = 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) {
|
|
// Break circular references in run
|
|
if (client.run.Graph) {
|
|
client.run.Graph.resetValues();
|
|
client.run.Graph.handlerRegistry = null;
|
|
client.run.Graph.runId = null;
|
|
client.run.Graph.tools = null;
|
|
client.run.Graph.signal = null;
|
|
client.run.Graph.config = null;
|
|
client.run.Graph.toolEnd = null;
|
|
client.run.Graph.toolMap = null;
|
|
client.run.Graph.provider = null;
|
|
client.run.Graph.streamBuffer = null;
|
|
client.run.Graph.clientOptions = null;
|
|
client.run.Graph.graphState = null;
|
|
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;
|
|
client.run.Graph.messages = null;
|
|
client.run.Graph.contentData = null;
|
|
client.run.Graph.stepKeyIds = null;
|
|
client.run.Graph.contentIndexMap = null;
|
|
client.run.Graph.toolCallStepIds = null;
|
|
client.run.Graph.messageIdsByStepKey = null;
|
|
client.run.Graph.messageStepHasToolCalls = null;
|
|
client.run.Graph.prelimMessageIdsByStepKey = null;
|
|
client.run.Graph.currentTokenType = null;
|
|
client.run.Graph.lastToken = null;
|
|
client.run.Graph.tokenTypeSwitch = null;
|
|
client.run.Graph.indexTokenCountMap = null;
|
|
client.run.Graph.currentUsage = null;
|
|
client.run.Graph.tokenCounter = null;
|
|
client.run.Graph.maxContextTokens = null;
|
|
client.run.Graph.pruneMessages = null;
|
|
client.run.Graph.lastStreamCall = null;
|
|
client.run.Graph.startIndex = null;
|
|
client.run.Graph = null;
|
|
}
|
|
if (client.run.handlerRegistry) {
|
|
client.run.handlerRegistry = null;
|
|
}
|
|
if (client.run.graphRunnable) {
|
|
if (client.run.graphRunnable.channels) {
|
|
client.run.graphRunnable.channels = null;
|
|
}
|
|
if (client.run.graphRunnable.nodes) {
|
|
client.run.graphRunnable.nodes = null;
|
|
}
|
|
if (client.run.graphRunnable.lc_kwargs) {
|
|
client.run.graphRunnable.lc_kwargs = null;
|
|
}
|
|
if (client.run.graphRunnable.builder?.nodes) {
|
|
client.run.graphRunnable.builder.nodes = null;
|
|
client.run.graphRunnable.builder = null;
|
|
}
|
|
client.run.graphRunnable = null;
|
|
}
|
|
client.run = null;
|
|
}
|
|
if (client.sendMessage) {
|
|
client.sendMessage = null;
|
|
}
|
|
if (client.savedMessageIds) {
|
|
client.savedMessageIds.clear();
|
|
client.savedMessageIds = null;
|
|
}
|
|
if (client.currentMessages) {
|
|
client.currentMessages = null;
|
|
}
|
|
if (client.streamHandler) {
|
|
client.streamHandler = null;
|
|
}
|
|
if (client.contentParts) {
|
|
client.contentParts = null;
|
|
}
|
|
if (client.abortController) {
|
|
client.abortController = null;
|
|
}
|
|
if (client.collectedUsage) {
|
|
client.collectedUsage = null;
|
|
}
|
|
if (client.indexTokenCountMap) {
|
|
client.indexTokenCountMap = null;
|
|
}
|
|
if (client.agentConfigs) {
|
|
client.agentConfigs = null;
|
|
}
|
|
if (client.artifactPromises) {
|
|
client.artifactPromises = null;
|
|
}
|
|
if (client.usage) {
|
|
client.usage = null;
|
|
}
|
|
if (typeof client.dispose === 'function') {
|
|
client.dispose();
|
|
}
|
|
if (client.options) {
|
|
if (client.options.req) {
|
|
client.options.req = null;
|
|
}
|
|
if (client.options.res) {
|
|
client.options.res = null;
|
|
}
|
|
if (client.options.attachments) {
|
|
client.options.attachments = null;
|
|
}
|
|
if (client.options.agent) {
|
|
client.options.agent = null;
|
|
}
|
|
}
|
|
client.options = null;
|
|
} catch {
|
|
// Ignore errors during disposal
|
|
}
|
|
}
|
|
|
|
function processReqData(data = {}, context) {
|
|
let {
|
|
abortKey,
|
|
userMessage,
|
|
userMessagePromise,
|
|
responseMessageId,
|
|
promptTokens,
|
|
conversationId,
|
|
userMessageId,
|
|
} = context;
|
|
for (const key in data) {
|
|
if (key === 'userMessage') {
|
|
userMessage = data[key];
|
|
userMessageId = data[key].messageId;
|
|
} else if (key === 'userMessagePromise') {
|
|
userMessagePromise = data[key];
|
|
} else if (key === 'responseMessageId') {
|
|
responseMessageId = data[key];
|
|
} else if (key === 'promptTokens') {
|
|
promptTokens = data[key];
|
|
} else if (key === 'abortKey') {
|
|
abortKey = data[key];
|
|
} else if (!conversationId && key === 'conversationId') {
|
|
conversationId = data[key];
|
|
}
|
|
}
|
|
return {
|
|
abortKey,
|
|
userMessage,
|
|
userMessagePromise,
|
|
responseMessageId,
|
|
promptTokens,
|
|
conversationId,
|
|
userMessageId,
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
disposeClient,
|
|
requestDataMap,
|
|
clientRegistry,
|
|
processReqData,
|
|
};
|