From 2fa33dde81e7c16436c5a03f1838b33bc6d5752e Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:00:52 -0700 Subject: [PATCH] fix: handle message length overflow in streaming mode (#1783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stream consumer now splits messages that exceed the platform's MAX_MESSAGE_LENGTH. When accumulated text grows past the safe limit, the current message is finalized and a new message is started for the overflow — same as how normal sends chunk long responses. Split point prefers line boundaries (rfind newline) for clean breaks. Works for all platforms (Telegram 4096, Discord 2000, etc.) by reading the adapter's MAX_MESSAGE_LENGTH at runtime. Also added a safety net in the Telegram adapter: if edit_message_text still hits MESSAGE_TOO_LONG (e.g. markdown formatting expansion), it truncates and returns success so the stream consumer doesn't die. Co-authored-by: Test --- gateway/platforms/telegram.py | 14 ++++++++++++++ gateway/stream_consumer.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index 5a3c80630..fe869f18e 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -430,6 +430,20 @@ class TelegramAdapter(BasePlatformAdapter): # "Message is not modified" — content identical, treat as success if "not modified" in err_str: return SendResult(success=True, message_id=message_id) + # Message too long — content exceeded 4096 chars (e.g. during + # streaming). Truncate and succeed so the stream consumer can + # split the overflow into a new message instead of dying. + if "message_too_long" in err_str or "too long" in err_str: + truncated = content[: self.MAX_MESSAGE_LENGTH - 20] + "…" + try: + await self._bot.edit_message_text( + chat_id=int(chat_id), + message_id=int(message_id), + text=truncated, + ) + except Exception: + pass # best-effort truncation + return SendResult(success=True, message_id=message_id) # Flood control / RetryAfter — back off and retry once retry_after = getattr(e, "retry_after", None) if retry_after is not None or "retry after" in err_str: diff --git a/gateway/stream_consumer.py b/gateway/stream_consumer.py index 1b264c534..2ceb0fb1d 100644 --- a/gateway/stream_consumer.py +++ b/gateway/stream_consumer.py @@ -87,6 +87,10 @@ class GatewayStreamConsumer: async def run(self) -> None: """Async task that drains the queue and edits the platform message.""" + # Platform message length limit — leave room for cursor + formatting + _raw_limit = getattr(self.adapter, "MAX_MESSAGE_LENGTH", 4096) + _safe_limit = max(500, _raw_limit - len(self.cfg.cursor) - 100) + try: while True: # Drain all available items from the queue @@ -112,6 +116,21 @@ class GatewayStreamConsumer: ) if should_edit and self._accumulated: + # Split overflow: if accumulated text exceeds the platform + # limit, finalize the current message and start a new one. + while ( + len(self._accumulated) > _safe_limit + and self._message_id is not None + ): + split_at = self._accumulated.rfind("\n", 0, _safe_limit) + if split_at < _safe_limit // 2: + split_at = _safe_limit + chunk = self._accumulated[:split_at] + await self._send_or_edit(chunk) + self._accumulated = self._accumulated[split_at:].lstrip("\n") + self._message_id = None + self._last_sent_text = "" + display_text = self._accumulated if not got_done: display_text += self.cfg.cursor