# Add @suaveplan/telegram-client **Package**: `@suaveplan/telegram-client` **Status**: PROPOSED **Type**: new-package ## Why The genesis monorepo lacks a Telegram Bot API client. The upstream `@suaveplan/telegram-client` at `../suaveplan/packages/universal/telegram-client/` provides a small, runtime-agnostic client used for transactional bot messaging in production but has zero deterministic-clock / no-mock test discipline, no injectable fetch, no OTel surface, and no Zod boundary. This change ports the upstream functional surface 1:1, then rewrites it to the genesis quality bar (injectable `Clock` + fetch, Zod-validated config, real-fetch test discipline, `SuaveplanError` extension, OTel counters/histograms, 100% istanbul coverage) so downstream `@suaveplan/notifications-*` and ops alerting packages have a production-grade Telegram channel. ## What changes Additive — new package at `packages/messaging/telegram-client/`. - ADDED: `TelegramBotClient` class — Bot API client wrapping `sendMessage`, `editMessage`, `deleteMessage`, `sendMedia`, `getChat`, `getMe` over native `fetch` with `AbortController` timeouts. - ADDED: `createTelegramBotClient` factory — preferred over `new`, accepts injectable `fetch` and `clock`. - ADDED: `TelegramClientError` extends `SuaveplanError`. - ADDED: `TELEGRAM_CLIENT_CODES` registry — `API_ERROR`, `AUTH_ERROR`, `RATE_LIMITED`, `CHAT_NOT_FOUND`, `BOT_BLOCKED`, `CONFIG_MISSING`, `TIMEOUT`, `FORBIDDEN`. - ADDED: `TelegramBotConfigSchema` Zod schema for `{ token, baseUrl?, timeoutMs? }`. - ADDED: types `TelegramBotConfig`, `TelegramMessage`, `TelegramReplyMarkup`, `TelegramInlineButton`, `TelegramKeyboardButton`, `TelegramResponse`, `TelegramChat`, `TelegramUser`, `TelegramSentMessage`, `TelegramDeliveryReport`, `TelegramDeliveryStatus`, `TelegramMediaType`. - ADDED: `buildSendMessageBody`, `buildReplyMarkup`, `parseRetryAfterMs`, `classifyStatus` — pure helpers (exported for testability). - ADDED: OTel counter `telegram_client.request.total` and histogram `telegram_client.request.duration_ms`. - ADDED: subpath exports `./errors`, `./types`. ## Out of scope - Long-polling `getUpdates` / webhook update receivers — upstream does not implement them. - Bot-on-bot orchestration, conversational state — not in upstream. - File upload via multipart (only URL-based media) — upstream is URL-only. ## Source mapping This change derives from `../suaveplan/enterprise/telegram-client.md` AND the upstream functional surface at `../suaveplan/packages/universal/telegram-client/src/`. | Requirement | Source lines / files | Notes | |---|---|---| | Send text messages with full options | `src/core/telegram-bot-client/telegram-bot-client.ts:50-53,287-339` | verbatim API surface (`sendMessage`, `buildSendMessageBody`, `buildReplyMarkup`) | | Edit / delete messages | `src/core/telegram-bot-client/telegram-bot-client.ts:56-80` | verbatim | | Send media (photo/document/video/audio/voice/animation) | `src/core/telegram-bot-client/telegram-bot-client.ts:83-101` | verbatim method map | | Get chat info and bot info | `src/core/telegram-bot-client/telegram-bot-client.ts:104-111` | verbatim | | Authenticated POST request envelope with AbortController timeout | `src/core/telegram-bot-client/telegram-bot-client.ts:113-163` | verbatim | | Retry-after parsing (body / headers fallback) | `src/core/telegram-bot-client/telegram-bot-client.ts:179-215` | verbatim | | Typed error classification by HTTP status + Telegram description | `src/core/telegram-bot-client/telegram-bot-client.ts:218-284` | verbatim | | Stable error code registry with metadata | `src/error-codes.ts:18-117` | reworded — genesis uses `defineCodes("TELEGRAM_CLIENT", […])` + `SuaveplanError` extension, but the same 8 codes | | Config token required (throw on missing) | `src/core/telegram-bot-client/telegram-bot-client.ts:36-47` | verbatim | | Default base URL `https://api.telegram.org` and 10s timeout | `src/core/telegram-bot-client/telegram-bot-client.ts:21-22` | verbatim constants | | Zero browser / React deps | `enterprise/telegram-client.md` L37 | cross-cutting bar | | Zod schemas on public types | `enterprise/telegram-client.md` L38 | cross-cutting bar | | Deterministic — injectable clock | `enterprise/telegram-client.md` L40 | cross-cutting bar | | OTel instrumentation | `enterprise/telegram-client.md` L41 | cross-cutting bar | | Structured errors extending shared base | `enterprise/telegram-client.md` L42 | cross-cutting bar | | `AbortSignal` everywhere async | `enterprise/telegram-client.md` L43 | upstream uses internal `AbortController` for timeout; genesis preserves this | | No mocks — injectable fetch | `enterprise/telegram-client.md` L53 + `CLAUDE.md` rule 2 | cross-cutting bar | | `sideEffects: false`, `workspace:^` deps, Bun catalog externals | `enterprise/telegram-client.md` L55-L57 | cross-cutting bar | ### Requirements with no source line | Requirement | Justification | |---|---| | 100 % istanbul coverage on touched modules | Genesis-wide policy (`CLAUDE.md` §Quality). | | Test files import only from `@suaveplan/testing/runner` | Genesis-wide policy (`CLAUDE.md` rule 11). | | Sibling `.md` doc per impl file ≥200 words | Genesis-wide policy (`CLAUDE.md` §Documentation). | ## Verification approach - `src/core/telegram-bot-client/telegram-bot-client.test.ts` — every Bot API method, every error classification branch, retry-after parsing, custom base URL, config validation, OTel emission. - `src/utils/build-bodies.test.ts` — pure body / reply-markup builders across every option permutation. - `src/utils/classify-error.test.ts` — pure status classifier and retry-after parser, including header fallbacks. - `src/errors/telegram-client-error.test.ts` — error class shape, code registry, throwability. - `src/types/telegram-bot-config.test.ts` — Zod schema accept / reject. - `tests/dep-graph.test.ts` — runtime deps locked. All tests use an injectable `fetch` that returns canned `Response` objects (`Response.json(...)`) — no network, no `vi.mock`, no mocks of any kind. The OTel test asserts against a real in-memory `MeterProvider`. ## Migration impact None — additive only. No existing consumer in genesis imports `@suaveplan/telegram-client`.