Telegram Integration
Telegram Integration
Section titled “Telegram Integration”Overview
Section titled “Overview”Telegram is the primary user-facing interface for Construct. The bot uses Grammy (a Telegram Bot API framework) with long polling to receive messages and reactions. It handles authorization, typing indicators, Markdown-to-HTML conversion, message chunking, reply threading, and reaction side-effects.
Key Files
Section titled “Key Files”| File | Role |
|---|---|
src/telegram/bot.ts | Bot creation, message/reaction handlers, markdown conversion, reply logic |
src/telegram/index.ts | Standalone Telegram-only entry point (runs bot without scheduler) |
src/telegram/types.ts | TelegramContext and TelegramSideEffects interfaces |
Bot Setup
Section titled “Bot Setup”createBot(db) in src/telegram/bot.ts creates a Grammy Bot instance with the token from env.TELEGRAM_BOT_TOKEN.
The bot listens for two event types:
message:text— Text messages from usersmessage_reaction— Emoji reactions on messages
A catch-all message handler replies “I can only process text messages for now.” for non-text messages (photos, stickers, etc.).
Authorization
Section titled “Authorization”Authorization is controlled by ALLOWED_TELEGRAM_IDS (comma-separated list of numeric Telegram user IDs). If the list is empty, all users are allowed. Otherwise, only listed user IDs can interact.
Unauthorized users receive a simple “Unauthorized.” reply. Unauthorized reactions are silently ignored.
Message Handling Flow
Section titled “Message Handling Flow”flowchart TD A[Telegram text message] --> B{Authorized?} B -- No --> C[Reply: Unauthorized] B -- Yes --> D[Start typing indicator every 4s] D --> E[Build TelegramContext with mutable sideEffects] E --> F[Extract reply context if replying] F --> G["processMessage(db, text, opts)"] G --> H{sideEffects.reactToUser set?} H -- Yes --> I[Set emoji reaction on incoming message] H -- No --> J{sideEffects.suppressText?} I --> J J -- Yes --> K[Skip text reply] J -- No --> L["sendReply(ctx, text, sideEffects, messageId)"] L --> M[Clear typing interval] K --> MTyping Indicator
Section titled “Typing Indicator”A “typing” chat action is sent immediately and then refreshed every 4 seconds (Telegram expires typing indicators after ~5 seconds). The interval is cleared in a finally block regardless of success or failure.
TelegramContext
Section titled “TelegramContext”Each message creates a TelegramContext passed to processMessage():
interface TelegramContext { bot: Bot // Grammy bot instance chatId: string // Telegram chat ID incomingMessageId: number // Message ID of the user's message sideEffects: TelegramSideEffects // Mutable object for tool side-effects}Side-Effects
Section titled “Side-Effects”Tools can set flags on sideEffects during execution:
interface TelegramSideEffects { reactToUser?: string // Emoji to react with replyToMessageId?: number // Message ID for reply threading suppressText?: boolean // If true, skip the text reply}After the agent finishes:
- If
reactToUseris set, the bot callssetMessageReaction()on the incoming message - If
suppressTextis false (default) and the response has text,sendReply()sends it
Reply Context
Section titled “Reply Context”When a user replies to a specific message in Telegram, the original message text is extracted from ctx.message.reply_to_message.text and passed as replyContext. This is included in the context preamble so the agent knows what is being referenced.
Reaction Handling
Section titled “Reaction Handling”When a user reacts to a message with an emoji:
- The bot looks up which conversation the reacted message belongs to
- It queries the database for the message by its Telegram message ID
- A synthetic message is constructed:
[User reacted with <emoji> to <whose> message: "<preview>"] - This synthetic message is processed through
processMessage()like a normal message - The agent may respond with text, a reaction, or nothing
This allows the agent to interpret reactions contextually (e.g., a thumbs-up on a suggestion).
Markdown to Telegram HTML
Section titled “Markdown to Telegram HTML”markdownToTelegramHtml() converts the agent’s Markdown response to Telegram-compatible HTML:
- Protect code: Extract code blocks (
) and inline code (`) to prevent processing - Escape HTML entities:
&,<,>in remaining text - Convert headers:
# Headingto<b>Heading</b> - Convert formatting:
***bold-italic***,**bold**,*italic* - Convert bullets:
*and-list items tobulletcharacters - Restore code: Re-insert code as
<pre>and<code>with HTML escaping
If HTML parsing fails when sending, the bot falls back to sending plain text.
Message Chunking
Section titled “Message Chunking”Telegram has a 4096-character message limit. The sendReply() function chunks long responses:
- Messages <= 4000 characters are sent as-is (with some margin)
- Longer messages are split into 4000-character chunks
- Only the first chunk uses
reply_parametersfor threading
Telegram Message ID Tracking
Section titled “Telegram Message ID Tracking”After sending a reply, the bot stores the Telegram message ID of the sent message in the database via updateTelegramMessageId(). This enables:
- Future
telegram_reply_tocalls referencing the bot’s own messages - Reaction handling on the bot’s messages (to determine
whosein the synthetic reaction message)
Message IDs appear as [tg:12345] prefixes in conversation history replay.
Standalone Telegram Mode
Section titled “Standalone Telegram Mode”src/telegram/index.ts provides a lightweight entry point that runs only the Telegram bot without the scheduler. It runs migrations, creates the database, and starts long polling.
Error Handling
Section titled “Error Handling”- Grammy errors are caught by
bot.catch()and logged - Individual message processing errors reply with “Something went wrong. Check the logs.”
- Reaction processing errors are logged but do not send error messages to the user