refactor(tools): compact signal RPC builder and blank squeeze

This commit is contained in:
Teknium
2026-09-02 23:09:31 -07:00
parent 3106c534c2
commit 6b226dbce9
4 changed files with 20 additions and 55 deletions

View File

@@ -15,7 +15,6 @@ def _open_session_db():
"""Open the SessionDB for the profile owning this turn, or ``None``."""
try:
from hermes_state import get_shared_session_db
return get_shared_session_db()
except Exception:
return None

View File

@@ -131,7 +131,6 @@ def _telegram_bot(token):
"""Bot honouring TELEGRAM_PROXY (``telegram.proxy_url``) — without it the standalone
path times out where api.telegram.org is blocked. Falls back to a direct connection."""
from telegram import Bot
try:
from gateway.platforms.base import resolve_proxy_url
proxy = resolve_proxy_url("TELEGRAM_PROXY", target_hosts=["api.telegram.org"])
@@ -259,7 +258,6 @@ def _telegram_format(message):
"""``(formatted, parse_mode, has_html)``: text already containing HTML tags is sent as
HTML; otherwise Markdown -> MarkdownV2 via the adapter's ``format_message``."""
from telegram.constants import ParseMode
has_html = bool(re.search(r'<[a-zA-Z/][^>]*>', message))
if has_html:
return message, ParseMode.HTML, True
@@ -372,7 +370,6 @@ def _plugin_standalone_sender(platform_name, *, label=None, discover=True):
"""``(standalone_sender_fn, None)`` for a registered plugin or ``(None, error_dict)``;
``discover`` runs the idempotent plugin scan first."""
from gateway.platform_registry import platform_registry
if discover:
from hermes_cli.plugins import discover_plugins
discover_plugins()
@@ -505,7 +502,6 @@ async def _send_signal(extra, chat_id, message, media_files=None):
from gateway.platforms import signal_rate_limit as rl
from gateway.platforms.signal_format import markdown_to_signal
try:
http_url = extra.get("http_url", "http://127.0.0.1:8080").rstrip("/")
account = extra.get("account", "")
@@ -517,35 +513,24 @@ async def _send_signal(extra, chat_id, message, media_files=None):
for media_path, _is_voice in valid_media:
if not os.path.exists(media_path):
logger.warning("Signal media file not found, skipping: %s", media_path)
# No attachments still means one (text-only) batch; with attachments
# the text rides on batch #0 so it isn't repeated per batch.
per_batch = rl.SIGNAL_MAX_ATTACHMENTS_PER_MSG
att_batches = [
attachment_paths[i:i + per_batch] for i in range(0, len(attachment_paths), per_batch)
] or [[]]
att_batches = [attachment_paths[i:i + per_batch]
for i in range(0, len(attachment_paths), per_batch)] or [[]]
n_batches = len(att_batches)
plain_text, text_styles = markdown_to_signal(message)
recipient = {"groupId": chat_id[6:]} if chat_id.startswith("group:") else {"recipient": [chat_id]}
async def _rpc_send(text, *, id_prefix, timeout, attachments=None, styled=False):
params = {"account": account, "message": text}
if chat_id.startswith("group:"):
params["groupId"] = chat_id[6:]
else:
params["recipient"] = [chat_id]
params = {"account": account, "message": text, **recipient}
if styled and text and text_styles:
if len(text_styles) == 1:
params["textStyle"] = text_styles[0]
else:
params["textStyles"] = text_styles
params["textStyle" if len(text_styles) == 1 else "textStyles"] = (
text_styles[0] if len(text_styles) == 1 else text_styles)
if attachments:
params["attachments"] = attachments
payload = {
"jsonrpc": "2.0",
"method": "send",
"params": params,
"id": f"{id_prefix}_{int(time.time() * 1000)}"}
payload = {"jsonrpc": "2.0", "method": "send", "params": params,
"id": f"{id_prefix}_{int(time.time() * 1000)}"}
async with httpx.AsyncClient(timeout=timeout) as client:
return await client.post(f"{http_url}/api/v1/rpc", json=payload)
@@ -563,18 +548,15 @@ async def _send_signal(extra, chat_id, message, media_files=None):
failed_batches: list[int] = []
for idx, att_batch in enumerate(att_batches):
n = len(att_batch)
if n > 0:
estimated = scheduler.estimate_wait(n)
if estimated >= rl.SIGNAL_BATCH_PACING_NOTICE_THRESHOLD:
# Best-effort one-shot RPC for a user-facing pacing notice.
notice = (
f"(More images coming — pausing ~{rl._format_wait(estimated)} "
f"for Signal rate limit, batch {idx + 1}/{n_batches}.)")
try:
await _rpc_send(notice, id_prefix="notice", timeout=30.0)
except Exception as _e:
logger.warning("Signal: inline notice failed: %s", _e)
estimated = scheduler.estimate_wait(n) if n > 0 else 0.0
if n > 0 and estimated >= rl.SIGNAL_BATCH_PACING_NOTICE_THRESHOLD:
# Best-effort one-shot RPC for a user-facing pacing notice.
notice = (f"(More images coming — pausing ~{rl._format_wait(estimated)} "
f"for Signal rate limit, batch {idx + 1}/{n_batches}.)")
try:
await _rpc_send(notice, id_prefix="notice", timeout=30.0)
except Exception as _e:
logger.warning("Signal: inline notice failed: %s", _e)
outcome = await _signal_send_batch(
_post, scheduler, rl, idx, n_batches, att_batch, plain_text if idx == 0 else "")
if outcome is False:
@@ -586,14 +568,10 @@ async def _send_signal(extra, chat_id, message, media_files=None):
if len(attachment_paths) < len(valid_media):
warnings.append("Some media files were skipped (not found on disk)")
if failed_batches:
warnings.append(
f"Signal rate-limited {len(failed_batches)} batch(es) "
f"(#{', #'.join(str(b) for b in failed_batches)})")
warnings.append(f"Signal rate-limited {len(failed_batches)} batch(es) "
f"(#{', #'.join(str(b) for b in failed_batches)})")
if failed_batches and len(failed_batches) == n_batches:
return _error(
f"Signal: every batch ({n_batches}) hit rate limit; no attachments delivered")
return _error(f"Signal: every batch ({n_batches}) hit rate limit; no attachments delivered")
return _success("signal", _display_chat_id("signal", chat_id), warnings)
except Exception as e:
return _error(f"Signal send failed: {e}")
@@ -720,7 +698,6 @@ async def _send_qqbot(pconfig, chat_id, message):
# Profile-scoped lookup so a multiplex profile never borrows another's QQ credentials.
from gateway.config import _getenv
extra = pconfig.extra or {}
appid = extra.get("app_id") or _getenv("QQ_APP_ID", "")
secret = (pconfig.token or extra.get("client_secret")

View File

@@ -66,7 +66,6 @@ def _parse_telegram(ref):
if parsed:
return parsed
from plugins.platforms.telegram.telegram_ids import parse_telegram_username_target
username = parse_telegram_username_target(ref)
return (username, None) if username else None
@@ -179,7 +178,6 @@ def resolve_send_target(
"""
from gateway.config import Platform
from gateway.platform_registry import platform_registry
entry = platform_registry.get(platform_name)
def _validate(candidate: str) -> str | None:
@@ -225,7 +223,6 @@ def resolve_send_target(
resolution_failed = False
try:
from gateway.channel_directory import resolve_channel_name
resolved = resolve_channel_name(platform_name, target_ref)
except Exception:
resolved = None

View File

@@ -29,7 +29,6 @@ from tools.registry import tool_error
def prepare_send_message_platforms() -> None:
"""Load enabled standalone plugins before tool schemas/cache keys are built."""
from hermes_cli.plugins import discover_plugins
discover_plugins()
@@ -209,7 +208,6 @@ def _resolve_platform_config(platform_name, config):
purely via .env (synthesized pconfig so cron delivery works without gateway.yaml)."""
from gateway.config import Platform
from gateway.platform_registry import platform_registry
entry = platform_registry.get(platform_name)
if entry is None and platform_name not in {member.value for member in Platform}:
return None, None, None, f"Unknown or unregistered plugin platform: {platform_name}"
@@ -357,7 +355,6 @@ async def _send_live_adapter_media(
return {"error": f"Adapter send failed: {_bounded_send_error(last_result.error)}"}
from gateway.platforms.base import BasePlatformAdapter
total = len(media_files)
for index, descriptor in enumerate(media_files):
media_path = descriptor[0] if isinstance(descriptor, (list, tuple)) and descriptor else None
@@ -494,7 +491,6 @@ def _platform_max_length(platform):
SignalAdapter's own chunking), the registry's ``max_message_length`` for plugins,
else None (no chunking)."""
from gateway.config import Platform
if platform == Platform.SIGNAL:
try:
from gateway.platforms.signal import MAX_MESSAGE_LENGTH
@@ -595,7 +591,6 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
unrelated optional imports such as lark-oapi), Telegram (chunks itself), plugin
standalone media routes, native chunked routes, then the generic text path."""
from gateway.config import Platform
platform_name = platform.value if hasattr(platform, "value") else str(platform)
media_files = media_files or []
@@ -611,7 +606,6 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
disable_link_previews=disable_link_previews, force_document=force_document)
from gateway.platforms.base import BasePlatformAdapter
max_len = _platform_max_length(platform)
chunks = BasePlatformAdapter.truncate_message(message, max_len) if max_len else [message]
@@ -644,14 +638,12 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
send_one = lambda chunk, is_last: text_sender(pconfig, chat_id, chunk, thread_id) # noqa: E731
else:
from gateway.platform_registry import platform_registry
entry = platform_registry.get(platform_name)
handler = entry.send_message_handler if entry is not None else None
if handler is not None:
# Custom handler receives the full typed request once (not per chunk).
try:
import inspect
result = handler(args or {}, chat_id, platform_name, pconfig)
if inspect.isawaitable(result):
result = await result