diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index a9b0447080de..97450e86bd3b 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -3048,7 +3048,7 @@ async def _dispatch_inbound_event(self, event: MessageEvent) -> None: def _should_batch_media_event(self, event: MessageEvent) -> bool: return bool( event.media_urls - and event.message_type in {MessageType.PHOTO, MessageType.VIDEO, MessageType.DOCUMENT, MessageType.AUDIO} + and event.message_type in {MessageType.PHOTO, MessageType.VIDEO, MessageType.DOCUMENT, MessageType.VOICE} ) def _media_batch_key(self, event: MessageEvent) -> str: @@ -3472,7 +3472,7 @@ async def _extract_message_content( text = normalized.text_content if ( - inbound_type in {MessageType.DOCUMENT, MessageType.AUDIO, MessageType.VIDEO, MessageType.PHOTO} + inbound_type in {MessageType.DOCUMENT, MessageType.VOICE, MessageType.VIDEO, MessageType.PHOTO} and len(media_urls) == 1 and normalized.preferred_message_type in {"document", "audio"} ): @@ -3519,7 +3519,7 @@ def _resolve_media_message_type(media_type: str, *, default: MessageType) -> Mes if normalized.startswith("image/"): return MessageType.PHOTO if normalized.startswith("audio/"): - return MessageType.AUDIO + return MessageType.VOICE if normalized.startswith("video/"): return MessageType.VIDEO return default @@ -3533,7 +3533,7 @@ def _resolve_normalized_message_type( if preferred == "photo": return self._resolve_media_message_type(media_types[0] if media_types else "", default=MessageType.PHOTO) if preferred == "audio": - return self._resolve_media_message_type(media_types[0] if media_types else "", default=MessageType.AUDIO) + return self._resolve_media_message_type(media_types[0] if media_types else "", default=MessageType.VOICE) if preferred == "document": return self._resolve_media_message_type(media_types[0] if media_types else "", default=MessageType.DOCUMENT) return MessageType.TEXT diff --git a/scripts/release.py b/scripts/release.py index 5d4cb3eb82ff..65b9889e4a34 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -47,6 +47,7 @@ AUTHOR_MAP = { # teknium (multiple emails) "teknium1@gmail.com": "teknium1", + "linux2010@github.com": "Linux2010", "30366221+WorldWriter@users.noreply.github.com": "WorldWriter", "dafeng@DafengdeMacBook-Pro.local": "WorldWriter", "anadi.jaggia@gmail.com": "Jaggia", diff --git a/tests/gateway/test_feishu.py b/tests/gateway/test_feishu.py index 63287d88cb4b..94985e5b5cd4 100644 --- a/tests/gateway/test_feishu.py +++ b/tests/gateway/test_feishu.py @@ -1334,10 +1334,38 @@ def test_extract_audio_message_downloads_and_caches(self): text, msg_type, media_urls, media_types, _mentions = asyncio.run(adapter._extract_message_content(message)) self.assertEqual(text, "") - self.assertEqual(msg_type.value, "audio") + self.assertEqual(msg_type.value, "voice") self.assertEqual(media_urls, ["/tmp/feishu-audio.ogg"]) self.assertEqual(media_types, ["audio/ogg"]) + @patch.dict(os.environ, {}, clear=True) + def test_audio_message_maps_to_voice_for_stt(self): + """Feishu audio messages should map to MessageType.VOICE so they go + through the STT pipeline, not MessageType.AUDIO which is treated as + a file attachment. Regression test for #30822.""" + from gateway.config import PlatformConfig + from gateway.platforms.base import MessageType + from gateway.platforms.feishu import FeishuAdapter + + adapter = FeishuAdapter(PlatformConfig()) + adapter._download_feishu_message_resource = AsyncMock( + return_value=("/tmp/voice.ogg", "audio/ogg") + ) + message = SimpleNamespace( + message_type="audio", + content='{"file_key":"voice_key","file_name":"voice.ogg"}', + message_id="om_voice", + ) + + text, msg_type, media_urls, media_types, _mentions = asyncio.run( + adapter._extract_message_content(message) + ) + + # Must be VOICE (STT pipeline), not AUDIO (file attachment) + self.assertEqual(msg_type, MessageType.VOICE) + self.assertEqual(media_urls, ["/tmp/voice.ogg"]) + self.assertEqual(media_types, ["audio/ogg"]) + @patch.dict(os.environ, {}, clear=True) def test_extract_file_message_downloads_and_caches(self): from gateway.config import PlatformConfig