From 9a4fb61f6e037d3f82f0ec700ec6a888964321d9 Mon Sep 17 00:00:00 2001 From: James Schindler Date: Tue, 10 Mar 2026 11:58:00 -0400 Subject: [PATCH] feat: add Markdown formatting for outbound messages Wrap outbound sendMessage calls with parse_mode: 'Markdown' so that Claude's natural formatting (*bold*, _italic_, `code`, etc.) renders correctly in Telegram instead of showing raw asterisks and underscores. Falls back to plain text if Telegram rejects the Markdown formatting. --- src/channels/telegram.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/channels/telegram.ts b/src/channels/telegram.ts index 4176f03..c7d19e5 100644 --- a/src/channels/telegram.ts +++ b/src/channels/telegram.ts @@ -1,4 +1,4 @@ -import { Bot } from 'grammy'; +import { Api, Bot } from 'grammy'; import { ASSISTANT_NAME, TRIGGER_PATTERN } from '../config.js'; import { readEnvFile } from '../env.js'; @@ -17,6 +17,29 @@ export interface TelegramChannelOpts { registeredGroups: () => Record; } +/** + * Send a message with Telegram Markdown parse mode, falling back to plain text. + * Claude's output naturally matches Telegram's Markdown v1 format: + * *bold*, _italic_, `code`, ```code blocks```, [links](url) + */ +async function sendTelegramMessage( + api: { sendMessage: Api['sendMessage'] }, + chatId: string | number, + text: string, + options: { message_thread_id?: number } = {}, +): Promise { + try { + await api.sendMessage(chatId, text, { + ...options, + parse_mode: 'Markdown', + }); + } catch (err) { + // Fallback: send as plain text if Markdown parsing fails + logger.debug({ err }, 'Markdown send failed, falling back to plain text'); + await api.sendMessage(chatId, text, options); + } +} + export class TelegramChannel implements Channel { name = 'telegram'; @@ -203,10 +226,11 @@ export class TelegramChannel implements Channel { // Telegram has a 4096 character limit per message — split if needed const MAX_LENGTH = 4096; if (text.length <= MAX_LENGTH) { - await this.bot.api.sendMessage(numericId, text); + await sendTelegramMessage(this.bot.api, numericId, text); } else { for (let i = 0; i < text.length; i += MAX_LENGTH) { - await this.bot.api.sendMessage( + await sendTelegramMessage( + this.bot.api, numericId, text.slice(i, i + MAX_LENGTH), );