跳转到主要内容
本页给出适配器的完整协议参考。先看 适配器总览适配器插件开发规范 获取最小落地路径。

Control 消息信封

control websocket 上的业务消息都是 JSON object。backend 和 worker 都可以发起调用。
{
  "aut_action": "adapter_codec_build_outbound",
  "aut_echo": "call_1",
  "aut_params": {}
}
成功响应:
{
  "aut_echo": "call_1",
  "ok": true,
  "aut_params": {}
}
失败响应:
{
  "aut_echo": "call_1",
  "ok": false,
  "aut_error": "missing account_id"
}
要求:
  • 响应必须原样返回 aut_echo
  • 响应不要带 aut_action
  • aut_params 必须是对象;没有数据时返回 {}
  • 捕获 worker 内部异常,并转成 ok:false

初始化和能力声明

control 连接建立后,backend 会发送 adapter_init。你应先缓存配置、重建账号索引、重置临时状态,再发送 adapter_worker_ready adapter_init.aut_params 的关键字段:
字段说明
im_type / adapter_route当前适配器标识和公开路由。
instance_id当前 worker 实例 ID。只读,不要自行生成。
control_endpoint / receive_endpointcontrol 和入站接入地址。
account_id / default_account_id默认发送账号。
account_id_key账号配置里用于识别账号的 key。
common通用开关、白名单、默认账号等设置。
global_config_values全局配置值。
account_configs账号配置列表。
账号配置示例:
{
  "account_id": "2400000000",
  "alias": "main",
  "enabled": true,
  "ingress_token": "***",
  "schema_values": {
    "self_id": "2400000000"
  }
}
声明能力:
{
  "aut_action": "adapter_worker_ready",
  "aut_params": {
    "resolve_account": true,
    "normalize_inbound": true,
    "build_outbound": true,
    "decode_receipt": true,
    "execute_outbound": false,
    "emit_inbound_events": false,
    "request_outbound": false
  }
}
能力对应动作何时开启
resolve_accountadapter_codec_resolve_account需要从入站请求或连接解析 bot 账号。
normalize_inboundadapter_codec_normalize_inbound需要把平台事件转成标准事件。
build_outboundadapter_codec_build_outbound需要把标准发送动作转成平台 payload。
decode_receiptadapter_codec_decode_receipt平台会返回发送回执或 echo。
execute_outboundadapter_execute_outboundworker 自己调用平台 API / SDK 完成发送。
emit_inbound_eventsadapter_emit_inbound_eventsworker 自己维护平台长连接并主动上报事件。
request_outboundadapter_request_outboundworker 的平台 workflow 需要请求 backend 发送标准消息。

账号解析

实现 adapter_codec_resolve_account,从 headers、query 或 raw_payload 找到当前 bot 账号。 请求:
{
  "aut_action": "adapter_codec_resolve_account",
  "aut_echo": "call_2",
  "aut_params": {
    "im_type": "qq",
    "transport": "ws",
    "headers": {"x-self-id": "2400000000"},
    "query": {"access_token": "***"},
    "raw_payload": {"self_id": 2400000000},
    "resolve_hint": {}
  }
}
成功响应:
{
  "aut_echo": "call_2",
  "ok": true,
  "aut_params": {
    "account_id": "2400000000",
    "alias": "main",
    "meta": {
      "platform": "onebot_v11",
      "client_role": "universal"
    }
  }
}
规范:
  • 成功时 account_id 必填,并与账号配置中的 account_id 一致。
  • meta 只放非敏感字符串,例如平台名、连接角色、短 ID。
  • 解析不到账号时返回 ok:false 和可排查的 aut_error

入站标准化

实现 adapter_codec_normalize_inbound,把平台 payload 转成 StandardMessageEvent 数组。字段名使用 Go 结构体字段名,例如 IMTypeAccountIDEventCategory
{
  "events": [
    {
      "IMType": "qq",
      "AccountID": "2400000000",
      "EventType": "message",
      "EventCategory": "message",
      "SessionType": "private",
      "SenderID": "qq:2400000000:private::10001",
      "UserID": "10001",
      "UserName": "Alice",
      "ChatID": "",
      "Message": "hello",
      "MessageID": "msg_1",
      "IsSelf": false,
      "MediaItems": [],
      "EventData": {
        "account_id": "2400000000",
        "raw_message": "hello"
      }
    }
  ]
}
字段说明
IMType渠道标识,通常等于 manifest im_type
AccountID当前 bot 账号 ID。必填。
EventType平台事件类型;普通消息用 message
EventCategory标准分类:messagenoticerequestmeta_eventpayment
SessionTypeprivategroup
SenderID建议使用 im:account:session:chat:user
UserID / UserName发送者 ID 和昵称。
ChatID / ChatName群或会话信息;非群聊时 ChatID 留空。
Message纯文本内容。没有文本时可留空。
MessageID平台消息 ID,用于撤回和历史消息。
MediaItems标准媒体数组。
HistoryMessageIDs / HistoryMessages可选历史上下文。
IsAdmin用户是否管理员。
IsSelf是否 bot 自己发出的消息。
EventData小体积补充字段。建议包含 account_id
媒体项格式:
{
  "type": "image",
  "source": "https://example.com/a.png",
  "file_path": "",
  "source_kind": "http",
  "name": "a.png",
  "mime_type": "image/png",
  "platform_payload": {}
}

出站构造

实现 adapter_codec_build_outbound,把 autClaw 标准动作转成平台 payload。 请求:
{
  "request_id": "egress_xxx",
  "im_type": "qq",
  "account_id": "2400000000",
  "action": "sendText",
  "session_type": "private",
  "chat_id": "",
  "user_id": "10001",
  "payload": {
    "text": "hello",
    "content": "hello",
    "message": "hello"
  }
}
Gateway 型返回:
{
  "transport": "ws",
  "await_receipt": true,
  "raw_payload": {
    "action": "send_private_msg",
    "params": {
      "user_id": "10001",
      "message": [{"type": "text", "data": {"text": "hello"}}]
    },
    "echo": "egress_xxx"
  }
}
Worker 自发送返回:
{
  "executor": "worker",
  "await_receipt": false,
  "raw_payload": {
    "kind": "send",
    "request_id": "egress_xxx",
    "account_id": "bot_1",
    "target_id": "10001",
    "operations": [{"kind": "text", "text": "hello"}]
  }
}
标准动作payload 约定
sendTexttextcontentmessage
sendMarkdownmarkdown,可兼容 text / content
sendImagemedia: {"type":"image","source":"..."}
sendVoicemedia: {"type":"voice","source":"..."}
sendVideomedia: {"type":"video","source":"..."}
sendFilemedia: {"type":"file","source":"...","name":"..."}
sendMixeditemsmessage_itemsmessageItems 数组。
recall / recallMessagemessage_idmessageid
approveFriendRequest平台好友请求参数,例如 flagapproveuser_id
getGroupMemberList群 ID,例如 group_idchat_id
目标规则:
  • 群消息使用 session_type: "group" + chat_id
  • 私聊消息使用 session_type: "private" + user_id
  • 需要回执时,把 request_id 写入平台 echononce 或等价字段。

出站执行和回执

如果 build_outbound 返回 executor: "worker",backend 会调用 adapter_execute_outbound
{
  "aut_action": "adapter_execute_outbound",
  "aut_echo": "call_3",
  "aut_params": {
    "request_id": "egress_xxx",
    "im_type": "fs",
    "account_id": "app_1",
    "raw_payload": {"kind": "send"}
  }
}
响应:
{
  "aut_echo": "call_3",
  "ok": true,
  "aut_params": {
    "ok": true,
    "data": {
      "request_id": "egress_xxx",
      "message_id": "msg_xxx"
    }
  }
}
注意两层 ok:外层 ok 表示 control RPC 是否成功;aut_params.ok 表示平台发送是否成功。 Gateway 型适配器要实现 adapter_codec_decode_receipt
{
  "matched": true,
  "request_id": "egress_xxx",
  "ok": true,
  "data": {
    "message_id": "123"
  }
}
如果 payload 不是回执,返回 {"matched":false},backend 会继续按普通入站事件处理。

主动动作

动作何时使用要求
adapter_emit_inbound_eventsworker 自己收到平台事件,例如官方 SDK 长连接events 每项必须有 AccountID。高频事件要分批和限流。
adapter_request_outboundworker 在平台 workflow 中需要发送标准消息,例如自动同意好友后回复会重新进入标准发送链路,避免同步等待造成死锁。
adapter_update_account_state登录成功、掉线、心跳或账号不可发送account_id 必填;last_heartbeat 用 RFC3339 时间。
主动发送示例:
{
  "aut_action": "adapter_request_outbound",
  "aut_echo": "worker_req_1",
  "aut_params": {
    "im_type": "qq",
    "action": "sendText",
    "account_id": "2400000000",
    "session_type": "private",
    "user_id": "10001",
    "payload": {"text": "好友已通过"}
  }
}
账号状态示例:
{
  "aut_action": "adapter_update_account_state",
  "aut_echo": "state_1",
  "aut_params": {
    "im_type": "fs",
    "account_id": "app_1",
    "status": "online",
    "message": "已连接",
    "last_error": "",
    "last_heartbeat": "2026-06-20T00:00:00Z",
    "sendable": true,
    "meta": {"platform": "fs"}
  }
}
可选二维码登录动作使用同一信封:adapter_login_qr_startadapter_login_qr_wait。不支持二维码登录的适配器不要实现这些动作。

媒体发送契约

普通插件调用 replyImagereplyVoicereplyVideoreplyFilereplyMixed 后,backend 会把媒体放进出站 payload.mediapayload.items 默认不要声明:
//[media: {"requires_file_path":true}]
默认模式下,backend 会尽量把本地文件转换成临时 URL,再传给 worker:
{
  "media": {
    "type": "video",
    "source": "https://public.example.com/api/plugins/runtime/media/temp/token/demo.mp4",
    "name": "demo.mp4",
    "source_kind": "local_path"
  }
}
只有 worker 与 backend 共用文件系统,且平台 SDK 必须读取本地文件时,才声明本地文件路径模式:
//[media: {"requires_file_path":true}]
这时 backend 会提供 file_path
{
  "media": {
    "type": "image",
    "source": "base64://aGk=",
    "file_path": "/opt/autclaw/plugin/.media/.../image.png",
    "name": "image.png",
    "source_kind": "base64_payload"
  }
}
处理规则:
  • 优先读取 media.source
  • 只有声明 requires_file_path 后才读取 media.file_path
  • 支持 http://https://data:base64://、平台 file key 和临时资源 URL。
  • 文件发送要保留 name / file_namemime_type
  • 不要把本地路径发给远端平台。

最小 Node.js 框架

const WebSocket = require('ws')

const IM_TYPE = process.env.AUTCLAW_ADAPTER_IM_TYPE || 'demo'
const controlURL =
  process.env.AUTCLAW_ADAPTER_CONNECT_URL ||
  `ws://127.0.0.1:${process.env.AUTCLAW_PORT || '8200'}/api/adapters/${IM_TYPE}/connect`

let readySent = false

function text(value) {
  return value === undefined || value === null ? '' : String(value).trim()
}

function sendJSON(ws, payload) {
  if (!ws || ws.readyState !== WebSocket.OPEN) throw new Error('control websocket is not open')
  ws.send(JSON.stringify(payload))
}

function replyWithEcho(ws, message, params, error) {
  const response = { aut_echo: text(message && message.aut_echo), ok: !error }
  if (error) response.aut_error = text(error.message || error) || 'worker call failed'
  else response.aut_params = params && typeof params === 'object' ? params : {}
  sendJSON(ws, response)
}

function emitReady(ws) {
  if (readySent) return
  readySent = true
  sendJSON(ws, {
    aut_action: 'adapter_worker_ready',
    aut_params: {
      resolve_account: true,
      normalize_inbound: true,
      build_outbound: true,
      decode_receipt: true,
      execute_outbound: false,
      emit_inbound_events: false,
      request_outbound: false,
    },
  })
}

function handleWorkerMessage(ws, message) {
  const action = text(message && message.aut_action)
  if (action === 'adapter_init') {
    emitReady(ws)
    return
  }
  if (!text(message && message.aut_echo)) return

  Promise.resolve().then(() => {
    switch (action) {
      case 'adapter_codec_resolve_account':
        return replyWithEcho(ws, message, { account_id: '2400000000' })
      case 'adapter_codec_normalize_inbound':
        return replyWithEcho(ws, message, { events: [] })
      case 'adapter_codec_build_outbound':
        return replyWithEcho(ws, message, { transport: 'ws', await_receipt: true, raw_payload: {} })
      case 'adapter_codec_decode_receipt':
        return replyWithEcho(ws, message, { matched: false })
      default:
        throw new Error(`unsupported action: ${action}`)
    }
  }).catch((error) => replyWithEcho(ws, message, null, error))
}

function connectControl() {
  const ws = new WebSocket(controlURL)
  ws.on('message', (data) => handleWorkerMessage(ws, JSON.parse(String(data || '{}'))))
  ws.on('close', () => setTimeout(connectControl, 1000))
}

connectControl()

发布前检查

  • 收到 adapter_init 后再处理平台业务消息。
  • 所有带 aut_echo 的调用都能响应成功或失败。
  • 主动调用 backend 时维护 pending map,并设置超时。
  • control 断开时清理 pending 调用并退避重连。
  • EventDatameta 和日志不要包含敏感信息。
  • 群聊、私聊、媒体、撤回、账号离线和平台错误都要做实测。
最后修改于 2026年6月20日