跳转到内容

cap_im_platform — 统一 IM 平台接入

入口:cap_im_platform.c · 头文件:cap_im_platform.h · Skill:SKILL.md

cap_im_platform 是 ESP-Claw 的统一 IM 源码组件。它把飞书、QQ、Telegram、微信以及共享附件处理实现放在同一个 ESP-IDF 组件中,但运行时接口仍按平台拆分。

这意味着构建依赖已经统一,但原有运行时 Group ID 与工具名保持稳定:

运行时 Group事件源文本图片文件
cap_im_feishufeishu_gatewayfeishu_send_messagefeishu_send_imagefeishu_send_file
cap_im_qqqq_gatewayqq_send_messageqq_send_imageqq_send_file
cap_im_tgtg_gatewaytg_send_messagetg_send_imagetg_send_file
cap_im_wechatwechat_gatewaywechat_send_messagewechat_send_image不支持

平台组件仍按后端拆分源码文件,避免把协议细节混在一起:

源码职责
cap_im_platform.c注册所有启用的 IM 运行时 Group。
cap_im_feishu.c飞书 WebSocket/Event API 入站、富文本展开与发送。
cap_im_qq.cQQ Bot WebSocket 入站、Token 处理与发送。
cap_im_tg.cTelegram 长轮询入站、附件下载队列与发送。
cap_im_wechat.c微信 ClawBot 轮询、扫码登录状态与发送。
cap_im_attachment.c共享本地附件路径辅助逻辑。

每个 IM 后端都遵循同一组职责:

  1. 事件源:从 IM 平台接收消息,归一化后发布到 claw_event_router
  2. 可调用工具:暴露平台专属发送函数,供 Agent、Console 或自动化规则发送文本和媒体。
  3. 附件处理:把入站媒体保存到配置的 inbox 根目录,并发布 attachment_saved 事件供下游规则处理。
Diagram

应用启动时会按启用的平台分别准备凭证和附件配置,再注册对应的运行时 Group。edge_agent 会把 Event Router 的出站 channel,例如 qqfeishutelegramwechat,绑定到对应发送工具。

统一 IM Skill 在 cap_groups 中声明四个平台的运行时 Group。激活该 Skill 后,模型会同时获得平台专属工具和通道选择说明,避免跨平台误发或重复回复。

平台入站模型会话目标说明
飞书WebSocket/Event API飞书 chat_id,或以 ou_ 开头的用户 open_id文本优先使用支持 Markdown 的交互卡片,失败后回退纯文本。媒体 caption 会作为后续文本发送。
QQQQ Bot WebSocket APIc2c:<openid>group:<group_openid>文件发送依赖 QQ 平台侧支持;图片和通用文件使用不同工具。
TelegramBot API 长轮询数字 chat id,例如 123456789-100...长文本会自动分片,文件通过 multipart 流式上传。
微信ClawBot 轮询 API明确的群 id 或联系人 id支持文本和图片发送,暂不支持通用非图片文件发送。

Telegram 仍是一个适合阅读的代表实现,因为它完整展示了 IM 后端的典型路径:长轮询入站、去重、异步附件下载,以及文本/媒体发送工具。

Diagram

Telegram 后端在 cap_im_tg Group 的 start 钩子中启动两个 FreeRTOS 任务。

tg_poll_task 持续调用 getUpdates(带 20 秒 long-poll 超时),将收到的每条消息解析后发布事件:

// 文本消息 -> 标准 message 事件
claw_event_router_publish_message(
    "tg_gateway",   // source_cap(事件来源标识)
    "telegram",     // source_channel(渠道)
    chat_id,        // 会话 ID
    text,           // 消息正文
    sender_id,      // 发送者 ID
    message_id      // 消息 ID
);

claw_event_router 收到此事件后,根据规则决定是交给 claw_core 运行 Agent,还是触发自动化动作。

由于网络抖动可能导致同一更新被重复下发,cap_im_tg 维护了一个循环 FNV-1a 哈希缓存,避免同一消息被处理两次:

#define CAP_IM_TG_DEDUP_CACHE_SIZE 64

static bool cap_im_tg_dedup_check_and_record(const char *update_key)
{
    uint64_t key = cap_im_tg_fnv1a64(update_key);
    for (size_t i = 0; i < CAP_IM_TG_DEDUP_CACHE_SIZE; i++) {
        if (s_tg.seen_update_keys[i] == key) return true; // 已见过
    }
    s_tg.seen_update_keys[s_tg.seen_update_idx] = key;
    s_tg.seen_update_idx = (s_tg.seen_update_idx + 1) % CAP_IM_TG_DEDUP_CACHE_SIZE;
    return false;
}

附件(图片/文档)的下载是耗时操作,Telegram 后端会异步处理:

  1. tg_poll_taskcap_im_tg_attachment_job_t 放入队列。
  2. tg_attachment_task 消费队列,调用 getFile 获取下载 URL,流式写入 FATFS。
  3. 下载完成后发布 attachment_saved 事件,携带本地路径、MIME、尺寸与平台元数据。
// attachment_saved 事件的 payload_json 示例
{
  "platform": "telegram",
  "attachment_kind": "photo",
  "saved_path": "/fatfs/inbox/telegram/-123456/789/photo.jpg",
  "saved_dir": "/fatfs/inbox/telegram/-123456/789",
  "saved_name": "photo.jpg",
  "mime": "image/jpeg",
  "caption": "看看这个",
  "platform_file_id": "AgACAgIAAxkBAAI...",
  "size_bytes": 45231,
  "saved_at_ms": 1714000000000
}

下游规则可以监听 attachment_saved 事件,触发 cap_llm_inspect、文件操作或自定义自动化动作。

cap_im_tg 运行时 Group 注册了四个 Capability 描述符:

工具 ID描述kind
tg_gateway轮询网关(事件源)EVENT_SOURCE
tg_send_message发送文本消息到指定 chat_idCALLABLE
tg_send_image发送本地图片文件到 TelegramCALLABLE
tg_send_file发送本地任意文件到 TelegramCALLABLE

tg_send_message 中,如果输入没有提供 chat_id,会从当前调用上下文取值:

// chat_id 优先级:JSON 参数 > 调用上下文
if (cJSON_IsString(chat_id_json) && chat_id_json->valuestring[0]) {
    chat_id = chat_id_json->valuestring;
} else if (ctx && ctx->chat_id && ctx->chat_id[0]) {
    chat_id = ctx->chat_id;  // 从会话上下文自动继承
}

超长文本会自动分片以适配 Telegram 限制。tg_send_imagetg_send_file 通过 multipart/form-data 上传文件,使用 stat() 预先获取精确 Content-Length,并通过 esp_http_client_open 手动流式写入各 part,避免将整个文件载入内存。

应用层通过 cap_im_platform 导出的 cap_im_tg_* API 配置 Telegram 后端:

// 设置 Bot Token(必须在 start 前调用)
cap_im_tg_set_token("YOUR_BOT_TOKEN");

// 配置附件接收(可选)
cap_im_tg_set_attachment_config(&(cap_im_tg_attachment_config_t){
    .storage_root_dir         = "/fatfs/inbox",
    .max_inbound_file_bytes   = 2 * 1024 * 1024,  // 最大 2 MB
    .enable_inbound_attachments = true,
});

// 手动启动(通常由 claw_cap_start_group 调用)
cap_im_tg_start();

其他后端使用相同的架构角色,只是在认证方式、消息格式和媒体 API 上存在平台差异。