Coverage for agentos/channels/message.py: 0%
65 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 10:59 +0800
1"""
2AgentOS Channels — 统一消息模型与渠道协议。
4所有渠道(微信/企业微信/飞书/钉钉/QQ)的消息
5均归一化为 ChannelMessage,确保 Agent Engine 零差异处理。
6"""
8from __future__ import annotations
10import time
11from dataclasses import dataclass, field
12from enum import Enum
15class ChannelType(str, Enum):
16 """渠道类型标识。"""
17 WECHAT_MP = "wechat-mp" # 微信公众号
18 WECOM = "wecom" # 企业微信
19 FEISHU = "feishu" # 飞书
20 DINGTALK = "dingtalk" # 钉钉
21 QQ = "qq" # QQ 机器人
22 SLACK = "slack" # Slack
23 DISCORD = "discord" # Discord
24 TELEGRAM = "telegram" # Telegram
25 WHATSAPP = "whatsapp" # WhatsApp Business
26 LINE = "line" # LINE Messaging
27 WEB = "web" # Web 端
28 MOBILE = "mobile" # 移动端
31class MessageType(str, Enum):
32 """消息类型。"""
33 TEXT = "text" # 文本
34 IMAGE = "image" # 图片
35 VOICE = "voice" # 语音
36 VIDEO = "video" # 视频
37 FILE = "file" # 文件
38 LOCATION = "location" # 位置
39 LINK = "link" # 链接
40 EVENT = "event" # 事件(关注/点击菜单等)
41 MINIPROGRAM = "miniprogram" # 小程序卡片
44@dataclass
45class ConversationContext:
46 """会话上下文 — 跨渠道统一。"""
47 channel: ChannelType
48 user_id: str # 渠道内用户唯一标识
49 session_id: str # AgentOS 内会话 ID
50 channel_config: dict = field(default_factory=dict)
51 metadata: dict = field(default_factory=dict)
52 history: list[dict] = field(default_factory=list) # 最近 N 轮对话
55@dataclass
56class ChannelMessage:
57 """统一消息模型 — 所有渠道归一化为此格式。
59 设计原则:
60 - 字段命名中性,不偏袒任何渠道
61 - 渠道特有数据塞入 extra
62 - Agent Engine 只看本模型,不感知渠道差异
63 """
65 msg_id: str # 渠道内消息唯一 ID
66 channel: ChannelType # 来源渠道
67 msg_type: MessageType = MessageType.TEXT
68 content: str = "" # 文本内容(image/voice 等为 media_url)
69 sender_id: str = "" # 发送者 ID
70 sender_name: str = "" # 发送者昵称
71 timestamp: float = field(default_factory=time.time)
72 conversation_id: str = "" # 渠道内会话/群 ID
73 reply_token: str = "" # 渠道回复令牌(用于被动回复)
74 session_id: str = "" # AgentOS 会话 ID
75 media_url: str = "" # 多媒体 URL
76 media_id: str = "" # 渠道媒体 ID(用于下载)
77 extra: dict = field(default_factory=dict) # 渠道特有字段
79 @property
80 def is_from_mobile(self) -> bool:
81 """是否来自移动端渠道。"""
82 return self.channel in (ChannelType.MOBILE, ChannelType.WECHAT_MP, ChannelType.QQ)
84 @property
85 def is_group_chat(self) -> bool:
86 """是否群聊消息。"""
87 return self.extra.get("is_group", False)
89 @property
90 def display_source(self) -> str:
91 """人类可读的来源标识。"""
92 labels = {
93 ChannelType.WECHAT_MP: "微信",
94 ChannelType.WECOM: "企业微信",
95 ChannelType.FEISHU: "飞书",
96 ChannelType.DINGTALK: "钉钉",
97 ChannelType.QQ: "QQ",
98 ChannelType.WEB: "Web",
99 ChannelType.MOBILE: "手机",
100 }
101 return labels.get(self.channel, self.channel.value)
103 def to_dict(self) -> dict:
104 return {
105 "msg_id": self.msg_id,
106 "channel": self.channel.value,
107 "msg_type": self.msg_type.value,
108 "content": self.content,
109 "sender_id": self.sender_id,
110 "sender_name": self.sender_name,
111 "timestamp": self.timestamp,
112 "conversation_id": self.conversation_id,
113 "session_id": self.session_id,
114 "media_url": self.media_url,
115 "extra": self.extra,
116 }
118 @classmethod
119 def from_dict(cls, d: dict) -> "ChannelMessage":
120 return cls(
121 msg_id=d.get("msg_id", ""),
122 channel=ChannelType(d.get("channel", "web")),
123 msg_type=MessageType(d.get("msg_type", "text")),
124 content=d.get("content", ""),
125 sender_id=d.get("sender_id", ""),
126 sender_name=d.get("sender_name", ""),
127 timestamp=d.get("timestamp", time.time()),
128 conversation_id=d.get("conversation_id", ""),
129 reply_token=d.get("reply_token", ""),
130 session_id=d.get("session_id", ""),
131 media_url=d.get("media_url", ""),
132 media_id=d.get("media_id", ""),
133 extra=d.get("extra", {}),
134 )