fix(gateway/dingtalk): support dingtalk-stream >= 0.24#8954
Closed
sputnicyoji wants to merge 1 commit into
Closed
Conversation
The adapter was written against an older dingtalk-stream release and no longer runs on the current PyPI version: - `DingTalkStreamClient.start()` became a coroutine; wrapping it in `asyncio.to_thread` raises `RuntimeWarning: coroutine ... was never awaited` and the stream never connects. Detect via `iscoroutinefunction` and await or thread-dispatch accordingly. - `ChatbotHandler.process()` became an async callback that receives a `CallbackMessage` (raw envelope), not a `ChatbotMessage`. Make the handler async and parse `callback.data` into `ChatbotMessage` via `from_dict`. Drops the now-unused `run_coroutine_threadsafe` plumbing and `_loop` field. - `ChatbotMessage.text` is now a `TextContent` dataclass; the old `str(text).strip()` path produces `<TextContent object at 0x...>`. Check `hasattr(text, "content")` first, keep dict/str fallbacks for older SDKs and custom handlers. Tests stay green against the existing test doubles (dict-shaped `text`). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 13, 2026
sputnicyoji
pushed a commit
to sputnicyoji/hermes-agent
that referenced
this pull request
Apr 13, 2026
…ync process handler # Conflicts: # gateway/platforms/dingtalk.py
sputnicyoji
pushed a commit
to sputnicyoji/hermes-agent
that referenced
this pull request
Apr 13, 2026
Previously the adapter replaced any `picture` or inline `richText` image with the literal string `[图片]`; vision tools therefore never saw the actual bytes. This patch exchanges each `downloadCode` for the temporary CDN URL via `/v1.0/robot/messageFiles/download`, fetches the bytes, and caches them locally so `MessageEvent.media_urls` points at a real file. Implementation notes: - Piggyback on the dingtalk-stream SDK: `register_callback_handler` wires `handler.dingtalk_client = stream_client`, which gives us the SDK's access-token helper and `get_image_download_url`. Keep the handler reference on the adapter (`self._handler`). - `get_image_download_url` is sync (requests-based) — offload via `asyncio.to_thread` to avoid blocking the event loop. - Failures at any stage (missing code, empty URL, HTTP error) are logged but degrade gracefully: the event still dispatches with the `[图片]` text placeholder so the agent knows something arrived. - `message.get_image_list()` handles both `picture` and `richText` shapes, so the same path supports inline images in rich messages. - On successful download the placeholder text is replaced with `[图片 × N]` as a short caption alongside the attached media list. Tests cover: missing handler, single-image success, empty downloadUrl, partial HTTP failure (continue on remaining codes), and full end-to-end picture dispatch through `_on_message`. Depends on NousResearch#8954 (SDK 0.24 async process) and NousResearch#8960 (msgtype dispatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 tasks
sputnicyoji
pushed a commit
to sputnicyoji/hermes-agent
that referenced
this pull request
Apr 13, 2026
Previously the adapter replaced any `picture` or inline `richText` image with the literal string `[图片]`; vision tools therefore never saw the actual bytes. This patch exchanges each `downloadCode` for the temporary CDN URL via `/v1.0/robot/messageFiles/download`, fetches the bytes, and caches them locally so `MessageEvent.media_urls` points at a real file. Implementation notes: - Piggyback on the dingtalk-stream SDK: `register_callback_handler` wires `handler.dingtalk_client = stream_client`, which gives us the SDK's access-token helper and `get_image_download_url`. Keep the handler reference on the adapter (`self._handler`). - `get_image_download_url` is sync (requests-based) — offload via `asyncio.to_thread` to avoid blocking the event loop. - Failures at any stage (missing code, empty URL, HTTP error) are logged but degrade gracefully: the event still dispatches with the `[图片]` text placeholder so the agent knows something arrived. - `message.get_image_list()` handles both `picture` and `richText` shapes, so the same path supports inline images in rich messages. - On successful download the placeholder text is replaced with `[图片 × N]` as a short caption alongside the attached media list. Tests cover: missing handler, single-image success, empty downloadUrl, partial HTTP failure (continue on remaining codes), and full end-to-end picture dispatch through `_on_message`. Depends on NousResearch#8954 (SDK 0.24 async process) and NousResearch#8960 (msgtype dispatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
👍🏻 works for me |
sputnicyoji
pushed a commit
to sputnicyoji/hermes-agent
that referenced
this pull request
Apr 15, 2026
Previously the adapter replaced any `picture` or inline `richText` image with the literal string `[图片]`; vision tools therefore never saw the actual bytes. This patch exchanges each `downloadCode` for the temporary CDN URL via `/v1.0/robot/messageFiles/download`, fetches the bytes, and caches them locally so `MessageEvent.media_urls` points at a real file. Implementation notes: - Piggyback on the dingtalk-stream SDK: `register_callback_handler` wires `handler.dingtalk_client = stream_client`, which gives us the SDK's access-token helper and `get_image_download_url`. Keep the handler reference on the adapter (`self._handler`). - `get_image_download_url` is sync (requests-based) — offload via `asyncio.to_thread` to avoid blocking the event loop. - Failures at any stage (missing code, empty URL, HTTP error) are logged but degrade gracefully: the event still dispatches with the `[图片]` text placeholder so the agent knows something arrived. - `message.get_image_list()` handles both `picture` and `richText` shapes, so the same path supports inline images in rich messages. - On successful download the placeholder text is replaced with `[图片 × N]` as a short caption alongside the attached media list. Tests cover: missing handler, single-image success, empty downloadUrl, partial HTTP failure (continue on remaining codes), and full end-to-end picture dispatch through `_on_message`. Depends on NousResearch#8954 (SDK 0.24 async process) and NousResearch#8960 (msgtype dispatch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
|
Closing as superseded by #11471 (#11471) which salvaged @kevinskysunny's minimal fix (#11257) and added a follow-up for the broken Thanks for the fix — a lot of contributors hit this SDK break at the same time. Your investigation helped confirm the root cause. |
Author
|
Thanks for the update and for getting the compatibility fix into main via #11471. Happy to see it resolved. Appreciate the review. |
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.
Summary
The DingTalk adapter was written against an older
dingtalk-streamrelease and no longer runs on the current PyPI version (>= 0.24). Three API changes need to be absorbed:DingTalkStreamClient.start()became a coroutine. Wrapping it inasyncio.to_threadraisesRuntimeWarning: coroutine ... was never awaitedand the stream never connects. Detect viaiscoroutinefunctionand await or thread-dispatch accordingly (keeps backward compatibility with older SDKs).ChatbotHandler.process()became an async callback and now receives aCallbackMessage(raw envelope) instead of aChatbotMessage. Make the handler async and parsecallback.dataintoChatbotMessageviafrom_dict. Drops the now-unusedrun_coroutine_threadsafeplumbing and_loopfield.ChatbotMessage.textis now aTextContentdataclass. The oldstr(text).strip()path produces<TextContent object at 0x...>. Checkhasattr(text, "content")first, keep dict/str fallbacks for older SDKs and custom handlers.Test plan
tests/gateway/test_dingtalk.pysuite passes (21 tests) — legacy dict-shapedtextmocks continue to work via the fallback path.dingtalk-stream==0.24.xwith a group-chat bot: connect, receivetext, and reply successfully.