4.3 KiB
Telegram Bridge Architecture
Overview
pi-telegram is a session-local pi extension that binds one Telegram DM to one running pi session. The bridge owns four main responsibilities:
- Poll Telegram updates and enforce single-user pairing
- Translate Telegram messages and media into pi inputs
- Stream and deliver pi responses back to Telegram
- Manage Telegram-specific controls such as queue reactions,
/status,/model, and/compact
Runtime Structure
The implementation currently lives in index.ts and is organized by logical sections rather than physical modules.
Main runtime areas:
- Telegram API types and local bridge state
- Generic utilities and Markdown/rendering helpers
- Message delivery, previews, and attachment sending
- Interactive model/status menu state and callback handling
- Queue management for pending and active Telegram turns
- Polling loop and pi lifecycle-hook integration
Message And Queue Flow
Inbound Path
- Telegram updates are polled through
getUpdates - The bridge filters to the paired private user
- Media groups are coalesced into a single Telegram turn when needed
- Files are downloaded into
~/.pi/agent/tmp/telegram - A
PendingTelegramTurnis created and queued locally - The queue dispatcher sends the turn into pi only when dispatch is safe
Queue Safety Model
The bridge keeps its own Telegram queue and does not rely only on pi's internal pending-message state.
Dispatch is gated by:
- No active Telegram turn
- No pending Telegram dispatch already sent to pi
- No compaction in progress
ctx.isIdle()being truectx.hasPendingMessages()being false
This prevents queue races around rapid follow-ups, /compact, and mixed local plus Telegram activity.
Abort Behavior
When /stop aborts an active Telegram turn, queued follow-up Telegram messages can be preserved as prior-user history for the next turn. This keeps later Telegram input from being silently dropped after an interrupted run.
Rendering Model
Telegram replies are rendered as Telegram HTML rather than raw Markdown.
Key rules:
- Rich text should render cleanly in Telegram chats
- Real code blocks must remain literal and escaped
- Long replies must be split below Telegram's 4096-character limit
- Chunking should avoid breaking HTML structure where possible
- Preview rendering is intentionally simpler than final rich rendering
The renderer is a Telegram-specific formatter, not a general Markdown engine, so rendering changes should be treated as regression-prone.
Streaming And Delivery
During generation, the bridge streams previews back to Telegram.
Preferred order:
- Try
sendMessageDraft - Fall back to
sendMessagepluseditMessageText - Replace the preview with the final rendered reply when generation ends
Outbound files are sent only after the active Telegram turn completes and must be staged through the telegram_attach tool.
Interactive Controls
The bridge exposes Telegram-side session controls in addition to regular chat forwarding.
Current operator controls include:
/statusfor model, usage, cost, and context visibility- Inline status buttons for model and thinking adjustments
/modelfor interactive model selection, including in-flight restart of the active Telegram-owned run on a newly selected model/compactfor Telegram-triggered pi session compaction when the bridge is idle- Queue reactions using
👍and👎
In-Flight Model Switching
When /model is used during an active Telegram-owned run, the bridge can emulate the interactive pi workflow of stopping, switching model, and continuing.
The current implementation does this by:
- Applying the newly selected model immediately
- Queuing or staging a synthetic Telegram continuation turn
- Aborting the active Telegram turn immediately, or delaying the abort until the current tool finishes when a tool call is in flight
- Dispatching the continuation turn after the abort completes
This behavior is intentionally limited to runs currently owned by the Telegram bridge. If pi is busy with non-Telegram work, the bridge still refuses the switch instead of hijacking unrelated session activity.