11.2 入站消息规范化
生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~560k tokens,输出 ~50k tokens(本章合计)
不同消息平台的消息格式千差万别:Telegram 用 chat.id 和 from.username,WhatsApp 用 JID(Jabber ID),Discord 用 snowflake ID,Signal 用 E.164 电话号码。OpenClaw 的入站消息管线必须将这些异构格式统一为内部的 MsgContext 结构,才能让下游的路由、会话管理和 AI 代理处理逻辑不需要关心消息来源。
11.2.1 消息格式统一:文本、图片、音频、视频、文件
统一消息上下文:MsgContext
所有通道的入站消息最终都会被转换为 MsgContext 结构(定义在 src/auto-reply/templating.ts),这是 OpenClaw 内部消息的"通用语言"。MsgContext 的核心字段包括:
通道标识
Channel, AccountId
来源通道和账号
对话类型
ChatType
"direct" / "group" / "channel"
发送者
From, SenderId, SenderName, SenderUsername, SenderE164
多维度发送者身份
接收者
To
目标标识(通常是 bot 自身)
消息内容
Body, BodyForAgent
原始文本 / 清洗后文本
媒体
MediaPath, MediaUrl, MediaType, MediaPaths, MediaUrls, MediaTypes
附件信息
回复上下文
ReplyToId, ReplyToBody, ReplyToIsQuote
引用的消息
群组信息
GroupSubject, GroupSpace, GroupChannel
群名、空间、频道
线程
ThreadLabel, MessageThreadId
线程标签和 ID
会话标签
ConversationLabel
用于会话路由的人类可读标签
对话类型归一化
入站消息的第一步是对话类型归一化。不同平台对"私聊"和"群聊"的叫法各不相同:
Telegram
private
direct
Telegram
group / supergroup
group
Telegram
channel
channel
Discord
DM
direct
Discord
GUILD_TEXT
channel
私聊
direct
@g.us JID
group
Slack
DM
direct
Slack
channel / group
channel
发送者身份的多维度表示
发送者身份是跨平台差异最大的部分。OpenClaw 用多个互补字段来表示同一个发送者:
衍生解释:E.164 格式
E.164 是国际电信联盟(ITU)定义的电话号码格式标准,以
+开头,后跟国家代码和号码,最长 15 位数字。例如+8613812345678(中国手机号)。Signal 和 WhatsApp 都以 E.164 格式标识用户。
不同通道提供不同的字段组合:
Telegram
12345678
"张三"
"zhangsan"
—
Discord
1234567890123456
"张三"
"zhangsan#1234"
—
Signal
—
"张三"
—
+8613812345678
Slack
U012AB3CD
"张三"
"zhangsan"
—
发送者标签的生成逻辑(用于系统提示词中的对话标识):
通道特有的目标归一化
每个通道都有自己的目标 ID 格式,需要归一化为统一的内部格式。以 Telegram 和 WhatsApp 为例:
归一化后的目标 ID 统一使用 通道:标识 格式(如 telegram:@username、whatsapp:[email protected]),使路由和会话管理可以统一处理。
11.2.2 媒体附件处理
附件解析管线
当入站消息包含图片、文件等附件时,OpenClaw 通过 parseMessageWithAttachments(位于 src/gateway/chat-attachments.ts)进行解析:
这个管线有几个值得注意的设计决策:
大小限制:默认 5 MB,防止超大文件导致内存溢出。
MIME 类型嗅探:不信任客户端声称的 MIME 类型,而是从实际 base64 数据的头部字节中检测。这防止了类型伪造攻击。
MIME 不匹配处理:当嗅探结果与声称的类型不同时,优先使用嗅探结果并记录警告。
仅图片:当前只支持图片类型的附件直接传递给 AI 模型(因为模型的多模态能力主要支持图片)。非图片附件被丢弃并记录日志。
衍生解释:MIME 类型嗅探(MIME Sniffing)
MIME(Multipurpose Internet Mail Extensions)类型标识文件的格式,如
image/jpeg、application/pdf。客户端发送的 MIME 类型可能不准确——要么是错误标注,要么是恶意伪造。MIME 嗅探通过读取文件的头部字节(魔数)来检测实际类型。例如 JPEG 文件以FF D8 FF开头,PNG 文件以89 50 4E 47开头。OpenClaw 使用detectMime函数从 base64 数据的前 256 字节中检测实际 MIME 类型。
Data URL 处理
base64 数据可能以 Data URL 格式传入(如 data:image/jpeg;base64,/9j/4AAQ...),需要剥离前缀:
base64 校验
解析后的 base64 数据要通过格式和大小校验:
衍生解释:base64 编码
base64 是一种将二进制数据编码为 ASCII 字符串的方法。它将每 3 个字节(24 位)分成 4 组 6 位,每组映射到一个可打印字符(A-Z、a-z、0-9、+、/),末尾不足时用
=填充。因此 base64 字符串的长度必须是 4 的倍数,编码后的大小约为原始数据的 4/3 倍。
11.2.3 消息清洗
信封剥离
OpenClaw 在多代理、多通道场景下,消息在转发时会被包裹在"信封"中——前面添加通道和时间戳标记。当这些消息最终到达 AI 模型时,信封需要被剥离,避免干扰模型理解:
信封检测有两个判定条件:
包含时间戳:如
[Telegram 2025-02-18T08:30Z] 你好→ 剥离后变为你好以通道名开头:如
[WhatsApp zhangsan] 消息内容→ 剥离后变为消息内容
消息 ID 提示清理
某些消息中可能包含用于回复引用的 [message_id: ...] 标注。这些标注对人类读者和 AI 都是噪音,需要清除:
批量消息清洗
清洗操作可以应用于整个消息历史数组,且只处理用户角色(role === "user")的消息——助手消息和系统消息不需要清洗:
注意这里对 content 字段的处理分为两种情况:
字符串格式:直接对整个字符串执行清洗
数组格式:遍历每个 content block,只对
type === "text"的块执行清洗
这是因为 Claude API 支持两种消息格式——简单字符串和结构化 content block 数组。OpenClaw 需要兼容两者。
清洗管线的完整流程
本节小结
MsgContext统一消息结构是所有通道的"通用语言",包含通道标识、发送者身份、消息内容、媒体附件、回复上下文等完整信息。发送者身份使用多维度表示(SenderId、SenderName、SenderUsername、SenderE164),适应不同平台的标识体系。E.164 格式用于电话号码标准化。
附件处理管线执行 base64 校验、MIME 嗅探和大小限制,优先使用嗅探的 MIME 类型而非客户端声称的类型,当前仅支持图片附件直接传递给 AI 模型。
消息清洗剥离信封前缀和 message_id 标注,且只作用于用户角色的消息,避免干扰 AI 模型的理解。
通道特有的目标归一化(如
telegram:@username、whatsapp:[email protected])将异构 ID 格式统一为通道:标识格式,为下游路由提供一致的寻址基础。
Last updated