> ## Documentation Index
> Fetch the complete documentation index at: https://autclaw.shop/llms.txt
> Use this file to discover all available pages before exploring further.

# 适配器参考

> 适配器 control 信封、核心动作、回执、主动事件和媒体契约的完整参考。

本页给出适配器的完整协议参考。先看 [`适配器总览`](/cn/plugin-sdk/adapter) 和 [`适配器插件开发规范`](/cn/plugin-sdk/adapter-development) 获取最小落地路径。

## Control 消息信封

control websocket 上的业务消息都是 JSON object。backend 和 worker 都可以发起调用。

```json theme={null}
{
  "aut_action": "adapter_codec_build_outbound",
  "aut_echo": "call_1",
  "aut_params": {}
}
```

成功响应：

```json theme={null}
{
  "aut_echo": "call_1",
  "ok": true,
  "aut_params": {}
}
```

失败响应：

```json theme={null}
{
  "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_endpoint` | control 和入站接入地址。           |
| `account_id` / `default_account_id`     | 默认发送账号。                    |
| `account_id_key`                        | 账号配置里用于识别账号的 key。          |
| `common`                                | 通用开关、白名单、默认账号等设置。          |
| `global_config_values`                  | 全局配置值。                     |
| `account_configs`                       | 账号配置列表。                    |

账号配置示例：

```json theme={null}
{
  "account_id": "2400000000",
  "alias": "main",
  "enabled": true,
  "ingress_token": "***",
  "schema_values": {
    "self_id": "2400000000"
  }
}
```

声明能力：

```json theme={null}
{
  "aut_action": "adapter_worker_ready",
  "aut_params": {
    "resolve_account": true,
    "normalize_inbound": true,
    "build_outbound": true,
    "decode_receipt": true,
    "execute_outbound": false,
    "supports_qr_login": false,
    "emit_inbound_events": false,
    "request_outbound": false
  }
}
```

| 能力                    | 对应动作                                             | 何时开启                                     |
| --------------------- | ------------------------------------------------ | ---------------------------------------- |
| `resolve_account`     | `adapter_codec_resolve_account`                  | 需要从入站请求或连接解析 bot 账号。                     |
| `normalize_inbound`   | `adapter_codec_normalize_inbound`                | 需要把平台事件转成标准事件。                           |
| `build_outbound`      | `adapter_codec_build_outbound`                   | 需要把标准发送动作转成平台 payload。                   |
| `decode_receipt`      | `adapter_codec_decode_receipt`                   | 平台会返回发送回执或 echo。                         |
| `execute_outbound`    | `adapter_execute_outbound`                       | worker 自己调用平台 API / SDK 完成发送。            |
| `supports_qr_login`   | `adapter_login_qr_start`、`adapter_login_qr_wait` | 当前在线 worker 支持扫码登录或扫码绑定账号。               |
| `emit_inbound_events` | `adapter_emit_inbound_events`                    | worker 自己维护平台长连接并主动上报事件。                 |
| `request_outbound`    | `adapter_request_outbound`                       | worker 的平台 workflow 需要请求 backend 发送标准消息。 |

<Note>
  `supports_qr_login` 有两层：

  * manifest 头注 `//[supports_qr_login: true]`：声明适配器提供扫码登录入口，供设置页展示。
  * `adapter_worker_ready.aut_params.supports_qr_login`：声明当前在线 worker 真正支持扫码动作。

  backend 真正发起 `adapter_login_qr_start`、`adapter_login_qr_wait` 前，只检查第二层运行时能力。
</Note>

## 账号解析

实现 `adapter_codec_resolve_account`，从 headers、query 或 `raw_payload` 找到当前 bot 账号。

请求：

```json theme={null}
{
  "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": {}
  }
}
```

成功响应：

```json theme={null}
{
  "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 结构体字段名，例如 `IMType`、`AccountID`、`EventCategory`。

```json theme={null}
{
  "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`                         | 标准分类：`message`、`notice`、`request`、`meta_event`、`payment`。 |
| `SessionType`                           | `private` 或 `group`。                                      |
| `SenderID`                              | 建议使用 `im:account:session:chat:user`。                      |
| `UserID` / `UserName`                   | 发送者 ID 和昵称。                                               |
| `ChatID` / `ChatName`                   | 群或会话信息；非群聊时 `ChatID` 留空。                                  |
| `Message`                               | 纯文本内容。没有文本时可留空。                                           |
| `MessageID`                             | 平台消息 ID，用于撤回和历史消息。                                        |
| `MediaItems`                            | 标准媒体数组。                                                   |
| `HistoryMessageIDs` / `HistoryMessages` | 可选历史上下文。                                                  |
| `IsAdmin`                               | 用户是否管理员。                                                  |
| `IsSelf`                                | 是否 bot 自己发出的消息。                                           |
| `EventData`                             | 小体积补充字段。建议包含 `account_id`。                                |

媒体项格式：

```json theme={null}
{
  "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。

请求：

```json theme={null}
{
  "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 型返回：

```json theme={null}
{
  "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 自发送返回：

```json theme={null}
{
  "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 约定                                            |
| -------------------------- | ----------------------------------------------------- |
| `sendText`                 | `text`、`content` 或 `message`。                         |
| `sendMarkdown`             | `markdown`，可兼容 `text` / `content`。                    |
| `sendImage`                | `media: {"type":"image","source":"..."}`。             |
| `sendVoice`                | `media: {"type":"voice","source":"..."}`。             |
| `sendVideo`                | `media: {"type":"video","source":"..."}`。             |
| `sendFile`                 | `media: {"type":"file","source":"...","name":"..."}`。 |
| `sendMixed`                | `items`、`message_items` 或 `messageItems` 数组。          |
| `recall` / `recallMessage` | `message_id` 或 `messageid`。                           |
| `approveFriendRequest`     | 平台好友请求参数，例如 `flag`、`approve`、`user_id`。               |
| `getGroupMemberList`       | 群 ID，例如 `group_id` 或 `chat_id`。                       |

目标规则：

* 群消息使用 `session_type: "group"` + `chat_id`。
* 私聊消息使用 `session_type: "private"` + `user_id`。
* 需要回执时，把 `request_id` 写入平台 `echo`、`nonce` 或等价字段。

## 出站执行和回执

如果 `build_outbound` 返回 `executor: "worker"`，backend 会调用 `adapter_execute_outbound`。

```json theme={null}
{
  "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"}
  }
}
```

响应：

```json theme={null}
{
  "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`：

```json theme={null}
{
  "matched": true,
  "request_id": "egress_xxx",
  "ok": true,
  "data": {
    "message_id": "123"
  }
}
```

如果 payload 不是回执，返回 `{"matched":false}`，backend 会继续按普通入站事件处理。

## 扫码登录

支持扫码登录的适配器需要先完成核心 codec，在 manifest 中声明 `//[supports_qr_login: true]` 作为入口提示，并在 `adapter_worker_ready` 中上报 `supports_qr_login: true`。backend 只会向已上报运行时能力的在线 worker 调用扫码动作。

启动扫码：

```json theme={null}
{
  "aut_action": "adapter_login_qr_start",
  "aut_echo": "call_qr_1",
  "aut_params": {
    "account_id": "",
    "session_key": "",
    "force": false
  }
}
```

返回二维码信息：

```json theme={null}
{
  "aut_echo": "call_qr_1",
  "ok": true,
  "aut_params": {
    "session_key": "qr_session_1",
    "qr_code_url": "https://example.com/qr.png",
    "message": "用手机扫描二维码。",
    "issued_at": 1783353600
  }
}
```

等待扫码结果：

```json theme={null}
{
  "aut_action": "adapter_login_qr_wait",
  "aut_echo": "call_qr_2",
  "aut_params": {
    "account_id": "",
    "session_key": "qr_session_1",
    "timeout_ms": 30000
  }
}
```

未完成时返回当前状态：

```json theme={null}
{
  "aut_echo": "call_qr_2",
  "ok": true,
  "aut_params": {
    "connected": false,
    "status": "wait",
    "message": "等待扫码。",
    "qr_code_url": "https://example.com/qr.png",
    "issued_at": 1783353600
  }
}
```

扫码成功时返回账号配置：

```json theme={null}
{
  "aut_echo": "call_qr_2",
  "ok": true,
  "aut_params": {
    "connected": true,
    "status": "confirmed",
    "message": "连接成功。",
    "account_id": "bot_1",
    "account_config": {
      "account_id": "bot_1",
      "alias": "主账号",
      "enabled": true,
      "schema_values": {
        "token": "***"
      }
    }
  }
}
```

字段约定：

| 字段               | 说明                                                               |
| ---------------- | ---------------------------------------------------------------- |
| `session_key`    | 一次扫码流程的会话 key。`start` 返回后，`wait` 必须带回。                           |
| `force`          | 为 `true` 时强制生成新的二维码。                                             |
| `timeout_ms`     | 单次等待超时。worker 应设置下限，避免长时间阻塞 control 调用。                          |
| `connected`      | 是否已完成扫码并拿到账号凭证。                                                  |
| `status`         | 建议使用 `wait`、`scanned`、`confirmed`、`expired` 或 `error`；也可以透传平台状态。 |
| `account_config` | 扫码成功后可返回账号配置；backend 会持久化到适配器设置。                                 |

要求：

* 不支持扫码登录的适配器不要声明 `//[supports_qr_login: true]`，也不要上报 `supports_qr_login: true`。
* 只声明 manifest 头注会显示入口，但 backend 仍不会调用扫码动作。
* 已上报 `supports_qr_login: true` 时，两个 action 都必须通过 control 信封响应，并原样返回 `aut_echo`。
* `account_config.schema_values` 不要写入无关字段；日志和 `message` 不要泄露 token、密码、私钥、验证码或完整签名。

## 主动动作

| 动作                             | 何时使用                                      | 要求                                             |
| ------------------------------ | ----------------------------------------- | ---------------------------------------------- |
| `adapter_emit_inbound_events`  | worker 自己收到平台事件，例如官方 SDK 长连接              | `events` 每项必须有 `AccountID`。高频事件要分批和限流。         |
| `adapter_request_outbound`     | worker 在平台 workflow 中需要发送标准消息，例如自动同意好友后回复 | 会重新进入标准发送链路，避免同步等待造成死锁。                        |
| `adapter_update_account_state` | 登录成功、掉线、心跳或账号不可发送                         | `account_id` 必填；`last_heartbeat` 用 RFC3339 时间。 |

主动发送示例：

```json theme={null}
{
  "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": "好友已通过"}
  }
}
```

账号状态示例：

```json theme={null}
{
  "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"}
  }
}
```

扫码登录动作也使用同一 control 信封，见上方“扫码登录”。

## 媒体发送契约

普通插件调用 `replyImage`、`replyVoice`、`replyVideo`、`replyFile` 或 `replyMixed` 后，backend 会把媒体放进出站 `payload.media` 或 `payload.items`。

默认不要声明：

```js theme={null}
//[media: {"requires_file_path":true}]
```

默认模式下，backend 会尽量把本地文件转换成临时 URL，再传给 worker：

```json theme={null}
{
  "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 必须读取本地文件时，才声明本地文件路径模式：

```js theme={null}
//[media: {"requires_file_path":true}]
```

这时 backend 会提供 `file_path`：

```json theme={null}
{
  "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_name` 和 `mime_type`。
* 不要把本地路径发给远端平台。

## 最小 Node.js 框架

```js theme={null}
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,
      supports_qr_login: 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 调用并退避重连。
* `EventData`、`meta` 和日志不要包含敏感信息。
* 群聊、私聊、媒体、撤回、账号离线和平台错误都要做实测。
