yuanbao platform (#16298)

Co-authored-by: loongzhao <loongzhao@tencent.com>
This commit is contained in:
Teknium 2026-04-26 18:50:49 -07:00 committed by GitHub
parent 5eb6cd82b2
commit ab6879634e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 10997 additions and 12 deletions

View file

@ -28,6 +28,7 @@ _FEISHU_TARGET_RE = re.compile(r"^\s*((?:oc|ou|on|chat|open)_[-A-Za-z0-9]+)(?::(
# through to channel-name resolution, which only matches by name and fails.
_SLACK_TARGET_RE = re.compile(r"^\s*([CGD][A-Z0-9]{8,})\s*$")
_WEIXIN_TARGET_RE = re.compile(r"^\s*((?:wxid|gh|v\d+|wm|wb)_[A-Za-z0-9_-]+|[A-Za-z0-9._-]+@chatroom|filehelper)\s*$")
_YUANBAO_TARGET_RE = re.compile(r"^\s*((?:group|direct):[^:]+)\s*$")
# Discord snowflake IDs are numeric, same regex pattern as Telegram topic targets.
_NUMERIC_TOPIC_RE = _TELEGRAM_TOPIC_TARGET_RE
# Platforms that address recipients by phone number and accept E.164 format
@ -127,11 +128,11 @@ SEND_MESSAGE_SCHEMA = {
},
"target": {
"type": "string",
"description": "Delivery target. Format: 'platform' (uses home channel), 'platform:#channel-name', 'platform:chat_id', or 'platform:chat_id:thread_id' for Telegram topics and Discord threads. Examples: 'telegram', 'telegram:-1001234567890:17585', 'discord:999888777:555444333', 'discord:#bot-home', 'slack:#engineering', 'signal:+155****4567', 'matrix:!roomid:server.org', 'matrix:@user:server.org'"
"description": "Delivery target. Format: 'platform' (uses home channel), 'platform:#channel-name', 'platform:chat_id', or 'platform:chat_id:thread_id' for Telegram topics and Discord threads. Examples: 'telegram', 'telegram:-1001234567890:17585', 'discord:999888777:555444333', 'discord:#bot-home', 'slack:#engineering', 'signal:+155****4567', 'matrix:!roomid:server.org', 'matrix:@user:server.org', 'yuanbao:direct:<account_id>' (DM), 'yuanbao:group:<group_code>' (group chat)"
},
"message": {
"type": "string",
"description": "The message text to send"
"description": "The message text to send. To send an image or file, include MEDIA:<local_path> (e.g. 'MEDIA:/tmp/hermes/cache/img_xxx.jpg') in the message — the platform will deliver it as a native media attachment."
}
},
"required": []
@ -222,6 +223,7 @@ def _handle_send(args):
"weixin": Platform.WEIXIN,
"email": Platform.EMAIL,
"sms": Platform.SMS,
"yuanbao": Platform.YUANBAO,
}
platform = platform_map.get(platform_name)
if not platform:
@ -341,6 +343,13 @@ def _parse_target_ref(platform_name: str, target_ref: str):
match = _WEIXIN_TARGET_RE.fullmatch(target_ref)
if match:
return match.group(1), None, True
if platform_name == "yuanbao":
match = _YUANBAO_TARGET_RE.fullmatch(target_ref)
if match:
return match.group(1), None, True
if target_ref.strip().isdigit():
return f"group:{target_ref.strip()}", None, True
return None, None, False
if platform_name in _PHONE_PLATFORMS:
match = _E164_TARGET_RE.fullmatch(target_ref)
if match:
@ -551,7 +560,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
if media_files and not message.strip():
return {
"error": (
f"send_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, and signal; "
f"send_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, signal and yuanbao; "
f"target {platform.value} had only media attachments"
)
}
@ -559,7 +568,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
if media_files:
warning = (
f"MEDIA attachments were omitted for {platform.value}; "
"native send_message media delivery is currently only supported for telegram, discord, matrix, weixin, and signal"
"native send_message media delivery is currently only supported for telegram, discord, matrix, weixin, signal and yuanbao"
)
last_result = None
@ -1529,6 +1538,35 @@ async def _send_qqbot(pconfig, chat_id, message):
return _error(f"QQBot send failed: {e}")
async def _send_yuanbao(chat_id, message, media_files=None):
"""Send via Yuanbao using the running gateway adapter's WebSocket connection.
Yuanbao uses a persistent WebSocket unlike HTTP-based platforms, we
cannot create a throwaway client. We obtain the running singleton from
the adapter module itself (``get_active_adapter``).
chat_id format:
- Group: "group:<group_code>"
- DM: "direct:<account_id>" or just "<account_id>"
"""
try:
from gateway.platforms.yuanbao import get_active_adapter, send_yuanbao_direct
except ImportError:
return _error("Yuanbao adapter module not available.")
adapter = get_active_adapter()
if adapter is None:
return _error(
"Yuanbao adapter is not running. "
"Start the gateway with yuanbao platform enabled first."
)
try:
return await send_yuanbao_direct(adapter, chat_id, message, media_files=media_files)
except Exception as e:
return _error(f"Yuanbao send failed: {e}")
# --- Registry ---
from tools.registry import registry, tool_error