refactor(yuanbao): 整理元宝适配器代码并新增功能支持
1. 调整导入顺序和导入项顺序优化代码结构 2. 新增位置消息类型映射支持 3. 新增频道帖子事件的消息分发处理 4. 重构WebSocket认证失败日志格式 5. 优化令牌刷新错误提示的换行格式 6. 简化事件队列满时的日志输出 7. 新增系统事件处理和打字状态上报支持 8. 实现打字指示器接口的实际调用逻辑 9. 更新通道能力配置,补充缺失的能力项
This commit is contained in:
parent
5c3611ff19
commit
ba060ca9c5
@ -11,13 +11,13 @@ import aiohttp
|
||||
|
||||
from yuxi.channels.base import BaseChannelAdapter
|
||||
from yuxi.channels.capabilities import ChannelCapabilities
|
||||
from yuxi.channels.meta import ChannelMeta
|
||||
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
|
||||
from yuxi.channels.exceptions import (
|
||||
ChannelAuthenticationError,
|
||||
ChannelNotConnectedError,
|
||||
DeliveryFailedError,
|
||||
)
|
||||
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
|
||||
from yuxi.channels.meta import ChannelMeta
|
||||
from yuxi.channels.models import (
|
||||
Attachment,
|
||||
ChannelIdentity,
|
||||
@ -29,14 +29,14 @@ from yuxi.channels.models import (
|
||||
DeliveryResult,
|
||||
EventType,
|
||||
HealthStatus,
|
||||
MessageType,
|
||||
MentionsInfo,
|
||||
MessageType,
|
||||
)
|
||||
from yuxi.channels.registry import register_builtin_adapter
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
from .chunking import chunk_text
|
||||
from .dispatch import InteractiveDispatcher, DispatchAction, DispatchContext, CardAction, BotMenuAction
|
||||
from .dispatch import BotMenuAction, CardAction, DispatchAction, DispatchContext, InteractiveDispatcher
|
||||
from .event_queue import EventQueue
|
||||
from .format import format_outbound
|
||||
from .monitor import YuanbaoMonitor
|
||||
@ -47,7 +47,7 @@ from .security_audit import SecurityAuditLogger
|
||||
from .send import send_with_retry
|
||||
from .send_cache import SendMessageCache
|
||||
from .token import YuanbaoTokenManager
|
||||
from .vision import download_and_analyze, VisionResult
|
||||
from .vision import VisionResult, download_and_analyze
|
||||
from .yb_accounts import (
|
||||
YuanbaoAccountManager,
|
||||
load_accounts_from_config,
|
||||
@ -92,6 +92,13 @@ class YuanbaoAdapter(BaseChannelAdapter):
|
||||
lane_streaming=True,
|
||||
reasoning_streaming=True,
|
||||
native_commands=True,
|
||||
edit=False,
|
||||
unsend=False,
|
||||
pin=False,
|
||||
polls=False,
|
||||
typing=False,
|
||||
send_ephemeral=False,
|
||||
effects=False,
|
||||
)
|
||||
meta = ChannelMeta(
|
||||
id="yuanbao",
|
||||
@ -663,6 +670,8 @@ class YuanbaoAdapter(BaseChannelAdapter):
|
||||
await self._handle_bot_menu(event)
|
||||
elif action == DispatchAction.READ_RECEIPT:
|
||||
self._handle_read_receipt(event)
|
||||
elif action == DispatchAction.SYSTEM_EVENT:
|
||||
await self._handle_system_event(event)
|
||||
else:
|
||||
await self._dispatcher.dispatch(ctx)
|
||||
except Exception as e:
|
||||
@ -682,6 +691,14 @@ class YuanbaoAdapter(BaseChannelAdapter):
|
||||
msg_id = event.get("msg_id", "")
|
||||
logger.debug(f"[Yuanbao] Read receipt: user={user_id} chat={chat_id} msg_id={msg_id}")
|
||||
|
||||
async def _handle_system_event(self, event: dict) -> None:
|
||||
sys_type = event.get("type", "unknown")
|
||||
logger.info(f"[Yuanbao] System event: {sys_type}")
|
||||
if sys_type == "typing":
|
||||
logger.debug(
|
||||
f"[Yuanbao] User typing: open_id={event.get('open_id')} group_open_id={event.get('group_open_id')}"
|
||||
)
|
||||
|
||||
async def _handle_card_action(self, event: dict) -> None:
|
||||
card = CardAction.from_event(event)
|
||||
logger.info(
|
||||
@ -880,12 +897,9 @@ class YuanbaoAdapter(BaseChannelAdapter):
|
||||
await asyncio.sleep(interval)
|
||||
|
||||
async def edit_message(self, chat_id: str, msg_id: str, content: str) -> DeliveryResult:
|
||||
self._send_cache.mark_edited(msg_id, content[:200])
|
||||
return DeliveryResult(success=False, error="edit_message not supported by Yuanbao adapter")
|
||||
|
||||
async def delete_message(self, chat_id: str, msg_id: str) -> DeliveryResult:
|
||||
self._send_cache.mark_deleted(msg_id)
|
||||
self._bot_message_ids.discard(msg_id)
|
||||
return DeliveryResult(success=False, error="delete_message not supported by Yuanbao adapter")
|
||||
|
||||
async def send_reaction(self, chat_id: str, msg_id: str, emoji: str) -> DeliveryResult:
|
||||
@ -929,7 +943,31 @@ class YuanbaoAdapter(BaseChannelAdapter):
|
||||
return DeliveryResult(success=False, error="send_chat_action not supported by Yuanbao adapter")
|
||||
|
||||
async def send_typing_indicator(self, chat_id: str) -> DeliveryResult:
|
||||
return DeliveryResult(success=False, error="send_typing_indicator not supported by Yuanbao adapter")
|
||||
if not self._http_client or not self._token_manager:
|
||||
return DeliveryResult(success=False, error="Client not initialized")
|
||||
token = await self._token_manager.get_token()
|
||||
api_base = self._token_manager.api_base
|
||||
|
||||
payload = {"open_id": chat_id}
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
try:
|
||||
async with self._http_client.post(
|
||||
f"{api_base}/api/v1/bot/typing",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=aiohttp.ClientTimeout(total=5),
|
||||
) as resp:
|
||||
if resp.status == 200:
|
||||
return DeliveryResult(success=True)
|
||||
return DeliveryResult(
|
||||
success=False,
|
||||
error=f"send_typing_indicator failed: HTTP {resp.status}",
|
||||
)
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def get_user_info(self, channel_user_id: str) -> dict[str, Any]:
|
||||
if not self._token_manager or not self._http_client:
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import aiohttp
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import aiohttp
|
||||
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
|
||||
@ -126,6 +126,8 @@ class InteractiveDispatcher:
|
||||
return DispatchAction.MESSAGE
|
||||
elif event_type == "edited_message":
|
||||
return DispatchAction.MESSAGE
|
||||
elif event_type in ("channel_post", "edited_channel_post"):
|
||||
return DispatchAction.MESSAGE
|
||||
elif event_type in ("reaction_added", "reaction_removed"):
|
||||
return DispatchAction.REACTION
|
||||
elif event_type == "bot_menu":
|
||||
|
||||
@ -5,7 +5,6 @@ from collections.abc import Awaitable, Callable
|
||||
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
SENTINEL = object()
|
||||
|
||||
|
||||
@ -41,10 +40,7 @@ class EventQueue:
|
||||
except asyncio.QueueFull:
|
||||
event_type = event.get("type", "unknown")
|
||||
msg_id = event.get("msg_id", "N/A")
|
||||
logger.warning(
|
||||
f"[Yuanbao] Event queue full, dropping event: "
|
||||
f"type={event_type} msg_id={msg_id}"
|
||||
)
|
||||
logger.warning(f"[Yuanbao] Event queue full, dropping event: type={event_type} msg_id={msg_id}")
|
||||
self._dropped_count += 1
|
||||
|
||||
async def _process_loop(self) -> None:
|
||||
|
||||
@ -64,5 +64,6 @@ def _map_message_type(message_type: MessageType, content: str) -> str:
|
||||
MessageType.STICKER: "sticker",
|
||||
MessageType.CARD: "card",
|
||||
MessageType.COMMAND: "text",
|
||||
MessageType.LOCATION: "location",
|
||||
}
|
||||
return _type_map.get(message_type, "text")
|
||||
|
||||
@ -131,7 +131,7 @@ class YuanbaoMonitor:
|
||||
if not self._running:
|
||||
break
|
||||
if self._auth_failed:
|
||||
logger.error(f"[Yuanbao] WebSocket auth permanently failed, stopping")
|
||||
logger.error("[Yuanbao] WebSocket auth permanently failed, stopping")
|
||||
break
|
||||
self._reconnect_count += 1
|
||||
if self._reconnect_count > self._max_reconnect:
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Awaitable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from yuxi.channels.models import ChannelResponse, ChannelIdentity, DeliveryResult
|
||||
from yuxi.channels.models import ChannelIdentity, ChannelResponse, DeliveryResult
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, UTC
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
@ -48,9 +48,10 @@ async def run_setup_wizard(
|
||||
result.add_step("input_validation", True, "输入参数校验通过")
|
||||
|
||||
try:
|
||||
import time
|
||||
import hashlib
|
||||
import hmac
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
|
||||
timestamp = int(time.time())
|
||||
|
||||
@ -137,7 +137,9 @@ class YuanbaoTokenManager:
|
||||
timeout=aiohttp.ClientTimeout(total=10),
|
||||
) as resp:
|
||||
if resp.status != 200:
|
||||
raise ChannelAuthenticationError(f"Token refresh failed: HTTP {resp.status} {await resp.text()}")
|
||||
raise ChannelAuthenticationError(
|
||||
f"Token refresh failed: HTTP {resp.status} {await resp.text()}"
|
||||
)
|
||||
data = await resp.json()
|
||||
self._access_token = data["access_token"]
|
||||
expires_in = data.get("expires_in", 7200)
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import aiohttp
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
from yuxi.utils.logging_config import logger
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user