项目地址

https://sales.zhxapp.com/

SDK API 接口文档

用途

本文档说明 wx-bot-encry 对外暴露的 SDK 侧 HTTP API,用于外部系统和本地工具接入。

典型调用方:

  • agent-console
  • 本地管理工具
  • 内部 webhook worker
  • 可信的本地自动化脚本

注意:这是 SDK 侧接口,不是 agent-console 的代理接口。

基础地址

默认本地地址:

http://127.0.0.1:5080

常见默认值:

  • host: 127.0.0.1
  • port: 5080
  • content type: application/json

注意事项

  • 本接口面向可信本地集成设计。
  • 当前 SDK API 自身不强制 bearer-token 鉴权。
  • 已为本地工具启用 CORS。
  • SSE 接口是长连接。
  • 队列和事件负载中的大部分时间戳为 Unix 秒。
  • 群聊 session_id 通常以 @chatroom 结尾。

健康检查与运行状态

GET /ping

简单存活探测。

响应示例:

pong

GET /status

返回 SDK 运行状态、授权状态、能力列表以及当前解析后的配置摘要。

响应示例:

{
  "status": "running",
  "auth_active": true,
  "capabilities": ["fetch_messages", "dispatch_messages"],
  "paused": {
    "ingest_paused": false,
    "send_paused": false
  },
  "config": {
    "auth_server": "https://auth.example.com",
    "api_endpoint": "http://127.0.0.1:5080",
    "wechat_data_dir": "C:\\Users\\...\\xwechat_files\\xxx",
    "decrypted_dir": "C:\\...\\data\\decrypted",
    "self_wxid": "wxid_xxx",
    "my_names": ["bot"],
    "group_require_at_me": false,
    "group_capture_mode": "all_group_messages"
  },
  "queue": {
    "max_inbound_id": 120,
    "max_event_id": 8,
    "max_stream_id": 188,
    "outbound_stats": {
      "pending": 3,
      "sent": 42
    }
  }
}

队列字段说明:

  • max_inbound_id: 旧版入站消息队列的最新游标
  • max_event_id: 旧版类型事件队列的最新游标
  • max_stream_id: 统一 /stream 事件流的最新游标

POST /control/pause

暂停或恢复 SDK worker。

请求体:

{
  "ingest": true,
  "send": false
}

字段说明:

  • ingest: 可选布尔值,控制消息采集 worker
  • send: 可选布尔值,控制发送 worker

GET /control/status

返回当前暂停状态。

消息进入

GET /messages

从 SDK 本地队列拉取入站消息。

查询参数:

  • cursor: 整数,默认 0
  • limit: 整数,默认 100,最大 500

请求示例:

curl "http://127.0.0.1:5080/messages?cursor=0&limit=100"

响应示例:

{
  "messages": [
    {
      "id": 101,
      "msg_svr_id": "1234567890",
      "session_id": "123456@chatroom",
      "session_name": "测试群",
      "sender_wxid": "wxid_xxx",
      "sender_name": "张三",
      "msg_text": "你好",
      "msg_type": "text",
      "image_path": null,
      "recv_ts": 1776681000,
      "mentioned_me": true,
      "at_wxids": ["zhx870255124"],
      "mention_mode": "metadata",
      "is_self_sent": false,
      "created_ts": 1776681001
    }
  ],
  "cursor": 101,
  "count": 1
}

@ 提及相关字段:

  • mentioned_me: SDK 判断当前账号是否被提及
  • at_wxids: 从微信 msgsource.atuserlist 中解析出的被 @ 目标
  • mention_mode: metadata 或空字符串
  • is_self_sent: 该消息是否由当前登录微信账号发出

当前 SDK 行为:

  • 群消息优先使用微信数据库元数据 source -> <atuserlist> 判断 @。
  • 如果没有 @ 元数据,则返回 mentioned_me=false
  • 私聊消息始终返回 mentioned_me=false
  • 自己发送的消息也会进入队列,便于审计和回放;消费方应使用 is_self_sent 避免回复循环。

GET /messages/stream

入站消息 SSE 流。

兼容说明:

  • 这是旧版兼容接口。
  • 新接入建议使用 GET /stream
  • /messages/stream 只推送入站消息,不包含成员、授权、运行时等事件。

查询参数:

  • cursor: 整数,默认 0
  • heartbeat: 心跳秒数,默认 15,范围 5-60

请求示例:

curl -N "http://127.0.0.1:5080/messages/stream?cursor=0"

消息帧格式:

data: {"id":101,"session_id":"123456@chatroom",...}

心跳格式:

: heartbeat 1776681001

统一事件流

GET /stream

SDK 侧统一 SSE 事件流。

这是新接入的主要订阅接口。它把旧的消息流和事件流合并为一个游标空间、一条 SSE 连接。

当前事件类型包括:

  • message.received
  • message.media.ready
  • group.member.joined
  • group.member.left
  • message.delivery.succeeded
  • message.delivery.failed
  • auth.revoked
  • runtime.warning

查询参数:

  • cursor: 整数,默认 0
  • heartbeat: 心跳秒数,默认 15,范围 5-60
  • event_type: 可选,精确事件类型过滤
  • session_id: 可选,会话过滤

请求示例:

curl -N "http://127.0.0.1:5080/stream?cursor=0"

消息帧格式:

id: 123
event: message.received
data: {"id":123,"event_id":"stream:123","event_type":"message.received",...}

心跳格式:

: heartbeat 1776681001

消费方约定:

  • 持久化最后处理成功的整数 id
  • 重连时带上 cursor=<last_id>
  • id 视为 SDK 侧权威回放游标。
  • /messages/events 仅作为旧版拉取兼容接口保留。

message.media.ready 是图片消息的补充更新事件。图片消息首次进入队列时本地文件可能还未解析完成,稍后解析成功后会再次发出该事件。消费方应把它合并到原始图片消息上,而不是当作新的用户聊天轮次。

示例:

{
  "event_type": "message.media.ready",
  "message": {
    "id": "1234567890",
    "type": "image",
    "text": "[图片]",
    "image_path": "images/abc.png",
    "recv_ts": 1777000000
  },
  "media": {
    "type": "image",
    "image_path": "images/abc.png",
    "ready_ts": 1777000012
  }
}

消息发送

POST /send

向本地发送队列写入一条出站消息。

文本消息示例:

{
  "session_id": "123456@chatroom",
  "session_name": "测试群",
  "sender_name": "客服",
  "mention_sender": false,
  "msg_type": "text",
  "text": "你好,这是一条测试消息"
}

图片消息示例:

{
  "session_id": "123456@chatroom",
  "session_name": "测试群",
  "sender_name": "客服",
  "mention_sender": false,
  "msg_type": "image",
  "image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png"
}

成功响应:

{
  "queued": true,
  "id": 88
}

校验规则:

  • session_id 必填。
  • 文本消息需要 textreply_text
  • 图片消息需要 image_pathimage_url
  • mention_sender 可选,仅对群聊有意义。
  • 如果 mention_sender=true,SDK 发送 worker 会在最终文本前拼接 @sender_name

可选上下文字段:

  • sender_wxid: 原始发送人 wxid
  • reply_to_msg_svr_id: 该回复对应的上游消息 id
  • session_kind: groupprivate
  • image_url: 跨机器投递图片时使用的远程图片 URL
  • source_message: 原始入站消息快照对象
  • delivery: 投递调试和审计元数据
  • idempotency_key / command_id: 可选稳定出站命令键

这些额外字段会写入 SDK 出站队列,并可通过 GET /queue/messages 再次读取。文本消息中,如果 source_message 带有明确引用信息,例如 is_quoterefermsgquote_text,当前 SDK 会在实际发送到微信时附加轻量引用预览。图片消息中,如果只提供了 image_url,SDK 会先下载到本地缓存目录,再调用微信桌面发送逻辑。

如果顶层或 delivery 内提供了 idempotency_keycommand_idoutbound_idempotency_key,重复请求会返回既有出站队列行 id,而不会创建新的发送任务。没有显式幂等键的普通消息保持历史追加队列行为。

POST /send/batch

一次请求写入多条出站消息。

请求示例:

{
  "messages": [
    {
      "session_id": "wxid_xxx",
      "session_name": "李四",
      "sender_name": "客服",
      "mention_sender": false,
      "msg_type": "text",
      "text": "第一条"
    },
    {
      "session_id": "123456@chatroom",
      "session_name": "测试群",
      "sender_name": "客服",
      "mention_sender": true,
      "msg_type": "text",
      "text": "第二条"
    }
  ]
}

POST /send/envelope

写入一条结构化出站 envelope,适合第三方系统集成。

该接口提供比旧版扁平 /send 更完整的协议结构,但内部仍写入同一张 SDK 出站队列表。对于文本消息,如果 source_message 包含引用元数据,实际发送时可能会渲染轻量引用后缀。

请求示例:

{
  "target": {
    "session_id": "123456@chatroom",
    "session_name": "测试群",
    "session_kind": "group"
  },
  "sender": {
    "wxid": "wxid_customer",
    "name": "张三"
  },
  "content": {
    "msg_type": "image",
    "image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png"
  },
  "reply": {
    "mention_sender": true,
    "reply_to_msg_svr_id": "1234567890"
  },
  "source_message": {
    "message_id": "1234567890",
    "sender_wxid": "wxid_customer"
  },
  "delivery": {
    "channel": "wechat",
    "command_id": "wxbot-reply-12345",
    "provider": "third-party-bridge"
  },
  "metadata": {
    "trace_id": "trace-demo"
  }
}

校验规则:

  • target.session_id 必填。
  • content.msg_type 必须是 textimage
  • 文本 envelope 需要 content.text
  • 图片 envelope 需要 content.image_pathcontent.image_url
  • target.session_kind 如提供,必须是 groupprivate

成功响应:

{
  "queued": true,
  "id": 99,
  "protocol": "envelope",
  "normalized": {
    "session_id": "123456@chatroom",
    "session_name": "测试群",
    "session_kind": "group",
    "sender_name": "张三",
    "sender_wxid": "wxid_customer",
    "msg_type": "image",
    "image_url": "http://192.168.3.10:8000/plugins/draw/files/demo.png",
    "mention_sender": true,
    "reply_to_msg_svr_id": "1234567890"
  }
}

POST /send/envelope/batch

一次请求写入多条结构化出站 envelope。

请求体数组键支持 messagesenvelopes

请求示例:

{
  "messages": [
    {
      "target": {
        "session_id": "123456@chatroom",
        "session_name": "测试群",
        "session_kind": "group"
      },
      "sender": {
        "wxid": "wxid_customer",
        "name": "张三"
      },
      "content": {
        "msg_type": "text",
        "text": "第一条结构化消息"
      },
      "reply": {
        "mention_sender": true,
        "reply_to_msg_svr_id": "1234567890"
      }
    }
  ]
}

成功响应:

{
  "results": [
    {
      "queued": true,
      "id": 100,
      "protocol": "envelope",
      "normalized": {
        "session_id": "123456@chatroom",
        "session_name": "测试群",
        "session_kind": "group",
        "sender_name": "张三",
        "sender_wxid": "wxid_customer",
        "msg_type": "text",
        "mention_sender": true,
        "reply_to_msg_svr_id": "1234567890"
      }
    }
  ],
  "count": 1
}

会话与队列元数据

GET /sessions

列出从已解密微信数据库映射出的已知会话。

响应示例:

{
  "sessions": [
    {
      "session_id": "123456@chatroom",
      "session_name": "测试群",
      "kind": "group"
    },
    {
      "session_id": "wxid_xxx",
      "session_name": "李四",
      "kind": "private"
    }
  ],
  "count": 2
}

GET /queue/stats

返回本地出站队列状态计数。

响应示例:

{
  "pending": 3,
  "sent": 42,
  "failed": 1
}

GET /queue/messages

从 SDK 本地 SQLite 队列库返回出站队列明细。

该接口主要供 agent-console 查看 SDK 本地待发送队列,而不是只展示聚合计数。

查询参数:

  • status: 可选,空值表示不过滤
  • limit: 整数,默认 100,范围 1-500

请求示例:

curl "http://127.0.0.1:5080/queue/messages?status=pending&limit=50"

响应示例:

{
  "items": [
    {
      "id": 7,
      "session_id": "123456@chatroom",
      "session_name": "测试群",
      "sender_name": "客服",
      "sender_wxid": "wxid_customer",
      "mention_sender": 1,
      "reply_to_msg_svr_id": "1234567890",
      "session_kind": "group",
      "reply_text": "你好",
      "image_path": "",
      "image_url": "",
      "msg_type": "text",
      "source_message": {
        "message_id": "1234567890"
      },
      "delivery": {
        "channel": "wechat",
        "command_id": "wxbot-reply-12345"
      },
      "idempotency_key": "wxbot-reply-12345",
      "status": "pending",
      "error": "",
      "attempt_count": 0,
      "claimed_ts": 0,
      "lease_until_ts": 0,
      "created_ts": 1776681000,
      "sent_ts": null
    }
  ],
  "count": 1
}

说明:

  • mention_sender 在本地队列表中以 0/1 保存。
  • 排序为 created_ts DESC, id DESC
  • 如果队列库尚未初始化,SDK 返回空列表而不是 404

GET /debug/trigger-config

返回当前群消息采集调试开关状态。

该接口供 agent-console 确认群消息是否仅限 @ 机器人时进入队列,还是允许所有群消息进入队列。

响应示例:

{
  "group_require_at_me": false,
  "group_capture_mode": "all_group_messages",
  "my_names": ["bot", "机器人"],
  "config_path": "C:\\Users\\...\\config.json"
}

语义:

  • group_require_at_me=true: 只采集明确 @ 机器人的群消息。
  • group_require_at_me=false: 采集所有群消息。
  • group_capture_mode: 同一状态的人类可读描述。

POST /debug/trigger-config

更新运行时群消息采集调试开关,并写回 config.json

请求体:

{
  "group_require_at_me": false
}

成功响应:

{
  "group_require_at_me": false,
  "group_capture_mode": "all_group_messages",
  "my_names": ["bot", "机器人"],
  "config_path": "C:\\Users\\...\\config.json",
  "saved": true
}

校验错误示例:

{
  "error": "group_require_at_me required"
}

运维说明:

  • 该开关只影响后续群消息扫描。
  • 默认推荐值是 true
  • 设置为 false 主要用于排查意外自动回复触发问题。

GET /logs

读取近期 SDK 日志。

查询参数:

  • lines: 默认 100,最大 500

响应示例:

{
  "lines": [
    "[19:40:44] [main] starting HTTP API on 127.0.0.1:5080"
  ],
  "count": 1
}

图片

GET /images/<path>

返回 SDK 图片基础目录下的已解密图片文件。

通常配合入站图片消息中的 image_path 使用。

群成员名册

GET /ext/roster/groups

从已解密的 contact.db 列出权威群会话。

响应示例:

{
  "ok": true,
  "sessions": [
    {
      "session_id": "123456@chatroom",
      "session_name": "测试群",
      "kind": "group",
      "count": 0,
      "last_ts": null,
      "owner": "wxid_owner",
      "avatar": {
        "big_head_url": "https://wx.qlogo.cn/mmhead/.../0",
        "small_head_url": "https://wx.qlogo.cn/mmhead/.../132",
        "head_img_md5": "",
        "cache_md5": "b83ce6640fbeb97ce5001790546f451e",
        "cache_url": "/ext/roster/avatars/2106355209%40chatroom",
        "cached": true,
        "size": 5463,
        "content_type": "image/jpeg",
        "update_time": 1753248121
      }
    }
  ],
  "count": 1
}

GET /ext/roster/groups/<session_id>/members

返回权威群成员名册以及消息历史统计。

响应示例:

{
  "ok": true,
  "session_id": "123456@chatroom",
  "source_kind": "authoritative_group_roster",
  "is_membership_roster": true,
  "message_scope": "text_only",
  "source_note": "候选列表来自已解密 contact.db 的真实群成员名册;发言列表仅表示当前保留消息中的历史发言统计。",
  "candidates": [
    {
      "wxid": "wxid_xxx",
      "name": "张三",
      "alias": "",
      "remark": "",
      "nick_name": "张三",
      "avatar": {
        "big_head_url": "https://wx.qlogo.cn/mmhead/.../0",
        "small_head_url": "https://wx.qlogo.cn/mmhead/.../132",
        "head_img_md5": "",
        "cache_md5": "ce210c0289fc727dcbf116d581373cee",
        "cache_url": "/ext/roster/avatars/wxid_xxx",
        "cached": true,
        "size": 4773,
        "content_type": "image/jpeg",
        "update_time": 1753242004
      },
      "msg_count": 18,
      "first_ts": 1776000000,
      "last_ts": 1776681000,
      "has_history": true
    }
  ]
}

GET /ext/roster/avatars/<username>

从已解密的 head_image.db 返回缓存头像图片字节。样本运行数据中 image_buffer 为普通图片字节,因此接口会按探测到的 Content-Type 直接返回,例如 image/jpegimage/png

当群或成员响应中的 avatar.cachedtrue 时,使用 avatar.cache_url 取头像。

成员事件与 Webhook 订阅

GET /events

拉取 SDK 类型事件。

查询参数:

  • cursor: 整数,默认 0
  • limit: 整数,默认 100,最大 500
  • event_type: 可选
  • session_id: 可选

当前重要事件类型:

  • group.member.joined
  • group.member.left

GET /events/stream

类型事件 SSE 流。

兼容说明:

  • 这是旧版兼容接口。
  • 新接入建议使用 GET /stream
  • /events/stream 不包含入站消息正文。

请求示例:

curl -N "http://127.0.0.1:5080/events/stream?cursor=0"

消息帧格式:

id: 12
event: group.member.joined
data: {"id":12,"event_type":"group.member.joined",...}

GET /event-subscriptions

列出 SDK 侧类型事件 webhook 订阅。

查询参数:

  • event_type: 可选
  • session_id: 可选,空字符串表示该事件类型的全局订阅

POST /event-subscriptions

创建或更新一个类型事件 webhook 订阅。

请求示例:

{
  "event_type": "group.member.joined",
  "session_id": "123456@chatroom",
  "target_url": "https://example.com/webhooks/wxbot-member-events",
  "enabled": true
}

响应示例:

{
  "ok": true,
  "subscription": {
    "id": 3,
    "event_type": "group.member.joined",
    "session_id": "123456@chatroom",
    "target_url": "https://example.com/webhooks/wxbot-member-events",
    "enabled": 1
  }
}

DELETE /event-subscriptions/<subscription_id>

按数字 id 删除订阅。

群成员设置

GET /group-members/settings/<session_id>

读取 SDK 侧单群成员事件行为设置。

响应示例:

{
  "session_id": "123456@chatroom",
  "welcome_enabled": 1,
  "welcome_template": "欢迎 {{member_name}} 加入群聊",
  "welcome_mention": 0,
  "updated_ts": 1776681000
}

POST /group-members/settings/<session_id>

更新群成员加入/离开相关设置。

请求示例:

{
  "welcome_enabled": true,
  "welcome_template": "欢迎 {{member_name}}",
  "welcome_mention": false
}

用途:

  • 成员进群欢迎语
  • 后续审计或事件处理配置

Persona 提取支持

POST /ext/persona/messages

直接从已解密微信消息数据库中,采集某个会话内某个成员的人设提取消息样本。

该接口供 agent-console 的 persona 提取或类似工具使用。

请求体:

{
  "session_id": "123456@chatroom",
  "target_wxid": "wxid_xxx",
  "target_name": "张三",
  "days_limit": 90,
  "max_messages": 2000
}

规则:

  • days_limit = 0 表示不限制时间范围。
  • max_messages = 0 表示不限制消息数量。
  • 当前实现只采集文本消息。

响应示例:

{
  "ok": true,
  "session_id": "123456@chatroom",
  "target_wxid": "wxid_xxx",
  "target_name": "张三",
  "source_kind": "wechat_decrypted_message_db",
  "message_scope": "text_only",
  "full_extract": false,
  "days_limit": 90,
  "max_messages": 2000,
  "count": 128,
  "messages": [
    {
      "sender_wxid": "wxid_xxx",
      "sender_name": "张三",
      "text": "你好",
      "timestamp": "2026-04-20 10:00:00",
      "ts": 1776650400
    }
  ],
  "first_timestamp": "2026-01-20 10:00:00",
  "last_timestamp": "2026-04-20 10:00:00"
}

群报告

GET /ext/reports/subscriptions

列出所有群报告订阅。

POST /ext/reports/subscriptions

创建或更新一个群报告订阅。

请求示例:

{
  "session_id": "123456@chatroom",
  "session_name": "测试群",
  "daily_enabled": true,
  "monthly_enabled": false,
  "daily_hour": 9,
  "monthly_day": 1,
  "tz": "Asia/Shanghai"
}

DELETE /ext/reports/subscriptions/<session_id>

删除一个群报告订阅。

GET /ext/reports/preview/<session_id>

预览一份报告,不发送。

查询参数:

  • report_type: dailymonthly
  • session_name: 可选覆盖值

GET /ext/reports/messages/<session_id>

返回一份日报或月报时间窗口背后的原始聊天记录。

查询参数:

  • report_type: dailymonthly
  • session_name: 可选覆盖值
  • date: 可选 YYYY-MM-DD,仅日报使用
  • year_month: 可选 YYYY-MM,仅月报使用

响应示例:

{
  "ok": true,
  "session_id": "123456@chatroom",
  "session_name": "测试群",
  "report_type": "daily",
  "period": "2026-04-20",
  "start_ts": 1776614400,
  "end_ts": 1776700800,
  "count": 2,
  "messages": [
    {
      "ts": 1776650400,
      "timestamp": "2026-04-20 10:00:00",
      "sender_wxid": "wxid_xxx",
      "sender_name": "张三",
      "msg_type": "text",
      "text": "你好"
    },
    {
      "ts": 1776650500,
      "timestamp": "2026-04-20 10:01:40",
      "sender_wxid": "wxid_yyy",
      "sender_name": "李四",
      "msg_type": "image",
      "text": "[图片]"
    }
  ]
}

说明:

  • 日报不传 date 时,默认前一天。
  • 月报不传 year_month 时,默认上个月。
  • 该接口返回原始记录,不返回 /ext/reports/preview 使用的汇总文本。

POST /ext/reports/send

生成并入队发送一份报告。

请求示例:

{
  "session_id": "123456@chatroom",
  "session_name": "测试群",
  "report_type": "daily"
}

重要行为:

  • 报告内容从已解密微信数据库生成。
  • 不依赖 agent-console 对话轮次。
  • 如果需要原始记录而不是摘要文本,请使用 GET /ext/reports/messages/<session_id>

只读查询扩展

POST /ext/query/read

对 SDK 本地 SQLite 数据库执行受限只读 SQL 查询。

该接口用于减少内部扩展时反复修改 SDK 代码,同时保留安全边界。

允许的数据库:

  • message
  • contact
  • queue

限制:

  • 只允许 SELECTWITH 查询。
  • 不允许分号分隔的多语句。
  • 不允许写入或修改 schema。
  • limit 上限为 500

请求示例:

{
  "database": "message",
  "sql": "SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name",
  "limit": 20
}

响应示例:

{
  "ok": true,
  "database": "message",
  "db_path": "C:\\...\\message_0.db",
  "columns": ["name"],
  "rows": [
    {"name": "Name2Id"},
    {"name": "Msg_xxxxx"}
  ],
  "count": 2,
  "limit": 20,
  "truncated": false
}

建议:

  • 稳定产品功能优先使用专用接口。
  • /ext/query/read 适合内部调试、数据检查或临时扩展场景。

建议接入顺序

典型外部系统接入流程:

  1. 调用 GET /status
  2. 拉取 GET /sessionsGET /ext/roster/groups
  3. 订阅 GET /stream
  4. 通过 POST /sendPOST /send/envelope 发送出站消息。
  5. 通过 /event-subscriptions 管理群 webhook。
  6. 通过 /ext/reports/* 管理群报告。

旧版兼容:

  • GET /messagesGET /messages/streamGET /eventsGET /events/stream 仍可用于阶段性迁移。
  • 新消费方应以 /stream 作为主要入站消息和事件通道。

已知数据语义

  • 群成员候选列表来自已解密 contact.db
  • Persona 提取样本来自已解密 message_0.db
  • 报告生成来自已解密 message_0.db
  • 出站队列状态来自 SDK 本地队列库。

兼容提醒

新增 SDK 接口时,应同步更新:

  • api/server.py
  • docs/README.md
  • 本文档

这样下游调用方才能与实际 SDK 协议保持一致。

统一 Stream API

用途

本文档定义 wx-bot-encry 计划采用的统一 SDK 事件流协议。

它面向上游系统集成,尤其是 agent-console。这样消费方可以先适配稳定协议,同时 SDK 实现逐步对齐该协议。

该事件流用于替代当前拆分的两类流:

  • GET /messages/stream
  • GET /events/stream

统一为一个端点:

  • GET /stream

迁移期间,旧端点仍可继续保留以兼容已有调用方;新集成建议直接使用 GET /stream

设计目标

  • 所有 SDK 入站事件共用一个游标。
  • 所有事件类型共用一条 SSE 连接。
  • 不同来源事件使用稳定一致的 envelope。
  • 断线重连后支持按游标回放。
  • 出站发送命令仍保留在 HTTP API 上,例如:
  • POST /send
  • POST /send/batch
  • POST /send/envelope
  • POST /send/envelope/batch

统一事件流只负责 SDK -> consumer 推送,不替代发送命令接口。

端点

GET /stream

SDK 事件统一 SSE 流。

请求示例:

curl -N "http://127.0.0.1:5080/stream?cursor=0"

查询参数

  • cursor: 整数,默认 0 表示返回 id > cursor 的事件。
  • limit: 整数,默认 100,最大 500 如果后续支持有限拉取模式,该参数用于控制返回数量;标准 SSE 消费通常可以省略。
  • heartbeat: 心跳秒数,默认 15,范围 5-60
  • event_type: 可选 精确事件类型过滤,例如 message.received
  • session_id: 可选 限定只返回某个会话的事件。

SSE 帧格式

普通事件帧:

id: 123
event: message.received
data: {"id":123,"event_id":"stream:123","event_type":"message.received",...}

心跳帧:

: heartbeat 1777000000

消费方应当:

  1. 持久化最后处理成功的整数 id
  2. 使用 cursor=<last_id> 重连。
  3. 忽略心跳帧。

通用事件 Envelope

/stream 上的每个事件都应遵循相同的顶层结构。

{
  "id": 123,
  "event_id": "stream:123",
  "event_type": "message.received",
  "occurred_ts": 1777000000,
  "occurred_at": "2026-04-21T21:30:00+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": null,
  "member": null,
  "operator": null,
  "message": null,
  "media": null,
  "delivery": null,
  "auth": null,
  "runtime": null,
  "raw": {},
  "meta": {}
}

通用字段

  • id: 整数,统一事件流的单调递增游标。
  • event_id: 字符串,稳定事件标识,适合消费方做幂等处理。
  • event_type: 字符串,事件类型。
  • occurred_ts: Unix 秒。
  • occurred_at: SDK 本地时区下的 RFC3339 时间字符串。
  • source: 固定为 wxbot-sdk
  • session: 会话上下文。
  • sender: 入站用户消息事件中存在。
  • member: 成员加入/离开事件中存在。
  • operator: 能识别操作人时存在。
  • message: 入站消息事件中存在。
  • media: 延迟媒体解析完成事件中存在。
  • delivery: 出站投递结果事件中存在。
  • auth: 授权状态事件中存在。
  • runtime: 运行时告警事件中存在。
  • raw: 来源相关原始负载快照。
  • meta: 额外调试或兼容元数据。

对象结构

session

{
  "id": "123456@chatroom",
  "name": "测试群",
  "kind": "group"
}

sender

{
  "id": "wxid_user",
  "name": "张三"
}

member

{
  "id": "wxid_new_member",
  "name": "李四"
}

operator

{
  "id": "wxid_operator",
  "name": "王五"
}

message

{
  "id": "1234567890",
  "type": "text",
  "text": "你好",
  "image_path": "",
  "mentioned_me": false,
  "at_wxids": [],
  "mention_mode": "",
  "is_self_sent": false,
  "recv_ts": 1777000000
}

语义:

  • mentioned_me 是 SDK 的最终 @ 判断。
  • at_wxids 来自微信 msgsource.atuserlist,前提是该元数据存在。
  • mention_mode="metadata" 表示 SDK 使用了解析后的微信元数据。
  • mention_mode 表示没有可用 @ 元数据。
  • is_self_sent=true 表示该记录由当前登录微信账号发出,并被纳入审计/回放。

delivery

{
  "outbound_id": 88,
  "command_id": "cmd_001",
  "status": "succeeded",
  "msg_type": "text",
  "reply_to_msg_svr_id": "1234567890",
  "attempt_count": 1,
  "error": "",
  "sent_ts": 1777000010
}

media

{
  "type": "image",
  "image_path": "images/abc.png",
  "ready_ts": 1777000012
}

投递状态语义:

  • status="succeeded" 表示 SDK 发送 worker 已完成一个出站项。
  • status="retry" 表示一次发送尝试失败,但该队列项仍可继续重试。
  • status="failed" 表示 SDK 认为该出站项已终态失败。
  • attempt_count 是产生当前投递事件的尝试次数。

auth

{
  "status": "revoked",
  "reason": "device revoked",
  "detail": "session inactive"
}

runtime

{
  "level": "warning",
  "code": "decrypt.missing_db",
  "message": "message_0.db not found",
  "detail": ""
}

事件类型

message.received

SDK 从已解密微信数据库采集到一条新的入站消息时产生。

示例:

{
  "id": 101,
  "event_id": "stream:101",
  "event_type": "message.received",
  "occurred_ts": 1777000000,
  "occurred_at": "2026-04-21T21:30:00+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": {
    "id": "wxid_user",
    "name": "张三"
  },
  "member": null,
  "operator": null,
  "message": {
    "id": "1234567890",
    "type": "text",
    "text": "你好",
    "image_path": "",
    "mentioned_me": true,
    "at_wxids": ["zhx870255124"],
    "mention_mode": "metadata",
    "is_self_sent": false,
    "recv_ts": 1777000000
  },
  "delivery": null,
  "auth": null,
  "runtime": null,
  "raw": {
    "msg_svr_id": "1234567890"
  },
  "meta": {
    "legacy_source": "inbound_queue"
  }
}

message.media.ready

SDK 首次采集到图片消息时,本地媒体文件可能还没有解析完成。稍后成功解析出图片后,会产生该事件。

消费方应把它视为对原始 message.id 的更新,而不是新的聊天消息。

{
  "id": 102,
  "event_id": "stream:102",
  "event_type": "message.media.ready",
  "occurred_ts": 1777000012,
  "occurred_at": "2026-04-21T21:30:12+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": {
    "id": "wxid_user",
    "name": "张三"
  },
  "member": null,
  "operator": null,
  "message": {
    "id": "1234567890",
    "type": "image",
    "text": "[图片]",
    "image_path": "images/abc.png",
    "mentioned_me": true,
    "at_wxids": ["zhx870255124"],
    "mention_mode": "metadata",
    "is_self_sent": false,
    "recv_ts": 1777000000
  },
  "media": {
    "type": "image",
    "image_path": "images/abc.png",
    "ready_ts": 1777000012
  },
  "delivery": null,
  "auth": null,
  "runtime": null,
  "raw": {
    "msg_svr_id": "1234567890",
    "inbound_id": 88
  },
  "meta": {
    "legacy_source": "inbound_queue",
    "update_kind": "media_ready"
  }
}

group.member.joined

SDK 判断有新用户加入群聊时产生。

{
  "id": 201,
  "event_id": "stream:201",
  "event_type": "group.member.joined",
  "occurred_ts": 1777000100,
  "occurred_at": "2026-04-21T21:31:40+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": null,
  "member": {
    "id": "wxid_new_member",
    "name": "李四"
  },
  "operator": {
    "id": "wxid_inviter",
    "name": "王五"
  },
  "message": null,
  "delivery": null,
  "auth": null,
  "runtime": null,
  "raw": {},
  "meta": {}
}

group.member.left

SDK 判断有成员离开群聊时产生。

{
  "id": 202,
  "event_id": "stream:202",
  "event_type": "group.member.left",
  "occurred_ts": 1777000200,
  "occurred_at": "2026-04-21T21:33:20+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": null,
  "member": {
    "id": "wxid_left_member",
    "name": "赵六"
  },
  "operator": null,
  "message": null,
  "delivery": null,
  "auth": null,
  "runtime": null,
  "raw": {},
  "meta": {}
}

message.delivery.succeeded

一个 SDK 出站队列项真正发送成功后产生。

{
  "id": 301,
  "event_id": "stream:301",
  "event_type": "message.delivery.succeeded",
  "occurred_ts": 1777000300,
  "occurred_at": "2026-04-21T21:35:00+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": null,
  "member": null,
  "operator": null,
  "message": null,
  "delivery": {
    "outbound_id": 88,
    "command_id": "cmd_001",
    "status": "succeeded",
    "msg_type": "text",
    "reply_to_msg_svr_id": "1234567890",
    "attempt_count": 1,
    "error": "",
    "sent_ts": 1777000300
  },
  "auth": null,
  "runtime": null,
  "raw": {},
  "meta": {}
}

message.delivery.failed

一个 SDK 出站队列项进入失败状态,或某次发送尝试失败且 SDK 希望暴露错误时产生。

{
  "id": 302,
  "event_id": "stream:302",
  "event_type": "message.delivery.failed",
  "occurred_ts": 1777000310,
  "occurred_at": "2026-04-21T21:35:10+08:00",
  "source": "wxbot-sdk",
  "session": {
    "id": "123456@chatroom",
    "name": "测试群",
    "kind": "group"
  },
  "sender": null,
  "member": null,
  "operator": null,
  "message": null,
  "delivery": {
    "outbound_id": 89,
    "command_id": "cmd_002",
    "status": "failed",
    "msg_type": "image",
    "reply_to_msg_svr_id": "",
    "attempt_count": 3,
    "error": "window not found",
    "sent_ts": 0
  },
  "auth": null,
  "runtime": null,
  "raw": {},
  "meta": {}
}

说明:

  • delivery.status 可以是 retryfailed
  • retry 表示队列项仍留在本地出站队列中,SDK 之后可能继续重试。
  • failed 表示 SDK 已停止重试该出站项。

auth.revoked

SDK 授权失效,运行能力即将被阻断时产生。

{
  "id": 401,
  "event_id": "stream:401",
  "event_type": "auth.revoked",
  "occurred_ts": 1777000400,
  "occurred_at": "2026-04-21T21:36:40+08:00",
  "source": "wxbot-sdk",
  "session": null,
  "sender": null,
  "member": null,
  "operator": null,
  "message": null,
  "delivery": null,
  "auth": {
    "status": "revoked",
    "reason": "device revoked",
    "detail": "signed session rejected by auth server"
  },
  "runtime": null,
  "raw": {},
  "meta": {}
}

runtime.warning

非致命运行时问题,消费方需要感知时产生。

{
  "id": 501,
  "event_id": "stream:501",
  "event_type": "runtime.warning",
  "occurred_ts": 1777000500,
  "occurred_at": "2026-04-21T21:38:20+08:00",
  "source": "wxbot-sdk",
  "session": null,
  "sender": null,
  "member": null,
  "operator": null,
  "message": null,
  "delivery": null,
  "auth": null,
  "runtime": {
    "level": "warning",
    "code": "decrypt.missing_db",
    "message": "message_0.db not found",
    "detail": ""
  },
  "raw": {},
  "meta": {}
}

游标语义

  • 游标是全局 stream 游标,不是按事件类型分别计数。
  • 消费方应把 id 视为权威回放游标。
  • 使用最后看到的 cursor 重连时,不应重复回放更旧事件。
  • 多种事件类型会自然交错出现。

过滤规则

如果提供 event_type,只推送匹配该类型的事件。

如果提供 session_id,只推送该会话下的事件。

过滤应在统一流排序确定后执行。

迁移说明

计划迁移方向:

  1. 继续保留 /messages/messages/stream/events/events/stream,用于过渡期兼容。
  2. 引入 /stream 作为主要订阅端点。
  3. 将出站投递结果事件加入统一事件流。
  4. agent-console 等上游消费方迁移为只追踪一个 stream 游标。

agent-console 消费建议

推荐初始订阅行为:

  1. 连接 GET /stream?cursor=<stored_cursor>
  2. 持久化每个已处理事件的 id
  3. 至少处理以下事件类型: - message.received - group.member.joined - group.member.left - message.delivery.succeeded - message.delivery.failed - auth.revoked - runtime.warning
  4. 出站发送仍通过 HTTP 命令接口完成。