openclaw-weixin 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,64 +1,60 @@
1
- # Backend API Protocol
1
+ # 后端 API 协议
2
2
 
3
- [Back to detailed guide](./guide.md) |
4
- [简体中文](./backend-api.zh_CN.md)
3
+ [返回详细指南](./guide.md) | [English](./backend-api_EN.md)
5
4
 
6
- This document covers every Weixin backend endpoint used by the plugin for QR
7
- login, lifecycle notifications, messaging, and media. The two QR login requests
8
- always use Tencent's fixed service. A backend selected by the account's
9
- post-login `baseurl` must implement the lifecycle, messaging, and media
10
- endpoints.
5
+ 本文档覆盖插件用于扫码登录、生命周期通知、消息和媒体的全部微信后端接口。两个扫码
6
+ 登录请求始终使用腾讯固定服务;账号登录后 `baseurl` 指定的后端需要实现生命周期、
7
+ 消息和媒体接口。
11
8
 
12
- QR creation and all post-login endpoints use `POST`; QR status polling uses
13
- `GET`. All requests include:
9
+ 二维码创建和登录后的接口使用 `POST`;二维码状态轮询使用 `GET`。所有 API
10
+ 请求均携带:
14
11
 
15
- | Header | Description |
16
- |--------|-------------|
17
- | `iLink-App-Id` | Plugin application ID |
18
- | `iLink-App-ClientVersion` | Plugin version encoded as an unsigned integer |
19
- | `SKRouteTag` | Optional configured route tag |
12
+ | Header | 说明 |
13
+ |--------|------|
14
+ | `iLink-App-Id` | 插件应用 ID |
15
+ | `iLink-App-ClientVersion` | 编码为无符号整数的插件版本 |
16
+ | `SKRouteTag` | 可选的配置路由标签 |
20
17
 
21
- `POST` requests additionally include `Content-Type: application/json`,
22
- `AuthorizationType: ilink_bot_token`, and a random base64-encoded
23
- `X-WECHAT-UIN`. Authenticated post-login requests also include
24
- `Authorization: Bearer <bot-token>`; QR status `GET` requests do not include
25
- these `POST`-specific headers.
18
+ `POST` 请求还会携带 `Content-Type: application/json`、
19
+ `AuthorizationType: ilink_bot_token` 和随机 uint32 的 base64 编码
20
+ `X-WECHAT-UIN`。登录后的鉴权请求还会携带
21
+ `Authorization: Bearer <bot-token>`;二维码状态 `GET` 请求不携带这些 `POST`
22
+ 专用请求头。
26
23
 
27
- Authenticated post-login `POST` bodies include `base_info`; QR creation does
28
- not. The message examples below omit it for readability.
24
+ 登录后的 `POST` 请求体包含 `base_info`,二维码创建请求则不包含。下方消息示例
25
+ 为简洁起见将其省略。
29
26
 
30
27
  ```json
31
28
  {
32
29
  "base_info": {
33
- "channel_version": "<plugin version>",
30
+ "channel_version": "<插件版本>",
34
31
  "bot_agent": "OpenClaw"
35
32
  }
36
33
  }
37
34
  ```
38
35
 
39
- `bot_agent` is for observability only. Its supported format and configuration
40
- are documented in the [detailed guide](./guide.md#custom-botagent-optional).
36
+ `bot_agent` 仅用于观测,其格式和配置方式见
37
+ [详细指南](./guide.md#自定义-botagent可选)。
41
38
 
42
- ## Endpoint List
39
+ ## 接口列表
43
40
 
44
- | Method | Path | Description |
45
- |--------|------|-------------|
46
- | `POST` | `/ilink/bot/get_bot_qrcode?bot_type=3` | Create a QR login session |
47
- | `GET` | `/ilink/bot/get_qrcode_status?qrcode=<opaque-id>` | Poll QR login status; accepts optional `verify_code` |
48
- | `POST` | `/ilink/bot/msg/notifystart` | Notify backend that the channel started |
49
- | `POST` | `/ilink/bot/msg/notifystop` | Notify backend that the channel stopped |
50
- | `POST` | `/ilink/bot/getupdates` | Long-poll for new messages |
51
- | `POST` | `/ilink/bot/sendmessage` | Send a message (text/image/video/file) |
52
- | `POST` | `/ilink/bot/getuploadurl` | Get CDN upload pre-signed parameters |
53
- | `POST` | `/ilink/bot/getconfig` | Get account config (typing ticket, etc.) |
54
- | `POST` | `/ilink/bot/sendtyping` | Send/cancel typing status |
41
+ | 方法 | 路径 | 说明 |
42
+ |------|------|------|
43
+ | `POST` | `/ilink/bot/get_bot_qrcode?bot_type=3` | 创建扫码登录会话 |
44
+ | `GET` | `/ilink/bot/get_qrcode_status?qrcode=<opaque-id>` | 轮询扫码状态;可选传入 `verify_code` |
45
+ | `POST` | `/ilink/bot/msg/notifystart` | 通知后端 channel 已启动 |
46
+ | `POST` | `/ilink/bot/msg/notifystop` | 通知后端 channel 已停止 |
47
+ | `POST` | `/ilink/bot/getupdates` | 长轮询获取新消息 |
48
+ | `POST` | `/ilink/bot/sendmessage` | 发送消息(文本/图片/视频/文件) |
49
+ | `POST` | `/ilink/bot/getuploadurl` | 获取 CDN 上传预签名参数 |
50
+ | `POST` | `/ilink/bot/getconfig` | 获取账号配置(typing ticket 等) |
51
+ | `POST` | `/ilink/bot/sendtyping` | 发送或取消输入状态 |
55
52
 
56
- The first two rows describe the fixed QR login service. They are not sent to the
57
- account's post-login `baseurl`.
53
+ 前两行描述固定的扫码登录服务,不会发送到账号登录后的 `baseurl`。
58
54
 
59
- ## QR Login and Lifecycle
55
+ ## 扫码登录与生命周期
60
56
 
61
- Create a QR session with:
57
+ 创建扫码会话:
62
58
 
63
59
  ```http
64
60
  POST /ilink/bot/get_bot_qrcode?bot_type=3
@@ -71,24 +67,23 @@ Content-Type: application/json
71
67
  }
72
68
  ```
73
69
 
74
- The response contains an opaque `qrcode` identifier and
75
- `qrcode_img_content`, the URL rendered as the QR code. Poll
76
- `GET /ilink/bot/get_qrcode_status?qrcode=<opaque-id>` until it reaches a
77
- terminal state. The optional `verify_code` query parameter handles verification
78
- challenges.
79
-
80
- | Field | Type | Description |
81
- |-------|------|-------------|
82
- | `status` | `string` | `wait`, `scaned`, `need_verifycode`, `verify_code_blocked`, `expired`, `scaned_but_redirect`, `binded_redirect`, or `confirmed` |
83
- | `bot_token` | `string?` | Bot credential returned after confirmation |
84
- | `ilink_bot_id` | `string?` | Required account ID after confirmation |
85
- | `baseurl` | `string?` | Account API base URL |
86
- | `ilink_user_id` | `string?` | ID of the user who scanned the QR code |
87
- | `redirect_host` | `string?` | New polling host for `scaned_but_redirect` |
88
-
89
- After authentication, call `/ilink/bot/msg/notifystart` when the channel starts
90
- and `/ilink/bot/msg/notifystop` when it stops. Both receive the standard
91
- `base_info` body and return:
70
+ 响应包含不透明的 `qrcode` 标识,以及用于生成二维码的 URL
71
+ `qrcode_img_content`。随后轮询
72
+ `GET /ilink/bot/get_qrcode_status?qrcode=<opaque-id>`,直到进入终止状态。遇到
73
+ 验证挑战时,可通过 `verify_code` 查询参数提交验证码。
74
+
75
+ | 字段 | 类型 | 说明 |
76
+ |------|------|------|
77
+ | `status` | `string` | `wait`、`scaned`、`need_verifycode`、`verify_code_blocked`、`expired`、`scaned_but_redirect`、`binded_redirect` 或 `confirmed` |
78
+ | `bot_token` | `string?` | 确认后返回的 bot 凭据 |
79
+ | `ilink_bot_id` | `string?` | 确认后必需的账号 ID |
80
+ | `baseurl` | `string?` | 账号 API 基础 URL |
81
+ | `ilink_user_id` | `string?` | 扫码用户 ID |
82
+ | `redirect_host` | `string?` | `scaned_but_redirect` 返回的新轮询主机 |
83
+
84
+ 鉴权完成后,channel 启动时调用 `/ilink/bot/msg/notifystart`,停止时调用
85
+ `/ilink/bot/msg/notifystop`。两者均接收标准 `base_info` 请求体,并返回
86
+ 以下结构:
92
87
 
93
88
  ```json
94
89
  {
@@ -97,15 +92,14 @@ and `/ilink/bot/msg/notifystop` when it stops. Both receive the standard
97
92
  }
98
93
  ```
99
94
 
100
- QR identifiers, verification codes, bot tokens, account IDs, user IDs, and
101
- context tokens are sensitive. Never place real values in logs or examples.
95
+ 二维码标识、验证码、bot token、账号 ID、用户 ID 和 context token 均属敏感
96
+ 信息,不要在日志或示例中写入真实值。
102
97
 
103
98
  ## getUpdates
104
99
 
105
- Long-polling endpoint. The server responds when new messages arrive or on
106
- timeout.
100
+ 长轮询接口。服务端在有新消息或超时后返回。
107
101
 
108
- **Request body:**
102
+ **请求体:**
109
103
 
110
104
  ```json
111
105
  {
@@ -113,52 +107,52 @@ timeout.
113
107
  }
114
108
  ```
115
109
 
116
- | Field | Type | Description |
117
- |-------|------|-------------|
118
- | `get_updates_buf` | `string` | Sync cursor from the previous response; empty string for the first request |
110
+ | 字段 | 类型 | 说明 |
111
+ |------|------|------|
112
+ | `get_updates_buf` | `string` | 上次响应返回的同步游标,首次请求传空字符串 |
119
113
 
120
- **Response body:**
114
+ **响应体:**
121
115
 
122
116
  ```json
123
117
  {
124
118
  "ret": 0,
125
119
  "msgs": [],
126
- "get_updates_buf": "<new cursor>",
120
+ "get_updates_buf": "<新游标>",
127
121
  "longpolling_timeout_ms": 35000
128
122
  }
129
123
  ```
130
124
 
131
- | Field | Type | Description |
132
- |-------|------|-------------|
133
- | `ret` | `number` | Return code, `0` = success |
134
- | `errcode` | `number?` | Error code (e.g., `-14` = stale token) |
135
- | `errmsg` | `string?` | Error description |
136
- | `msgs` | `WeixinMessage[]` | Message list (structure below) |
137
- | `get_updates_buf` | `string` | New sync cursor to pass in the next request |
138
- | `longpolling_timeout_ms` | `number?` | Server-suggested long-poll timeout for the next request (ms) |
125
+ | 字段 | 类型 | 说明 |
126
+ |------|------|------|
127
+ | `ret` | `number` | 返回码,`0` = 成功 |
128
+ | `errcode` | `number?` | 错误码(如 `-14` = token 失效) |
129
+ | `errmsg` | `string?` | 错误描述 |
130
+ | `msgs` | `WeixinMessage[]` | 消息列表(结构见下方) |
131
+ | `get_updates_buf` | `string` | 新的同步游标,下次请求时回传 |
132
+ | `longpolling_timeout_ms` | `number?` | 服务端建议的下次长轮询超时(ms) |
139
133
 
140
134
  ## sendMessage
141
135
 
142
- Send a message to a user.
136
+ 发送一条消息给用户。
143
137
 
144
- **Request body:**
138
+ **请求体:**
145
139
 
146
140
  ```json
147
141
  {
148
142
  "msg": {
149
- "to_user_id": "<target user ID>",
150
- "context_token": "<conversation context token>",
143
+ "to_user_id": "<目标用户 ID>",
144
+ "context_token": "<会话上下文令牌>",
151
145
  "item_list": [
152
146
  {
153
147
  "type": 1,
154
- "text_item": { "text": "Hello" }
148
+ "text_item": { "text": "你好" }
155
149
  }
156
150
  ]
157
151
  }
158
152
  }
159
153
  ```
160
154
 
161
- **Response body:**
155
+ **响应体:**
162
156
 
163
157
  ```json
164
158
  {
@@ -169,96 +163,96 @@ Send a message to a user.
169
163
 
170
164
  ## getUploadUrl
171
165
 
172
- Get CDN upload pre-signed parameters. Call this endpoint before uploading a file
173
- to obtain `upload_param` and `thumb_upload_param`.
166
+ 获取 CDN 上传预签名参数。上传文件前需先调用此接口获取 `upload_param` 和
167
+ `thumb_upload_param`。
174
168
 
175
- **Request body:**
169
+ **请求体:**
176
170
 
177
171
  ```json
178
172
  {
179
- "filekey": "<file identifier>",
173
+ "filekey": "<文件标识>",
180
174
  "media_type": 1,
181
- "to_user_id": "<target user ID>",
175
+ "to_user_id": "<目标用户 ID>",
182
176
  "rawsize": 12345,
183
- "rawfilemd5": "<plaintext MD5>",
177
+ "rawfilemd5": "<明文 MD5>",
184
178
  "filesize": 12352,
185
179
  "no_need_thumb": true,
186
- "aeskey": "<32-character hex AES key>"
180
+ "aeskey": "<32 位十六进制 AES 密钥>"
187
181
  }
188
182
  ```
189
183
 
190
- | Field | Type | Description |
191
- |-------|------|-------------|
192
- | `filekey` | `string` | Per-upload file identifier |
193
- | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE, `4` = VOICE |
194
- | `to_user_id` | `string` | Target user ID |
195
- | `rawsize` | `number` | Original file plaintext size |
196
- | `rawfilemd5` | `string` | Original file plaintext MD5 |
197
- | `filesize` | `number` | Ciphertext size after AES-128-ECB encryption |
198
- | `no_need_thumb` | `boolean?` | Set `true` to omit thumbnail upload parameters |
199
- | `aeskey` | `string?` | AES-128 key as 32 hexadecimal characters |
200
- | `thumb_rawsize` | `number?` | Thumbnail plaintext size when a thumbnail is requested |
201
- | `thumb_rawfilemd5` | `string?` | Thumbnail plaintext MD5 when requested |
202
- | `thumb_filesize` | `number?` | Thumbnail ciphertext size when requested |
203
-
204
- **Response body:**
184
+ | 字段 | 类型 | 说明 |
185
+ |------|------|------|
186
+ | `filekey` | `string` | 本次上传的文件标识 |
187
+ | `media_type` | `number` | `1` = IMAGE、`2` = VIDEO、`3` = FILE、`4` = VOICE |
188
+ | `to_user_id` | `string` | 目标用户 ID |
189
+ | `rawsize` | `number` | 原文件明文大小 |
190
+ | `rawfilemd5` | `string` | 原文件明文 MD5 |
191
+ | `filesize` | `number` | AES-128-ECB 加密后的密文大小 |
192
+ | `no_need_thumb` | `boolean?` | 设为 `true` 时不请求缩略图上传参数 |
193
+ | `aeskey` | `string?` | 32 位十六进制 AES-128 密钥 |
194
+ | `thumb_rawsize` | `number?` | 请求缩略图时的明文大小 |
195
+ | `thumb_rawfilemd5` | `string?` | 请求缩略图时的明文 MD5 |
196
+ | `thumb_filesize` | `number?` | 请求缩略图时的密文大小 |
197
+
198
+ **响应体:**
205
199
 
206
200
  ```json
207
201
  {
208
- "upload_param": "<original image upload encrypted parameters>",
202
+ "upload_param": "<原图上传加密参数>",
209
203
  "upload_full_url": "https://cdn.example.test/upload",
210
- "thumb_upload_param": "<optional thumbnail upload parameters>"
204
+ "thumb_upload_param": "<可选的缩略图上传参数>"
211
205
  }
212
206
  ```
213
207
 
214
- | Field | Type | Description |
215
- |-------|------|-------------|
216
- | `upload_param` | `string?` | Parameters used to construct the CDN upload URL |
217
- | `upload_full_url` | `string?` | Complete CDN upload URL; takes precedence over `upload_param` |
218
- | `thumb_upload_param` | `string?` | Optional thumbnail upload parameters |
208
+ | 字段 | 类型 | 说明 |
209
+ |------|------|------|
210
+ | `upload_param` | `string?` | 用于构造 CDN 上传 URL 的参数 |
211
+ | `upload_full_url` | `string?` | 完整 CDN 上传 URL;优先于 `upload_param` |
212
+ | `thumb_upload_param` | `string?` | 可选的缩略图上传参数 |
219
213
 
220
214
  ## getConfig
221
215
 
222
- Get account configuration, including the typing ticket.
216
+ 获取账号配置,包括 typing ticket。
223
217
 
224
- **Request body:**
218
+ **请求体:**
225
219
 
226
220
  ```json
227
221
  {
228
- "ilink_user_id": "<user ID>",
229
- "context_token": "<optional, conversation context token>"
222
+ "ilink_user_id": "<用户 ID>",
223
+ "context_token": "<可选,会话上下文令牌>"
230
224
  }
231
225
  ```
232
226
 
233
- **Response body:**
227
+ **响应体:**
234
228
 
235
229
  ```json
236
230
  {
237
231
  "ret": 0,
238
232
  "errmsg": "",
239
- "typing_ticket": "<base64-encoded typing ticket>"
233
+ "typing_ticket": "<base64 编码的 typing ticket>"
240
234
  }
241
235
  ```
242
236
 
243
237
  ## sendTyping
244
238
 
245
- Send or cancel the typing status indicator.
239
+ 发送或取消输入状态指示。
246
240
 
247
- **Request body:**
241
+ **请求体:**
248
242
 
249
243
  ```json
250
244
  {
251
- "ilink_user_id": "<user ID>",
252
- "typing_ticket": "<obtained from getConfig>",
245
+ "ilink_user_id": "<用户 ID>",
246
+ "typing_ticket": "<从 getConfig 获取>",
253
247
  "status": 1
254
248
  }
255
249
  ```
256
250
 
257
- | Field | Type | Description |
258
- |-------|------|-------------|
259
- | `status` | `number` | `1` = typing, `2` = cancel typing |
251
+ | 字段 | 类型 | 说明 |
252
+ |------|------|------|
253
+ | `status` | `number` | `1` = 正在输入,`2` = 取消输入 |
260
254
 
261
- **Response body:**
255
+ **响应体:**
262
256
 
263
257
  ```json
264
258
  {
@@ -267,81 +261,78 @@ Send or cancel the typing status indicator.
267
261
  }
268
262
  ```
269
263
 
270
- ## Message Structure
264
+ ## 消息结构
271
265
 
272
266
  ### WeixinMessage
273
267
 
274
- | Field | Type | Description |
275
- |-------|------|-------------|
276
- | `seq` | `number?` | Message sequence number |
277
- | `message_id` | `number?` | Unique message ID |
278
- | `from_user_id` | `string?` | Sender ID |
279
- | `to_user_id` | `string?` | Receiver ID |
280
- | `client_id` | `string?` | Client-generated message ID |
281
- | `create_time_ms` | `number?` | Creation timestamp (ms) |
282
- | `update_time_ms` | `number?` | Update timestamp (ms) |
283
- | `delete_time_ms` | `number?` | Deletion timestamp (ms) |
284
- | `session_id` | `string?` | Session ID |
285
- | `group_id` | `string?` | Group ID |
268
+ | 字段 | 类型 | 说明 |
269
+ |------|------|------|
270
+ | `seq` | `number?` | 消息序列号 |
271
+ | `message_id` | `number?` | 消息唯一 ID |
272
+ | `from_user_id` | `string?` | 发送者 ID |
273
+ | `to_user_id` | `string?` | 接收者 ID |
274
+ | `client_id` | `string?` | 客户端生成的消息 ID |
275
+ | `create_time_ms` | `number?` | 创建时间戳(ms) |
276
+ | `update_time_ms` | `number?` | 更新时间戳(ms) |
277
+ | `delete_time_ms` | `number?` | 删除时间戳(ms) |
278
+ | `session_id` | `string?` | 会话 ID |
279
+ | `group_id` | `string?` | 群组 ID |
286
280
  | `message_type` | `number?` | `1` = USER, `2` = BOT |
287
281
  | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH |
288
- | `item_list` | `MessageItem[]?` | Message content list |
289
- | `context_token` | `string?` | Conversation context token, must be passed back when replying |
290
- | `run_id` | `string?` | OpenClaw run ID for generated replies |
282
+ | `item_list` | `MessageItem[]?` | 消息内容列表 |
283
+ | `context_token` | `string?` | 会话上下文令牌,回复时需回传 |
284
+ | `run_id` | `string?` | 生成回复对应的 OpenClaw run ID |
291
285
 
292
286
  ### MessageItem
293
287
 
294
- | Field | Type | Description |
295
- |-------|------|-------------|
296
- | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO, `11` TOOL_CALL_START, `12` TOOL_CALL_RESULT |
297
- | `create_time_ms` | `number?` | Item creation timestamp |
298
- | `update_time_ms` | `number?` | Item update timestamp |
299
- | `is_completed` | `boolean?` | Whether a progress item is complete |
300
- | `msg_id` | `string?` | Item message ID |
301
- | `text_item` | `{ text: string }?` | Text content |
302
- | `image_item` | `ImageItem?` | Image (with CDN reference and AES key) |
303
- | `voice_item` | `VoiceItem?` | Voice (SILK encoded) |
304
- | `file_item` | `FileItem?` | File attachment |
305
- | `video_item` | `VideoItem?` | Video |
306
- | `ref_msg` | `RefMessage?` | Referenced message |
307
- | `tool_call_start_item` | `{ tool_name?: string; tool_call_id?: string }?` | Tool invocation metadata |
308
- | `tool_call_result_item` | `{ tool_name?: string; tool_call_id?: string; status?: string }?` | Tool completion metadata |
309
-
310
- ### Nested Item Structures
311
-
312
- | Structure | Fields |
313
- |-----------|--------|
314
- | `RefMessage` | `message_item?: MessageItem`, `title?: string` |
315
- | `ImageItem` | `media?: CDNMedia`, `thumb_media?: CDNMedia`, `aeskey?: string`, `url?: string`, `mid_size?: number`, `thumb_size?: number`, `thumb_height?: number`, `thumb_width?: number`, `hd_size?: number` |
316
- | `VoiceItem` | `media?: CDNMedia`, `encode_type?: number`, `bits_per_sample?: number`, `sample_rate?: number`, `playtime?: number`, `text?: string` |
317
- | `FileItem` | `media?: CDNMedia`, `file_name?: string`, `md5?: string`, `len?: string` |
318
- | `VideoItem` | `media?: CDNMedia`, `video_size?: number`, `play_length?: number`, `video_md5?: string`, `thumb_media?: CDNMedia`, `thumb_size?: number`, `thumb_height?: number`, `thumb_width?: number` |
319
- | `ToolCallStartItem` | `tool_name?: string`, `tool_call_id?: string` |
320
- | `ToolCallResultItem` | `tool_name?: string`, `tool_call_id?: string`, `status?: string` |
321
-
322
- ### CDN Media Reference (CDNMedia)
323
-
324
- All media types (image/voice/file/video) are transferred via CDN using
325
- AES-128-ECB encryption:
326
-
327
- | Field | Type | Description |
328
- |-------|------|-------------|
329
- | `encrypt_query_param` | `string?` | Encrypted parameters for CDN download/upload |
330
- | `aes_key` | `string?` | Base64-encoded AES-128 key |
331
- | `encrypt_type` | `number?` | Encryption metadata mode |
332
- | `full_url` | `string?` | Complete download URL returned by the backend |
333
-
334
- ## CDN Upload Flow
335
-
336
- 1. Calculate the file's plaintext size, MD5, and ciphertext size after
337
- AES-128-ECB encryption
338
- 2. If a thumbnail is needed (image/video), calculate the thumbnail's plaintext
339
- and ciphertext parameters as well
340
- 3. Call `getUploadUrl` to get `upload_full_url` or `upload_param` (and optional
341
- `thumb_upload_param`)
342
- 4. Encrypt the file content with AES-128-ECB and `POST` it to the CDN URL as
343
- `application/octet-stream`
344
- 5. Encrypt and upload the thumbnail in the same way when requested
345
- 6. Read `x-encrypted-param` from the CDN response and use it as
346
- `encrypt_query_param` in the `CDNMedia` reference
347
- 7. Include the reference in the `MessageItem` and send
288
+ | 字段 | 类型 | 说明 |
289
+ |------|------|------|
290
+ | `type` | `number` | `1` TEXT、`2` IMAGE、`3` VOICE、`4` FILE、`5` VIDEO、`11` TOOL_CALL_START、`12` TOOL_CALL_RESULT |
291
+ | `create_time_ms` | `number?` | 条目创建时间戳 |
292
+ | `update_time_ms` | `number?` | 条目更新时间戳 |
293
+ | `is_completed` | `boolean?` | 进度条目是否完成 |
294
+ | `msg_id` | `string?` | 条目消息 ID |
295
+ | `text_item` | `{ text: string }?` | 文本内容 |
296
+ | `image_item` | `ImageItem?` | 图片(含 CDN 引用和 AES 密钥) |
297
+ | `voice_item` | `VoiceItem?` | 语音(SILK 编码) |
298
+ | `file_item` | `FileItem?` | 文件附件 |
299
+ | `video_item` | `VideoItem?` | 视频 |
300
+ | `ref_msg` | `RefMessage?` | 引用消息 |
301
+ | `tool_call_start_item` | `{ tool_name?: string; tool_call_id?: string }?` | 工具调用信息 |
302
+ | `tool_call_result_item` | `{ tool_name?: string; tool_call_id?: string; status?: string }?` | 工具完成信息 |
303
+
304
+ ### 嵌套条目结构
305
+
306
+ | 结构 | 字段 |
307
+ |------|------|
308
+ | `RefMessage` | `message_item?: MessageItem`、`title?: string` |
309
+ | `ImageItem` | `media?: CDNMedia`、`thumb_media?: CDNMedia`、`aeskey?: string`、`url?: string`、`mid_size?: number`、`thumb_size?: number`、`thumb_height?: number`、`thumb_width?: number`、`hd_size?: number` |
310
+ | `VoiceItem` | `media?: CDNMedia`、`encode_type?: number`、`bits_per_sample?: number`、`sample_rate?: number`、`playtime?: number`、`text?: string` |
311
+ | `FileItem` | `media?: CDNMedia`、`file_name?: string`、`md5?: string`、`len?: string` |
312
+ | `VideoItem` | `media?: CDNMedia`、`video_size?: number`、`play_length?: number`、`video_md5?: string`、`thumb_media?: CDNMedia`、`thumb_size?: number`、`thumb_height?: number`、`thumb_width?: number` |
313
+ | `ToolCallStartItem` | `tool_name?: string`、`tool_call_id?: string` |
314
+ | `ToolCallResultItem` | `tool_name?: string`、`tool_call_id?: string`、`status?: string` |
315
+
316
+ ### CDN 媒体引用 (CDNMedia)
317
+
318
+ 所有媒体类型(图片/语音/文件/视频)通过 CDN 传输,使用 AES-128-ECB 加密:
319
+
320
+ | 字段 | 类型 | 说明 |
321
+ |------|------|------|
322
+ | `encrypt_query_param` | `string?` | CDN 下载/上传的加密参数 |
323
+ | `aes_key` | `string?` | base64 编码的 AES-128 密钥 |
324
+ | `encrypt_type` | `number?` | 加密元数据模式 |
325
+ | `full_url` | `string?` | 后端返回的完整下载 URL |
326
+
327
+ ## CDN 上传流程
328
+
329
+ 1. 计算文件明文大小、MD5,以及 AES-128-ECB 加密后的密文大小
330
+ 2. 如需缩略图(图片/视频),同样计算缩略图的明文和密文参数
331
+ 3. 调用 `getUploadUrl` 获取 `upload_full_url` 或 `upload_param`(以及可选的
332
+ `thumb_upload_param`)
333
+ 4. 使用 AES-128-ECB 加密文件内容,以 `application/octet-stream` 通过 `POST`
334
+ 上传到 CDN URL
335
+ 5. 需要缩略图时,同理加密并上传
336
+ 6. 从 CDN 响应读取 `x-encrypted-param`,作为 `CDNMedia` 引用中的
337
+ `encrypt_query_param`
338
+ 7. 将引用放入 `MessageItem` 后发送