Skip to content

feat: render tables, large code blocks, and thinking as native Telegram rich messages - #203

Open
Rhonstin wants to merge 3 commits into
grinev:mainfrom
Rhonstin:feat/native-table-rendering
Open

feat: render tables, large code blocks, and thinking as native Telegram rich messages#203
Rhonstin wants to merge 3 commits into
grinev:mainfrom
Rhonstin:feat/native-table-rendering

Conversation

@Rhonstin

@Rhonstin Rhonstin commented Aug 7, 2026

Copy link
Copy Markdown

What

Assistant markdown output is currently rendered as plain text with entities (tables as ASCII text, code as preformatted text, thinking as blockquote). Since Bot API 10.1 (Rich Messages, June 2026) Telegram supports native rich message blocks. This PR renders three high-value elements as native Telegram blocks, keeping the existing text rendering as a fallback:

  1. Markdown tables → native table blocks (InputRichBlockTable, bordered).
  2. Large fenced code blocks (8+ lines) → collapsible details blocks (summary = language + line count) wrapping a native pre block.
  3. Thinking/analysis sections → native thinking blocks on the streaming path.

How

  • src/bot/render/types.ts — add tableRows, codeDetails, thinkingText to TelegramRenderedBlock/TelegramRenderedPart.
  • src/bot/render/block-renderer.ts — table blocks carry raw rows; code blocks (≥8 lines) carry codeDetails.
  • src/bot/messages/thinking-rendering.ts — thinking parts carry thinkingText.
  • src/bot/render/chunker.ts — table/details parts stay standalone; metadata preserved through splitting/cloning.
  • src/bot/streaming/response-streamer.tsclonePart/createSignature preserve the new fields.
  • src/bot/messages/telegram-text.ts:
    • parts with table/code metadata are sent via sendRichMessage (send + complete), sendRichMessageDraft (streaming), or editMessageText with a rich message; text fallback on error or when the method is unavailable.
    • thinking is draft-only: sendRichMessageDraft accepts the thinking block (verified HTTP 200), but sendRichMessage rejects it (RICH_MESSAGE_BLOCK_UNSUPPORTED), so native thinking is sent only while streaming and the completed message falls back to the existing blockquote text.

Verification

  • npm run typecheck / npm run lint / npm run build — clean.
  • npm test1425 tests pass, including new tests (table send, details send, thinking draft, both fallbacks) and updated renderer/streamer expectations.
  • Live smoke tests against the Telegram Bot API:
    • sendRichMessage table block → HTTP 200
    • sendRichMessage details+pre → HTTP 200
    • sendRichMessageDraft thinking block → HTTP 200; sendRichMessage thinking → HTTP 400 (RICH_MESSAGE_BLOCK_UNSUPPORTED), confirming the draft-only handling.

Notes

  • Native rich messages require a Telegram client that supports Rich Messages; text fallback keeps older clients working.
  • Small code blocks (<8 lines) intentionally keep the existing preformatted rendering.
  • No stored tokens or config changes.

Bohdan Kikot added 2 commits August 7, 2026 10:08
Markdown tables are currently rendered as aligned preformatted text.
Telegram's Rich Messages (Bot API 10.1+, sendRichMessage) support native
table blocks (InputRichBlockTable), which render as real tables in the
client.

Render table blocks as native rich-table messages when the API supports
it, falling back to the existing preformatted text otherwise:

- block-renderer: attach raw table rows to rendered table blocks
- chunker: keep table parts standalone (do not merge with adjacent text)
  and preserve tableRows through splitting/cloning
- response-streamer: preserve tableRows in part clones and signatures
- telegram-text: send table parts via sendRichMessage /
  sendRichMessageDraft / editMessageText (rich_message); text fallback on
  error or when the method is unavailable
Large fenced code blocks (8+ lines) now render as native Telegram
'details' blocks (collapsible) via Rich Messages, with a summary showing
the language and line count, instead of always-expanded preformatted text.
Small code blocks keep the existing preformatted rendering. Falls back to
text on error or when the API does not support rich messages.

- types: add codeDetails to rendered blocks/parts
- block-renderer: carry codeDetails on large code blocks
- chunker: keep details parts standalone, preserve codeDetails
- response-streamer: preserve codeDetails in clones/signatures
- telegram-text: build details rich message via buildNativeRichMessage
@Rhonstin Rhonstin changed the title feat: render markdown tables as native Telegram tables feat: render markdown tables and large code blocks as native Telegram rich messages Aug 7, 2026
Thinking/analysis sections are currently streamed as expandable
blockquote text. Rich Messages provide a dedicated 'thinking' block,
which Telegram renders as a collapsed AI-thinking element. It is only
supported on the streaming path (sendRichMessageDraft), so:

- thinking parts carry a thinkingText payload
- draft sends (sendDraftBotPart) use sendRichMessageDraft with a native
  'thinking' block; send/complete fall back to the blockquote text since
  sendRichMessage rejects the thinking block (RICH_MESSAGE_BLOCK_UNSUPPORTED)

- types: add thinkingText to rendered blocks/parts
- thinking-rendering: carry thinkingText in thinking parts
- response-streamer/chunker: preserve thinkingText in clones/signatures
- telegram-text: buildNativeDraftRichMessage includes the thinking block
@Rhonstin Rhonstin changed the title feat: render markdown tables and large code blocks as native Telegram rich messages feat: render tables, large code blocks, and thinking as native Telegram rich messages Aug 7, 2026
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