fix(telegram): draft stream preview not threaded when replyToMode is on (#17880) (#17928)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: cfd4181a23
Co-authored-by: yinghaosang <261132136+yinghaosang@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
This commit is contained in:
yinghaosang
2026-02-16 20:40:24 +08:00
committed by GitHub
parent b2aa6e094d
commit 244ed9db39
5 changed files with 211 additions and 33 deletions

View File

@@ -91,12 +91,15 @@ export const dispatchTelegramMessage = async ({
? telegramCfg.blockStreaming
: cfg.agents?.defaults?.blockStreamingDefault === "on";
const canStreamDraft = streamMode !== "off" && !accountBlockStreamingEnabled;
const draftReplyToMessageId =
replyToMode !== "off" && typeof msg.message_id === "number" ? msg.message_id : undefined;
const draftStream = canStreamDraft
? createTelegramDraftStream({
api: bot.api,
chatId,
maxChars: draftMaxChars,
thread: threadSpec,
replyToMessageId: draftReplyToMessageId,
log: logVerbose,
warn: logVerbose,
})
@@ -117,6 +120,16 @@ export const dispatchTelegramMessage = async ({
return;
}
if (streamMode === "partial") {
// Some providers briefly emit a shorter prefix snapshot (for example
// "Sure." -> "Sure" -> "Sure."). Keep the longer preview to avoid
// visible punctuation flicker.
if (
lastPartialText &&
lastPartialText.startsWith(text) &&
text.length < lastPartialText.length
) {
return;
}
lastPartialText = text;
draftStream.update(text);
return;
@@ -281,42 +294,53 @@ export const dispatchTelegramMessage = async ({
await flushDraft();
const hasMedia = Boolean(payload.mediaUrl) || (payload.mediaUrls?.length ?? 0) > 0;
const previewMessageId = draftStream?.messageId();
const previewButtons = (
payload.channelData?.telegram as
| { buttons?: Array<Array<{ text: string; callback_data: string }>> }
| undefined
)?.buttons;
let draftStoppedForPreviewEdit = false;
if (!hasMedia && payload.text && typeof previewMessageId === "number") {
const canFinalizeViaPreviewEdit = payload.text.length <= draftMaxChars;
if (canFinalizeViaPreviewEdit) {
draftStream?.stop();
draftStoppedForPreviewEdit = true;
try {
await editMessageTelegram(chatId, previewMessageId, payload.text, {
api: bot.api,
cfg,
accountId: route.accountId,
linkPreview: telegramCfg.linkPreview,
buttons: previewButtons,
});
finalizedViaPreviewMessage = true;
deliveryState.delivered = true;
return;
} catch (err) {
logVerbose(
`telegram: preview final edit failed; falling back to standard send (${String(err)})`,
);
}
} else {
const finalText = payload.text;
const canFinalizeViaPreviewEdit =
!hasMedia &&
typeof finalText === "string" &&
finalText.length > 0 &&
typeof previewMessageId === "number" &&
finalText.length <= draftMaxChars;
if (canFinalizeViaPreviewEdit) {
draftStream?.stop();
const currentPreviewText = streamMode === "block" ? draftText : lastPartialText;
if (
currentPreviewText &&
currentPreviewText.startsWith(finalText) &&
finalText.length < currentPreviewText.length
) {
// Ignore regressive final edits (e.g., "Okay." -> "Ok"), which
// can appear transiently in some provider streams.
return;
}
const previewButtons = (
payload.channelData?.telegram as
| { buttons?: Array<Array<{ text: string; callback_data: string }>> }
| undefined
)?.buttons;
try {
await editMessageTelegram(chatId, previewMessageId, finalText, {
api: bot.api,
cfg,
accountId: route.accountId,
linkPreview: telegramCfg.linkPreview,
buttons: previewButtons,
});
finalizedViaPreviewMessage = true;
deliveryState.delivered = true;
return;
} catch (err) {
logVerbose(
`telegram: preview final too long for edit (${payload.text.length} > ${draftMaxChars}); falling back to standard send`,
`telegram: preview final edit failed; falling back to standard send (${String(err)})`,
);
}
}
if (!draftStoppedForPreviewEdit) {
draftStream?.stop();
if (payload.text && payload.text.length > draftMaxChars) {
logVerbose(
`telegram: preview final too long for edit (${payload.text.length} > ${draftMaxChars}); falling back to standard send`,
);
}
draftStream?.stop();
}
const result = await deliverReplies({
...deliveryBaseOptions,