Skip to content

fix(feishu,dingtalk): upload local video replies instead of dropping them - #3299

Merged
zhayujie merged 1 commit into
zhayujie:masterfrom
Lesereingrape:fix/video-reply-dispatch
Sep 29, 2026
Merged

zhayujie merged 1 commit into
zhayujie:masterfrom
Lesereingrape:fix/video-reply-dispatch

Conversation

@Lesereingrape

Copy link
Copy Markdown
Contributor

Problem

ChatChannel delivers an agent-produced local video to every channel as
Reply(ReplyType.VIDEO, "file://" + path) (channel/chat_channel.py:414-419). Feishu
and DingTalk never dispatch that reply type, and the two symptoms differ:

  • Feishu keeps the defaults set above the branch chain (msg_type = "text",
    content_key = "text", channel/feishu/feishu_channel.py:899-902), so the message
    body the user receives is the literal server path:
    {"msg_type": "text", "content": "{\"text\": \"file:///tmp/clip.mp4\"}"}.
  • DingTalk falls to logger.warning("[DingTalk] Unsupported reply type: VIDEO, not sent") (channel/dingtalk/dingtalk_channel.py:1079). send() is its only outbound
    path, so the user receives nothing at all.

Fixes #3298

Change

Both channels already contain a working video send path — it is just not wired to the
reply type that says "this is a video":

  • Feishu _upload_video_url() (:1948) takes a file:// path and returns the
    file_key + duration pair, and the FILE branch already uses it with
    msg_type = "media" for .mp4 (:941-948).
  • DingTalk upload_media(..., media_type="video") + _send_file_message(..., "sampleVideo", ...) are already the video half of its FILE branch (:942-957).

So each channel needs its FILE branch to also accept VIDEO, and the video decision
must stop relying on the file name alone — the producer only guarantees the path
exists (os.path.exists(url)), so a generated clip can well be
/tmp/agent-output/4f2c… with no suffix, which today would be sent as a plain file:

-        elif reply.type == ReplyType.FILE:
+        elif reply.type in (ReplyType.FILE, ReplyType.VIDEO):
...
-            is_video = file_path.lower().endswith(('.mp4', '.avi', '.mov', '.wmv', '.flv'))
+            is_video = reply.type == ReplyType.VIDEO or file_path.lower().endswith(
+                ('.mp4', '.avi', '.mov', '.wmv', '.flv'))

FILE replies keep their existing extension sniffing, and a failed upload still
returns before anything is posted, so the path can no longer leak as text either.

The tests go into tests/test_local_video_reply.py, which is already the cross-channel
coverage file for this reply type (slack, telegram), plus one FILE regression case per
channel to pin the existing sniffing.

Verification

Red, on 208db92f without the production change:

$ pytest tests/test_local_video_reply.py -q
FAILED tests/test_local_video_reply.py::test_feishu_uploads_a_local_video_as_media
FAILED tests/test_local_video_reply.py::test_dingtalk_uploads_a_local_video_as_video
2 failed, 4 passed

The captured output is the reported defect itself: Feishu's POST body is
{"msg_type": "text", "content": "{\"text\": \"file:///srv/cow/tmp/clip\"}"}, and the
DingTalk log line is [DingTalk] Unsupported reply type: VIDEO, not sent. The two
FILE cases already passed before the change, which is why they show up in the 4.

Green:

  • pytest tests/test_local_video_reply.py -q → 6 passed
  • pytest tests/test_feishu_file_upload_url.py tests/test_feishu_file_audio_download_bounds.py tests/test_dingtalk_file_receive.py tests/test_discord_video_reply.py -q → 27 passed
  • ruff check channel/feishu/feishu_channel.py channel/dingtalk/dingtalk_channel.py tests/test_local_video_reply.py --select F --ignore F403,F405 → All checks passed
  • Full suite pytest tests/ -q: the FAILED set is byte-for-byte identical to the one
    on the 208db92f baseline (32 pre-existing failures, unrelated modules), so no
    regression is introduced here.

Scope

Confined to the two channels named above. It touches no file that #3297 (wechat_kf) or
the other open PRs change. #3297 notes that Feishu and DingTalk "have no video upload
path at all" and leaves them out; the line references above are the plumbing that is
already there — only the dispatch was missing — so this closes out the remaining two
channels of the same class.

…them

ChatChannel hands every channel an agent-produced video as
Reply(ReplyType.VIDEO, "file://" + path). Neither channel dispatched that type:
Feishu kept the default msg_type="text" and posted the raw server path, while
DingTalk logged "Unsupported reply type: VIDEO" and sent nothing at all.

Both channels already own the video send path -- _upload_video_url() with
msg_type="media" in the Feishu FILE branch, upload_media(media_type="video")
plus the sampleVideo key in the DingTalk one -- so route VIDEO into it. The
video decision no longer depends on the file suffix, because the producer only
guarantees the path exists.
@zhayujie
zhayujie merged commit bdcc7bf into zhayujie:master Sep 29, 2026
3 checks passed
@zhayujie

Copy link
Copy Markdown
Owner

Merged, thanks! With this, Feishu and DingTalk join Discord, Slack and Telegram in uploading local video replies, and the plain-file tests keep the existing path pinned.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feishu sends a local video reply's file:// path as text, DingTalk drops it silently

2 participants