fix(telegram): dedupe reasoning block preview sends

This commit is contained in:
Ayaan Zaidi
2026-02-19 17:21:11 +05:30
parent 7e71131f45
commit 58e7dab1e2
2 changed files with 109 additions and 12 deletions

View File

@@ -687,6 +687,49 @@ describe("dispatchTelegramMessage draft streaming", () => {
expect(deliverReplies).not.toHaveBeenCalled();
});
it("updates reasoning preview for reasoning block payloads instead of sending duplicates", async () => {
setupDraftStreams({ answerMessageId: 999, reasoningMessageId: 111 });
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(
async ({ dispatcherOptions, replyOptions }) => {
await replyOptions?.onReasoningStream?.({
text: "Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and",
});
await replyOptions?.onReasoningEnd?.();
await replyOptions?.onPartialReply?.({ text: "3" });
await dispatcherOptions.deliver({ text: "3" }, { kind: "final" });
await dispatcherOptions.deliver(
{
text: "Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and 9. So the total is 3.",
},
{ kind: "block" },
);
return { queuedFinal: true };
},
);
deliverReplies.mockResolvedValue({ delivered: true });
editMessageTelegram.mockResolvedValue({ ok: true, chatId: "123", messageId: "999" });
await dispatchWithContext({ context: createContext(), streamMode: "partial" });
expect(editMessageTelegram).toHaveBeenNthCalledWith(1, 123, 999, "3", expect.any(Object));
expect(editMessageTelegram).toHaveBeenNthCalledWith(
2,
123,
111,
"Reasoning:\nIf I count r in strawberry, I see positions 3, 8, and 9. So the total is 3.",
expect.any(Object),
);
expect(deliverReplies).not.toHaveBeenCalledWith(
expect.objectContaining({
replies: [
expect.objectContaining({
text: expect.stringContaining("Reasoning:\nIf I count r in strawberry"),
}),
],
}),
);
});
it("splits reasoning preview only when next reasoning block starts in partial mode", async () => {
const { reasoningDraftStream } = setupDraftStreams({
answerMessageId: 999,

View File

@@ -399,6 +399,49 @@ export const dispatchTelegramMessage = async ({
return false;
}
};
const tryEditExistingPreviewForLane = async (params: {
lane: DraftLaneState;
laneName: "answer" | "reasoning";
finalText: string;
previewButtons?: TelegramInlineButtons;
}): Promise<boolean> => {
const { lane, laneName, finalText, previewButtons } = params;
if (!lane.stream) {
return false;
}
const previewMessageId = lane.stream.messageId();
if (typeof previewMessageId !== "number") {
return false;
}
const currentPreviewText = streamMode === "block" ? lane.draftText : lane.lastPartialText;
if (
currentPreviewText &&
currentPreviewText.startsWith(finalText) &&
finalText.length < currentPreviewText.length
) {
// Avoid regressive punctuation/wording flicker from occasional shorter finals.
deliveryState.delivered = true;
return true;
}
try {
await editMessageTelegram(chatId, previewMessageId, finalText, {
api: bot.api,
cfg,
accountId: route.accountId,
linkPreview: telegramCfg.linkPreview,
buttons: previewButtons,
});
lane.lastPartialText = finalText;
lane.draftText = finalText;
deliveryState.delivered = true;
return true;
} catch (err) {
logVerbose(
`telegram: ${laneName} preview update failed; falling back to standard send (${String(err)})`,
);
return false;
}
};
let queuedFinal = false;
try {
@@ -408,21 +451,21 @@ export const dispatchTelegramMessage = async ({
dispatcherOptions: {
...prefixOptions,
deliver: async (payload, info) => {
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
const finalText = payload.text;
const reasoningMessage = isReasoningMessage(finalText);
const previewButtons = (
payload.channelData?.telegram as { buttons?: TelegramInlineButtons } | undefined
)?.buttons;
const canFinalizeViaPreviewEdit =
!hasMedia &&
typeof finalText === "string" &&
finalText.length > 0 &&
finalText.length <= draftMaxChars &&
!payload.isError;
if (info.kind === "final") {
await flushDraftLane(answerLane);
await flushDraftLane(reasoningLane);
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
const finalText = payload.text;
const reasoningMessage = isReasoningMessage(finalText);
const previewButtons = (
payload.channelData?.telegram as { buttons?: TelegramInlineButtons } | undefined
)?.buttons;
const canFinalizeViaPreviewEdit =
!hasMedia &&
typeof finalText === "string" &&
finalText.length > 0 &&
finalText.length <= draftMaxChars &&
!payload.isError;
if (canFinalizeViaPreviewEdit && reasoningMessage) {
const finalizedReasoning = await tryFinalizePreviewForLane({
lane: reasoningLane,
@@ -459,6 +502,17 @@ export const dispatchTelegramMessage = async ({
await answerLane.stream?.stop();
await reasoningLane.stream?.stop();
}
if (info.kind !== "final" && canFinalizeViaPreviewEdit && reasoningMessage) {
const updatedReasoning = await tryEditExistingPreviewForLane({
lane: reasoningLane,
laneName: "reasoning",
finalText,
previewButtons,
});
if (updatedReasoning) {
return;
}
}
const result = await deliverReplies({
...deliveryBaseOptions,
replies: [payload],