#13278: feat(telegram): full sticker support — thumbnail fallback, set indexing, vision, cache
channel: telegram
agents
size: XL
## Summary
Unlock the existing sticker pipeline for **all** sticker types (video, animated, static). Currently, `resolveMedia()` drops `is_animated` (TGS) and `is_video` (WEBM) stickers — which covers 90%+ of modern Telegram sticker packs. The cache stays empty, `sticker-search` returns nothing, and agents can't build sticker vocabulary.
### What this PR does
- **Thumbnail fallback** — download `sticker.thumbnail` (static WEBP/JPEG ~320px, present on all types) instead of the full WEBM/TGS. Enables vision description for video and animated stickers.
- **Sticker set indexing** — on first encounter with a sticker, pre-index the entire set (configurable limit, default 20) via fire-and-forget. Builds vocabulary exponentially faster.
- **Cache TTL & eviction** — configurable TTL (default 90 days) and max entries (default 5000) with LRU eviction.
- **Custom emoji vision** — resolve `custom_emoji` entities in messages via vision API, annotate text with `[emoji: description]`. Gated behind `customEmojiVision` (default: false).
- **Vision model resolution** — remove hardcoded `VISION_PROVIDERS` list. New order: explicit `stickerVisionModel` config → agent's default model → catalog scan → auto-resolver.
- **Sticker action hints** — add per-action usage hints to `message` tool description so agents understand `sticker-search` and `sticker` workflows.
- **ReplyPayload stickerId** — extend `ReplyPayload` with optional `stickerId` for native sticker delivery.
### Config options (all in `channels.telegram`)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `stickerVisionModel` | `string` | agent's model | Explicit vision model (`provider/model`) |
| `stickerSetIndexing` | `boolean` | `true` | Pre-index sticker sets on first encounter |
| `stickerSetIndexLimit` | `number` | `20` | Max stickers to describe per set |
| `stickerCacheTtlDays` | `number` | `90` | Cache entry TTL in days |
| `stickerCacheMaxEntries` | `number` | `5000` | Max cache entries (LRU eviction) |
| `customEmojiVision` | `boolean` | `false` | Enable vision for custom emoji |
### Files changed
| Area | Files | Change |
|------|-------|--------|
| Core | `sticker-cache.ts` | Set indexing, cache eviction, vision model resolution |
| Core | `bot/delivery.ts` | Thumbnail fallback for video/animated stickers |
| Core | `bot-message-dispatch.ts` | Wire visionModel through dispatch, trigger set indexing |
| New | `custom-emoji.ts` | Custom emoji resolution via vision API |
| Schema | `types.telegram.ts`, `zod-schema.providers-core.ts` | Config types + Zod validation |
| UX | `message-tool.ts` | ACTION_HINTS for sticker/sticker-search |
| Types | `bot/types.ts`, `auto-reply/types.ts` | isVideo/isAnimated, stickerId |
## Test plan
- [x] 47 test cases: thumbnail fallback, set indexing, cache eviction, custom emoji, sticker search
- [x] Build passes (`npm run build`)
- [x] Manual: send video sticker to bot → vision + cache → sticker-search returns it → agent sends it back
- [ ] CI
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR expands Telegram sticker support to cover animated/video stickers by downloading static thumbnails for vision, adds a sticker cache with TTL/max-entry eviction, and introduces optional custom emoji vision annotation. It also adds fire-and-forget sticker set indexing to build sticker vocabulary faster, threads a `stickerVisionModel` override into dispatch, and extends reply payloads with `stickerId` so agents can send cached stickers back natively.
Key integration points are `src/telegram/bot/delivery.ts` (sticker media resolution + thumbnail fallback), `src/telegram/bot-message-dispatch.ts` (vision description + caching + set indexing + emoji annotation), and `src/telegram/sticker-cache.ts` (persistence + eviction + model resolution + set indexing).
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable but has correctness issues in the sticker pipeline around cached/pathless cases.
- Core functionality is well-scoped and tests were added, but current gating/return-shape in sticker media resolution and dispatch can cause cached stickers to be skipped (especially when thumbnail download fails) and can prevent cached descriptions from being applied for non-vision models. These are user-visible behavior regressions for sticker handling in certain scenarios.
- src/telegram/bot/delivery.ts, src/telegram/bot-message-dispatch.ts
<!-- greptile_other_comments_section -->
<sub>(5/5) You can turn off certain types of comments like style [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
---
## Validation
- [x] `pnpm build` — passes
- [x] `pnpm check` — passes
- [x] `pnpm test` — 47 test cases pass
## Contribution checklist
- [x] **Focused scope**: Full sticker support (thumbnail fallback, set indexing, vision, cache)
- [x] **What + why**: described above
- [x] **AI-assisted**: Yes, Claude Code was used for implementation and iterative development. Testing level: fully tested (47 tests + manual)
Most Similar PRs
#6516: feat(telegram): add sticker and custom emoji vision support
by Shabablinchikow · 2026-02-01
85.4%
#19056: feat(inbound-meta): expose sticker metadata in system prompt
by xuandung38 · 2026-02-17
82.2%
#7502: feat(whatsapp): send WebP files as stickers
by giannisanni · 2026-02-02
77.8%
#19592: feat(whatsapp): add sendAsSticker support
by qualiobra · 2026-02-18
76.3%
#8479: feat(telegram): extract animation/GIF metadata & support animated/v...
by anon019 · 2026-02-04
76.2%
#7980: feat(telegram): multi-stage reaction system for message pipeline vi...
by macmimi23 · 2026-02-03
73.6%
#23572: feat(voice): enable voice note conversation loop for Telegram and W...
by davidrudduck · 2026-02-22
71.4%
#21346: [AI-assisted] Telegram: add reaction state machine with fallback an...
by Archie818 · 2026-02-19
70.8%
#14367: feat(telegram): add message read via inbound message store
by michaelquinlan88 · 2026-02-12
70.6%
#3174: Telegram: restore types after removing ts-nocheck
by yashgo0018 · 2026-01-28
70.2%