🪙 fix: Count quote tokens on message edit so context stays accurate

The message-edit route recomputed a user message's tokenCount from the edited
`text` alone, ignoring its persisted `quotes`. But the send path re-prepends
those quotes into the prompt on every turn (mergeQuotedText), so after editing a
quoted message the stored tokenCount under-reported by the whole quote block,
skewing the context gauge and any other tokenCount consumer.

The full-recount path now fetches the message's quotes and counts the merged
text+quotes via a new `mergeQuotedTextForCount` helper in packages/api (mirrors
the send path), so the stored count stays authoritative. The incremental
content-part path is left as-is: it deltas only the edited part and preserves the
rest of the count (incl. the quote contribution), and applies to content-array
messages rather than text+quotes user turns.

Deferred follow-up from #13953.
This commit is contained in:
Danny Avila 2026-06-25 15:49:01 -04:00
parent 5706e414fd
commit ec04a62b42
3 changed files with 72 additions and 1 deletions

View file

@ -7,6 +7,7 @@ const {
countTokens,
sendFeedbackScore,
traceIdForMessage,
mergeQuotedTextForCount,
} = require('@librechat/api');
const { findAllArtifacts, replaceArtifactContent } = require('~/server/services/Artifacts/update');
const { requireJwtAuth, validateMessageReq } = require('~/server/middleware');
@ -329,7 +330,22 @@ router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) =
const { text, index, model } = req.body;
if (index === undefined) {
const tokenCount = await countTokens(text, model);
/** A user turn's persisted `quotes` are re-prepended into the prompt on
* every send, but this edit only changes `text`. Count the merged
* text+quotes so the stored `tokenCount` stays authoritative (matching the
* send path); a plain text-only count under-reports by the quote block. */
const existing = (
await db.getMessages(
{ conversationId, messageId, user: req.user.id },
'quotes isCreatedByUser',
)
)?.[0];
const textToCount = mergeQuotedTextForCount(
text,
existing?.quotes,
existing?.isCreatedByUser === true,
);
const tokenCount = await countTokens(textToCount, model);
const result = await db.updateMessage(req?.user?.id, { messageId, text, tokenCount });
return res.status(200).json(result);
}