mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-03 08:15:53 +00:00
feat(telegram): render tool hints as expandable blockquotes (#2752)
This commit is contained in:
parent
a662ace8dd
commit
a6aa0b7932
@ -28,6 +28,16 @@ TELEGRAM_MAX_MESSAGE_LEN = 4000 # Telegram message character limit
|
|||||||
TELEGRAM_REPLY_CONTEXT_MAX_LEN = TELEGRAM_MAX_MESSAGE_LEN # Max length for reply context in user message
|
TELEGRAM_REPLY_CONTEXT_MAX_LEN = TELEGRAM_MAX_MESSAGE_LEN # Max length for reply context in user message
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_telegram_html(text: str) -> str:
|
||||||
|
"""Escape text for Telegram HTML parse mode."""
|
||||||
|
return text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_hint_to_telegram_blockquote(text: str) -> str:
|
||||||
|
"""Render tool hints as an expandable blockquote (collapsed by default)."""
|
||||||
|
return f"<blockquote expandable>{_escape_telegram_html(text)}</blockquote>" if text else ""
|
||||||
|
|
||||||
|
|
||||||
def _strip_md(s: str) -> str:
|
def _strip_md(s: str) -> str:
|
||||||
"""Strip markdown inline formatting from text."""
|
"""Strip markdown inline formatting from text."""
|
||||||
s = re.sub(r'\*\*(.+?)\*\*', r'\1', s)
|
s = re.sub(r'\*\*(.+?)\*\*', r'\1', s)
|
||||||
@ -120,7 +130,7 @@ def _markdown_to_telegram_html(text: str) -> str:
|
|||||||
text = re.sub(r'^>\s*(.*)$', r'\1', text, flags=re.MULTILINE)
|
text = re.sub(r'^>\s*(.*)$', r'\1', text, flags=re.MULTILINE)
|
||||||
|
|
||||||
# 5. Escape HTML special characters
|
# 5. Escape HTML special characters
|
||||||
text = text.replace("&", "&").replace("<", "<").replace(">", ">")
|
text = _escape_telegram_html(text)
|
||||||
|
|
||||||
# 6. Links [text](url) - must be before bold/italic to handle nested cases
|
# 6. Links [text](url) - must be before bold/italic to handle nested cases
|
||||||
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', text)
|
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', r'<a href="\2">\1</a>', text)
|
||||||
@ -141,13 +151,13 @@ def _markdown_to_telegram_html(text: str) -> str:
|
|||||||
# 11. Restore inline code with HTML tags
|
# 11. Restore inline code with HTML tags
|
||||||
for i, code in enumerate(inline_codes):
|
for i, code in enumerate(inline_codes):
|
||||||
# Escape HTML in code content
|
# Escape HTML in code content
|
||||||
escaped = code.replace("&", "&").replace("<", "<").replace(">", ">")
|
escaped = _escape_telegram_html(code)
|
||||||
text = text.replace(f"\x00IC{i}\x00", f"<code>{escaped}</code>")
|
text = text.replace(f"\x00IC{i}\x00", f"<code>{escaped}</code>")
|
||||||
|
|
||||||
# 12. Restore code blocks with HTML tags
|
# 12. Restore code blocks with HTML tags
|
||||||
for i, code in enumerate(code_blocks):
|
for i, code in enumerate(code_blocks):
|
||||||
# Escape HTML in code content
|
# Escape HTML in code content
|
||||||
escaped = code.replace("&", "&").replace("<", "<").replace(">", ">")
|
escaped = _escape_telegram_html(code)
|
||||||
text = text.replace(f"\x00CB{i}\x00", f"<pre><code>{escaped}</code></pre>")
|
text = text.replace(f"\x00CB{i}\x00", f"<pre><code>{escaped}</code></pre>")
|
||||||
|
|
||||||
return text
|
return text
|
||||||
@ -433,8 +443,12 @@ class TelegramChannel(BaseChannel):
|
|||||||
|
|
||||||
# Send text content
|
# Send text content
|
||||||
if msg.content and msg.content != "[empty message]":
|
if msg.content and msg.content != "[empty message]":
|
||||||
|
render_as_blockquote = bool(msg.metadata.get("_tool_hint"))
|
||||||
for chunk in split_message(msg.content, TELEGRAM_MAX_MESSAGE_LEN):
|
for chunk in split_message(msg.content, TELEGRAM_MAX_MESSAGE_LEN):
|
||||||
await self._send_text(chat_id, chunk, reply_params, thread_kwargs)
|
await self._send_text(
|
||||||
|
chat_id, chunk, reply_params, thread_kwargs,
|
||||||
|
render_as_blockquote=render_as_blockquote,
|
||||||
|
)
|
||||||
|
|
||||||
async def _call_with_retry(self, fn, *args, **kwargs):
|
async def _call_with_retry(self, fn, *args, **kwargs):
|
||||||
"""Call an async Telegram API function with retry on pool/network timeout and RetryAfter."""
|
"""Call an async Telegram API function with retry on pool/network timeout and RetryAfter."""
|
||||||
@ -468,10 +482,11 @@ class TelegramChannel(BaseChannel):
|
|||||||
text: str,
|
text: str,
|
||||||
reply_params=None,
|
reply_params=None,
|
||||||
thread_kwargs: dict | None = None,
|
thread_kwargs: dict | None = None,
|
||||||
|
render_as_blockquote: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Send a plain text message with HTML fallback."""
|
"""Send a plain text message with HTML fallback."""
|
||||||
try:
|
try:
|
||||||
html = _markdown_to_telegram_html(text)
|
html = _tool_hint_to_telegram_blockquote(text) if render_as_blockquote else _markdown_to_telegram_html(text)
|
||||||
await self._call_with_retry(
|
await self._call_with_retry(
|
||||||
self._app.bot.send_message,
|
self._app.bot.send_message,
|
||||||
chat_id=chat_id, text=html, parse_mode="HTML",
|
chat_id=chat_id, text=html, parse_mode="HTML",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user