fix(telegram): sanitize outbound markdown for legacy parse mode

The @chat-adapter/telegram adapter hardcodes parse_mode=Markdown (legacy)
but its converter emits CommonMark. Messages containing **bold** or list
bullets that round-trip to `*` produce "can't parse entities" errors and
get dropped after retries.

Add an opt-in transformOutboundText hook on the chat-sdk bridge and wire
a Telegram-specific sanitizer that downgrades **bold** to *bold*, rewrites
dash/plus list bullets to a Unicode bullet so the adapter's re-stringify
doesn't inject stray `*`, and strips unbalanced delimiters or brackets.
Only Telegram opts in; other channels are unaffected.

Workaround until upstream (vercel/chat) ships mode-aware conversion —
PR #367 adds a parseMode knob but not the converter fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Koshkoshinsk
2026-04-14 10:30:32 +00:00
parent c303b6eb14
commit f304c67318
4 changed files with 133 additions and 2 deletions

View File

@@ -57,10 +57,18 @@ export interface ChatSdkBridgeConfig {
* way and the default depends on installation style.
*/
supportsThreads: boolean;
/**
* Optional transform applied to outbound text/markdown before it reaches the
* adapter. Used by channels that need to sanitize for a platform-specific
* quirk (e.g. Telegram's legacy Markdown parse mode).
*/
transformOutboundText?: (text: string) => string;
}
export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter {
const { adapter } = config;
const transformText = (t: string): string =>
config.transformOutboundText ? config.transformOutboundText(t) : t;
let chat: Chat;
let state: SqliteStateAdapter;
let setupConfig: ChannelSetup;
@@ -321,7 +329,7 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
if (content.operation === 'edit' && content.messageId) {
await adapter.editMessage(tid, content.messageId as string, {
markdown: (content.text as string) || (content.markdown as string) || '',
markdown: transformText((content.text as string) || (content.markdown as string) || ''),
});
return;
}
@@ -370,7 +378,8 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
}
// Normal message
const text = (content.markdown as string) || (content.text as string);
const rawText = (content.markdown as string) || (content.text as string);
const text = rawText ? transformText(rawText) : rawText;
if (text) {
// Attach files if present (FileUpload format: { data, filename })
const fileUploads = message.files?.map((f: { data: Buffer; filename: string }) => ({