Split long Telegram responses into multiple messages#71
Conversation
Telegram rejects messages over 4096 characters, causing agent replies that exceed the limit to fail to send. Split responses into chunks that respect line boundaries (falling back to a hard split for oversized lines) before sending, so long replies are delivered across multiple messages instead of erroring out. Fixes ClawRunr#62
| for (String line : message.split("\n", -1)) { | ||
| while (line.length() > maxLength) { | ||
| flush(chunks, current); | ||
| chunks.add(line.substring(0, maxLength)); |
There was a problem hiding this comment.
This can split a surrogate pair (emoji) → invalid string, Telegram rejects it. Check Character.isHighSurrogate(line.charAt(maxLength - 1)) and back off by one.
There was a problem hiding this comment.
Fixed: findSafeSplitIndex now backs off one code unit when the cut index lands right after a high surrogate (Character.isHighSurrogate), so an emoji's surrogate pair is never split across chunks. Added test doesNotSplitEmojiSurrogatePairAcrossChunks covering this.
| } | ||
|
|
||
| public void sendMessage(long chatId, Integer messageThreadId, String message) { | ||
| for (String chunk : splitMessage(message, MAX_MESSAGE_LENGTH)) { |
There was a problem hiding this comment.
Splitting happens on raw markdown, but chunks are HTML-converted after escaping (< → <) and tags (** → ) make the rendered text longer, so a 4000-char chunk can still exceed 4096 and get rejected. Either measure rendered length when chunking
There was a problem hiding this comment.
Fixed: chunk boundaries are now chosen by binary-searching the HTML-rendered length (renderedLength, which runs the markdown-to-HTML conversion) rather than the raw markdown length, so escaping/tag expansion is accounted for before a chunk is emitted. Added test splitChunksRespectRenderedHtmlLengthNotRawMarkdownLength covering this.
Summary
sendMessagecalls over 4096 characters, so agent replies longer than that limit were failing to send.TelegramChannel.sendMessagenow splits the response into chunks (respecting line boundaries, with a hard-split fallback for oversized single lines) and sends each chunk as a separate Telegram message.Fixes #62
Test plan
./gradlew :plugins:telegram:testpasses.