mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-16 09:31:37 +00:00
fix(telegram): guard rich details math crash (#46102)
This commit is contained in:
parent
cf7d5932f8
commit
9459057d7f
2 changed files with 90 additions and 0 deletions
|
|
@ -953,6 +953,32 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
"""
|
||||
return inspect.iscoroutinefunction(getattr(self._bot, "do_api_request", None))
|
||||
|
||||
_RICH_DETAILS_RE = re.compile(r"<details\b[^>]*>.*?</details>", re.IGNORECASE | re.DOTALL)
|
||||
_RICH_MATH_IN_DETAILS_RE = re.compile(
|
||||
r"(\$\$.*?\$\$|"
|
||||
r"\\\[.*?\\\]|"
|
||||
r"\\\(.*?\\\)|"
|
||||
r"\\(?:sum|frac|alpha|beta|gamma|delta|theta|lambda|mu|pi|sigma|"
|
||||
r"int|prod|sqrt|lim|infty|begin\{(?:equation|align|matrix|cases)\}))",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
def _has_telegram_desktop_details_math_crash_shape(self, content: str) -> bool:
|
||||
"""Return True for rich-message details+math content that crashes TDesktop.
|
||||
|
||||
Telegram Desktop 6.9.1 can crash while rendering Bot API 10.1 rich
|
||||
messages containing math inside a collapsible details block
|
||||
(telegramdesktop/tdesktop#30808). The Bot API accepts the payload, so
|
||||
Hermes must skip rich delivery up front and use the legacy MarkdownV2
|
||||
path until affected Desktop clients age out.
|
||||
"""
|
||||
if not content:
|
||||
return False
|
||||
for details_block in self._RICH_DETAILS_RE.findall(content):
|
||||
if self._RICH_MATH_IN_DETAILS_RE.search(details_block):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _should_attempt_rich(
|
||||
self, content: str, metadata: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
|
|
@ -962,6 +988,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
and not (metadata or {}).get("expect_edits")
|
||||
and content
|
||||
and content.strip()
|
||||
and not self._has_telegram_desktop_details_math_crash_shape(content)
|
||||
and self._content_fits_rich_limits(content)
|
||||
and self._bot_supports_rich()
|
||||
)
|
||||
|
|
@ -1194,6 +1221,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
and not getattr(self, "_rich_draft_disabled", False)
|
||||
and content
|
||||
and content.strip()
|
||||
and not self._has_telegram_desktop_details_math_crash_shape(content)
|
||||
and self._content_fits_rich_limits(content)
|
||||
and self._bot_supports_rich()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,12 @@ from telegram.error import BadRequest, NetworkError, TimedOut
|
|||
# Content exercising rich-only constructs: a heading, a real Markdown table,
|
||||
# and a task list. Pipes / brackets must survive untouched into the payload.
|
||||
RICH_CONTENT = "## Results\n\n| Case | Status |\n|---|---|\n| rich | ✅ |\n\n- [x] table renders"
|
||||
DANGEROUS_DETAILS_MATH = (
|
||||
"<details><summary>Complex proof</summary>\n\n"
|
||||
"$$\\sum_{i=1}^{n} i = \\frac{n(n+1)}{2}$$\n\n"
|
||||
"And inline \\(\\alpha + \\beta\\)\n"
|
||||
"</details>"
|
||||
)
|
||||
|
||||
# PTB 22.6's real unknown-endpoint errors: do_api_request can raise
|
||||
# EndPointNotFound for Bot API 404s, and the request layer can wrap that same
|
||||
|
|
@ -105,6 +111,48 @@ async def test_rich_happy_path_sends_raw_markdown():
|
|||
adapter._bot.send_message.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_details_with_math_skips_rich_send_to_avoid_tdesktop_crash():
|
||||
adapter = _make_adapter()
|
||||
|
||||
result = await adapter.send("12345", DANGEROUS_DETAILS_MATH)
|
||||
|
||||
assert result.success is True
|
||||
bot = adapter._bot
|
||||
assert bot is not None
|
||||
bot.do_api_request.assert_not_called()
|
||||
bot.send_message.assert_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_details_without_math_still_uses_rich_send():
|
||||
adapter = _make_adapter()
|
||||
|
||||
result = await adapter.send(
|
||||
"12345",
|
||||
"<details><summary>Notes</summary>\nNo equations here.\n</details>",
|
||||
)
|
||||
|
||||
assert result.success is True
|
||||
bot = adapter._bot
|
||||
assert bot is not None
|
||||
bot.do_api_request.assert_awaited_once()
|
||||
bot.send_message.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_math_outside_details_still_uses_rich_send():
|
||||
adapter = _make_adapter()
|
||||
|
||||
result = await adapter.send("12345", "Outside details: $$x^2 + y^2$$")
|
||||
|
||||
assert result.success is True
|
||||
bot = adapter._bot
|
||||
assert bot is not None
|
||||
bot.do_api_request.assert_awaited_once()
|
||||
bot.send_message.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rich_messages_opt_out_uses_legacy_send_path():
|
||||
adapter = _make_adapter(extra={"rich_messages": False})
|
||||
|
|
@ -393,6 +441,20 @@ async def test_rich_gate_tolerates_minimal_bot_without_raw_endpoint():
|
|||
# ── Streaming drafts: sendRichMessageDraft ─────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_details_with_math_skips_rich_draft_to_avoid_tdesktop_crash():
|
||||
adapter = _make_adapter()
|
||||
bot = adapter._bot
|
||||
assert bot is not None
|
||||
bot.do_api_request = AsyncMock(return_value=True)
|
||||
|
||||
result = await adapter.send_draft("12345", draft_id=7, content=DANGEROUS_DETAILS_MATH)
|
||||
|
||||
assert result.success is True
|
||||
bot.do_api_request.assert_not_called()
|
||||
bot.send_message_draft.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rich_draft_happy_path_sends_raw_markdown():
|
||||
adapter = _make_adapter()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue