DEV Community

Mukunda Rao Katta
Mukunda Rao Katta

Posted on

bitte-telegram-launcher: Turn Any Bitte Agent into a Telegram Bot

I had a Bitte agent working in the NEAR ecosystem. It could check wallet balances, look up transaction history, and explain what a given smart contract did. The problem was the interface: to use it you had to know about Bitte, have the right context, and interact through the Bitte-specific UI. Everyone I showed it to said the same thing: "Can I just talk to it on Telegram?"

Telegram is where people actually are. For a lot of users, especially in the crypto and DeFi world, Telegram is the default async communication layer. Bots are a normal thing there. You search for a bot, you start chatting, you get answers. The user does not need to know that behind the bot is a Bitte agent running on NEAR infrastructure. They just want to ask questions and get answers in the place they already use.

Connecting Bitte to Telegram is not complicated in concept, but the wiring has a few parts: you need a Telegram webhook or polling loop, you need to forward messages to the Bitte agent API, and you need to handle sessions so the agent can maintain context across messages in a conversation. bitte-telegram-launcher puts those three pieces together in one class that takes your credentials and starts the bot.

Shape of the fix

import { BitteTegramLauncher } from "bitte-telegram-launcher";

const launcher = new BitteTegramLauncher({
  telegramToken: process.env.TELEGRAM_TOKEN!,
  bitteApiKey: process.env.BITTE_API_KEY!,
  agentId: "my-bitte-agent.near",
});

// Start polling Telegram and forwarding to Bitte
await launcher.start();

// Bot is now live. Graceful shutdown:
await launcher.stop();

// With optional config:
const launcher = new BitteTegramLauncher({
  telegramToken: process.env.TELEGRAM_TOKEN!,
  bitteApiKey: process.env.BITTE_API_KEY!,
  agentId: "my-bitte-agent.near",
  sessionTtlMs: 30 * 60 * 1000, // 30 min session timeout, default is 60 min
  maxSessionMessages: 50,         // drop oldest when window exceeds this, default 20
  parseMode: "Markdown",          // Telegram message parse mode, default "Markdown"
  onError: (err, ctx) => {
    console.error("Bot error:", err.message, "chat:", ctx?.chat?.id);
  },
});

// Query session state for a chat (useful for debugging):
const session = launcher.getSession(chatId);
console.log(`Messages in session: ${session?.messages.length}`);

// Clear a session manually (e.g. when user types /reset):
launcher.clearSession(chatId);
Enter fullscreen mode Exit fullscreen mode

Each Telegram chat gets its own session. Messages accumulate in that session up to maxSessionMessages. When a new message arrives, the full session history is sent to the Bitte agent along with the new user message. The agent's reply is sent back to Telegram. Sessions expire after sessionTtlMs of inactivity.

What it does NOT do

It does not handle webhook mode: it uses Telegram's long-polling API. Webhook mode requires a public HTTPS endpoint and TLS certificate management. Long-polling works from a local machine or a simple server without those requirements. If you need webhook mode for production scale, you can wrap this package's message handling logic yourself against the Telegram Bot API, but the launcher itself does not expose a webhook server. It does not persist sessions across process restarts. Sessions are in-memory only. If the process restarts, conversations start fresh. For persistent sessions, store the session state externally and restore it on startup. It does not implement command routing: if you want /balance, /history, and /help as distinct commands with different handlers, that routing lives in your code. The launcher passes all messages, including command messages, to the Bitte agent as plain text.

Inside the lib

The Telegram polling loop calls getUpdates with a timeout of 30 seconds and an offset that advances after each batch of updates. This is standard Telegram long-polling. Errors in getUpdates (network issues, timeout) are caught and logged, and the loop continues. The loop runs in a while loop controlled by a flag that stop() sets to false.

Session management is a Map<number, Session> keyed by Telegram chat ID. Each session holds the message history and a last-active timestamp. On each incoming message, the session is looked up or created, the message is appended, and the session's last-active timestamp is updated. A background setInterval timer runs every minute and evicts sessions whose last-active timestamp is older than sessionTtlMs. This means sessions do not accumulate unbounded in long-running bots.

The Bitte API call takes the agent ID, the API key, and the full message history in Bitte's format. The agent response is extracted from the API response and sent to Telegram using sendMessage. Both the Bitte API call and the Telegram send are wrapped in try/catch. If either fails, the error is passed to onError if provided, otherwise it is logged to stderr. The bot continues handling other chats even if one chat's message fails.

44 tests cover: basic message routing from Telegram to Bitte and back, session creation on first message, session expiry eviction, session accumulation up to max window size, clearSession behavior, getSession read, polling loop start and stop, error handler invocation on Bitte API failure, error handler invocation on Telegram send failure, and onError fallback to stderr when no handler is provided.

When useful

  • NEAR or Bitte ecosystem agents that you want to expose to users who live in Telegram
  • Demos and prototypes: start the bot locally with ts-node and share a Telegram bot link, no deployment needed
  • Community bots in NEAR project Telegram groups where members can ask the agent questions without leaving Telegram
  • Development and testing: it is easier to iterate on agent behavior by chatting in Telegram than by writing test scripts

When not useful

  • High-throughput production bots: long-polling has latency and polling overhead that webhook mode avoids
  • Bots that need persistent sessions across restarts: add your own session persistence layer around the in-memory store
  • Complex command routing with multiple distinct handlers: this is a single-handler bot that passes everything to Bitte
  • Providers other than Bitte: this launcher is specific to the Bitte agent protocol on NEAR

Install

npm install bitte-telegram-launcher
Enter fullscreen mode Exit fullscreen mode

TypeScript-first. Requires Node 18+. Peer dependencies: none forced at runtime. You supply your own TELEGRAM_TOKEN and BITTE_API_KEY via environment variables or config.

Siblings

Library Language What it does
agentguard TypeScript/npm Egress allowlist for agent tool calls, pairs well with any agent stack
agentsnap TypeScript/npm Capture and replay agent traces for debugging
agentcast TypeScript/npm Structured output enforcer for agent responses
agentvet TypeScript/npm Validate tool call shape before execution

What's next

The two most-requested additions are webhook mode and persistent session storage. Webhook mode would make production deployments simpler: one HTTPS endpoint, no polling overhead, no long-open connections. The launcher already separates the message-handling logic from the polling loop, so adding a webhook path is a matter of wiring an HTTP server to call the same handler. Persistent sessions via a pluggable storage interface (memory, Redis, SQLite) would let conversations survive process restarts without the user having to start the conversation over. Both are clean additions that do not require changing the existing public API. If you need either of those now, open an issue and describe your deployment setup.


Part of the Hermes Agent Challenge sprint. Source at github.com/MukundaKatta/bitte-telegram-launcher. Built for the NEAR Agents Without Masters Bitte sub-bounty.

Top comments (0)