[Feature] DingTalk channel improvements: file/image receive, streaming output & markdown cards, QR-code login
Background
Developer feedback on the DingTalk channel (channel/dingtalk/) has produced three improvement requests that are closely related — they all touch the same channel, the same DingTalk open-platform capabilities, and the same permission/compatibility constraints. They are tracked together here rather than as three separate issues.
- Receive files and images. The channel currently only parses a subset of incoming message types.
- Streaming output and markdown card rendering. Replies are sent once, after the model has finished, and card markdown is rendered inconsistently.
- QR-code login. Onboarding requires the user to copy credentials by hand from the DingTalk developer console.
Each item is described in detail below.
1. Support receiving file and image messages
Current behaviour
In channel/dingtalk/dingtalk_message.py, DingTalkMessage maps message_type as follows:
Incoming message_type |
Handling |
|---|---|
text |
ContextType.TEXT |
audio |
speech-to-text, then ContextType.TEXT |
picture |
ContextType.IMAGE (image downloaded to tmp) |
richText |
text + downloaded images attached as [图片: path] |
| anything else | falls through to ContextType.IMAGE with content [未找到图片] |
So image receive already works (single picture and images embedded in richText), but there is no branch for files.
Problem
- Files cannot be received. When a user sends a document (PDF/Word/Excel/zip/txt, ...) to the bot, DingTalk delivers it with a different
message_type(e.g.file) that has no branch inDingTalkMessage. It falls through to the genericelse, is mis-typed asContextType.IMAGE, and the content becomes[未找到图片]. The user's attachment is silently dropped and the agent has nothing to work with. - No download path for incoming files.
download_image_file()only handles picturedownloadCodes; there is no equivalent for files, so even if the type were recognised the file would not be fetched into the workspace tmp dir. (Compare: the outgoing side already uploads files viaupload_media(..., media_type="file")andsampleFile, so send works — only receive is missing.)
Expected behaviour
- Recognise the incoming file
message_type, download the attachment into the workspace tmp dir, and expose it to the agent (same pattern as images: cache it and attach a[文件: path]reference to the following text query, viachannel.file_cache). - Keep the existing behaviour for
text/audio/picture/richTextunchanged. - Images: verify the
picturepath against the current DingTalk Robot message spec and make sure the download works for bothdownloadCodeand direct URLs.
Acceptance criteria
- A file sent to the bot in both single chat and group chat is downloaded and made available to the agent.
- Images continue to work; unsupported types produce an explicit log line instead of a misleading
[未找到图片]. - Added/updated tests cover the new message-type mapping.
2. Streaming output and markdown card improvements
Current behaviour
The DingTalk channel sends replies in one of two ways (channel/dingtalk/dingtalk_channel.py):
- Default (
dingtalk_card_enabled = false):reply_text(reply.content, incoming_message)— a plain text message. - With
dingtalk_card_enabled = true:generate_button_markdown_content()builds a single markdown string which is posted once viareply_ai_markdown_button(...).
Either way the whole answer is produced and sent once, after the model has finished. There is no incremental delivery.
Problems
- No streaming output. Other channels already stream (see
agent/chat/service.pysend_chunk_fnand the Feishu progress-card path inchannel/feishu/). DingTalk users wait for the full answer with no feedback. For long answers this is a noticeably worse experience than Web / Feishu. - Markdown rendering is inconsistent.
generate_button_markdown_content()embedsreply.contentverbatim inside a fixed template. Notes:- Tables, nested lists, fenced code blocks and long lines are not guaranteed to render correctly in the AI card; DingTalk's markdown support is a subset of GitHub-flavoured markdown.
- When
dingtalk_card_enabledis false, the text is sent as plain text, so markdown is shown raw. - In group chat the card path also posts a
您有一条新的消息,请查看。notice; combined with the card this can read as duplicated content.
- Card status is never finalised.
CustomAICardReplier.start()setsflowStatus = AICardStatus.PROCESSINGbut nothing transitions it to the finished state, so a card may keep showing the "processing" indicator after the answer is complete.
Expected behaviour
- Stream the answer into the AI card incrementally (create card → append/update content → mark finished), mirroring the approach already used for Feishu.
- Render markdown in a way that is safe for DingTalk's supported subset (or degrade gracefully to plain text when a feature is unsupported).
- Properly close the card lifecycle (
flowStatus→ finished) so it does not stay in "processing".
Acceptance criteria
- With streaming enabled, the card updates as tokens arrive and is finalised when the turn ends.
- Markdown renders correctly for common cases (code blocks, lists, tables) or degrades explicitly.
- Card never remains in the
PROCESSINGstate after completion. - Behaviour with
dingtalk_card_enabled = falseis unchanged.
3. QR-code login / easier onboarding
Current behaviour
Setting up the DingTalk channel today requires the user to go through the DingTalk developer console by hand: create an app, open 凭证与基础信息, and copy Client ID and Client Secret into config.json (dingtalk_client_id / dingtalk_client_secret). See docs/zh/channels/dingtalk.mdx.
Problem
This manual flow is the main friction point for new users and a common source of setup mistakes (wrong credential pair, insufficient Robot permissions, missing stream-mode configuration). Other channels / clients in this project offer guided onboarding, so DingTalk is comparatively harder to get running.
Proposal
Provide a QR-code / scan-to-login onboarding path for the DingTalk channel:
- Generate a QR code that the user scans with the DingTalk mobile app, in order to authorise and provision the bot credentials automatically, instead of copying
Client ID/Client Secretby hand. - After authorisation, write the resulting credentials into the config the same way the manual flow does, so runtime behaviour is unchanged.
- Keep the existing manual configuration working as a fallback.
Acceptance criteria
- A user can complete DingTalk channel setup by scanning a QR code, without manually copying credentials.
- Existing manual
dingtalk_client_id/dingtalk_client_secretconfiguration keeps working. - Credentials are never printed to logs or committed to the repository.
Open question
Is this intended to be implemented inside the CowAgent runtime, or in the desktop / web console onboarding UI? Worth aligning before implementation.
Permissions / compatibility notes (apply to all three items)
- Permissions. Items 1 and 2 require the corresponding message / AI-card permissions to be enabled for the Robot in the DingTalk developer console; item 3 depends on the DingTalk open-platform authorisation flow available to the developer account. When a capability is unavailable, the channel must log an explicit error and fall back to the current behaviour rather than failing or silently dropping traffic.
- Compatibility. Existing behaviour must be preserved:
dingtalk_card_enabled = falsestays plain text, existing manual credential configuration keeps working, and already-configured installs must not break. New branches should be additive. - SDK. The incoming payload shape (
event.message_type,event.get_image_list(), ...) and the card update API are both defined by thedingtalk_streamSDK; the supported version should be verified and guarded/pinned accordingly. - Secrets. Credentials obtained through any of these paths are secrets: store them the same way as the manual ones, never log them, and never write them into git-tracked files.
Source: zhayujie/CowAgent