mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-15 09:21:36 +00:00
feat(telegram): restore rich messages opt-out
Salvages PR #45840's client-compatibility opt-out while keeping rich messages enabled by default via telegram.extra.rich_messages: true.
This commit is contained in:
parent
8d5d36d793
commit
12682d96b9
7 changed files with 114 additions and 14 deletions
|
|
@ -719,6 +719,7 @@ platform_toolsets:
|
|||
# # allowed_chats: ["-1001234567890"]
|
||||
# extra:
|
||||
# disable_link_previews: false # Set true to suppress Telegram URL previews in bot messages
|
||||
# rich_messages: true # Set false to force legacy MarkdownV2 for clients that cannot render Bot API rich messages
|
||||
#
|
||||
# Discord-specific settings (config.yaml top-level, not under platforms:):
|
||||
#
|
||||
|
|
|
|||
|
|
@ -421,6 +421,9 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
self._disable_link_previews: bool = self._coerce_bool_extra("disable_link_previews", False)
|
||||
# Bot API 10.1 Rich Messages: send final replies via sendRichMessage
|
||||
# with the raw agent markdown so tables/task lists/etc. render natively.
|
||||
# Enabled by default; users can opt out for clients that accept but do
|
||||
# not render rich messages via platforms.telegram.extra.rich_messages.
|
||||
self._rich_messages_enabled: bool = self._coerce_bool_extra("rich_messages", True)
|
||||
# Latched off after a capability failure on sendRichMessage /
|
||||
# sendRichMessageDraft (e.g. older python-telegram-bot without the
|
||||
# endpoint) so later sends skip the doomed rich attempt entirely.
|
||||
|
|
@ -954,7 +957,8 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
self, content: str, metadata: Optional[Dict[str, Any]] = None
|
||||
) -> bool:
|
||||
return bool(
|
||||
not getattr(self, "_rich_send_disabled", False)
|
||||
getattr(self, "_rich_messages_enabled", True)
|
||||
and not getattr(self, "_rich_send_disabled", False)
|
||||
and not (metadata or {}).get("expect_edits")
|
||||
and content
|
||||
and content.strip()
|
||||
|
|
@ -995,7 +999,8 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
streams split exactly as before.
|
||||
"""
|
||||
if (
|
||||
not getattr(self, "_rich_send_disabled", False)
|
||||
getattr(self, "_rich_messages_enabled", True)
|
||||
and not getattr(self, "_rich_send_disabled", False)
|
||||
and self._bot_supports_rich()
|
||||
):
|
||||
return self.RICH_MESSAGE_MAX_CHARS
|
||||
|
|
@ -1184,7 +1189,8 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
|
||||
def _should_attempt_rich_draft(self, content: str) -> bool:
|
||||
return bool(
|
||||
not getattr(self, "_rich_send_disabled", False)
|
||||
getattr(self, "_rich_messages_enabled", True)
|
||||
and not getattr(self, "_rich_send_disabled", False)
|
||||
and not getattr(self, "_rich_draft_disabled", False)
|
||||
and content
|
||||
and content.strip()
|
||||
|
|
|
|||
|
|
@ -1982,6 +1982,9 @@ DEFAULT_CONFIG = {
|
|||
"reactions": False, # Add 👀/✅/❌ reactions to messages during processing
|
||||
"channel_prompts": {}, # Per-chat/topic ephemeral system prompts (topics inherit from parent group)
|
||||
"allowed_chats": "", # If set, bot ONLY responds in these group/supergroup chat IDs (whitelist)
|
||||
"extra": {
|
||||
"rich_messages": True, # Bot API 10.1 rich messages; set false to force legacy MarkdownV2
|
||||
},
|
||||
},
|
||||
|
||||
# Mattermost platform settings (gateway mode)
|
||||
|
|
|
|||
|
|
@ -813,6 +813,37 @@ class TestLoadGatewayConfig:
|
|||
|
||||
assert config.platforms[Platform.TELEGRAM].extra["disable_link_previews"] is True
|
||||
|
||||
def test_loads_telegram_rich_messages_from_gateway_platform_extra(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
config_path = hermes_home / "config.yaml"
|
||||
config_path.write_text(
|
||||
"gateway:\n"
|
||||
" platforms:\n"
|
||||
" telegram:\n"
|
||||
" extra:\n"
|
||||
" rich_messages: false\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
config = load_gateway_config()
|
||||
|
||||
assert config.platforms[Platform.TELEGRAM].extra["rich_messages"] is False
|
||||
|
||||
def test_load_config_default_includes_telegram_rich_messages(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
config = load_config()
|
||||
|
||||
assert config["telegram"]["extra"]["rich_messages"] is True
|
||||
|
||||
def test_bridges_telegram_extra_base_url_from_config_yaml(self, tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
|
|
|
|||
|
|
@ -106,16 +106,42 @@ async def test_rich_happy_path_sends_raw_markdown():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_legacy_rich_messages_config_is_ignored():
|
||||
async def test_rich_messages_opt_out_uses_legacy_send_path():
|
||||
adapter = _make_adapter(extra={"rich_messages": False})
|
||||
|
||||
result = await adapter.send("12345", RICH_CONTENT)
|
||||
|
||||
assert result.success is True
|
||||
# The legacy toggle was removed; stale config entries must not disable the
|
||||
# rich path.
|
||||
adapter._bot.do_api_request.assert_awaited_once()
|
||||
adapter._bot.send_message.assert_not_called()
|
||||
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_rich_messages_opt_out_accepts_string_false():
|
||||
adapter = _make_adapter(extra={"rich_messages": "false"})
|
||||
|
||||
result = await adapter.send("12345", RICH_CONTENT)
|
||||
|
||||
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_rich_messages_default_is_enabled():
|
||||
adapter = _make_adapter()
|
||||
|
||||
result = await adapter.send("12345", RICH_CONTENT)
|
||||
|
||||
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
|
||||
|
|
@ -441,9 +467,9 @@ def test_prefers_fresh_final_streaming_when_rich_enabled():
|
|||
assert adapter.prefers_fresh_final_streaming(RICH_CONTENT) is True
|
||||
|
||||
|
||||
def test_prefers_fresh_final_streaming_ignores_legacy_toggle():
|
||||
def test_prefers_fresh_final_streaming_honors_rich_opt_out():
|
||||
adapter = _make_adapter(extra={"rich_messages": False})
|
||||
assert adapter.prefers_fresh_final_streaming(RICH_CONTENT) is True
|
||||
assert adapter.prefers_fresh_final_streaming(RICH_CONTENT) is False
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
|
@ -456,12 +482,25 @@ def test_streaming_overflow_limit_is_rich_cap_when_enabled():
|
|||
assert adapter.streaming_overflow_limit() == TelegramAdapter.RICH_MESSAGE_MAX_CHARS
|
||||
|
||||
|
||||
def test_streaming_overflow_limit_ignores_legacy_toggle():
|
||||
def test_streaming_overflow_limit_none_when_rich_opted_out():
|
||||
adapter = _make_adapter(extra={"rich_messages": False})
|
||||
assert adapter.streaming_overflow_limit() == TelegramAdapter.RICH_MESSAGE_MAX_CHARS
|
||||
assert adapter.streaming_overflow_limit() is None
|
||||
|
||||
|
||||
def test_streaming_overflow_limit_none_when_rich_latched_off():
|
||||
adapter = _make_adapter()
|
||||
adapter._rich_send_disabled = True
|
||||
assert adapter.streaming_overflow_limit() is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rich_draft_opt_out_uses_legacy():
|
||||
adapter = _make_adapter(extra={"rich_messages": False})
|
||||
|
||||
result = await adapter.send_draft("12345", draft_id=7, content=RICH_CONTENT)
|
||||
|
||||
assert result.success is True
|
||||
bot = adapter._bot
|
||||
assert bot is not None
|
||||
bot.do_api_request.assert_not_called()
|
||||
bot.send_message_draft.assert_awaited_once()
|
||||
|
|
|
|||
|
|
@ -909,7 +909,17 @@ The rich path is skipped automatically when content exceeds the 32,768-byte rich
|
|||
- **Small tables** are flattened into **row-group bullets** — each row becomes a readable bulleted list under the column headings. Good for 2–4 columns and short cells.
|
||||
- **Larger or wider tables** fall back to a **fenced code block** with aligned columns so nothing collapses.
|
||||
|
||||
There's nothing to configure for the fallback — the adapter picks the right rendering per message. If you want the legacy "always code-block" behavior, disable table normalization by setting `telegram.pretty_tables: false` in `config.yaml` (default: `true`).
|
||||
There's nothing to configure for the API fallback — the adapter picks the right rendering per message. If a Telegram client accepts but cannot render rich messages (for example, a watch client that shows them as opaque media blocks), opt out and force the MarkdownV2 path:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
platforms:
|
||||
telegram:
|
||||
extra:
|
||||
rich_messages: false
|
||||
```
|
||||
|
||||
Rich messages are enabled by default (`rich_messages: true`). This setting is for client-rendering compatibility; Hermes already falls back automatically when Telegram rejects the rich API call. If you only want the legacy "always code-block" table behavior while keeping rich messages enabled, disable table normalization by setting `telegram.pretty_tables: false` in `config.yaml` (default: `true`).
|
||||
|
||||
**Link previews.** Telegram auto-generates link previews for URLs in bot messages. If you'd rather suppress those (long `/tools` output, agent reply that mentions ten links, etc.):
|
||||
|
||||
|
|
|
|||
|
|
@ -886,7 +886,17 @@ gateway:
|
|||
- **小表格**被展平为**行组项目符号**——每行在列标题下变为可读的项目符号列表。适合 2-4 列和短单元格。
|
||||
- **较大或较宽的表格**回退为带对齐列的**围栏代码块**,以防内容折叠。
|
||||
|
||||
回退无需配置——适配器会为每条消息选择正确的渲染方式。如果你想要旧版"始终使用代码块"行为,可在 `config.yaml` 中设置 `telegram.pretty_tables: false` 禁用表格规范化(默认:`true`)。
|
||||
API 回退无需配置——适配器会为每条消息选择正确的渲染方式。如果某个 Telegram 客户端能接收但不能渲染富消息(例如手表客户端把它们显示为不透明媒体块),可以选择退出并强制使用 MarkdownV2 路径:
|
||||
|
||||
```yaml
|
||||
gateway:
|
||||
platforms:
|
||||
telegram:
|
||||
extra:
|
||||
rich_messages: false
|
||||
```
|
||||
|
||||
富消息默认启用(`rich_messages: true`)。这个设置用于客户端渲染兼容性;当 Telegram 拒绝富消息 API 调用时,Hermes 已经会自动回退。如果你只是想在保持富消息启用的同时恢复旧版「始终使用代码块」表格行为,可在 `config.yaml` 中设置 `telegram.pretty_tables: false` 禁用表格规范化(默认:`true`)。
|
||||
|
||||
**链接预览。** Telegram 会为机器人消息中的 URL 自动生成链接预览。如果你希望抑制这些预览(长 `/tools` 输出、提及十个链接的 Agent 回复等):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue