opencode-config/docs/decisions/073-pr-174-telegram-send-tool.md
Sergey 1bc8b8ed05
feat(telegram): add telegram-send plugin-tool for Bot API messaging (#174)
* feat(telegram): add CLI project with Bot API client

* feat(telegram): add telegram-send plugin-tool

* test(telegram): add unit tests for markdown, config, api

* chore(env): add TELEGRAM_CHAT_ID to .env.example

* docs(handoff): add handoff and ADR for telegram-send

* docs(handoff): set PR number 174

* docs: update project map for telegram-send tool

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-31 20:42:33 +03:00

5.1 KiB
Raw Permalink Blame History

ADR-073: telegram-send plugin-tool — Bot API messaging via CLI

Статус

Accepted (PR #174)

Контекст

Оркестратору нужен быстрый способ показывать пользователю результаты работы — сгенерированный draw-image PNG, .md-файл с архитектурой, текстовый вывод. Существующий способ показать файл — tunnel skill (запуск процесса + проброс порта через Cloudflare), что тяжело для разовой отправки и требует живого процесса.

В репо уже есть паттерн plugin-tool + CLI-проекта на примере draw-image: тонкая обёртка .opencode/tools/draw-image.ts дёргает CLI из .opencode/draw-image/cli.ts через spawnSync("node", ["--experimental-strip-types", cliPath, ...args]). Plugin-tools авто-дискаверятся opencode из папки .opencode/tools/ — без записи в opencode.json, без MCP-сервера.

Решение

Создан telegram-send plugin-tool + CLI-проект .opencode/telegram/ по паттерну draw-image:

  1. Plugin-tool, не MCP-сервер.opencode/tools/telegram-send.ts авто-дискаверится opencode; spawnSync запускает CLI как дочерний процесс; результат парсится из stdout; ошибка → ⚠️ telegram-send failed (exit N): <stderr>. Не требует записи в opencode.json, не требует long-running процесса.

  2. Без runtime-зависимостейpackage.json имеет только devDeps (@types/node, typescript, vitest). Рантайм использует встроенный fetch/FormData/Blob/File Node 22+. Это упрощает установку (только npm install для devDeps) и аудит.

  3. Один tool с параметром actionaction: "text" | "document" | "photo" вместо трёх отдельных tools. Причины: единый контрак ({ ok, message_id, chat_id }), один набор env-настроек, меньше шума в списке tools оркестратора. Параметры path/caption/text условно-обязательные (зависят от action).

  4. CLI как тонкий слой над api.tscli.ts парсит argv, применяет parse_mode по умолчанию ("MarkdownV2"), диспетчеризует в sendMessage/sendDocument/sendPhoto. api.ts — низкоуровневый, не применяет дефолты (добавляет parse_mode в body только при явной передаче). Разделение: api.ts тестируется изолированно, cli.ts тестирует дефолты.

  5. lib: ["ES2022", "DOM"] в tsconfig — DOM-типы нужны только для fetch/BodyInit/Blob/FormData/File (все используются в src/api.ts). Рантайм-значения берутся из Node 22+ глобалов, не из браузера. allowImportingTsExtensions: true — сознательный паттерн (как draw-image) для node --experimental-strip-types.

Альтернативы

  1. MCP-сервер для Telegram — отвергнуто: требует long-running процесса, записи в opencode.json, сложнее отладка. Plugin-tool проще и следует существующему паттерну draw-image.

  2. Прямой fetch из plugin-tool (без CLI) — отвергнуто: plugin-tool выполняется в Bun plugin-host (import.meta.dir доступен), но fetch там может вести себя иначе; CLI-процесс на Node 22+ даёт предсказуемый рантайм с встроенным fetch. Плюс CLI тестируется изолированно (npm test).

  3. Внешние зависимости (node-telegram-bot-api, telegraf) — отвергнуто: добавляют audit-поверхность, усложняют установку, избыточны для 3 endpoint'ов (sendMessage, sendDocument, sendPhoto). Встроенный fetch + FormData достаточны.

  4. Три отдельных tools (telegram-send-text, telegram-send-document, telegram-send-photo) — отвергнуто: засоряет список tools, дублирует env-логику и контрак. Один tool с action-enum чище.

  5. Markdown → MarkdownV2 авто-конвертер — отвергнуто (out-of-scope): пользователь сам экранирует через escapeMarkdownV2 (экспортируется из src/markdown.ts) или использует parse_mode: "plain"/"HTML". Конвертер — отдельная нетривиальная задача.