fix(feishu): include audio duration in voice message payload#35168
Open
hrygo wants to merge 1 commit into
Open
fix(feishu): include audio duration in voice message payload#35168hrygo wants to merge 1 commit into
hrygo wants to merge 1 commit into
Conversation
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
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Feishu's audio message API requires a positive
durationfield (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_messagewas 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
Changes Made
gateway/platforms/feishu.py— new module-level_get_audio_duration_ms()helper with multi-tier fallback: mutagen → ffprobe (withshutil.whichguard) → size-based heuristic → fixed 1s minimum. Always returns a positive int so thedurationfield is always populated.gateway/platforms/feishu.py:_send_uploaded_file_message— only theaudiorouting branch now calls the probe viaawait asyncio.to_thread()to avoid blocking the event loop. Video, document, image, and post-with-caption payloads are unchanged.tests/gateway/test_feishu.py— updatedtest_send_voiceto assert a positive integerdurationfield; addedtest_send_video_does_not_inject_audio_duration_fieldregression test; addedTestFeishuAudioDurationProbecovering 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) insideasync def _send_uploaded_file_message. The same function already wraps other blocking calls withawait asyncio.to_thread(...)— this PR applies the same pattern consistently.How to Test
Result on macOS 15 (Apple Silicon, Python 3.11): 209 passed, 0 failed.
Checklist
pytest tests/gateway/test_feishu.py -qand all 209 tests pass