@@ -19,17 +19,22 @@ import {
1919import { TELEGRAM_TEXT_CHUNK_LIMIT } from "./outbound-adapter.js" ;
2020import { normalizeTelegramReplyToMessageId } from "./outbound-params.js" ;
2121import {
22- buildTelegramRichMarkdown ,
22+ buildTelegramRichHtmlPlan ,
23+ buildTelegramRichMarkdownPlan ,
2324 getTelegramRichRawApi ,
2425 isTelegramRichMessageWithinStructuralLimits ,
2526 TELEGRAM_RICH_TEXT_LIMIT ,
2627 type TelegramInputRichMessage ,
2728 type TelegramSendRichMessageParams ,
2829} from "./rich-message.js" ;
30+ import {
31+ buildTelegramPlainFallbackPlan ,
32+ isTelegramHtmlParseError ,
33+ warnTelegramRichHtmlDegradations ,
34+ } from "./rich-plain-fallback.js" ;
2935
3036const TELEGRAM_STREAM_MAX_CHARS = TELEGRAM_TEXT_CHUNK_LIMIT ;
3137const DEFAULT_THROTTLE_MS = 1000 ;
32- const TELEGRAM_PARSE_ERR_RE = / c a n ' t p a r s e e n t i t i e s | p a r s e e n t i t i e s | f i n d e n d o f t h e e n t i t y / i;
3338// Retryable preview failures keep the latest text pending for the next throttle
3439// tick; cap consecutive misses so a persistent outage stops the preview instead
3540// of warn-spamming for the rest of the run.
@@ -106,10 +111,6 @@ function renderTelegramDraftPreview(
106111 return renderText ?.( trimmed ) ?? { text : trimmed } ;
107112}
108113
109- function isTelegramHtmlParseError ( err : unknown ) : boolean {
110- return TELEGRAM_PARSE_ERR_RE . test ( formatErrorMessage ( err ) ) ;
111- }
112-
113114function telegramRichHtmlToParseModeHtml ( html : string ) : string {
114115 return html . replace ( / < b r \s * \/ ? > / giu, "\n" ) ;
115116}
@@ -160,10 +161,25 @@ function telegramDraftRichPayloadLength(preview: TelegramDraftPreview): number {
160161 if ( ! isTelegramRichMessageWithinStructuralLimits ( sourceMessage ) ) {
161162 return TELEGRAM_RICH_TEXT_LIMIT + 1 ;
162163 }
163- const richMessage = preview . richMessage ?? buildTelegramRichMarkdown ( preview . text ) ;
164+ const richMessage =
165+ preview . richMessage ?? buildTelegramRichMarkdownPlan ( preview . text ) . richMessage ;
164166 return richMessage . html ?. length ?? richMessage . markdown ?. length ?? 0 ;
165167}
166168
169+ function buildTelegramDraftRichPlan ( preview : TelegramDraftPreview ) {
170+ if ( preview . richMessage ?. html !== undefined ) {
171+ return buildTelegramRichHtmlPlan ( preview . richMessage . html , {
172+ skipEntityDetection : preview . richMessage . skip_entity_detection === true ,
173+ } ) ;
174+ }
175+ if ( preview . richMessage ?. markdown !== undefined ) {
176+ return buildTelegramRichMarkdownPlan ( preview . richMessage . markdown , {
177+ skipEntityDetection : preview . richMessage . skip_entity_detection === true ,
178+ } ) ;
179+ }
180+ return buildTelegramRichMarkdownPlan ( preview . text ) ;
181+ }
182+
167183function resolveTelegramDraftRenderedText (
168184 preview : TelegramDraftPreview ,
169185 richMessages : boolean ,
@@ -267,11 +283,30 @@ export function createTelegramDraftStream(params: {
267283 } ;
268284 const sendRenderedMessage = async ( preview : TelegramDraftPreview ) => {
269285 if ( richMessages ) {
270- return await getTelegramRichRawApi ( params . api ) . sendRichMessage ( {
271- chat_id : chatId ,
272- rich_message : preview . richMessage ?? buildTelegramRichMarkdown ( preview . text ) ,
273- ...richMessageParams ,
286+ const richPlan = buildTelegramDraftRichPlan ( preview ) ;
287+ warnTelegramRichHtmlDegradations ( {
288+ context : "stream preview" ,
289+ reasons : richPlan . degradationReasons ,
290+ warn : ( message ) => params . warn ?.( message ) ,
274291 } ) ;
292+ try {
293+ return await getTelegramRichRawApi ( params . api ) . sendRichMessage ( {
294+ chat_id : chatId ,
295+ rich_message : richPlan . richMessage ,
296+ ...richMessageParams ,
297+ } ) ;
298+ } catch ( err ) {
299+ const fallbackPlan = buildTelegramPlainFallbackPlan ( {
300+ html : richPlan . richMessage . html ,
301+ err,
302+ context : "stream preview" ,
303+ warn : ( message ) => params . warn ?.( message ) ,
304+ } ) ;
305+ if ( ! fallbackPlan ) {
306+ throw err ;
307+ }
308+ return await params . api . sendMessage ( chatId , fallbackPlan . plainText , sendMessageParams ) ;
309+ }
275310 }
276311 const transportPreview = normalizeTelegramDraftTransportPreview ( preview ) ;
277312 const sendPlain = async ( ) =>
@@ -298,11 +333,30 @@ export function createTelegramDraftStream(params: {
298333 if ( typeof streamMessageId === "number" ) {
299334 streamVisibleSinceMs ??= Date . now ( ) ;
300335 if ( richMessages ) {
301- await getTelegramRichRawApi ( params . api ) . editMessageText ( {
302- chat_id : chatId ,
303- message_id : streamMessageId ,
304- rich_message : preview . richMessage ?? buildTelegramRichMarkdown ( preview . text ) ,
336+ const richPlan = buildTelegramDraftRichPlan ( preview ) ;
337+ warnTelegramRichHtmlDegradations ( {
338+ context : "stream preview edit" ,
339+ reasons : richPlan . degradationReasons ,
340+ warn : ( message ) => params . warn ?.( message ) ,
305341 } ) ;
342+ try {
343+ await getTelegramRichRawApi ( params . api ) . editMessageText ( {
344+ chat_id : chatId ,
345+ message_id : streamMessageId ,
346+ rich_message : richPlan . richMessage ,
347+ } ) ;
348+ } catch ( err ) {
349+ const fallbackPlan = buildTelegramPlainFallbackPlan ( {
350+ html : richPlan . richMessage . html ,
351+ err,
352+ context : "stream preview edit" ,
353+ warn : ( message ) => params . warn ?.( message ) ,
354+ } ) ;
355+ if ( ! fallbackPlan ) {
356+ throw err ;
357+ }
358+ await params . api . editMessageText ( chatId , streamMessageId , fallbackPlan . plainText ) ;
359+ }
306360 return true ;
307361 }
308362 const transportPreview = normalizeTelegramDraftTransportPreview ( preview ) ;
@@ -632,7 +686,11 @@ export function createTelegramDraftStream(params: {
632686 // Rewind WITHOUT deleting; the old id is captured above.
633687 resetStreamToNewMessage ( ) ;
634688 if ( typeof supersededMessageId === "number" && Number . isFinite ( supersededMessageId ) ) {
635- scheduleDetachedDelete ( supersededMessageId , supersededVisibleSince , REPOSITION_DELETE_DELAY_MS ) ;
689+ scheduleDetachedDelete (
690+ supersededMessageId ,
691+ supersededVisibleSince ,
692+ REPOSITION_DELETE_DELAY_MS ,
693+ ) ;
636694 return supersededMessageId ;
637695 }
638696 return undefined ;
@@ -651,9 +709,7 @@ export function createTelegramDraftStream(params: {
651709 return streamMessageId ;
652710 } ;
653711
654- const finalizeToPreview = async (
655- preview : TelegramDraftPreview ,
656- ) : Promise < number | undefined > => {
712+ const finalizeToPreview = async ( preview : TelegramDraftPreview ) : Promise < number | undefined > => {
657713 const text = preview . text . trimEnd ( ) ;
658714 if ( ! text ) {
659715 return undefined ;
0 commit comments