diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts
index f7f9a6bcb76..d4740a5f5ed 100644
--- a/src/telegram/bot-message-dispatch.ts
+++ b/src/telegram/bot-message-dispatch.ts
@@ -26,6 +26,7 @@ import type { TelegramStreamMode } from "./bot/types.js";
import type { TelegramInlineButtons } from "./button-types.js";
import { resolveTelegramDraftStreamingChunking } from "./draft-chunking.js";
import { createTelegramDraftStream } from "./draft-stream.js";
+import { renderTelegramHtmlText } from "./format.js";
import { editMessageTelegram } from "./send.js";
import { cacheSticker, describeStickerImage } from "./sticker-cache.js";
@@ -101,6 +102,15 @@ export const dispatchTelegramMessage = async ({
} = context;
const draftMaxChars = Math.min(textLimit, 4096);
+ const tableMode = resolveMarkdownTableMode({
+ cfg,
+ channel: "telegram",
+ accountId: route.accountId,
+ });
+ const renderDraftPreview = (text: string) => ({
+ text: renderTelegramHtmlText(text, { tableMode }),
+ parseMode: "HTML" as const,
+ });
const accountBlockStreamingEnabled =
typeof telegramCfg.blockStreaming === "boolean"
? telegramCfg.blockStreaming
@@ -117,6 +127,7 @@ export const dispatchTelegramMessage = async ({
thread: threadSpec,
replyToMessageId: draftReplyToMessageId,
minInitialChars: draftMinInitialChars,
+ renderText: renderDraftPreview,
log: logVerbose,
warn: logVerbose,
})
@@ -129,6 +140,7 @@ export const dispatchTelegramMessage = async ({
thread: threadSpec,
replyToMessageId: draftReplyToMessageId,
minInitialChars: draftMinInitialChars,
+ renderText: renderDraftPreview,
log: logVerbose,
warn: logVerbose,
})
@@ -259,11 +271,6 @@ export const dispatchTelegramMessage = async ({
channel: "telegram",
accountId: route.accountId,
});
- const tableMode = resolveMarkdownTableMode({
- cfg,
- channel: "telegram",
- accountId: route.accountId,
- });
const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId);
// Handle uncached stickers: get a dedicated vision description before dispatch
diff --git a/src/telegram/draft-stream.test.ts b/src/telegram/draft-stream.test.ts
index d01a6c29c5e..ab385d2e5a4 100644
--- a/src/telegram/draft-stream.test.ts
+++ b/src/telegram/draft-stream.test.ts
@@ -133,6 +133,26 @@ describe("createTelegramDraftStream", () => {
expect(api.sendMessage).toHaveBeenCalledTimes(2);
expect(api.sendMessage).toHaveBeenLastCalledWith(123, "After thinking", undefined);
});
+
+ it("supports rendered previews with parse_mode", async () => {
+ const api = createMockDraftApi();
+ const stream = createTelegramDraftStream({
+ // oxlint-disable-next-line typescript/no-explicit-any
+ api: api as any,
+ chatId: 123,
+ renderText: (text) => ({ text: `${text}`, parseMode: "HTML" }),
+ });
+
+ stream.update("hello");
+ await stream.flush();
+ expect(api.sendMessage).toHaveBeenCalledWith(123, "hello", { parse_mode: "HTML" });
+
+ stream.update("hello again");
+ await stream.flush();
+ expect(api.editMessageText).toHaveBeenCalledWith(123, 17, "hello again", {
+ parse_mode: "HTML",
+ });
+ });
});
describe("draft stream initial message debounce", () => {
diff --git a/src/telegram/draft-stream.ts b/src/telegram/draft-stream.ts
index 9d87358671d..beb4d63a836 100644
--- a/src/telegram/draft-stream.ts
+++ b/src/telegram/draft-stream.ts
@@ -15,6 +15,11 @@ export type TelegramDraftStream = {
forceNewMessage: () => void;
};
+type TelegramDraftPreview = {
+ text: string;
+ parseMode?: "HTML";
+};
+
export function createTelegramDraftStream(params: {
api: Bot["api"];
chatId: number;
@@ -24,6 +29,8 @@ export function createTelegramDraftStream(params: {
throttleMs?: number;
/** Minimum chars before sending first message (debounce for push notifications) */
minInitialChars?: number;
+ /** Optional preview renderer (e.g. markdown -> HTML + parse mode). */
+ renderText?: (text: string) => TelegramDraftPreview;
log?: (message: string) => void;
warn?: (message: string) => void;
}): TelegramDraftStream {
@@ -42,6 +49,7 @@ export function createTelegramDraftStream(params: {
let streamMessageId: number | undefined;
let lastSentText = "";
+ let lastSentParseMode: "HTML" | undefined;
let stopped = false;
let isFinal = false;
@@ -63,24 +71,43 @@ export function createTelegramDraftStream(params: {
);
return false;
}
- if (trimmed === lastSentText) {
+ const rendered = params.renderText?.(trimmed) ?? { text: trimmed };
+ const renderedText = rendered.text.trimEnd();
+ const renderedParseMode = rendered.parseMode;
+ if (!renderedText) {
+ return false;
+ }
+ if (renderedText === lastSentText && renderedParseMode === lastSentParseMode) {
return true;
}
// Debounce first preview send for better push notification quality.
if (typeof streamMessageId !== "number" && minInitialChars != null && !isFinal) {
- if (trimmed.length < minInitialChars) {
+ if (renderedText.length < minInitialChars) {
return false;
}
}
- lastSentText = trimmed;
+ lastSentText = renderedText;
+ lastSentParseMode = renderedParseMode;
try {
if (typeof streamMessageId === "number") {
- await params.api.editMessageText(chatId, streamMessageId, trimmed);
+ if (renderedParseMode) {
+ await params.api.editMessageText(chatId, streamMessageId, renderedText, {
+ parse_mode: renderedParseMode,
+ });
+ } else {
+ await params.api.editMessageText(chatId, streamMessageId, renderedText);
+ }
return true;
}
- const sent = await params.api.sendMessage(chatId, trimmed, replyParams);
+ const sendParams = renderedParseMode
+ ? {
+ ...replyParams,
+ parse_mode: renderedParseMode,
+ }
+ : replyParams;
+ const sent = await params.api.sendMessage(chatId, renderedText, sendParams);
const sentMessageId = sent?.message_id;
if (typeof sentMessageId !== "number" || !Number.isFinite(sentMessageId)) {
stopped = true;
@@ -138,6 +165,7 @@ export function createTelegramDraftStream(params: {
const forceNewMessage = () => {
streamMessageId = undefined;
lastSentText = "";
+ lastSentParseMode = undefined;
loop.resetPending();
};