From 98d661775e8c69915e9367257f0e8b0c11ef07f1 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:59:25 -0700 Subject: [PATCH] fix(telegram): hard-cut fence splits when the closer cannot fit Adaptive HTML limits can shrink max_len to the fence prefix size. Treat budget <= min_code_pos as a hard cut so the splitter still advances. --- nanobot/channels/telegram/runtime.py | 20 +++++++------- .../telegram/tests/test_telegram_channel.py | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/nanobot/channels/telegram/runtime.py b/nanobot/channels/telegram/runtime.py index 1bccbcfe..39c28619 100644 --- a/nanobot/channels/telegram/runtime.py +++ b/nanobot/channels/telegram/runtime.py @@ -93,7 +93,7 @@ def _split_telegram_markdown(content: str, max_len: int) -> list[str]: # When the only break in range is the opening fence newline, # cutting there re-emits the same fence and never advances. if pos < min_code_pos: - if min_code_pos + len(closing) > max_len: + if min_code_pos + len(closing) >= max_len: chunks.append(content[:max_len]) content = content[max_len:].lstrip() continue @@ -105,15 +105,15 @@ def _split_telegram_markdown(content: str, max_len: int) -> list[str]: pos = adjusted if adjusted >= min_code_pos else budget elif pos + len(closing) > max_len: budget = max_len - len(closing) - if budget > 0: - recut = content[:budget] - adjusted = recut.rfind("\n", min_code_pos) - if adjusted < min_code_pos: - adjusted = recut.rfind(" ", min_code_pos) - pos = adjusted if adjusted >= min_code_pos else budget - else: - closing = "```" - pos = max_len - len(closing) + if budget <= min_code_pos: + chunks.append(content[:max_len]) + content = content[max_len:].lstrip() + continue + recut = content[:budget] + adjusted = recut.rfind("\n", min_code_pos) + if adjusted < min_code_pos: + adjusted = recut.rfind(" ", min_code_pos) + pos = adjusted if adjusted >= min_code_pos else budget chunks.append(content[:pos] + closing) remainder = content[pos:] if remainder.startswith("\n"): diff --git a/nanobot/channels/telegram/tests/test_telegram_channel.py b/nanobot/channels/telegram/tests/test_telegram_channel.py index 8693157f..5a37c582 100644 --- a/nanobot/channels/telegram/tests/test_telegram_channel.py +++ b/nanobot/channels/telegram/tests/test_telegram_channel.py @@ -268,6 +268,32 @@ def test_split_telegram_markdown_long_single_line_code_body() -> None: _assert_code_blocks_render_balanced(chunks) +def test_split_telegram_markdown_tiny_limit_hard_cuts_fence_prefix() -> None: + """Adaptive HTML limits can shrink max_len to the fence+closer size.""" + body = "a" * 100 + content = f"```\n{body}" + + chunks = _split_telegram_markdown(content, max_len=8) + + assert chunks + assert all(len(chunk) <= 8 for chunk in chunks) + assert "".join(chunks).replace("```", "").replace("\n", "") == body + + +def test_split_telegram_markdown_tiny_limit_with_early_body_newline() -> None: + body = "a" * 100 + content = f"```\na\n{body}" + + chunks = _split_telegram_markdown(content, max_len=8) + + assert chunks + assert all(len(chunk) <= 8 for chunk in chunks) + plain = "".join(chunks).replace("```", "") + assert "a" in plain + assert plain.count("a") >= 100 + + + @pytest.mark.asyncio async def test_start_creates_separate_pools_with_proxy(monkeypatch) -> None: _FakeHTTPXRequest.clear()