Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion plugins/platforms/feishu/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3683,7 +3683,15 @@ 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)
# Lark's native "audio" msg_type is an in-app voice recording, not
# an uploaded audio file (those arrive as "file"/"media" and are
# normalized to "document"). Classify it as VOICE so the gateway
# auto-transcribes it (Opus → STT) the same way
# Discord/DingTalk/Telegram/etc. do — otherwise a Feishu voice note
# reaches the agent as an untranscribable AUDIO attachment and is
# silently ignored. Follow-up to #28993, which added native
# voice-note transcription for Discord + DingTalk.
return MessageType.VOICE
if preferred == "document":
return self._resolve_media_message_type(media_types[0] if media_types else "", default=MessageType.DOCUMENT)
return MessageType.TEXT
Expand Down
6 changes: 5 additions & 1 deletion tests/gateway/test_feishu.py
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,11 @@ 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")
# Lark "audio" msg_type is a native voice recording (the fixture is
# literally voice.ogg) — it must classify as VOICE so the gateway
# auto-transcribes it, not AUDIO (a non-transcribed file attachment).
# See the #28993 follow-up fix in _resolve_normalized_message_type.
self.assertEqual(msg_type.value, "voice")
self.assertEqual(media_urls, ["/tmp/feishu-audio.ogg"])
self.assertEqual(media_types, ["audio/ogg"])

Expand Down
46 changes: 46 additions & 0 deletions tests/gateway/test_feishu_voice_message_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Regression tests for Feishu native voice-note classification.

Lark's native ``audio`` msg_type is an in-app voice recording (uploaded
audio files arrive as ``file``/``media`` and normalize to ``document``). It
must be classified as MessageType.VOICE so the gateway auto-transcribes it
(Opus → STT), the same way Discord/DingTalk/Telegram do. Before the fix it
resolved to MessageType.AUDIO, which the gateway treats as a non-transcribed
file attachment — so a Feishu voice note silently reached the agent as
untranscribable audio. Follow-up to #28993 (Discord + DingTalk).
"""

from gateway.platforms.base import MessageType
from plugins.platforms.feishu.adapter import FeishuAdapter, FeishuNormalizedMessage


def _resolve(preferred: str, media_types):
"""Call _resolve_normalized_message_type without a full adapter init.

The method only reads normalized.preferred_message_type and delegates to
the static _resolve_media_message_type — no instance state — so we bypass
__init__ (which needs Lark credentials/config) via __new__.
"""
adapter = FeishuAdapter.__new__(FeishuAdapter)
normalized = FeishuNormalizedMessage(
raw_type=preferred,
text_content="",
preferred_message_type=preferred,
)
return adapter._resolve_normalized_message_type(normalized, media_types)


def test_native_voice_audio_is_classified_as_voice():
"""Lark audio msg_type (voice recording) → VOICE, so it gets transcribed."""
assert _resolve("audio", ["audio/opus"]) is MessageType.VOICE


def test_native_voice_audio_without_media_type_is_voice():
"""A voice note with no resolved mime still classifies as VOICE."""
assert _resolve("audio", []) is MessageType.VOICE


def test_photo_and_document_unaffected():
"""The fix is scoped to the audio branch — other types are unchanged."""
assert _resolve("photo", ["image/png"]) is MessageType.PHOTO
assert _resolve("document", ["application/pdf"]) is MessageType.DOCUMENT
assert _resolve("text", []) is MessageType.TEXT
Loading