fix(dingtalk): adapt message handler to dingtalk-stream SDK CallbackMessage format#9609
fix(dingtalk): adapt message handler to dingtalk-stream SDK CallbackMessage format#9609audanye-sudo wants to merge 1 commit into
Conversation
…essage format The dingtalk-stream SDK passes CallbackMessage objects whose business fields (msgId, senderId, sessionWebhook, etc.) live inside message.data dict, not as top-level attributes. The existing code used getattr() on the top-level object, which always fell through to defaults — causing empty text, missing sender info, and broken reply routing. Key changes: - Extract message.data dict from CallbackMessage and parse fields from it - Rename _extract_text() to _extract_text_from_data() to work with data dict - Make _on_message handler and _CallbackHandler.process() async — the SDK's start() is a native coroutine, so no thread bridging is needed - Add diagnostic logging for session webhook routing
|
nice change |
|
Some context on this one — this is the second of three DingTalk PRs (#9608, this one, #9610). Why this mattersAs I mentioned in #9608, DingTalk is the largest enterprise messaging platform in China (700M+ users, think "Chinese Slack/Teams"). The current DingTalk adapter in Hermes has a critical bug that makes it completely non-functional in stream mode — which is the primary way DingTalk bots work. The core problemThe The result? Every single incoming message gets logged as "Empty message, skipping" and silently dropped. From the user's perspective: the bot is online, they send a message, and... nothing. No error, no response, just silence. What this PR does
Together with #9608 (webhook domain fix), these two PRs take DingTalk from "completely broken" to "actually works." They're independent commits but both are needed for a working end-to-end experience. |
RuckVibeCodes
left a comment
There was a problem hiding this comment.
[gus-first-pass] fix(dingtalk): adapt message handler to dingtalk-stream SDK CallbackMessage format - Clear fix, no issues found.
Jiangxuejian
left a comment
There was a problem hiding this comment.
This works on my WSL2. (Hermes Agent v0.9.0 (2026.4.13))
Summary
The DingTalk platform adapter fails to process any incoming messages in stream mode because it reads business fields (
msgId,senderId,sessionWebhook,text, etc.) from the wrong layer of the SDK's message object. This PR fixes the data extraction to match the actualdingtalk-streamSDK wire format.Root Cause
The
dingtalk-streamPython SDK delivers incoming messages asCallbackMessageobjects. The actual business payload (sender info, message text, conversation context, session webhook) is nested insidemessage.data(a dict/JSON string), not exposed as top-level attributes.The existing code used
getattr(message, "sender_id", ""),getattr(message, "text", ""), etc. — all of which silently fell through to empty defaults. The result:_extract_text()readmessage.text→ always emptymessage.session_webhook→ always emptymessage.sender_id,message.sender_nick→ defaultsAdditionally,
DingTalkStreamClient.start()is a native async coroutine in the current SDK, but the code wrapped it inasyncio.to_thread()as if it were blocking — this caused event loop conflicts and the_CallbackHandler.process()had to bridge between threads unnecessarily.Fix
Data extraction — Read all business fields from
message.datadict instead of top-level attributes:Async handling — Removed thread-bridging (
asyncio.to_thread,run_coroutine_threadsafe) sincestart()is already async:Diagnostic logging — Added
session_webhooklogging to aid future debugging of reply routing issues.Changed Methods
_on_message()message.datadict instead of top-level attrs_extract_text()→_extract_text_from_data()data: Dictinstead ofChatbotMessage_run_stream()await self._stream_client.start()directly (noto_thread)_CallbackHandler.process()async def; awaits_on_message()directlyCommits
fix(dingtalk): adapt message handler to dingtalk-stream SDK CallbackMessage formatgateway/platforms/dingtalk.pyTest Plan
CallbackMessage.datadict contains all expected fields (msgId,senderId,senderNick,text,sessionWebhook,conversationId,conversationType,conversationTitle,createAt,senderStaffId,richText)richTextfield) still worksmsgIdstill functions correctly_stream_client.start()runs as native coroutine without event loop conflictssession_webhookandchat_idvaluesRisk Assessment
Medium risk — changes the core message processing path for the DingTalk adapter. However:
dataextraction falls back to{}if the attribute is missing, andgetattrfallbacks remain formessage_idRecommendation: This PR should be merged together with the webhook domain fix PR (which adds
oapi.dingtalk.comto the allowed webhook regex), as both are required for end-to-end DingTalk stream functionality.