From 4c477acca3672a752dbbf94805941c7fc1c288a2 Mon Sep 17 00:00:00 2001 From: Koshkoshinsk Date: Sun, 12 Apr 2026 09:32:12 +0000 Subject: [PATCH] fix(v2): retry as plain text when adapter rejects markdown A single message with markdown the adapter couldn't parse (e.g. Telegram MarkdownV2 entity errors) would fail in deliverSessionMessages and be retried forever, blocking every subsequent reply on that session. Catch ValidationError from postMessage and retry once with the markdown stripped to plain text via markdownToPlainText. Files re-attach in a follow-up post since the plain-text retry drops the files payload shape. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/channels/chat-sdk-bridge.ts | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/channels/chat-sdk-bridge.ts b/src/channels/chat-sdk-bridge.ts index 5cbf0c6..df3e305 100644 --- a/src/channels/chat-sdk-bridge.ts +++ b/src/channels/chat-sdk-bridge.ts @@ -14,10 +14,12 @@ import { Button, Modal, TextInput, + markdownToPlainText, type Adapter, type ConcurrencyStrategy, type Message as ChatMessage, } from 'chat'; +import { ValidationError } from '@chat-adapter/shared'; import { log } from '../log.js'; import { SqliteStateAdapter } from '../state-sqlite.js'; import type { ChannelAdapter, ChannelSetup, ConversationConfig, InboundMessage } from './adapter.js'; @@ -373,12 +375,30 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter data: f.data, filename: f.filename, })); - if (fileUploads && fileUploads.length > 0) { - const result = await adapter.postMessage(tid, { markdown: text, files: fileUploads }); - return result?.id; - } else { - const result = await adapter.postMessage(tid, { markdown: text }); - return result?.id; + try { + if (fileUploads && fileUploads.length > 0) { + const result = await adapter.postMessage(tid, { markdown: text, files: fileUploads }); + return result?.id; + } else { + const result = await adapter.postMessage(tid, { markdown: text }); + return result?.id; + } + } catch (err) { + // Permanent formatting failure (e.g. Telegram MarkdownV2 entity parse error): + // retry once as plain text so the queue isn't blocked forever. + if (err instanceof ValidationError) { + log.warn('Markdown rejected by adapter, retrying as plain text', { + adapter: adapter.name, + err: err.message, + }); + const plain = markdownToPlainText(text); + const result = await adapter.postMessage(tid, plain); + if (fileUploads && fileUploads.length > 0) { + await adapter.postMessage(tid, { markdown: '', files: fileUploads }); + } + return result?.id; + } + throw err; } } else if (message.files && message.files.length > 0) { // Files only, no text