Skip to content

fix(feishu): include audio duration in voice message payload#35168

Open
hrygo wants to merge 1 commit into
NousResearch:mainfrom
hrygo:fix/feishu-audio-duration-async-safe
Open

fix(feishu): include audio duration in voice message payload#35168
hrygo wants to merge 1 commit into
NousResearch:mainfrom
hrygo:fix/feishu-audio-duration-async-safe

Conversation

@hrygo

@hrygo hrygo commented May 30, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Feishu's audio message API requires a positive duration field (ms) for uploaded audio to render as a playable voice bubble. Without it, the client falls back to a generic file attachment with a music-note icon.

_send_uploaded_file_message was sending only {"file_key": ...} for the audio routing branch, so every voice message — TTS output, MEDIA: deliveries — landed as a plain file attachment.

Related Issues

Fixes #16524, Fixes #8300

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests
  • ♻️ Refactor

Changes Made

  • gateway/platforms/feishu.py — new module-level _get_audio_duration_ms() helper with multi-tier fallback: mutagen → ffprobe (with shutil.which guard) → size-based heuristic → fixed 1s minimum. Always returns a positive int so the duration field is always populated.
  • gateway/platforms/feishu.py:_send_uploaded_file_message — only the audio routing branch now calls the probe via await asyncio.to_thread() to avoid blocking the event loop. Video, document, image, and post-with-caption payloads are unchanged.
  • tests/gateway/test_feishu.py — updated test_send_voice to assert a positive integer duration field; added test_send_video_does_not_inject_audio_duration_field regression test; added TestFeishuAudioDurationProbe covering real-file, missing-file, and size-heuristic monotonicity branches.

How This Differs From Existing PRs (#8631, #16736)

This PR fixes a blocking event-loop bug present in both existing PRs: _get_audio_duration_ms() performs blocking I/O (file reads, subprocess.run, os.path.getsize) inside async def _send_uploaded_file_message. The same function already wraps other blocking calls with await asyncio.to_thread(...) — this PR applies the same pattern consistently.

How to Test

pytest tests/gateway/test_feishu.py -q

Result on macOS 15 (Apple Silicon, Python 3.11): 209 passed, 0 failed.

Checklist

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate — see "How This Differs" above
  • My PR contains only changes related to this fix (no unrelated commits)
  • I've run pytest tests/gateway/test_feishu.py -q and all 209 tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 15 (Apple Silicon, Python 3.11)

Feishu's audio message API requires a positive `duration` field (ms)
for uploaded audio to render as a playable voice bubble with a pre-play
length indicator.  Without it, the client falls back to a generic file
attachment with a green music-note icon.

`_send_uploaded_file_message` was sending only `{"file_key": ...}`
for the audio routing branch, so every voice message — TTS output,
MEDIA: deliveries — landed as a plain file attachment.

Changes:
- New module-level `_get_audio_duration_ms()` helper with multi-tier
  fallback: mutagen → ffprobe (with shutil.which guard) → size-based
  heuristic → fixed 1s minimum.  Always returns a positive int.
- `_send_uploaded_file_message` now calls the probe via
  `await asyncio.to_thread()` to avoid blocking the event loop.
- Duration is only injected for the direct audio message path — the
  post/rich-text "media" tag has no duration field per Feishu API spec.
- Updated `test_send_voice` to assert a positive integer duration.
- Added `test_send_video_does_not_inject_audio_duration_field`
  regression test.
- Added `TestFeishuAudioDurationProbe` covering real-file, missing-file,
  and size-heuristic monotonicity branches.

Fixes NousResearch#16524, Fixes NousResearch#8300
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter comp/gateway Gateway runner, session dispatch, delivery labels May 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Competing with #8631 and #16736 — all add audio duration to Feishu voice payloads. This PR additionally wraps blocking I/O in asyncio.to_thread() to avoid blocking the event loop, which the other two don't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Include audio duration in Feishu voice message payload Feishu outbound audio upload omits duration, causing incorrect/0s duration display

3 participants