Codumentor logo Codumentor

Telegram Plugin

Connects Codumentor to a Telegram bot so people can talk to the agent from their phone or desktop — including voice:

Speech-to-text and text-to-speech come from the shared Speech service (config.speech), which uses OpenAI-compatible audio APIs.

The plugin long-polls Telegram (getUpdates) — no public URL or webhook is required.

Two modes

Chosen by mode:

ModeBotTurns run asToken fromBest for
technical_userone shared bota single service usertelegram_bot_token_global user secret (stored under the service user), or the bot_token_env env var as fallbacka team/instance bot
per_userone bot per userthe bot's ownereach user's telegram_bot_token user secreteveryone brings their own private bot

Both modes resolve the bot token through User Secrets. The Settings → Extensions → User Secrets pane lists the secret as needed by Telegram. technical_user also accepts the legacy bot_token_env environment variable when the secret is unset.

Load user_secrets alongside this plugin. The env-var fallback for technical_user still works if user_secrets is absent.

Quick start (technical_user)

# codumentor.yaml
speech:
  enabled: true
  api_key: "${OPENAI_API_KEY}"          # voice; omit to run text-only

plugins:
  - module: codumentor.plugins.telegram
    class: TelegramPlugin
    args:
      enabled: true
      mode: technical_user
      service_user_id: telegram-bot       # the shared token is this user's secret
      allowed_user_ids: [123456789]       # who may talk to the bot (your Telegram id)

Provide the bot token one of two ways:

export TELEGRAM_BOT_TOKEN="123456789:AA..."   # only if not using the secret
export OPENAI_API_KEY="sk-..."                # only needed for voice

If neither the telegram_bot_token_global secret nor bot_token_env resolves, the plugin loads in degraded mode — no poller, no crash — and logs a warning.

With allow_all: false (the default) and an empty allowed_user_ids, the bot rejects everyone. Add your numeric Telegram id (ask @userinfobot) before the bot will respond.

Configuration — technical_user mode

plugins:
  - module: codumentor.plugins.telegram
    class: TelegramPlugin
    priority: 50
    args:
      enabled: true
      mode: technical_user
      global_token_secret_name: telegram_bot_token_global  # user-secret holding the shared token
      bot_token_env: TELEGRAM_BOT_TOKEN   # env-var fallback when the secret is unset
      service_user_id: telegram-bot       # Codumentor user that owns these conversations (and the token secret)
      allow_all: false
      allowed_user_ids: [123456789, 987654321]
      permission_policy: auto_allow
      target_agents: main
      voice_reply: true                   # honor voice-in → voice-out
      long_poll_timeout: 50
      request_timeout: 70.0

Configuration — per_user mode

Each Codumentor user registers their own bot token. The system polls every configured bot and runs turns as the owning user (their secrets, their repo context). No global token is needed.

plugins:
  - module: codumentor.plugins.telegram
    class: TelegramPlugin
    priority: 50
    args:
      enabled: true
      mode: per_user
      token_secret_name: telegram_bot_token       # user-secret each user stores their token under
      per_user_authz: tofu                         # tofu | allowlist
      per_user_allowed_ids_secret: telegram_allowed_ids   # only for allowlist authz
      bot_discovery: periodic                      # periodic | startup
      reconcile_interval: 60                        # seconds between rescans (periodic)
      voice_reply: true

See Per-user private bot setup for the end-user steps.

Parameters

ParameterTypeDefaultDescription
enabledbooltrueMaster on/off switch.
modestringtechnical_usertechnical_user (one shared bot) or per_user (one bot per user).
global_token_secret_namestringtelegram_bot_token_globaltechnical_user: name of the user secret (stored under service_user_id) holding the shared bot token. Preferred over bot_token_env.
bot_token_envstringTELEGRAM_BOT_TOKENtechnical_user: env var holding the bot token, used only as a fallback when the secret is unset. Only the env-var name is recorded, never the token.
service_user_idstringtelegram-bottechnical_user: Codumentor user that owns Telegram-originated conversations and stores the shared bot-token secret. With auth.provider: none, any string is accepted.
allow_allboolfalsetechnical_user: if true, anyone who finds the bot may use it. Leave false and use allowed_user_ids.
allowed_user_idslist[int][]technical_user: Telegram numeric user-ids allowed to use the bot. Empty + allow_all: false ⇒ nobody is allowed.
token_secret_namestringtelegram_bot_tokenper_user: user-secret name each user stores their bot token under.
per_user_authzstringtofuper_user: who may use a user's bot — tofu or allowlist (see Authorization).
per_user_allowed_ids_secretstringtelegram_allowed_idsper_user/allowlist: user-secret holding the owner's allowed Telegram ids (comma/space separated).
bot_discoverystringperiodicper_user: periodic rescans for added/removed bots every reconcile_interval; startup discovers once.
reconcile_intervalint60per_user/periodic: seconds between rescans.
permission_policystringauto_allowTool-permission policy for turns from this channel. Only auto_allow is available: tool prompts are auto-allowed (global deny rules still apply), and a turn stuck on an approval is auto-approved and resumed.
target_agentsstringmainWhich agent types this applies to: main, subagent, all.
voice_replybooltrueHonor voice-in → voice-out. Requires config.speech enabled; otherwise replies stay text.
long_poll_timeoutint50getUpdates long-poll timeout (seconds).
request_timeoutfloat70.0HTTP timeout (seconds); must exceed long_poll_timeout.

Speech service (STT / TTS)

Voice is a system-level service under the top-level speech: key. It is not Telegram-specific — the same block powers Voice Mode and Read Aloud.

speech:
  enabled: true
  base_url: "https://api.openai.com/v1"   # OpenAI-compatible audio endpoint
  api_key: "${OPENAI_API_KEY}"             # ${ENV_VAR} or ${secret:<name>}
  stt_model: "whisper-1"                   # speech-to-text model
  stt_language: null                       # null = auto-detect
  tts_model: "tts-1"                       # text-to-speech model
  tts_voice: "alloy"                       # alloy | echo | fable | onyx | nova | shimmer
  tts_format: "opus"                       # opus → native Telegram voice note
  tts_speed: 1.0
ParameterDefaultDescription
enabledfalseMaster switch. Even when true, the service is not usable until base_url and api_key resolve.
base_urlhttps://api.openai.com/v1OpenAI-compatible audio endpoint.
api_key${ENV_VAR} or ${secret:<name>}. For a service-account bot, ${OPENAI_API_KEY} is simplest. When set to ${secret:<name>}, Telegram lists that secret in User Secrets as needed by Telegram.
stt_modelwhisper-1Transcription model.
stt_languagenullISO code to force a language, or null to auto-detect.
stt_max_audio_bytes26214400Reject larger uploads before calling the provider (OpenAI caps at 25 MB).
tts_modeltts-1Synthesis model.
tts_voicealloyVoice name.
tts_formatopusOutput format. opus yields Ogg/Opus, which Telegram renders as a native voice note.
tts_speed1.0Playback speed.
tts_max_input_chars4096Truncate longer text before synthesis (the full text is still sent as a message).
read_timeout120.0HTTP read timeout (seconds).
max_retries2Transient-error retries.

If speech.enabled is false or the key/URL don't resolve, the plugin runs text-only: voice messages get "Voice messages aren't enabled for this bot.", and replies are always text.

Speech key as a user secret

The speech api_key is a shared service credential, so an environment variable (${OPENAI_API_KEY}) is the simplest setup.

To keep it out of the environment, set api_key: "${secret:speech_api_key}" (any name). Store that secret under the same account that owns the bot token: service_user_id for technical_user, or each owner for per_user. Mixing a ${secret:...} speech key with a technical_user bot whose token comes from bot_token_env is fine — the two credentials resolve independently.

Per-user private bot setup

In per_user mode each user runs their own bot under their own Codumentor account:

  1. Create a bot: message @BotFather/newbot → follow the prompts → copy the token (123456789:AA...).
  2. Store the token: Settings → Extensions → User Secrets → Add, name it telegram_bot_token (or whatever token_secret_name is set to), paste the token. The pane shows "Telegram Bot Token — needed by Telegram" with set/unset state.
  3. With bot_discovery: periodic (default), the bot starts polling within reconcile_interval seconds — no restart needed. Removing the secret stops it on the next rescan.
  4. First message binds you as the owner (default tofu authorization): open your bot in Telegram, send any message, and you're bound as the only account allowed to use it. Turns then run as your Codumentor user, with your secrets and repo context.

Voice messages

When voice is enabled (config.speech.enabled and voice_reply: true):

  1. A user sends a Telegram voice note (Ogg/Opus).
  2. The plugin transcribes it and prefixes the transcript with [Voice message — transcribed] so the agent knows it was speech.
  3. The agent's final answer is synthesized (tts_format: opus) and sent as a Telegram voice note, followed by the full text.

Text messages are unaffected: text in → text out. Long replies are a poor fit for a single voice note, so the text follow-up is always sent. TTS input is capped at tts_max_input_chars.

Authorization

A Telegram bot is reachable by anyone who discovers its @username, and a turn runs with real agent capabilities (and, in per_user mode, the owner's secrets). Authorization gates who may actually use a bot.

technical_user mode — static allowlist:

per_user mode (per_user_authz):

Find your numeric Telegram id by messaging @userinfobot.

Telegram bot setup

  1. Open @BotFather in Telegram and send /newbot.
  2. Choose a display name and a unique @username ending in bot.
  3. Copy the token BotFather gives you (123456789:AA...).
  4. technical_user: store the token as the telegram_bot_token_global user secret under the service account (or, as a fallback, in the bot_token_env environment variable). per_user: each user stores it as the telegram_bot_token user secret.
  5. (Optional) /setprivacyDisable if you want the bot to see all messages in groups; for 1:1 chats the default is fine.
  6. Open a chat with your bot and send a message. (In per_user/tofu this binds you as owner; in technical_user make sure your id is in allowed_user_ids.)

Behavior

Limitations

See also