ForcePilot/backend/package/yuxi/channels/adapters/yuanbao/yb_commands.py
Kris eb25707668 feat(yuanbao): 新增元宝渠道适配器完整实现
新增元宝(Yuanbao)渠道的完整适配器实现,包含以下核心模块:
- 基础适配器与导出入口
- 协议编解码与WebSocket帧处理
- 会话管理与路由逻辑
- 事件队列与出站消息队列
- 消息格式转换与发送重试
- 安全审计与权限校验
- 配置映射与账户管理
- 视觉分析与工具函数
- 文档生成与设置向导
2026-05-12 00:52:20 +08:00

227 lines
6.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from yuxi.utils.logging_config import logger
COMMAND_DEFINITIONS: list[dict[str, Any]] = [
{
"name": "help",
"description": "显示可用命令列表和使用说明",
"usage": "/help [命令名]",
},
{
"name": "status",
"description": "显示当前机器人的运行状态和会话信息",
"usage": "/status",
},
{
"name": "new",
"description": "开启一个新的对话会话",
"usage": "/new",
},
{
"name": "stop",
"description": "停止当前对话中正在进行的任务",
"usage": "/stop",
},
{
"name": "restart",
"description": "重新启动当前对话会话",
"usage": "/restart",
},
{
"name": "compact",
"description": "压缩当前对话历史,释放上下文空间",
"usage": "/compact",
},
]
@dataclass
class CommandResult:
command: str
action: str
response_text: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class NativeCommandContext:
user_id: str
chat_id: str
chat_type: str
thread_key: str | None = None
adapter_status: str = "connected"
ws_connected: bool = False
history_length: int = 0
agent_id: str = "default"
def parse_command(content: str) -> tuple[str | None, str | None]:
content = content.strip()
if not content.startswith("/"):
return None, None
parts = content.split(maxsplit=1)
command = parts[0][1:]
args = parts[1] if len(parts) > 1 else None
return command, args
async def handle_command(
command: str,
args: str | None,
ctx: NativeCommandContext,
) -> CommandResult:
handlers = {
"help": _handle_help,
"status": _handle_status,
"new": _handle_new,
"stop": _handle_stop,
"restart": _handle_restart,
"compact": _handle_compact,
}
handler = handlers.get(command)
if handler is None:
return CommandResult(
command=command,
action="unknown",
response_text=(f"未知命令: /{command}\n输入 /help 查看可用命令列表"),
)
try:
return await handler(args, ctx)
except Exception as e:
logger.error(f"[Yuanbao] Command handler error for /{command}: {e}")
return CommandResult(
command=command,
action="error",
response_text=f"执行命令 /{command} 时出错: {e}",
)
async def _handle_help(args: str | None, ctx: NativeCommandContext) -> CommandResult:
if args:
for cmd in COMMAND_DEFINITIONS:
if cmd["name"] == args:
return CommandResult(
command="help",
action="help_detail",
response_text=(f"**/{cmd['name']}**\n{cmd['description']}\n用法: `{cmd['usage']}`"),
)
return CommandResult(
command="help",
action="help_not_found",
response_text=f"未找到命令: /{args}",
)
lines = ["**可用命令列表:**\n"]
for cmd in COMMAND_DEFINITIONS:
lines.append(f"- **/{cmd['name']}** — {cmd['description']}")
lines.append("\n输入 `/help 命令名` 查看详细用法")
return CommandResult(
command="help",
action="help_list",
response_text="\n".join(lines),
)
async def _handle_status(args: str | None, ctx: NativeCommandContext) -> CommandResult:
lines = [
"**机器人状态:**",
f"- 连接状态: {ctx.adapter_status}",
f"- WebSocket: {'已连接' if ctx.ws_connected else '未连接'}",
f"- 对话类型: {ctx.chat_type}",
f"- 当前 Agent: {ctx.agent_id}",
f"- 历史消息数: {ctx.history_length}",
]
return CommandResult(
command="status",
action="status_report",
response_text="\n".join(lines),
)
async def _handle_new(args: str | None, ctx: NativeCommandContext) -> CommandResult:
return CommandResult(
command="new",
action="new_session",
response_text="已开启新的对话会话。",
)
async def _handle_stop(args: str | None, ctx: NativeCommandContext) -> CommandResult:
return CommandResult(
command="stop",
action="stop_task",
response_text="已请求停止当前任务。",
metadata={"request_stop": True},
)
async def _handle_restart(args: str | None, ctx: NativeCommandContext) -> CommandResult:
return CommandResult(
command="restart",
action="restart_session",
response_text="正在重新启动对话会话...",
metadata={"request_restart": True},
)
async def _handle_compact(args: str | None, ctx: NativeCommandContext) -> CommandResult:
return CommandResult(
command="compact",
action="compact_history",
response_text="已请求压缩对话历史,释放上下文空间。",
metadata={"request_compact": True},
)
async def sync_commands_menu(
api_base: str,
token: str,
http_client,
commands: list[dict[str, Any]] | None = None,
) -> bool:
if commands is None:
commands = COMMAND_DEFINITIONS
try:
import aiohttp
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
payload = {
"commands": [
{
"command": cmd["name"],
"description": cmd["description"],
}
for cmd in commands
]
}
async with http_client.post(
f"{api_base}/api/v1/bot/commands",
json=payload,
headers=headers,
timeout=aiohttp.ClientTimeout(total=10),
) as resp:
if resp.status in (200, 201, 204):
logger.info(f"[Yuanbao] Commands menu synced ({len(commands)} commands)")
return True
else:
body = await resp.text()
logger.warning(f"[Yuanbao] Failed to sync commands menu: HTTP {resp.status} {body}")
return False
except Exception as e:
logger.warning(f"[Yuanbao] Commands menu sync failed: {e}")
return False