Skip to content

feat: render replies as Bot API 10.1 rich messages - #19

Merged
AaronCQL merged 1 commit into
mainfrom
feat/rich-messages
Jun 17, 2026
Merged

feat: render replies as Bot API 10.1 rich messages#19
AaronCQL merged 1 commit into
mainfrom
feat/rich-messages

Conversation

@AaronCQL

Copy link
Copy Markdown
Owner

What

Adopts Telegram Bot API 10.1 rich messages (sendRichMessage) so agent replies render with native tables, headings, LaTeX formulas, real lists, block quotes and horizontal rules, and gain the larger 32768-character limit (vs 4096).

Rich messages take an HTML (or Markdown) string that Telegram parses into blocks server-side - there's no block-tree JSON to build. We send via the html field to keep full escaping control (so the agent's stray </& and dollar amounts in prose stay literal).

How

  • New FormatTelegramRichHTML (internal/formatter_rich.go) - a second goldmark renderer that emits the structural Rich HTML tags instead of downgrading them the way legacy FormatTelegramHTML does (headings→bold, tables→key/value lists, ---→em-dashes). ```math fences become <tg-math-block> formulas. Inline $...$ is deliberately not auto-detected, so currency in prose ($15K, $1.2M) stays literal.
  • Transport (internal/telegram.go) - sendRich / SendRichMessage / EditRichMessage via gotgbot's generic Request escape hatch, since the typed method isn't generated yet (one-line swap once it lands upstream).
  • Reply + status both go rich - sendResult/editResult and the live status bubble (SendStatusMessage/EditStatusMessage) send as rich with a graceful fallback to the legacy chunked-HTML path. A disabled flag, over-length reply, or Telegram rejection degrades cleanly rather than breaking.
  • Status bubble joins lines with <br> (rich HTML collapses literal newlines) and renders streamed intermediate text as escaped inline, so partial mid-stream markdown can't break the parse.
  • Gated by MINICLAW_RICH_MESSAGES (default on), honoured for both the reply and status paths.
  • agent/CLAUDE.md updated to tell the agent it can use tables/headings/math freely.

Testing

  • go build ./..., go vet ./..., go test ./... all green.
  • New formatter_rich_test.go (16 cases) covers headings, aligned tables, math fences, lists/task lists, HTML escaping, and the dollar-amount safety case; status tests updated for the <br> separators.
  • Verified live against real Telegram: tables, headings, LaTeX and lists all render, and the status bubble no longer flips format mid-run.

Notes

  • This reused gotgbot's Request escape hatch deliberately; the typed SendRichMessage will replace it when gotgbot ships 10.1 support.
  • A future cleanup could share the inline AST-walk cases between the legacy and rich formatters (skipped here to avoid restructuring both walks + the table-cell path).

@AaronCQL
AaronCQL force-pushed the feat/rich-messages branch from 65ea65f to 2dbaaf9 Compare June 16, 2026 03:57
Telegram Bot API 10.1 adds "rich messages" (sendRichMessage), which parse
an HTML or Markdown string into native blocks server-side - tables,
headings, LaTeX formulas, real lists, block quotes and horizontal rules -
with a 32768-character limit instead of 4096.

This adds a second formatter, FormatTelegramRichHTML, that emits the
structural Rich HTML tags instead of downgrading them the way the legacy
FormatTelegramHTML does (headings to bold, tables to key/value lists,
hr to em-dashes). ```math fences become <tg-math-block> formulas; inline
$...$ is deliberately not auto-detected so currency in prose stays literal.

Transport uses gotgbot's generic Request escape hatch (sendRich /
EditRichMessage) since the typed method isn't generated yet; swap to the
typed call once it lands upstream. Both the final reply (sendResult /
editResult) and the live status bubble now send as rich messages with a
graceful fallback to the legacy chunked-HTML path, so a disabled flag,
an over-length reply, or a Telegram rejection degrades cleanly rather
than breaking. The status bubble joins lines with <br> (rich HTML
collapses literal newlines) and renders streamed intermediate text as
escaped inline so partial mid-stream markdown can't break the parse.

Gated by MINICLAW_RICH_MESSAGES (default on), honoured for both the reply
and status paths.
@AaronCQL
AaronCQL force-pushed the feat/rich-messages branch from 2dbaaf9 to 1c8ac3a Compare June 16, 2026 04:02
@AaronCQL
AaronCQL merged commit 04f7b26 into main Jun 17, 2026
1 check passed
@AaronCQL
AaronCQL deleted the feat/rich-messages branch June 17, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant