hermes - 💡(How to fix) Fix [Feishu] send_message MEDIA attachments silently dropped; send_voice sends file attachment instead of native audio bubble [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#18831Fetched 2026-05-03 04:54:02
View on GitHub
Comments
2
Participants
2
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×5commented ×2

Root Cause

In tools/send_message_tool.py, _send_to_platform() routes Feishu to the plain-text branch (line 626-627) instead of the media-aware branch. Compare:

# Telegram (line 503-520) — passes media_files:
if platform == Platform.TELEGRAM:
    result = await _send_telegram(..., media_files=media_files if is_last else [], ...)

# Discord (527-541) — passes media_files:
if platform == Platform.DISCORD:
    result = await _send_discord(..., media_files=media_files if is_last else [], ...)

# Feishu (626-627) — NO media_files passed:
elif platform == Platform.FEISHU:
    result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)

In gateway/platforms/feishu.py, send_voice() (line ~1822) calls _send_uploaded_file_message() with outbound_message_type="file" instead of using Feishu's native audio API.


Code Example

# tools/send_message_tool.py line 626-627
# Feishu falls through to this branch — media_files is NEVER passed:
elif platform == Platform.FEISHU:
    result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)

---

# Telegram (line 503-520) — passes media_files:
if platform == Platform.TELEGRAM:
    result = await _send_telegram(..., media_files=media_files if is_last else [], ...)

# Discord (527-541) — passes media_files:
if platform == Platform.DISCORD:
    result = await _send_discord(..., media_files=media_files if is_last else [], ...)

# Feishu (626-627)NO media_files passed:
elif platform == Platform.FEISHU:
    result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)

---

# Add after the Discord block:
if platform == Platform.FEISHU and media_files:
    last_result = None
    for i, chunk in enumerate(chunks):
        is_last = (i == len(chunks) - 1)
        result = await _send_feishu(
            pconfig, chat_id, chunk,
            thread_id=thread_id,
            media_files=media_files if is_last else [],
        )
        ...

---

async def send_audio_message(self, chat_id: str, audio_path: str, ...) -> SendResult:
    # Step 1: Upload as opus file → get file_key
    with open(audio_path, "rb") as f:
        audio_data = f.read()
    audio_io = io.BytesIO(audio_data)
    audio_io.name = os.path.basename(audio_path)
    
    file_req = CreateFileRequest.builder().request_body(
        CreateFileRequestBody.builder()
        .file_type("opus")
        .file_name(os.path.basename(audio_path))
        .duration(duration_ms)
        .file(audio_io).build()
    ).build()
    upload_result = await asyncio.to_thread(self._client.im.v1.file.create, file_req)
    file_key = upload_result.data.file_key
    
    # Step 2: Send as native audio message
    content = json.dumps({"file_key": file_key})
    msg_req = CreateMessageRequest.builder()
        .receive_id_type("chat_id")
        .request_body(
            CreateMessageRequestBody.builder()
            .receive_id(chat_id)
            .msg_type("audio")
            .content(content)
            .build()
        ).build()
    result = await asyncio.to_thread(self._client.im.v1.message.create, msg_req)
    ...
RAW_BUFFERClick to expand / collapse

Bug Description

Two related issues in Feishu audio/voice message handling:

  1. send_message(target="feishu:...", message="MEDIA:/path/to.mp3") silently drops audio attachments — they are never sent
  2. FeishuAdapter.send_voice() internally calls _send_uploaded_file_message() which sends msg_type: "file" — the recipient receives an MP3 file attachment, NOT a native Feishu audio bubble that plays inline

Reproduction Steps

Issue 1: MEDIA attachments dropped

# tools/send_message_tool.py line 626-627
# Feishu falls through to this branch — media_files is NEVER passed:
elif platform == Platform.FEISHU:
    result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)

All other media-capable platforms (Telegram, Discord, Matrix, Signal, Weixin, Yuanbao) explicitly pass media_files=... in _send_to_platform. Feishu does not.

Issue 2: send_voice sends file, not audio bubble

_send_uploaded_file_message sends with outbound_message_type="file" (the default). Feishu native audio messages require:

  1. Upload via POST /im/v1/files with file_type="opus" → get file_key
  2. Send via POST /im/v1/messages with msg_type="audio" and content={"file_key": "..."}

Root Cause

In tools/send_message_tool.py, _send_to_platform() routes Feishu to the plain-text branch (line 626-627) instead of the media-aware branch. Compare:

# Telegram (line 503-520) — passes media_files:
if platform == Platform.TELEGRAM:
    result = await _send_telegram(..., media_files=media_files if is_last else [], ...)

# Discord (527-541) — passes media_files:
if platform == Platform.DISCORD:
    result = await _send_discord(..., media_files=media_files if is_last else [], ...)

# Feishu (626-627) — NO media_files passed:
elif platform == Platform.FEISHU:
    result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)

In gateway/platforms/feishu.py, send_voice() (line ~1822) calls _send_uploaded_file_message() with outbound_message_type="file" instead of using Feishu's native audio API.


Proposed Fix

Fix 1: Pass media_files to _send_feishu

In _send_to_platform(), add a Feishu media branch similar to Telegram/Discord:

# Add after the Discord block:
if platform == Platform.FEISHU and media_files:
    last_result = None
    for i, chunk in enumerate(chunks):
        is_last = (i == len(chunks) - 1)
        result = await _send_feishu(
            pconfig, chat_id, chunk,
            thread_id=thread_id,
            media_files=media_files if is_last else [],
        )
        ...

Fix 2: Add native audio support to FeishuAdapter

Add a new method that uses the two-step Feishu audio API:

async def send_audio_message(self, chat_id: str, audio_path: str, ...) -> SendResult:
    # Step 1: Upload as opus file → get file_key
    with open(audio_path, "rb") as f:
        audio_data = f.read()
    audio_io = io.BytesIO(audio_data)
    audio_io.name = os.path.basename(audio_path)
    
    file_req = CreateFileRequest.builder().request_body(
        CreateFileRequestBody.builder()
        .file_type("opus")
        .file_name(os.path.basename(audio_path))
        .duration(duration_ms)
        .file(audio_io).build()
    ).build()
    upload_result = await asyncio.to_thread(self._client.im.v1.file.create, file_req)
    file_key = upload_result.data.file_key
    
    # Step 2: Send as native audio message
    content = json.dumps({"file_key": file_key})
    msg_req = CreateMessageRequest.builder()
        .receive_id_type("chat_id")
        .request_body(
            CreateMessageRequestBody.builder()
            .receive_id(chat_id)
            .msg_type("audio")
            .content(content)
            .build()
        ).build()
    result = await asyncio.to_thread(self._client.im.v1.message.create, msg_req)
    ...

Then update _send_feishu() to call send_audio_message for audio files.


Environment

  • hermes-agent v0.12.0
  • Feishu adapter (WebSocket mode)
  • Python 3.11 / lark-oapi

extent analysis

TL;DR

To fix the Feishu audio/voice message handling issues, update the _send_to_platform() function to pass media_files to _send_feishu() and add native audio support to FeishuAdapter by implementing a two-step upload and send process.

Guidance

  • Update the _send_to_platform() function to include a Feishu media branch, similar to Telegram and Discord, to pass media_files to _send_feishu().
  • Implement a new method in FeishuAdapter to handle native audio messages by uploading the audio file as "opus" and then sending it as a native audio message.
  • Verify that the updated code correctly sends audio attachments and plays them inline in Feishu.
  • Test the changes with different audio file formats and sizes to ensure compatibility.

Example

async def send_audio_message(self, chat_id: str, audio_path: str, ...) -> SendResult:
    # Upload audio file as "opus"
    file_req = CreateFileRequest.builder().request_body(
        CreateFileRequestBody.builder()
        .file_type("opus")
        .file_name(os.path.basename(audio_path))
        .duration(duration_ms)
        .file(audio_io).build()
    ).build()
    upload_result = await asyncio.to_thread(self._client.im.v1.file.create, file_req)
    file_key = upload_result.data.file_key
    
    # Send as native audio message
    content = json.dumps({"file_key": file_key})
    msg_req = CreateMessageRequest.builder()
        .receive_id_type("chat_id")
        .request_body(
            CreateMessageRequestBody.builder()
            .receive_id(chat_id)
            .msg_type("audio")
            .content(content)
            .build()
        ).build()
    result = await asyncio.to_thread(self._client.im.v1.message.create, msg_req)
    ...

Notes

The proposed fix assumes that the

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix [Feishu] send_message MEDIA attachments silently dropped; send_voice sends file attachment instead of native audio bubble [2 comments, 2 participants]