Skip to content

fix(send_message_tool): add sendRichMessage fast-path for Telegram - #46118

Open
michaeltanyk wants to merge 2 commits into
NousResearch:mainfrom
michaeltanyk:fix/send-message-tool-rich-send
Open

fix(send_message_tool): add sendRichMessage fast-path for Telegram#46118
michaeltanyk wants to merge 2 commits into
NousResearch:mainfrom
michaeltanyk:fix/send-message-tool-rich-send

Conversation

@michaeltanyk

Copy link
Copy Markdown

Problem

The send_message tool's _send_telegram function creates a raw Bot object and calls bot.sendMessage(parse_mode=MarkdownV2). It bypasses the gateway's rich message path entirely. Tables, task lists, math, and other rich constructs always degrade to bulleted lists when sent via the send_message tool.

Agent streaming responses work fine (they go through the gateway adapter's rich path), but any explicit send_message call — notifications, cron outputs, cross-channel messages — gets MarkdownV2 degradation.

Fix

Two additions to tools/send_message_tool.py:

  1. _RichMsg helper class — wraps the message_id from sendRichMessage responses so the return-value pipeline (which expects a message object) works unchanged.

  2. Rich fast-path in _send_telegram — before the existing MarkdownV2 path, tries bot.do_api_request("sendRichMessage", ...) with rich_message: {markdown: ...}. On any failure, falls through to the existing MarkdownV2 path — purely additive, zero regression risk.

Verification

Tested on a Hermes instance with Telegram PTB 22.6+:

grep "sendRichMessage" agent.log | tail -5
# Shows sendRichMessage calls with rich_message.blocks containing native table/list/heading blocks

Tables, task lists, blockquotes, and math expressions now render natively when sent via the send_message tool.

Related

  • Feat/telegram rich messages #45741 — original rich message implementation (gateway adapter streaming path)
  • The gateway adapter already has rich send support — this PR extends it to the send_message tool path.

…ch sends

The _send_telegram function bypasses the gateway's rich message
path entirely by creating a raw Bot and calling sendMessage with
MarkdownV2.  Tables, task lists, math, and other rich constructs
always degrade to bulleted lists when sent via the send_message tool.

This adds a sendRichMessage fast-path via bot.do_api_request before
the MarkdownV2 fallback.  On any failure the existing MarkdownV2
path runs as before — purely additive.

The _RichMsg helper class wraps the message_id from sendRichMessage
responses so the rest of the return-value pipeline works unchanged.

Related: NousResearch#45741 (original rich message implementation)
@liuhao1024

Copy link
Copy Markdown
Contributor

**Performance: Version: ImageMagick 7.1.2-8 Q16-HDRI aarch64 23412 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI Modules OpenMP
Delegates (built-in): bzlib fontconfig freetype heic jng jp2 jpeg jxl lcms lqr ltdl lzma openexr png raw tiff webp xml zip zlib zstd
Compiler: clang (17.0.0)
Usage: import [options ...] [ file ]

Image Settings:
-adjoin join images into a single multi-image file
-border include window border in the output image
-channel type apply option to select image channels
-colorspace type alternate image colorspace
-comment string annotate image with comment
-compress type type of pixel compression when writing the image
-define format:option
define one or more image format options
-density geometry horizontal and vertical density of the image
-depth value image depth
-descend obtain image by descending window hierarchy
-display server X server to contact
-dispose method layer disposal method
-dither method apply error diffusion to image
-delay value display the next image after pausing
-encipher filename convert plain pixels to cipher pixels
-endian type endianness (MSB or LSB) of the image
-encoding type text encoding type
-filter type use this filter when resizing an image
-format "string" output formatted image characteristics
-frame include window manager frame
-gravity direction which direction to gravitate towards
-identify identify the format and characteristics of the image
-interlace type None, Line, Plane, or Partition
-interpolate method pixel color interpolation method
-label string assign a label to an image
-limit type value Area, Disk, Map, or Memory resource limit
-monitor monitor progress
-page geometry size and location of an image canvas
-pause seconds seconds delay between snapshots
-pointsize value font point size
-quality value JPEG/MIFF/PNG compression level
-quiet suppress all warning messages
-regard-warnings pay attention to warning messages
-repage geometry size and location of an image canvas
-respect-parentheses settings remain in effect until parenthesis boundary
-sampling-factor geometry
horizontal and vertical sampling factor
-scene value image scene number
-screen select image from root window
-seed value seed a new sequence of pseudo-random numbers
-set property value set an image property
-silent operate silently, i.e. don't ring any bells
-snaps value number of screen snapshots
-support factor resize support: > 1.0 is blurry, < 1.0 is sharp
-synchronize synchronize image to storage device
-taint declare the image as modified
-transparent-color color
transparent color
-treedepth value color tree depth
-verbose print detailed information about the image
-virtual-pixel method
Constant, Edge, Mirror, or Tile
-window id select window with this id or name
root selects whole screen

Image Operators:
-annotate geometry text
annotate the image with text
-colors value preferred number of colors in the image
-crop geometry preferred size and location of the cropped image
-encipher filename convert plain pixels to cipher pixels
-extent geometry set the image size
-geometry geometry preferred size or location of the image
-help print program options
-monochrome transform image to black and white
-negate replace every pixel with its complementary color
-quantize colorspace reduce colors in this colorspace
-resize geometry resize the image
-rotate degrees apply Paeth rotation to the image
-strip strip image of all profiles and comments
-thumbnail geometry create a thumbnail of the image
-transparent color make this color transparent within the image
-trim trim image edges
-type type image type

Miscellaneous Options:
-debug events display copious debugging information
-help print program options
-list type print a list of supported option arguments
-log format format of debugging information
-version print version information

By default, 'file' is written in the MIFF image format. To
specify a particular image format, precede the filename with an image
format name and a colon (i.e. ps:image) or specify the image type as
the filename suffix (i.e. image.ps). Specify 'file' as '-' for
standard input or output. on every Telegram message send**

The rich-path guard does import inspect inside the hot path of _send_telegram, which runs on every message. Python module imports are cached after first load, but the import machinery still acquires the import lock and walks sys.modules on each call — nontrivial overhead on a per-message basis.

Move it to the top of the file:

import inspect  # module-level

Then the hot-path check becomes just:

if inspect.iscoroutinefunction(getattr(bot, "do_api_request", None)):

Everything else looks solid — clean fallback to legacy MarkdownV2 on any failure, correct scoping inside _send_telegram, and the _RichMsg carrier is a good minimal shim.

@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists comp/tools Tool registry, model_tools, toolsets platform/telegram Telegram bot adapter labels Jun 14, 2026
Per PR review — the inline  inside _send_telegram
hits the import lock and sys.modules walk on every message send.
Move to module-level import.
@michaeltanyk

Copy link
Copy Markdown
Author

Good catch — moved import inspect to module level in 068d2d0. The import lock and sys.modules walk on every message send was unnecessary overhead.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Adds sendRichMessage fast-path for Telegram in send_message_tool, avoiding the generic JSON path.

Looks Good

  • Clean targeted optimization for Telegram platform.
  • No impact on other platforms.

Reviewed by Hermes Agent

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying a real standalone-send gap. Current main still routes Telegram send_message through tools/send_message_tool.py:849-859 and _send_telegram, whose text loop uses bot.send_message at tools/send_message_tool.py:1228-1246.

Problems

  • The proposed fast path sends every non-HTML body as rich. Current adapter policy intentionally limits rich delivery to qualifying constructs and skips known unsafe shapes in plugins/platforms/telegram/adapter.py:1393-1462.
  • The proposed catch-all fallback can duplicate delivery after a timeout or other transient failure. The current rich path specifically returns such failures without a legacy resend in plugins/platforms/telegram/adapter.py:1545-1691.
  • The raw payload omits the current line-break normalization and successful-send reply index used by plugins/platforms/telegram/adapter.py:1506-1521 and :1700-1707.

Suggested changes

  • Share or extract the adapter's eligibility, payload, error-classification, and rich-send recording behavior for the standalone sender.
  • Add standalone-path coverage for successful rich delivery, permanent fallback, transient no-resend, and the configured safety gates.

Automated hermes-sweeper review.

# sendRichMessage preserves tables, task lists, math, etc.
# Falls through to legacy MarkdownV2 on any failure.
_rich_ok = False
if not _has_html and message.strip() and len(message) <= 32768:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not route every non-HTML body through rich delivery. Current Telegram policy only selects qualifying rich constructs and also skips known unsafe details+math/CJK shapes (plugins/platforms/telegram/adapter.py:1393-1462); this standalone path needs to preserve those gates and the rich_messages configuration.

if rich_msg_id is not None:
last_msg = _RichMsg(rich_msg_id)
_rich_ok = True
except Exception:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This catch-all fallback can duplicate a message when the rich request times out after Telegram accepted it. The adapter only falls back for permanent/capability failures and returns transient failures without a legacy resend (plugins/platforms/telegram/adapter.py:1545-1691). Please share that classification here.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@michaeltanyk

Copy link
Copy Markdown
Author

Thanks for the review. I worked with my AI agent on the initial PR, but the plugin migration changes you've outlined are beyond me — I'm not familiar with that part of the codebase.

I'm currently in a country with internet restrictions and fighting infrastructure fires just to stay connected, so I don't have the bandwidth to dig in properly, and I won't let my agent touch the code without me reviewing it.

I'm respectfully recusing myself from this PR. Please take the idea and run with it, or close if it's not worth the team's time. Apologies for the noise, and thanks for the thoughtful review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants