diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index c1e3a8a57..ec4fbd97d 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -187,7 +187,8 @@ class BaseClient { this.user = user; const saveOptions = this.getSaveOptions(); this.abortController = opts.abortController ?? new AbortController(); - const conversationId = overrideConvoId ?? opts.conversationId ?? crypto.randomUUID(); + const requestConvoId = overrideConvoId ?? opts.conversationId; + const conversationId = requestConvoId ?? crypto.randomUUID(); const parentMessageId = opts.parentMessageId ?? Constants.NO_PARENT; const userMessageId = overrideUserMessageId ?? opts.overrideParentMessageId ?? crypto.randomUUID(); @@ -212,11 +213,12 @@ class BaseClient { ...opts, user, head, + saveOptions, + userMessageId, + requestConvoId, conversationId, parentMessageId, - userMessageId, responseMessageId, - saveOptions, }; } @@ -235,11 +237,12 @@ class BaseClient { const { user, head, + saveOptions, + userMessageId, + requestConvoId, conversationId, parentMessageId, - userMessageId, responseMessageId, - saveOptions, } = await this.setMessageOptions(opts); const userMessage = opts.isEdited @@ -261,7 +264,8 @@ class BaseClient { } if (typeof opts?.onStart === 'function') { - opts.onStart(userMessage, responseMessageId); + const isNewConvo = !requestConvoId && parentMessageId === Constants.NO_PARENT; + opts.onStart(userMessage, responseMessageId, isNewConvo); } return { diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index b5ae5b80b..4708ed9b2 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -579,6 +579,8 @@ describe('BaseClient', () => { expect(onStart).toHaveBeenCalledWith( expect.objectContaining({ text: 'Hello, world!' }), expect.any(String), + /** `isNewConvo` */ + true, ); }); diff --git a/api/server/middleware/abortMiddleware.js b/api/server/middleware/abortMiddleware.js index c5fc48780..9ceff1bc0 100644 --- a/api/server/middleware/abortMiddleware.js +++ b/api/server/middleware/abortMiddleware.js @@ -1,6 +1,6 @@ const { logger } = require('@librechat/data-schemas'); const { countTokens, isEnabled, sendEvent } = require('@librechat/api'); -const { isAssistantsEndpoint, ErrorTypes } = require('librechat-data-provider'); +const { isAssistantsEndpoint, ErrorTypes, Constants } = require('librechat-data-provider'); const { truncateText, smartTruncateText } = require('~/app/clients/prompts'); const clearPendingReq = require('~/cache/clearPendingReq'); const { sendError } = require('~/server/middleware/error'); @@ -11,6 +11,10 @@ const { abortRun } = require('./abortRun'); const abortDataMap = new WeakMap(); +/** + * @param {string} abortKey + * @returns {boolean} + */ function cleanupAbortController(abortKey) { if (!abortControllers.has(abortKey)) { return false; @@ -71,6 +75,20 @@ function cleanupAbortController(abortKey) { return true; } +/** + * @param {string} abortKey + * @returns {function(): void} + */ +function createCleanUpHandler(abortKey) { + return function () { + try { + cleanupAbortController(abortKey); + } catch { + // Ignore cleanup errors + } + }; +} + async function abortMessage(req, res) { let { abortKey, endpoint } = req.body; @@ -172,11 +190,15 @@ const createAbortController = (req, res, getAbortData, getReqData) => { /** * @param {TMessage} userMessage * @param {string} responseMessageId + * @param {boolean} [isNewConvo] */ - const onStart = (userMessage, responseMessageId) => { + const onStart = (userMessage, responseMessageId, isNewConvo) => { sendEvent(res, { message: userMessage, created: true }); - const abortKey = userMessage?.conversationId ?? req.user.id; + const prelimAbortKey = userMessage?.conversationId ?? req.user.id; + const abortKey = isNewConvo + ? `${prelimAbortKey}${Constants.COMMON_DIVIDER}${Constants.NEW_CONVO}` + : prelimAbortKey; getReqData({ abortKey }); const prevRequest = abortControllers.get(abortKey); const { overrideUserMessageId } = req?.body ?? {}; @@ -194,16 +216,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => { }; abortControllers.set(addedAbortKey, { abortController, ...minimalOptions }); - - // Use a simple function for cleanup to avoid capturing context - const cleanupHandler = () => { - try { - cleanupAbortController(addedAbortKey); - } catch (e) { - // Ignore cleanup errors - } - }; - + const cleanupHandler = createCleanUpHandler(addedAbortKey); res.on('finish', cleanupHandler); return; } @@ -216,16 +229,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => { }; abortControllers.set(abortKey, { abortController, ...minimalOptions }); - - // Use a simple function for cleanup to avoid capturing context - const cleanupHandler = () => { - try { - cleanupAbortController(abortKey); - } catch (e) { - // Ignore cleanup errors - } - }; - + const cleanupHandler = createCleanUpHandler(abortKey); res.on('finish', cleanupHandler); }; @@ -364,15 +368,7 @@ const handleAbortError = async (res, req, error, data) => { }; } - // Create a simple callback without capturing parent scope - const callback = async () => { - try { - cleanupAbortController(conversationId); - } catch (e) { - // Ignore cleanup errors - } - }; - + const callback = createCleanUpHandler(conversationId); await sendError(req, res, options, callback); }; diff --git a/api/server/services/Endpoints/agents/title.js b/api/server/services/Endpoints/agents/title.js index 2e5f00ecd..5f200fcde 100644 --- a/api/server/services/Endpoints/agents/title.js +++ b/api/server/services/Endpoints/agents/title.js @@ -1,8 +1,8 @@ +const { isEnabled } = require('@librechat/api'); +const { logger } = require('@librechat/data-schemas'); const { CacheKeys } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); -const { isEnabled } = require('~/server/utils'); const { saveConvo } = require('~/models'); -const { logger } = require('~/config'); /** * Add title to conversation in a way that avoids memory retention