diff --git a/gateway/platforms/whatsapp.py b/gateway/platforms/whatsapp.py index c616f7244887e..65fc483dff1dd 100644 --- a/gateway/platforms/whatsapp.py +++ b/gateway/platforms/whatsapp.py @@ -704,6 +704,23 @@ async def send_document( file_name or os.path.basename(file_path), ) + async def send_voice( + self, + chat_id: str, + audio_path: str, + caption: Optional[str] = None, + reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs, + ) -> SendResult: + """Send an audio file natively via the WhatsApp bridge. + + OGG/Opus files are treated by the Node bridge as PTT voice notes + (ptt=true); MP3/WAV/M4A are sent as normal audio attachments. + """ + del reply_to, metadata, kwargs + return await self._send_media_to_bridge(chat_id, audio_path, "audio", caption) + async def send_typing(self, chat_id: str, metadata=None) -> None: """Send typing indicator via bridge.""" if not self._running or not self._http_session: diff --git a/tests/gateway/test_whatsapp_connect.py b/tests/gateway/test_whatsapp_connect.py index 61ff8f361ad79..ebaab01286717 100644 --- a/tests/gateway/test_whatsapp_connect.py +++ b/tests/gateway/test_whatsapp_connect.py @@ -211,6 +211,24 @@ async def test_closed_when_bridge_dies_phase1(self): assert adapter._bridge_log_fh is None +class TestSendVoice: + """Regression tests for native WhatsApp audio sending.""" + + @pytest.mark.asyncio + async def test_send_voice_routes_to_bridge_audio_media(self): + adapter = _make_adapter() + adapter._send_media_to_bridge = AsyncMock( + return_value=MagicMock(success=True, message_id="wa-audio-1") + ) + + result = await adapter.send_voice("chat-123", "/tmp/voice.ogg", caption="hello") + + assert result.success is True + adapter._send_media_to_bridge.assert_awaited_once_with( + "chat-123", "/tmp/voice.ogg", "audio", "hello" + ) + + class TestBridgeRuntimeFailure: """Verify runtime bridge death is surfaced as a fatal adapter error."""