From dc15bc508fab8dddab654c0d3dd0ee60dd9de675 Mon Sep 17 00:00:00 2001 From: sai-samarth Date: Tue, 17 Mar 2026 15:31:13 +0000 Subject: [PATCH] fix(tools): add outbound WhatsApp send_message routing --- tests/tools/test_send_message_tool.py | 18 ++++++++++++++++ tools/send_message_tool.py | 30 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/tools/test_send_message_tool.py b/tests/tools/test_send_message_tool.py index 2b03847e5..12eb6c55b 100644 --- a/tests/tools/test_send_message_tool.py +++ b/tests/tools/test_send_message_tool.py @@ -398,6 +398,24 @@ class TestSendToPlatformChunking: # --------------------------------------------------------------------------- +class TestSendToPlatformWhatsapp: + def test_whatsapp_routes_via_local_bridge_sender(self): + async_mock = AsyncMock(return_value={"success": True, "platform": "whatsapp", "chat_id": "43121572348102@lid", "message_id": "abc123"}) + + with patch("tools.send_message_tool._send_whatsapp", async_mock): + result = asyncio.run( + _send_to_platform( + Platform.WHATSAPP, + SimpleNamespace(enabled=True, token=None, extra={"bridge_port": 3000}), + "43121572348102@lid", + "hello from hermes", + ) + ) + + assert result["success"] is True + async_mock.assert_awaited_once_with({"bridge_port": 3000}, "43121572348102@lid", "hello from hermes") + + class TestSendTelegramHtmlDetection: """Verify that messages containing HTML tags are sent with parse_mode=HTML and that plain / markdown messages use MarkdownV2.""" diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index 4b0c4815f..bd25e1dbc 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -331,6 +331,8 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None, result = await _send_discord(pconfig.token, chat_id, chunk) elif platform == Platform.SLACK: result = await _send_slack(pconfig.token, chat_id, chunk) + elif platform == Platform.WHATSAPP: + result = await _send_whatsapp(pconfig.extra, chat_id, chunk) elif platform == Platform.SIGNAL: result = await _send_signal(pconfig.extra, chat_id, chunk) elif platform == Platform.EMAIL: @@ -514,6 +516,34 @@ async def _send_slack(token, chat_id, message): return {"error": f"Slack send failed: {e}"} +async def _send_whatsapp(extra, chat_id, message): + """Send via the local WhatsApp bridge HTTP API.""" + try: + import aiohttp + except ImportError: + return {"error": "aiohttp not installed. Run: pip install aiohttp"} + try: + bridge_port = extra.get("bridge_port", 3000) + async with aiohttp.ClientSession() as session: + async with session.post( + f"http://localhost:{bridge_port}/send", + json={"chatId": chat_id, "message": message}, + timeout=aiohttp.ClientTimeout(total=30), + ) as resp: + if resp.status == 200: + data = await resp.json() + return { + "success": True, + "platform": "whatsapp", + "chat_id": chat_id, + "message_id": data.get("messageId"), + } + body = await resp.text() + return {"error": f"WhatsApp bridge error ({resp.status}): {body}"} + except Exception as e: + return {"error": f"WhatsApp send failed: {e}"} + + async def _send_signal(extra, chat_id, message): """Send via signal-cli JSON-RPC API.""" try: