From e370a87ebe8c53b51ff4d971b032e25d8599fdc4 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Mon, 14 Jul 2025 00:57:20 -0400 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20fix:=20Correct=20Message?= =?UTF-8?q?=20ID=20Assignment=20Logic=20(#8439)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Add `isRegenerate` flag to chat payload to avoid saving temporary response IDs * fix: Remove unused `isResubmission` flag * ci: Add tests for responseMessageId regeneration logic in BaseClient --- api/app/clients/BaseClient.js | 4 ++ api/app/clients/specs/BaseClient.test.js | 40 +++++++++++++++++++ api/server/controllers/agents/request.js | 2 + client/src/common/types.ts | 4 +- .../Chat/Messages/Content/EditMessage.tsx | 1 - client/src/hooks/Chat/useChatFunctions.ts | 7 ++-- packages/data-provider/src/createPayload.ts | 16 ++++---- packages/data-provider/src/types.ts | 2 +- 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/api/app/clients/BaseClient.js b/api/app/clients/BaseClient.js index 0598f0da21..3815f159be 100644 --- a/api/app/clients/BaseClient.js +++ b/api/app/clients/BaseClient.js @@ -197,6 +197,10 @@ class BaseClient { this.currentMessages[this.currentMessages.length - 1].messageId = head; } + if (opts.isRegenerate && responseMessageId.endsWith('_')) { + responseMessageId = crypto.randomUUID(); + } + this.responseMessageId = responseMessageId; return { diff --git a/api/app/clients/specs/BaseClient.test.js b/api/app/clients/specs/BaseClient.test.js index 6d44915804..b5ae5b80b6 100644 --- a/api/app/clients/specs/BaseClient.test.js +++ b/api/app/clients/specs/BaseClient.test.js @@ -422,6 +422,46 @@ describe('BaseClient', () => { expect(response).toEqual(expectedResult); }); + test('should replace responseMessageId with new UUID when isRegenerate is true and messageId ends with underscore', async () => { + const mockCrypto = require('crypto'); + const newUUID = 'new-uuid-1234'; + jest.spyOn(mockCrypto, 'randomUUID').mockReturnValue(newUUID); + + const opts = { + isRegenerate: true, + responseMessageId: 'existing-message-id_', + }; + + await TestClient.setMessageOptions(opts); + + expect(TestClient.responseMessageId).toBe(newUUID); + expect(TestClient.responseMessageId).not.toBe('existing-message-id_'); + + mockCrypto.randomUUID.mockRestore(); + }); + + test('should not replace responseMessageId when isRegenerate is false', async () => { + const opts = { + isRegenerate: false, + responseMessageId: 'existing-message-id_', + }; + + await TestClient.setMessageOptions(opts); + + expect(TestClient.responseMessageId).toBe('existing-message-id_'); + }); + + test('should not replace responseMessageId when it does not end with underscore', async () => { + const opts = { + isRegenerate: true, + responseMessageId: 'existing-message-id', + }; + + await TestClient.setMessageOptions(opts); + + expect(TestClient.responseMessageId).toBe('existing-message-id'); + }); + test('sendMessage should work with provided conversationId and parentMessageId', async () => { const userMessage = 'Second message in the conversation'; const opts = { diff --git a/api/server/controllers/agents/request.js b/api/server/controllers/agents/request.js index 2c8e424b5d..b4a0e40b24 100644 --- a/api/server/controllers/agents/request.js +++ b/api/server/controllers/agents/request.js @@ -12,6 +12,7 @@ const { saveMessage } = require('~/models'); const AgentController = async (req, res, next, initializeClient, addTitle) => { let { text, + isRegenerate, endpointOption, conversationId, isContinued = false, @@ -167,6 +168,7 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => { onStart, getReqData, isContinued, + isRegenerate, editedContent, conversationId, parentMessageId, diff --git a/client/src/common/types.ts b/client/src/common/types.ts index 52575e180d..a222cb29b4 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -345,9 +345,7 @@ export type TOptions = { isContinued?: boolean; isEdited?: boolean; overrideMessages?: t.TMessage[]; - /** This value is only true when the user submits a message with "Save & Submit" for a user-created message */ - isResubmission?: boolean; - /** Currently only utilized when `isResubmission === true`, uses that message's currently attached files */ + /** Currently only utilized when resubmitting user-created message, uses that message's currently attached files */ overrideFiles?: t.TMessage['files']; }; diff --git a/client/src/components/Chat/Messages/Content/EditMessage.tsx b/client/src/components/Chat/Messages/Content/EditMessage.tsx index 200cf7d402..e2781aaa5f 100644 --- a/client/src/components/Chat/Messages/Content/EditMessage.tsx +++ b/client/src/components/Chat/Messages/Content/EditMessage.tsx @@ -60,7 +60,6 @@ const EditMessage = ({ conversationId, }, { - isResubmission: true, overrideFiles: message.files, }, ); diff --git a/client/src/hooks/Chat/useChatFunctions.ts b/client/src/hooks/Chat/useChatFunctions.ts index 6bb35b0019..cf2f020ddf 100644 --- a/client/src/hooks/Chat/useChatFunctions.ts +++ b/client/src/hooks/Chat/useChatFunctions.ts @@ -83,7 +83,6 @@ export default function useChatFunctions({ { editedContent = null, editedMessageId = null, - isResubmission = false, isRegenerate = false, isContinued = false, isEdited = false, @@ -230,7 +229,10 @@ export default function useChatFunctions({ } const responseMessageId = - editedMessageId ?? (latestMessage?.messageId ? latestMessage?.messageId + '_' : null) ?? null; + editedMessageId ?? + (latestMessage?.messageId && isRegenerate ? latestMessage?.messageId + '_' : null) ?? + null; + const initialResponse: TMessage = { sender: responseSender, text: '', @@ -307,7 +309,6 @@ export default function useChatFunctions({ isEdited: isEditOrContinue, isContinued, isRegenerate, - isResubmission, initialResponse, isTemporary, ephemeralAgent, diff --git a/packages/data-provider/src/createPayload.ts b/packages/data-provider/src/createPayload.ts index c21a115fac..a0eacb244d 100644 --- a/packages/data-provider/src/createPayload.ts +++ b/packages/data-provider/src/createPayload.ts @@ -4,14 +4,15 @@ import * as s from './schemas'; export default function createPayload(submission: t.TSubmission) { const { - conversation, - userMessage, - endpointOption, isEdited, + userMessage, isContinued, isTemporary, - ephemeralAgent, + isRegenerate, + conversation, editedContent, + ephemeralAgent, + endpointOption, } = submission; const { conversationId } = s.tConvoUpdateSchema.parse(conversation); const { endpoint: _e, endpointType } = endpointOption as { @@ -31,11 +32,12 @@ export default function createPayload(submission: t.TSubmission) { ...userMessage, ...endpointOption, endpoint, - ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent, - isContinued: !!(isEdited && isContinued), - conversationId, isTemporary, + isRegenerate, editedContent, + conversationId, + isContinued: !!(isEdited && isContinued), + ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent, }; return { server, payload }; diff --git a/packages/data-provider/src/types.ts b/packages/data-provider/src/types.ts index 3c8ccc870f..b1fbc42653 100644 --- a/packages/data-provider/src/types.ts +++ b/packages/data-provider/src/types.ts @@ -105,6 +105,7 @@ export type TEphemeralAgent = { export type TPayload = Partial & Partial & { isContinued: boolean; + isRegenerate?: boolean; conversationId: string | null; messages?: TMessages; isTemporary: boolean; @@ -125,7 +126,6 @@ export type TSubmission = { isTemporary: boolean; messages: TMessage[]; isRegenerate?: boolean; - isResubmission?: boolean; initialResponse?: TMessage; conversation: Partial; endpointOption: TEndpointOption;