Skip to content

fix(tts): include feishu in want_opus platforms - #45761

Closed
Asanilo wants to merge 2 commits into
NousResearch:mainfrom
Asanilo:fix/feishu-want-opus-clean
Closed

fix(tts): include feishu in want_opus platforms#45761
Asanilo wants to merge 2 commits into
NousResearch:mainfrom
Asanilo:fix/feishu-want-opus-clean

Conversation

@Asanilo

@Asanilo Asanilo commented Jun 13, 2026

Copy link
Copy Markdown

What does this PR do?

Extends want_opus platform set to include feishu alongside telegram, so minimax TTS on Feishu converts mp3 → ogg and gets delivered as voice bubble instead of file attachment.

Related Issue

Fixes #45557

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • tools/tts_tool.py: changed want_opus = (platform == 'telegram')want_opus = platform in {'telegram', 'feishu'}
  • tests/tools/test_tts_opus_routing.py: added test_minimax_feishu_converts_to_opus_voice

How to Test

  1. Send a TTS message via Feishu DM with minimax TTS provider
  2. Confirm message arrives as voice bubble (not mp3 attachment)
  3. Run: PYTHONPATH=. venv/bin/python -m pytest tests/tools/test_tts_opus_routing.py -v — all 3 tests pass

Checklist

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(tts):)
  • I've searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run PYTHONPATH=. venv/bin/python -m pytest tests/tools/test_tts_opus_routing.py -q — all pass
  • I've added tests for my changes

@alt-glitch alt-glitch added type/bug Something isn't working tool/tts Text-to-speech and transcription platform/feishu Feishu / Lark adapter comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jun 13, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Duplicate of #45637 — identical one-line want_opus Feishu fix for #45557 (earliest still-open PR with this approach; #45555 is closed).

@imjoey

imjoey commented Jun 15, 2026

Copy link
Copy Markdown

Great fix — the want_opus guard is clearly the right place to start. I was looking at the same issue and noticed a sibling bug in the Feishu adapter that this PR could also address (or leave as a follow-up).

The problem: send_voice() already passes outbound_message_type="audio" to _send_uploaded_file_message(), but _resolve_outbound_file_routing() silently ignores it for non-Opus audio files:

# feishu.py:4861
def _resolve_outbound_file_routing(file_path, requested_message_type):
    ext = Path(file_path).suffix.lower()
    if ext in _FEISHU_OPUS_UPLOAD_EXTENSIONS:    # {.ogg, .opus}
        return "opus", "audio"
    if ext in _FEISHU_MEDIA_UPLOAD_EXTENSIONS:    # {.mp4, .mov, ...}
        return "mp4", "media"
    if ext in _FEISHU_DOC_UPLOAD_TYPES:
        return _FEISHU_DOC_UPLOAD_TYPES[ext], "file"
    if requested_message_type == "file":          # ← only checks "file"
        return _FEISHU_FILE_UPLOAD_TYPE, "file"
    return _FEISHU_FILE_UPLOAD_TYPE, "file"        # ← .mp3/.wav/.m4a fall through here

So even with this PR's want_opus fix, if ffmpeg is unavailable and the mp3→ogg conversion in tts_tool.py fails, the audio file still gets routed as msg_type=file instead of msg_type=audio — same user-facing symptom (file attachment instead of voice bubble).

Suggested addition (after the Opus check):

if requested_message_type == "audio" and ext in _AUDIO_EXTENSIONS:
    return "stream", "audio"

This makes the requested_message_type parameter actually meaningful for audio delivery, regardless of whether the Opus conversion succeeded.

Happy to help test or split this into a separate PR if you'd prefer to keep this one focused. Either way, the want_opus fix here is correct and necessary 👍

Asanilo added 2 commits June 15, 2026 11:47
_requested_message_type_ was being ignored for non-Opus audio files
(MP3/WAV/M4A).  When send_voice() passes outbound_message_type='audio',
_resolve_outbound_file_routing() still fell through to msg_type=file
because it only checked the file extension against _FEISHU_OPUS_UPLOAD_EXTENSIONS.

Feishu's im.v1.file.create accepts any audio uploaded as file_type=opus
without codec validation — MP3/WAV/M4A all render as voice bubbles when
sent with msg_type=audio.  Tested end-to-end against the live API.

Giving requested_message_type='audio' priority over the extension check
fixes the fallback and makes the parameter meaningful.

Co-authored-by: <reviewer>@users.noreply.github.com
@Asanilo
Asanilo force-pushed the fix/feishu-want-opus-clean branch from f4b0bb3 to 89749a8 Compare June 15, 2026 03:52
@Asanilo

Asanilo commented Jun 15, 2026

Copy link
Copy Markdown
Author

Great fix — the want_opus guard is clearly the right place to start. I was looking at the same issue and noticed a sibling bug in the Feishu adapter that this PR could also address (or leave as a follow-up).

The problem: send_voice() already passes outbound_message_type="audio" to _send_uploaded_file_message(), but _resolve_outbound_file_routing() silently ignores it for non-Opus audio files:

# feishu.py:4861
def _resolve_outbound_file_routing(file_path, requested_message_type):
    ext = Path(file_path).suffix.lower()
    if ext in _FEISHU_OPUS_UPLOAD_EXTENSIONS:    # {.ogg, .opus}
        return "opus", "audio"
    if ext in _FEISHU_MEDIA_UPLOAD_EXTENSIONS:    # {.mp4, .mov, ...}
        return "mp4", "media"
    if ext in _FEISHU_DOC_UPLOAD_TYPES:
        return _FEISHU_DOC_UPLOAD_TYPES[ext], "file"
    if requested_message_type == "file":          # ← only checks "file"
        return _FEISHU_FILE_UPLOAD_TYPE, "file"
    return _FEISHU_FILE_UPLOAD_TYPE, "file"        # ← .mp3/.wav/.m4a fall through here

So even with this PR's want_opus fix, if ffmpeg is unavailable and the mp3→ogg conversion in tts_tool.py fails, the audio file still gets routed as msg_type=file instead of msg_type=audio — same user-facing symptom (file attachment instead of voice bubble).

Suggested addition (after the Opus check):

if requested_message_type == "audio" and ext in _AUDIO_EXTENSIONS:
    return "stream", "audio"

This makes the requested_message_type parameter actually meaningful for audio delivery, regardless of whether the Opus conversion succeeded.

Happy to help test or split this into a separate PR if you'd prefer to keep this one focused. Either way, the want_opus fix here is correct and necessary 👍

Good catch on _resolve_outbound_file_routing — I checked and you're right, non-Opus audio files fall through to msg_type=file even when send_voice() passes outbound_message_type="audio".
I tested return "stream", "audio" against the live Feishu API though, and it doesn't work. Feishu returns error 230055 — "The type of file upload does not match the type of message being sent." The file upload itself succeeds, but sending with msg_type=audio is rejected because the file was uploaded as stream, not opus.
What does work: uploading any audio file as file_type=opus and sending with msg_type=audio. Feishu doesn't validate the actual codec, it just trusts the marker. I tested MP3, WAV, and M4A end-to-end — all three render as voice bubbles.
So the fix should give requested_message_type priority over the extension check:
if requested_message_type == "audio":

return "opus", "audio"

I'll push this to the PR along with the want_opus fix since they're the same bug chain. Thanks for pointing it out.

@alt-glitch alt-glitch added P2 Medium — degraded but workaround exists and removed P3 Low — cosmetic, nice to have labels Jun 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #45712 — identical one-line want_opus Feishu fix for #45557, and #45712 is the earliest still-open PR with this approach (the earlier #45637 and #45555 cited above are now closed). Broader superset #51819 additionally covers Lark. Flagging the open canonical for the maintainer to pick.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for covering both the TTS conversion path and the no-ffmpeg fallback. The premise remains valid on current main: tools/tts_tool.py:2202 still limits want_opus to Telegram, while plugins/platforms/feishu/adapter.py:2174 requests audio but its resolver falls back to a stream/file route at :5025-5037 for non-Opus files.

Problems

  • The PR changes the pre-plugin path gateway/platforms/feishu.py. Current main moved this adapter to plugins/platforms/feishu/adapter.py; git apply --check fails because the old file is absent. The tests need the same import/patch-target migration.
  • website/docs/user-guide/messaging/feishu.md:404-413 currently documents send_voice as a file attachment with extension-only routing. It should be updated with the salvaged behavior.

Suggested changes

  • Transplant the adapter override and MP3 regression coverage to the current plugin path.
  • Update the English and zh-Hans Feishu media docs to match native voice delivery.

Automated hermes-sweeper review.

@alt-glitch alt-glitch added comp/plugins Plugin system and bundled plugins sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have and removed duplicate This issue or pull request already exists comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels Jul 14, 2026
@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@alt-glitch alt-glitch added comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists and removed comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation labels Jul 14, 2026
@teknium1

Copy link
Copy Markdown
Contributor

The root cause here (gateway auto-TTS choosing MP3 vs Ogg/Opus via the cleared HERMES_SESSION_PLATFORM contextvar, so opus platforms got audio attachments instead of native voice bubbles) was fixed class-wide in #73508: platform-awareness now comes from the caller via build_auto_tts_output_path(platform) keyed off OPUS_VOICE_PLATFORMS (based on @giladbau's #62040), with the central container repair guaranteeing real Ogg/Opus bytes.

(Landed via #73508, merge f440a44753.) Closing.

@teknium1 teknium1 closed this Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

minimax TTS on Feishu sends mp3 as file attachment instead of voice bubble

4 participants