anthropics / anthropics/claude-plugins-official
feat(telegram): native streaming via sendMessageDraft (Bot API 9.3+)
- 主要言語
- Python
- スター
- 36.3k
- フォーク
- 4.1k
- 平均マージ
- 2日 14時間
- マージ済み PR(30日)
- 539
説明
## Summary
The Telegram plugin currently delivers model output via `bot.api.sendMessage` (and, for long-running tasks, `editMessageText` to edit a placeholder in place). That works, but it's the pre-2026 pattern: every interim update is a real `editMessageText` round-trip against Bot API global rate limits (1 msg/sec per chat, 30 msg/sec total), and the UX is choppy compared to ChatGPT/Claude.ai web, where text streams character-by-character as the model generates.
Telegram's Bot API added native streaming over the last six months specifically for AI-bot use cases. The plugin should adopt it so Claude-in-Telegram feels as live as Claude-in-the-web-app, without paying for it in 429s.
## Telegram Bot API timeline (relevant changelog entries)
| Date | Version | Change |
|---|---|---|
| 2025-12-31 | 9.3 | Introduced `sendMessageDraft` — partial-message streaming during generation. Initially gated to bots in the Telegram Business beta. ([changelog](https://core.telegram.org/bots/api#december-31-2025)) |
| 2026-03-01 | 9.5 | `sendMessageDraft` opened to **all** bots, no allow-list. ([changelog](https://core.telegram.org/bots/api#march-1-2026)) |
| 2026-05-08 | 10.0 | `sendMessageDraft` accepts an empty `text` — can be sent as a placeholder before any tokens are generated. ([changelog](https://core.telegram.org/bots/api#may-8-2026)) |
| 2026-06-11 | 10.1 | `sendRichMessageDraft` — streaming variant for rich messages (formatted text, entities, inline keyboards). ([changelog](https://core.telegram.org/bots/api#june-11-2026)) |
Mechanism: the draft is an ephemeral message (TTL ~30s) that the client renders incrementally as the bot calls `sendMessageDraft` with the latest cumulative text. Draft updates do **not** consume the per-chat sendMessage budget. When generation completes, the bot issues a final `sendMessage` (or `sendRichMessage`) for persistence, and the draft is replaced atomically.
## Current implementation
- `server.ts:435` — the `reply` tool schema. No streaming option today; the contract is single-shot text + optional files.
- `server.ts:510-576` — the `reply` handler. Splits text into chunks and calls `await bot.api.sendMessage(...)` per chunk (line 541). Each chunk is a fully-persisted message; long Claude outputs land as 3-5 separate messages in the chat.
- `server.ts:761`, `server.ts:777` — `ctx.editMessageText` for the permission flow. This is where the "edit a placeholder repeatedly" pattern is used today; in practice users (and other plugins built on top of this) extend it for progress updates and hit rate-limit ceilings under load.
- `package.json` — depends on `grammy ^1.21.0`. grammY's auto-generated API surface already exposes `bot.api.sendMessageDraft` and `bot.api.sendRichMessageDraft` (see https://grammy.dev/ref/core/api), so no new transport work is needed — only wiring.
## Proposal
Add streaming as an opt-in to the `reply` tool. Two equivalent shapes:
**Option A — extend `reply` with a `stream` flag:**
```jsonc
{
"name": "reply",
"inputSchema": {
"properties": {
"chat_id": { "type": "string" },
"text": { "type": "string" },
"stream": {
"type": "boolean",
"description": "When true, the message is delivered as a sendMessageDraft stream that updates as text grows, then committed with a final sendMessage. Provides ChatGPT-style live rendering and does not consume the per-chat rate-limit budget."
},
// ... existing fields
}
}
}
```
**Option B — separate `reply_stream` tool.** Clearer contract (caller passes incremental `text` deltas + a final `commit: true`), at the cost of one extra tool entry.
Either way, the server-side flow is:
1. First call → `bot.api.sendMessageDraft(chat_id, text)` returns a `draft_id`.
2. Subsequent calls during the same generation → `bot.api.sendMessageDraft(chat_id, text, { draft_id })` with cumulative text. No rate-limit budget consumed.
3. Final call → `bot.api.sendMessage(chat_id, text, { ...reply_parameters, parse_mode })` to persist. Draft is auto-replaced by the client.
If the draft expires mid-generation (~30s TTL with no update), fall back to the current chunked `sendMessage` path so the message is never lost.
## Compatibility
Fully backward compatible. Callers that don't pass `stream: true` (or don't use the new tool) hit the existing `sendMessage` path unchanged. The `format`, `reply_to`, `files`, and chunking semantics in `server.ts:510-576` stay as-is. grammY version bump may be needed only if a future grammY release renames the method — current `^1.21.0` resolves to a version that already exposes both draft methods.
## Why this matters
The Telegram plugin is the primary "Claude on my phone" path for a lot of users. Right now a 30-second Claude response feels like staring at a "Typing..." indicator and then receiving a wall of text. With `sendMessageDraft` it feels like the same Claude session you have in the browser — tokens flow in, and the message commits when generation ends. That's the difference between "useful when I'm at a desk" and "useful as a daily driver from a phone."
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
Start in server.ts:435 and server.ts:510-576, then inspect package.json and grammY's sendMessageDraft API. Determine which proposed reply contract fits the existing tool lifecycle while preserving the non-streaming path. Done means opted-in streaming updates cumulative text, commits the final output, and retains format, reply_to, files, chunking, and expiry fallback behavior.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- typescript
- 領域
- api, backend
- issue の種類
- 機能追加
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 48/100