feat(telegram): voice tool sends spoken replies as Telegram voice notes

OpenAI gpt-4o-mini-tts (female voice nova by default; TTS_MODEL/TTS_VOICE) renders OGG/Opus,
sent with sendVoice in order with rendered output. Up to 4500 characters per call, split
into 1500-character parts at paragraph/sentence/word boundaries; all parts are synthesized
before any is sent, and API errors come back to the agent as text with the key hidden.
On whenever OPENAI_API_KEY is set.
This commit is contained in:
EugeneTes
2026-10-09 10:09:46 +00:00
parent 923a6e04ac
commit 87240238cc
19 changed files with 551 additions and 13 deletions

View File

@@ -74,8 +74,10 @@ The compose file maps them into the container.
| `TELEGRAM_MODEL` | `claude-code/claude-opus-5-5` | Model every chat uses; compose passes it as `Telegram__Model` | Yes, as `Telegram__Model` |
| `TZ` | `UTC` | The container's time zone | The agent's `bash` gets the chat's zone as `TZ` instead |
| `LOG_LEVEL` | `Information` | Passed as `Logging__LogLevel__Default` | Yes |
| `OPENAI_API_KEY` | — | Turns on speech to text for voice notes | No, and redacted |
| `OPENAI_API_KEY` | — | Turns on speech to text for voice notes and the `voice` tool | No, and redacted |
| `STT_MODEL` | `gpt-4o-transcribe` | Transcription model | Yes |
| `TTS_MODEL` | `gpt-4o-mini-tts` | Speech model of the `voice` tool | Yes |
| `TTS_VOICE` | `nova` | Default voice of the `voice` tool | Yes |
| `OPENROUTER_API_KEY` | — | Turns on group watching with topic triggers, classified by Jev through OpenRouter ([telegram-bot.md](telegram-bot.md#groups-and-triggers)) | No, and redacted |
| `TRIGGER_MODEL` | `typesafe/jev-1.13` | The Jev model triggers use | Yes |
| `GOOGLE_CLIENT_ID` | — | With the secret, turns on Google Calendar ([google-calendar.md](google-calendar.md)) | Yes |

View File

@@ -42,9 +42,12 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
| `Scheduler:DefaultScriptTimeout` | `00:05:00` | Watcher timeout when a job sets none (1 s to 1 h) |
| `Scheduler:MaxJobsPerTarget` | `50` | Jobs per chat |
| `Scheduler:Shell` | `/bin/sh -c` (`cmd.exe /d /s /c` on Windows) | The watcher shell as a list, command appended; same as `bash` by default |
| `OPENAI_API_KEY` / `Telegram:Speech:ApiKey` | — | Turns voice transcription on. Hidden from `bash` and redacted from tool output |
| `OPENAI_API_KEY` / `Telegram:Speech:ApiKey` | — | Turns voice transcription and the `voice` tool on. Hidden from `bash` and redacted from tool output |
| `STT_MODEL` / `Telegram:Speech:Model` | `gpt-4o-transcribe` | `gpt-4o-mini-transcribe` and `whisper-1` also work |
| `Telegram:Speech:BaseUrl` | `https://api.openai.com/v1` | Any OpenAI-compatible transcription endpoint |
| `TTS_MODEL` / `Telegram:Speech:TtsModel` | `gpt-4o-mini-tts` | Speech model of the `voice` tool; `tts-1` and `tts-1-hd` also work (they ignore `instructions`) |
| `TTS_VOICE` / `Telegram:Speech:TtsVoice` | `nova` | Default voice of the `voice` tool |
| `Telegram:Speech:TtsInstructions` | — | Default delivery instructions (tone, pace) for the `voice` tool |
| `Telegram:Speech:BaseUrl` | `https://api.openai.com/v1` | Any OpenAI-compatible audio endpoint (transcription and speech) |
| `Telegram:Speech:Language` | — | Optional ISO-639-1 hint, e.g. `de` |
| `OPENROUTER_API_KEY` / `Telegram:Triggers:ApiKey` | — | Turns on [groups and triggers](#groups-and-triggers). Hidden from `bash` and redacted from tool output |
| `TRIGGER_MODEL` / `Telegram:Triggers:Model` | `typesafe/jev-1.13` | The Jev model that classifies group messages |
@@ -154,6 +157,12 @@ question in that chat (for example from an extension) replaces the pending promp
line). Without an API key the bot replies with how to set one up (`OPENAI_API_KEY`). A transcription is tried up
to 3 times (1 s, then 2 s apart) when the request fails on the network, times out, or gets HTTP 408, 429 or 5xx;
other errors (a bad key, a rejected file) fail at once.
- **Voice replies.** With an OpenAI key every chat's agent has a `voice` tool (`text`, optional `voice` and
`instructions`): it speaks the text with `gpt-4o-mini-tts` (voice `nova` by default) as OGG/Opus and sends it with
`sendVoice`, so it shows as a voice message, in order with the other replies. One call takes up to 4500 characters;
text over 1500 characters is split at paragraph, sentence or word boundaries into several voice messages. Longer
text is refused. Every part is synthesized before the first is sent, so an API error (returned to the agent as text,
key hidden) never leaves half a message; synthesis is retried like transcription.
- Telegram lets bots download at most 20 MB. A bigger file is refused with a note, and nothing reaches the agent.
- A message with an attachment is never a command and never answers a pending question: its caption is prompt text.
- Anything else (stickers, video notes, locations, polls) gets `I can read text, photos, files and voice messages.`