Skip to content

fix(weixin): streaming cursor, media uploads, markdown links, blank messages - #8665

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-f37b1d3f
Apr 12, 2026
Merged

fix(weixin): streaming cursor, media uploads, markdown links, blank messages#8665
teknium1 merged 1 commit into
mainfrom
hermes/hermes-f37b1d3f

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Four fixes for the Weixin/WeChat adapter, synthesized from the best aspects of ~25 community PRs.

1. Streaming cursor (▉) stuck permanently

WeChat doesn't support message editing, so the cursor appended during streaming can never be removed. Adds SUPPORTS_MESSAGE_EDITING = False to WeixinAdapter and checks it in gateway/run.py to suppress the cursor for non-editable platforms.

Fixes: #8307, #8326

2. Media upload failures (grey boxes, 404s)

Two bugs in _send_file():

  • upload_full_url path used PUT → 404 on WeChat CDN. Now uses POST.
  • aes_key was base64(raw_bytes) but the iLink API expects base64(hex_string) — images showed as grey boxes on the receiver side.

Also: unified both upload paths into _upload_ciphertext() (prefers upload_full_url), added send_video/send_voice methods, voice_item media builder for audio/.silk files, and video_md5 field.

Fixes: #8352, #7529

3. Markdown links stripped

WeChat can't render [text](url) markdown links. format_message() now converts them to text (url) plaintext. Links inside code blocks are preserved.

Fixes: #7617

4. Blank message prevention

Three layered guards:

  1. _split_text_for_weixin_delivery('')[] not ['']
  2. send() filters empty/whitespace chunks before _send_text_chunk
  3. _send_message() raises ValueError for empty text as safety net

Files changed

  • gateway/platforms/weixin.py — all four fixes
  • gateway/run.py — cursor suppression for non-editable platforms
  • tests/gateway/test_weixin.py — 15 new tests (34 total, all pass)

Test plan

python -m pytest tests/gateway/test_weixin.py -v          # 34 passed
python -m pytest tests/gateway/test_stream_consumer.py -v # all pass
python -m pytest tests/gateway/test_wecom.py -v           # 40 pass (sibling adapter)

Community credit

Synthesized from: joei4cm (#8407), lyonDan (#8521), SKFDJKLDG (#8360), tomqiaozc (#7695), joshleeeeee (#8308), luoxiao6645 (#8525), longsizhuo (#7531), Astral-Yang (#8144), QingWei-Li (#8251)

Stale PRs to close after merge

The following PRs are either fully covered by this change or were fixing the same bugs:

…essages

Four fixes for the Weixin/WeChat adapter, synthesized from the best
aspects of community PRs #8407, #8521, #8360, #7695, #8308, #8525,
#7531, #8144, #8251.

1. Streaming cursor (▉) stuck permanently — WeChat doesn't support
   message editing, so the cursor appended during streaming can never
   be removed.  Add SUPPORTS_MESSAGE_EDITING = False to WeixinAdapter
   and check it in gateway/run.py to use an empty cursor for non-edit
   platforms.  (Fixes #8307, #8326)

2. Media upload failures — two bugs in _send_file():
   a) upload_full_url path used PUT (404 on WeChat CDN); now uses POST.
   b) aes_key was base64(raw_bytes) but the iLink API expects
      base64(hex_string); images showed as grey boxes.  (Fixes #8352, #7529)
   Also: unified both upload paths into _upload_ciphertext(), preferring
   upload_full_url.  Added send_video/send_voice methods and voice_item
   media builder for audio/.silk files.  Added video_md5 field.

3. Markdown links stripped — WeChat can't render [text](url), so
   format_message() now converts them to 'text (url)' plaintext.
   Code blocks are preserved.  (Fixes #7617)

4. Blank message prevention — three guards:
   a) _split_text_for_weixin_delivery('') returns [] not ['']
   b) send() filters empty/whitespace chunks before _send_text_chunk
   c) _send_message() raises ValueError for empty text as safety net

Community credit: joei4cm (#8407), lyonDan (#8521), SKFDJKLDG (#8360),
tomqiaozc (#7695), joshleeeeee (#8308), luoxiao6645(#8525),
longsizhuo (#7531), Astral-Yang (#8144), QingWei-Li (#8251).
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: base64 encoding/decoding detected

Base64 has legitimate uses (images, JWT, etc.) but is also commonly used to obfuscate malicious payloads. Verify the usage is appropriate.

Matches (first 20):

342:+        aes_key_for_api = base64.b64encode(aes_key.hex().encode("ascii")).decode("ascii")
1575:+        expected_aes = base64.b64encode(fake_hex_key.encode("ascii")).decode("ascii")

⚠️ WARNING: Install hook files modified

These files can execute code during package installation or interpreter startup.

Files:

hermes_cli/setup.py
tests/hermes_cli/test_setup.py

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1
teknium1 merged commit a266238 into main Apr 12, 2026
2 of 4 checks passed
@teknium1
teknium1 deleted the hermes/hermes-f37b1d3f branch April 12, 2026 23:43
This was referenced Apr 12, 2026
@longsizhuo

longsizhuo commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Thanks for the consolidation, really clean way to handle the overlapping PRs.

Would it be possible to amend the merge commit with Co-authored-by trailers for the community contributors whose code was synthesized? GitHub only picks up contributor attribution from commit metadata, not from PR body text, so without the trailers none of us show up in the repo's contributor graph.

For my part:

Co-authored-by: longsizhuo <longsizhuo@gmail.com>

Totally understand if it's too much hassle to rewrite a merged commit though, just figured I'd ask. Thanks again!

vorngocnnguyene-hash pushed a commit to vorngocnnguyene-hash/hermes-agent that referenced this pull request Apr 13, 2026
aj-nt pushed a commit to aj-nt/hermes-agent that referenced this pull request May 1, 2026
…essages (NousResearch#8665)

Four fixes for the Weixin/WeChat adapter, synthesized from the best
aspects of community PRs NousResearch#8407, NousResearch#8521, NousResearch#8360, NousResearch#7695, NousResearch#8308, NousResearch#8525,
NousResearch#7531, NousResearch#8144, NousResearch#8251.

1. Streaming cursor (▉) stuck permanently — WeChat doesn't support
   message editing, so the cursor appended during streaming can never
   be removed.  Add SUPPORTS_MESSAGE_EDITING = False to WeixinAdapter
   and check it in gateway/run.py to use an empty cursor for non-edit
   platforms.  (Fixes NousResearch#8307, NousResearch#8326)

2. Media upload failures — two bugs in _send_file():
   a) upload_full_url path used PUT (404 on WeChat CDN); now uses POST.
   b) aes_key was base64(raw_bytes) but the iLink API expects
      base64(hex_string); images showed as grey boxes.  (Fixes NousResearch#8352, NousResearch#7529)
   Also: unified both upload paths into _upload_ciphertext(), preferring
   upload_full_url.  Added send_video/send_voice methods and voice_item
   media builder for audio/.silk files.  Added video_md5 field.

3. Markdown links stripped — WeChat can't render [text](url), so
   format_message() now converts them to 'text (url)' plaintext.
   Code blocks are preserved.  (Fixes NousResearch#7617)

4. Blank message prevention — three guards:
   a) _split_text_for_weixin_delivery('') returns [] not ['']
   b) send() filters empty/whitespace chunks before _send_text_chunk
   c) _send_message() raises ValueError for empty text as safety net

Community credit: joei4cm (NousResearch#8407), lyonDan (NousResearch#8521), SKFDJKLDG (NousResearch#8360),
tomqiaozc (NousResearch#7695), joshleeeeee (NousResearch#8308), luoxiao6645(NousResearch#8525),
longsizhuo (NousResearch#7531), Astral-Yang (NousResearch#8144), QingWei-Li (NousResearch#8251).
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…essages (NousResearch#8665)

Four fixes for the Weixin/WeChat adapter, synthesized from the best
aspects of community PRs NousResearch#8407, NousResearch#8521, NousResearch#8360, NousResearch#7695, NousResearch#8308, NousResearch#8525,
NousResearch#7531, NousResearch#8144, NousResearch#8251.

1. Streaming cursor (▉) stuck permanently — WeChat doesn't support
   message editing, so the cursor appended during streaming can never
   be removed.  Add SUPPORTS_MESSAGE_EDITING = False to WeixinAdapter
   and check it in gateway/run.py to use an empty cursor for non-edit
   platforms.  (Fixes NousResearch#8307, NousResearch#8326)

2. Media upload failures — two bugs in _send_file():
   a) upload_full_url path used PUT (404 on WeChat CDN); now uses POST.
   b) aes_key was base64(raw_bytes) but the iLink API expects
      base64(hex_string); images showed as grey boxes.  (Fixes NousResearch#8352, NousResearch#7529)
   Also: unified both upload paths into _upload_ciphertext(), preferring
   upload_full_url.  Added send_video/send_voice methods and voice_item
   media builder for audio/.silk files.  Added video_md5 field.

3. Markdown links stripped — WeChat can't render [text](url), so
   format_message() now converts them to 'text (url)' plaintext.
   Code blocks are preserved.  (Fixes NousResearch#7617)

4. Blank message prevention — three guards:
   a) _split_text_for_weixin_delivery('') returns [] not ['']
   b) send() filters empty/whitespace chunks before _send_text_chunk
   c) _send_message() raises ValueError for empty text as safety net

Community credit: joei4cm (NousResearch#8407), lyonDan (NousResearch#8521), SKFDJKLDG (NousResearch#8360),
tomqiaozc (NousResearch#7695), joshleeeeee (NousResearch#8308), luoxiao6645(NousResearch#8525),
longsizhuo (NousResearch#7531), Astral-Yang (NousResearch#8144), QingWei-Li (NousResearch#8251).
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…essages (NousResearch#8665)

Four fixes for the Weixin/WeChat adapter, synthesized from the best
aspects of community PRs NousResearch#8407, NousResearch#8521, NousResearch#8360, NousResearch#7695, NousResearch#8308, NousResearch#8525,
NousResearch#7531, NousResearch#8144, NousResearch#8251.

1. Streaming cursor (▉) stuck permanently — WeChat doesn't support
   message editing, so the cursor appended during streaming can never
   be removed.  Add SUPPORTS_MESSAGE_EDITING = False to WeixinAdapter
   and check it in gateway/run.py to use an empty cursor for non-edit
   platforms.  (Fixes NousResearch#8307, NousResearch#8326)

2. Media upload failures — two bugs in _send_file():
   a) upload_full_url path used PUT (404 on WeChat CDN); now uses POST.
   b) aes_key was base64(raw_bytes) but the iLink API expects
      base64(hex_string); images showed as grey boxes.  (Fixes NousResearch#8352, NousResearch#7529)
   Also: unified both upload paths into _upload_ciphertext(), preferring
   upload_full_url.  Added send_video/send_voice methods and voice_item
   media builder for audio/.silk files.  Added video_md5 field.

3. Markdown links stripped — WeChat can't render [text](url), so
   format_message() now converts them to 'text (url)' plaintext.
   Code blocks are preserved.  (Fixes NousResearch#7617)

4. Blank message prevention — three guards:
   a) _split_text_for_weixin_delivery('') returns [] not ['']
   b) send() filters empty/whitespace chunks before _send_text_chunk
   c) _send_message() raises ValueError for empty text as safety net

Community credit: joei4cm (NousResearch#8407), lyonDan (NousResearch#8521), SKFDJKLDG (NousResearch#8360),
tomqiaozc (NousResearch#7695), joshleeeeee (NousResearch#8308), luoxiao6645(NousResearch#8525),
longsizhuo (NousResearch#7531), Astral-Yang (NousResearch#8144), QingWei-Li (NousResearch#8251).
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.

2 participants