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