feat: render tables, large code blocks, and thinking as native Telegram rich messages - #203
Open
Rhonstin wants to merge 3 commits into
Open
feat: render tables, large code blocks, and thinking as native Telegram rich messages#203Rhonstin wants to merge 3 commits into
Rhonstin wants to merge 3 commits into
Conversation
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
InputRichBlockTable, bordered).detailsblocks (summary = language + line count) wrapping a nativepreblock.thinkingblocks on the streaming path.How
src/bot/render/types.ts— addtableRows,codeDetails,thinkingTexttoTelegramRenderedBlock/TelegramRenderedPart.src/bot/render/block-renderer.ts— table blocks carry raw rows; code blocks (≥8 lines) carrycodeDetails.src/bot/messages/thinking-rendering.ts— thinking parts carrythinkingText.src/bot/render/chunker.ts— table/details parts stay standalone; metadata preserved through splitting/cloning.src/bot/streaming/response-streamer.ts—clonePart/createSignaturepreserve the new fields.src/bot/messages/telegram-text.ts:sendRichMessage(send + complete),sendRichMessageDraft(streaming), oreditMessageTextwith a rich message; text fallback on error or when the method is unavailable.sendRichMessageDraftaccepts thethinkingblock (verified HTTP 200), butsendRichMessagerejects 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 test— 1425 tests pass, including new tests (table send, details send, thinking draft, both fallbacks) and updated renderer/streamer expectations.sendRichMessagetable block → HTTP 200sendRichMessagedetails+pre→ HTTP 200sendRichMessageDraftthinkingblock → HTTP 200;sendRichMessagethinking→ HTTP 400 (RICH_MESSAGE_BLOCK_UNSUPPORTED), confirming the draft-only handling.Notes