fix: telegram ESM compatibility and improved diagnostics (#161)
- Replace telegram-markdown-v2 with telegramify-markdown (ESM compatible) - Add raw text fallback when Telegram formatting fails, with error notice - Improve empty response diagnostics: log agent ID, show conversation ID - Add reset-conversation command hint to user messages - Add telegram-format.test.ts with 7 tests Fixes Railway deployment ERR_REQUIRE_ESM error with remark package. Written by Cameron and Letta Code "The best error message is the one that never shows up." - Thomas Fuchs
This commit is contained in:
1733
package-lock.json
generated
1733
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -56,7 +56,7 @@
|
||||
"open": "^11.0.0",
|
||||
"openai": "^6.17.0",
|
||||
"qrcode-terminal": "^0.12.0",
|
||||
"telegram-markdown-v2": "^0.0.4",
|
||||
"telegramify-markdown": "^1.0.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"update-notifier": "^7.3.1",
|
||||
|
||||
43
src/channels/telegram-format.test.ts
Normal file
43
src/channels/telegram-format.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { markdownToTelegramV2 } from './telegram-format.js';
|
||||
|
||||
describe('markdownToTelegramV2', () => {
|
||||
it('converts bold text', async () => {
|
||||
const result = await markdownToTelegramV2('**hello**');
|
||||
expect(result).toContain('*hello*');
|
||||
});
|
||||
|
||||
it('converts inline code', async () => {
|
||||
const result = await markdownToTelegramV2('use `npm install`');
|
||||
expect(result).toContain('`npm install`');
|
||||
});
|
||||
|
||||
it('escapes special characters outside formatting', async () => {
|
||||
const result = await markdownToTelegramV2('Hello! How are you?');
|
||||
expect(result).toContain('\\!');
|
||||
});
|
||||
|
||||
it('handles code blocks', async () => {
|
||||
const result = await markdownToTelegramV2('```js\nconsole.log("hi")\n```');
|
||||
expect(result).toContain('```');
|
||||
});
|
||||
|
||||
it('returns something for any input (never throws)', async () => {
|
||||
// Even weird inputs should return without throwing
|
||||
const weirdInputs = ['', '\\', '[](){}', '****', '```'];
|
||||
for (const input of weirdInputs) {
|
||||
const result = await markdownToTelegramV2(input);
|
||||
expect(typeof result).toBe('string');
|
||||
}
|
||||
});
|
||||
|
||||
it('handles links', async () => {
|
||||
const result = await markdownToTelegramV2('Check out [Google](https://google.com)');
|
||||
expect(result).toContain('https://google.com');
|
||||
});
|
||||
|
||||
it('preserves plain text', async () => {
|
||||
const result = await markdownToTelegramV2('Just some plain text');
|
||||
expect(result).toContain('Just some plain text');
|
||||
});
|
||||
});
|
||||
@@ -11,13 +11,13 @@
|
||||
*/
|
||||
export async function markdownToTelegramV2(markdown: string): Promise<string> {
|
||||
try {
|
||||
// Dynamic import to avoid ESM/CommonJS compatibility issues
|
||||
const { convert } = await import('telegram-markdown-v2');
|
||||
// Use 'keep' strategy to preserve blockquotes (>) and other elements
|
||||
return convert(markdown, 'keep');
|
||||
// Dynamic import to handle ESM module
|
||||
const telegramifyMarkdown = (await import('telegramify-markdown')).default;
|
||||
// Use 'keep' strategy to preserve blockquotes (>) and other unsupported elements
|
||||
return telegramifyMarkdown(markdown, 'keep');
|
||||
} catch (e) {
|
||||
console.error('[Telegram] Markdown conversion failed, using fallback:', e);
|
||||
// Fallback: escape special characters manually
|
||||
console.error('[Telegram] Markdown conversion failed, using escape fallback:', e);
|
||||
// Fallback: escape special characters manually (loses formatting)
|
||||
return escapeMarkdownV2(markdown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,14 +347,24 @@ export class TelegramAdapter implements ChannelAdapter {
|
||||
async sendMessage(msg: OutboundMessage): Promise<{ messageId: string }> {
|
||||
const { markdownToTelegramV2 } = await import('./telegram-format.js');
|
||||
|
||||
// Convert markdown to Telegram MarkdownV2 format
|
||||
// Try MarkdownV2 first
|
||||
try {
|
||||
const formatted = await markdownToTelegramV2(msg.text);
|
||||
|
||||
const result = await this.bot.api.sendMessage(msg.chatId, formatted, {
|
||||
parse_mode: 'MarkdownV2',
|
||||
reply_to_message_id: msg.replyToMessageId ? Number(msg.replyToMessageId) : undefined,
|
||||
});
|
||||
return { messageId: String(result.message_id) };
|
||||
} catch (e) {
|
||||
// If MarkdownV2 fails, send raw text with notice
|
||||
console.warn('[Telegram] MarkdownV2 send failed, falling back to raw text:', e);
|
||||
const errorMsg = e instanceof Error ? e.message : String(e);
|
||||
const fallbackText = `${msg.text}\n\n(Telegram formatting failed: ${errorMsg.slice(0, 50)}. Report: github.com/letta-ai/lettabot/issues)`;
|
||||
const result = await this.bot.api.sendMessage(msg.chatId, fallbackText, {
|
||||
reply_to_message_id: msg.replyToMessageId ? Number(msg.replyToMessageId) : undefined,
|
||||
});
|
||||
return { messageId: String(result.message_id) };
|
||||
}
|
||||
}
|
||||
|
||||
async sendFile(file: OutboundFile): Promise<{ messageId: string }> {
|
||||
|
||||
@@ -549,24 +549,25 @@ export class LettaBot {
|
||||
// Only show "no response" if we never sent anything
|
||||
if (!sentAnyMessage) {
|
||||
if (!receivedAnyData) {
|
||||
// Stream timed out with NO data at all - likely stuck approval or connection issue
|
||||
console.error('[Bot] Stream received NO DATA - possible stuck tool approval');
|
||||
// Stream timed out with NO data at all - likely stuck state
|
||||
console.error('[Bot] Stream received NO DATA - possible stuck state');
|
||||
console.error('[Bot] Agent:', this.store.agentId);
|
||||
console.error('[Bot] Conversation:', this.store.conversationId);
|
||||
console.error('[Bot] This can happen when a previous session disconnected mid-tool-approval');
|
||||
console.error('[Bot] Recovery will be attempted automatically on the next message.');
|
||||
await adapter.sendMessage({
|
||||
chatId: msg.chatId,
|
||||
text: '(Session interrupted. Please try your message again - recovery in progress.)',
|
||||
text: '(Session interrupted. Try: lettabot reset-conversation)',
|
||||
threadId: msg.threadId
|
||||
});
|
||||
} else {
|
||||
console.warn('[Bot] Stream received data but no assistant message');
|
||||
console.warn('[Bot] Message types received:', msgTypeCounts);
|
||||
console.warn('[Bot] This may indicate: ADE session conflict, agent processing, or internal error');
|
||||
// Give user informative message - avoid suggesting reset
|
||||
console.warn('[Bot] Agent:', this.store.agentId);
|
||||
console.warn('[Bot] Conversation:', this.store.conversationId);
|
||||
const convIdShort = this.store.conversationId?.slice(0, 8) || 'none';
|
||||
await adapter.sendMessage({
|
||||
chatId: msg.chatId,
|
||||
text: '(Agent is processing but returned no response. Please try again.)',
|
||||
text: `(No response. Conversation: ${convIdShort}... Try: lettabot reset-conversation)`,
|
||||
threadId: msg.threadId
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user