From a54106bbc83208650fe5bf8efaece844e77c2341 Mon Sep 17 00:00:00 2001 From: Leihb Date: Tue, 28 Apr 2026 09:31:17 +0800 Subject: [PATCH] fix(weixin): split long messages (>2000 chars) into chunks to prevent truncation - Change MAX_MESSAGE_LENGTH from 4000 to 2000 to match Weixin iLink API limit - Add RATE_LIMIT_ERRCODE = -2 handling with 3x backoff retry - Increase default send_chunk_delay_seconds from 0.35 to 1.5 to avoid rate limits - Increase default send_chunk_retries from 2 to 4 for better reliability - Use _split_text() in send() to chunk long messages before delivery Fixes #16411 --- gateway/platforms/weixin.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/gateway/platforms/weixin.py b/gateway/platforms/weixin.py index 958e71da..5445c6fd 100644 --- a/gateway/platforms/weixin.py +++ b/gateway/platforms/weixin.py @@ -89,6 +89,7 @@ MAX_CONSECUTIVE_FAILURES = 3 RETRY_DELAY_SECONDS = 2 BACKOFF_DELAY_SECONDS = 30 SESSION_EXPIRED_ERRCODE = -14 +RATE_LIMIT_ERRCODE = -2 # iLink frequency limit — backoff and retry MESSAGE_DEDUP_TTL_SECONDS = 300 MEDIA_IMAGE = 1 @@ -1113,7 +1114,7 @@ async def qr_login( class WeixinAdapter(BasePlatformAdapter): """Native Hermes adapter for Weixin personal accounts.""" - MAX_MESSAGE_LENGTH = 4000 + MAX_MESSAGE_LENGTH = 2000 # WeChat does not support editing sent messages — streaming must use the # fallback "send-final-only" path so the cursor (▉) is never left visible. @@ -1138,10 +1139,10 @@ class WeixinAdapter(BasePlatformAdapter): extra.get("cdn_base_url") or os.getenv("WEIXIN_CDN_BASE_URL", WEIXIN_CDN_BASE_URL) ).strip().rstrip("/") self._send_chunk_delay_seconds = float( - extra.get("send_chunk_delay_seconds") or os.getenv("WEIXIN_SEND_CHUNK_DELAY_SECONDS", "0.35") + extra.get("send_chunk_delay_seconds") or os.getenv("WEIXIN_SEND_CHUNK_DELAY_SECONDS", "1.5") ) self._send_chunk_retries = int( - extra.get("send_chunk_retries") or os.getenv("WEIXIN_SEND_CHUNK_RETRIES", "2") + extra.get("send_chunk_retries") or os.getenv("WEIXIN_SEND_CHUNK_RETRIES", "4") ) self._send_chunk_retry_delay_seconds = float( extra.get("send_chunk_retry_delay_seconds") @@ -1531,6 +1532,19 @@ class WeixinAdapter(BasePlatformAdapter): self.name, _safe_id(chat_id), ) continue + # Rate limit (-2) — backoff and retry + is_rate_limited = ( + ret == RATE_LIMIT_ERRCODE + or errcode == RATE_LIMIT_ERRCODE + ) + if is_rate_limited: + wait = self._send_chunk_retry_delay_seconds * 3 # 3s backoff for rate limit + logger.warning( + "[%s] rate limited for %s; backing off %.1fs before retry", + self.name, _safe_id(chat_id), wait, + ) + await asyncio.sleep(wait) + continue errmsg = resp.get("errmsg") or resp.get("msg") or "unknown error" raise RuntimeError( f"iLink sendmessage error: ret={ret} errcode={errcode} errmsg={errmsg}"