@xgjktech/xg_cwork_im 1.0.7 → 1.0.9
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.
- package/README.md +367 -360
- package/package.json +1 -1
- package/src/channel.ts +393 -175
- package/src/connection.ts +362 -351
- package/src/group-history-tool.ts +21 -6
- package/src/inbound-media-local.ts +159 -0
- package/src/resource-file.ts +253 -0
- package/src/send-group-message-tool.ts +98 -98
- package/src/send-service.ts +215 -68
- package/src/tool-json-result.ts +24 -0
- package/src/types.ts +122 -27
package/src/send-service.ts
CHANGED
|
@@ -1,68 +1,215 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* XG-IM 消息发送模块
|
|
3
|
-
*
|
|
4
|
-
* 调用 IM 接口将 AI 回复发送到指定群聊,并 @ 原始发送者。
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
...
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
1
|
+
/**
|
|
2
|
+
* XG-IM 消息发送模块
|
|
3
|
+
*
|
|
4
|
+
* 调用 IM 接口将 AI 回复发送到指定群聊,并 @ 原始发送者。
|
|
5
|
+
* 支持 RICH_TEXT 与带资源 fileId 的 FILE 类型(attachments)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import axios from "axios";
|
|
9
|
+
import type { Log } from "./auth.js";
|
|
10
|
+
import {
|
|
11
|
+
downloadMediaBuffer,
|
|
12
|
+
formatFromFilename,
|
|
13
|
+
resolveMaxAttachmentBytes,
|
|
14
|
+
resolveResourceUploadUrl,
|
|
15
|
+
uploadWholeResourceFile,
|
|
16
|
+
} from "./resource-file.js";
|
|
17
|
+
import type { SendMessageBody, SendMessageFileAttachment, SendMessageReply, XgImConfig } from "./types.js";
|
|
18
|
+
|
|
19
|
+
function formatDeliverError(err: unknown): string {
|
|
20
|
+
if (axios.isAxiosError(err)) {
|
|
21
|
+
const parts = [err.message];
|
|
22
|
+
if (err.code) parts.push(`axiosCode=${err.code}`);
|
|
23
|
+
if (err.response?.status != null) parts.push(`httpStatus=${err.response.status}`);
|
|
24
|
+
return parts.join(" ");
|
|
25
|
+
}
|
|
26
|
+
if (err instanceof Error) return err.message;
|
|
27
|
+
return String(err);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function postImMessage(
|
|
31
|
+
config: XgImConfig,
|
|
32
|
+
token: string,
|
|
33
|
+
groupId: string,
|
|
34
|
+
body: SendMessageBody,
|
|
35
|
+
atUserIds: string[] = [],
|
|
36
|
+
log?: Log,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const url = `${config.baseUrl}/im/message/send`;
|
|
39
|
+
const full: SendMessageBody = {
|
|
40
|
+
...body,
|
|
41
|
+
groupId,
|
|
42
|
+
...(atUserIds.length > 0 ? { atUserIds } : {}),
|
|
43
|
+
} as SendMessageBody;
|
|
44
|
+
|
|
45
|
+
log?.info(`[cwork_im:send] POST ${url} groupId=${groupId} type=${full.type} atUsers=${JSON.stringify(atUserIds)}`);
|
|
46
|
+
if (config.debug) {
|
|
47
|
+
log?.debug?.(`[cwork_im:send] Request body: ${JSON.stringify(full)}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const res = await axios.post(url, full, {
|
|
52
|
+
headers: {
|
|
53
|
+
"Content-Type": "application/json",
|
|
54
|
+
"access-token": token,
|
|
55
|
+
},
|
|
56
|
+
timeout: 10_000,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
log?.info(`[cwork_im:send] Response status=${res.status}`);
|
|
60
|
+
if (config.debug) {
|
|
61
|
+
log?.debug?.(`[cwork_im:send] Response body: ${JSON.stringify(res.data)}`);
|
|
62
|
+
}
|
|
63
|
+
} catch (err: unknown) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
log?.error(`[cwork_im:send] Failed to send message to groupId=${groupId}: ${msg}`);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** OpenClaw dispatch deliver 单块载荷:文本 + 可选媒体 URL(与 plugin-sdk 约定对齐)。 */
|
|
71
|
+
export type ReplyDeliverPayload = {
|
|
72
|
+
markdown?: string;
|
|
73
|
+
text?: string;
|
|
74
|
+
isThinking?: boolean;
|
|
75
|
+
mediaUrl?: string;
|
|
76
|
+
mediaUrls?: string[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 将 deliver 块发到 IM:若有媒体则先 POST baseUrl/file/upDownload/uploadWholeFile 再发 FILE;否则发 RICH_TEXT。
|
|
81
|
+
*/
|
|
82
|
+
export async function sendReplyDeliverBlock(
|
|
83
|
+
config: XgImConfig,
|
|
84
|
+
token: string,
|
|
85
|
+
groupId: string,
|
|
86
|
+
payload: ReplyDeliverPayload,
|
|
87
|
+
atUserIds: string[] = [],
|
|
88
|
+
log?: Log,
|
|
89
|
+
msgId?: string,
|
|
90
|
+
reply?: SendMessageReply,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const textToSend = (payload.markdown || payload.text || "").trim();
|
|
93
|
+
const mediaList = [
|
|
94
|
+
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
95
|
+
...((payload.mediaUrls ?? []).filter((u) => typeof u === "string" && u.trim())),
|
|
96
|
+
];
|
|
97
|
+
const uniqueUrls = [...new Set(mediaList)];
|
|
98
|
+
const maxBytes = resolveMaxAttachmentBytes(config);
|
|
99
|
+
const formField = config.fileUploadFormField?.trim() || "file";
|
|
100
|
+
|
|
101
|
+
const wantFile = uniqueUrls.length > 0 && !payload.isThinking;
|
|
102
|
+
|
|
103
|
+
if (wantFile) {
|
|
104
|
+
const fileUploadUrl = resolveResourceUploadUrl(config.baseUrl);
|
|
105
|
+
try {
|
|
106
|
+
const attachments: SendMessageFileAttachment[] = [];
|
|
107
|
+
for (const u of uniqueUrls) {
|
|
108
|
+
const { buffer, filename } = await downloadMediaBuffer(u, maxBytes, log);
|
|
109
|
+
const { fileId } = await uploadWholeResourceFile({
|
|
110
|
+
fileUploadUrl,
|
|
111
|
+
token,
|
|
112
|
+
buffer,
|
|
113
|
+
filename,
|
|
114
|
+
formField,
|
|
115
|
+
log,
|
|
116
|
+
});
|
|
117
|
+
attachments.push({
|
|
118
|
+
fileId,
|
|
119
|
+
fileType: "FILE",
|
|
120
|
+
format: formatFromFilename(filename),
|
|
121
|
+
name: filename,
|
|
122
|
+
size: buffer.length,
|
|
123
|
+
source: "resource",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const caption = textToSend.length > 0 ? textToSend : " ";
|
|
127
|
+
await postImMessage(
|
|
128
|
+
config,
|
|
129
|
+
token,
|
|
130
|
+
groupId,
|
|
131
|
+
{
|
|
132
|
+
type: "FILE",
|
|
133
|
+
text: caption,
|
|
134
|
+
attachments,
|
|
135
|
+
...(msgId ? { msgId } : {}),
|
|
136
|
+
...(reply ? { reply } : {}),
|
|
137
|
+
},
|
|
138
|
+
atUserIds,
|
|
139
|
+
log,
|
|
140
|
+
);
|
|
141
|
+
return;
|
|
142
|
+
} catch (err: unknown) {
|
|
143
|
+
log?.error(`[cwork_im:send] FILE deliver failed, fallback to text if any: ${formatDeliverError(err)}`);
|
|
144
|
+
if (textToSend.length > 0) {
|
|
145
|
+
await postImMessage(
|
|
146
|
+
config,
|
|
147
|
+
token,
|
|
148
|
+
groupId,
|
|
149
|
+
{
|
|
150
|
+
type: "RICH_TEXT",
|
|
151
|
+
text: `${textToSend}\n\n(媒体未发出:${formatDeliverError(err)})`,
|
|
152
|
+
...(msgId ? { msgId } : {}),
|
|
153
|
+
...(reply ? { reply } : {}),
|
|
154
|
+
},
|
|
155
|
+
atUserIds,
|
|
156
|
+
log,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (textToSend.length > 0) {
|
|
164
|
+
await postImMessage(
|
|
165
|
+
config,
|
|
166
|
+
token,
|
|
167
|
+
groupId,
|
|
168
|
+
{
|
|
169
|
+
type: "RICH_TEXT",
|
|
170
|
+
text: textToSend,
|
|
171
|
+
...(msgId ? { msgId } : {}),
|
|
172
|
+
...(reply ? { reply } : {}),
|
|
173
|
+
},
|
|
174
|
+
atUserIds,
|
|
175
|
+
log,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 发送文本消息到指定 IM 群聊。
|
|
182
|
+
*
|
|
183
|
+
* @param config 插件配置
|
|
184
|
+
* @param token 鉴权 token
|
|
185
|
+
* @param groupId 目标群聊 ID(gid / groupId)
|
|
186
|
+
* @param content 消息内容文本
|
|
187
|
+
* @param atUserIds 需要 @ 的用户 ID 列表(可为空)
|
|
188
|
+
* @param log 日志接口
|
|
189
|
+
* @param msgId 可选的业务消息 ID(用于覆盖/更新已有消息)
|
|
190
|
+
* @param reply 可选的被回复消息信息(用于在 IM 中建立“回复某条消息”的关联)
|
|
191
|
+
*/
|
|
192
|
+
export async function sendTextMessage(
|
|
193
|
+
config: XgImConfig,
|
|
194
|
+
token: string,
|
|
195
|
+
groupId: string,
|
|
196
|
+
content: string,
|
|
197
|
+
atUserIds: string[] = [],
|
|
198
|
+
log?: Log,
|
|
199
|
+
msgId?: string,
|
|
200
|
+
reply?: SendMessageReply,
|
|
201
|
+
): Promise<void> {
|
|
202
|
+
await postImMessage(
|
|
203
|
+
config,
|
|
204
|
+
token,
|
|
205
|
+
groupId,
|
|
206
|
+
{
|
|
207
|
+
type: "RICH_TEXT",
|
|
208
|
+
text: content,
|
|
209
|
+
...(msgId ? { msgId } : {}),
|
|
210
|
+
...(reply ? { reply } : {}),
|
|
211
|
+
},
|
|
212
|
+
atUserIds,
|
|
213
|
+
log,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type ToolExecuteReturn = Awaited<ReturnType<NonNullable<AnyAgentTool["execute"]>>>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 与 `openclaw/plugin-sdk` 的 `jsonResult` 返回结构一致(AgentToolResult),
|
|
7
|
+
* 供工具 `execute` 使用。
|
|
8
|
+
*
|
|
9
|
+
* 部分环境下网关打包插件后,`import { jsonResult } from "openclaw/plugin-sdk"` 会变成
|
|
10
|
+
* `_pluginSdk.jsonResult` 且值为 `undefined`,调用时报
|
|
11
|
+
* `(0 , _pluginSdk.jsonResult) is not a function`。
|
|
12
|
+
* 使用本函数可避免依赖该命名导出在打包产物中是否保留。
|
|
13
|
+
*/
|
|
14
|
+
export function toolJsonResult(payload: unknown): ToolExecuteReturn {
|
|
15
|
+
if (payload === undefined) {
|
|
16
|
+
return { content: [{ type: "text", text: "" }] } as ToolExecuteReturn;
|
|
17
|
+
}
|
|
18
|
+
const text =
|
|
19
|
+
typeof payload === "string" ? JSON.stringify(payload) : JSON.stringify(payload, null, 2);
|
|
20
|
+
return {
|
|
21
|
+
content: [{ type: "text", text }],
|
|
22
|
+
details: payload,
|
|
23
|
+
} as ToolExecuteReturn;
|
|
24
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -37,6 +37,11 @@ export interface XgImAccountConfig {
|
|
|
37
37
|
name?: string;
|
|
38
38
|
/** 群聊策略:open = 不需要 @,mention = 必须 @ 机器人才触发 */
|
|
39
39
|
groupPolicy?: "open" | "mention";
|
|
40
|
+
/** multipart 整文件上传字段名,默认 file(与 `/file/upDownload/uploadWholeFile` 约定一致时可不改) */
|
|
41
|
+
fileUploadFormField?: string;
|
|
42
|
+
maxAttachmentBytes?: number;
|
|
43
|
+
/** 覆盖顶层的入站附件 workspace 子目录(相对 Agent workspace) */
|
|
44
|
+
inboundMediaWorkspaceSubdir?: string;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
export interface XgImConfig extends OpenClawConfig {
|
|
@@ -76,6 +81,21 @@ export interface XgImConfig extends OpenClawConfig {
|
|
|
76
81
|
* - 仅用于保护「思考中」占位消息,避免长时间不被更新。
|
|
77
82
|
*/
|
|
78
83
|
firstReplyTimeoutMs?: number;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 附件上传地址固定为 `{baseUrl}/file/upDownload/uploadWholeFile`,请求头 `access-token` 与发 IM 相同。
|
|
87
|
+
* multipart 字段名默认 `file`,可通过 fileUploadFormField 覆盖。
|
|
88
|
+
*/
|
|
89
|
+
fileUploadFormField?: string;
|
|
90
|
+
/** 单个附件下载/上传允许的最大字节数,默认 50MB */
|
|
91
|
+
maxAttachmentBytes?: number;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 若配置为非空相对路径(相对于当前 Agent workspace 根目录):入站附件会先下载到
|
|
95
|
+
* `{workspace}/{本字段}/{userId}/{YYYY-MM-DD}/{name}_{HHmmssmmm}.ext`,再向 OpenClaw 传入本地 `file://` 路径。
|
|
96
|
+
* 未配置或为空则仍使用 IM 返回的远程 URL。
|
|
97
|
+
*/
|
|
98
|
+
inboundMediaWorkspaceSubdir?: string;
|
|
79
99
|
}
|
|
80
100
|
|
|
81
101
|
// ─── IM 接口 Request / Response ─────────────────────────────────────────────
|
|
@@ -113,6 +133,75 @@ export interface WsMessage {
|
|
|
113
133
|
ts: number;
|
|
114
134
|
}
|
|
115
135
|
|
|
136
|
+
/**
|
|
137
|
+
* 与 IM 服务 `MsgFileVO` 对齐的附件项(WebSocket `msgContent.files[]`)。
|
|
138
|
+
*/
|
|
139
|
+
export interface MsgFileVO {
|
|
140
|
+
/** 文件下载链接,短期有效 */
|
|
141
|
+
url: string;
|
|
142
|
+
/** 文件格式,小写:pdf、png、docx、doc、ppt、txt、md 等 */
|
|
143
|
+
format?: string;
|
|
144
|
+
/** 文件 ID;若来自七牛等无业务 id 的场景可为空 */
|
|
145
|
+
fileId?: string;
|
|
146
|
+
/** 文件大小(字节) */
|
|
147
|
+
size?: number;
|
|
148
|
+
/** 文件名称,如 xxx.pdf */
|
|
149
|
+
name?: string;
|
|
150
|
+
/**
|
|
151
|
+
* IM 侧已解析的正文(如图片 OCR/说明、文档抽取的文本等)。
|
|
152
|
+
* 非空时插件会拼入入站 `Body`/`RawBody`,一并交给 OpenClaw。
|
|
153
|
+
*/
|
|
154
|
+
content?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @deprecated 使用 {@link MsgFileVO} */
|
|
158
|
+
export type WsInboundFileItem = MsgFileVO;
|
|
159
|
+
|
|
160
|
+
/** 常见 IM format → MIME,供 OpenClaw 媒体理解;未知格式返回 undefined */
|
|
161
|
+
export function imFormatToMimeType(format: string | undefined): string | undefined {
|
|
162
|
+
const f = format?.trim().toLowerCase();
|
|
163
|
+
if (!f) return undefined;
|
|
164
|
+
const map: Record<string, string> = {
|
|
165
|
+
pdf: "application/pdf",
|
|
166
|
+
png: "image/png",
|
|
167
|
+
jpg: "image/jpeg",
|
|
168
|
+
jpeg: "image/jpeg",
|
|
169
|
+
gif: "image/gif",
|
|
170
|
+
webp: "image/webp",
|
|
171
|
+
doc: "application/msword",
|
|
172
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
173
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
174
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
175
|
+
xls: "application/vnd.ms-excel",
|
|
176
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
177
|
+
txt: "text/plain",
|
|
178
|
+
md: "text/markdown",
|
|
179
|
+
csv: "text/csv",
|
|
180
|
+
zip: "application/zip",
|
|
181
|
+
mp3: "audio/mpeg",
|
|
182
|
+
wav: "audio/wav",
|
|
183
|
+
mp4: "video/mp4",
|
|
184
|
+
};
|
|
185
|
+
return map[f];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 入站消息内容体(与 IM WebSocket `params.msgContent` 对齐)。
|
|
190
|
+
*
|
|
191
|
+
* - **type=`text`**:纯文本;**text** 建议非空。
|
|
192
|
+
* - **type=`file`**:带 1..N 个文件,**files** 建议至少 1 项;**text** 可为配文(同时上传多文件 + 一句话)。**files[].content** 可由 IM 填已解析文本(如图/文档),插件会拼入入站正文。
|
|
193
|
+
* - 语音在 IM 侧已转写为文字时,应以 **type=`text`** 推送转写结果,不再使用 voice。
|
|
194
|
+
*/
|
|
195
|
+
export interface WsMessageContent {
|
|
196
|
+
/** 文本正文;纯文本消息为主内容;file 消息上为可选配文 */
|
|
197
|
+
text?: string;
|
|
198
|
+
/** `text` | `file`(可扩展其它枚举,插件对未知类型按文本兜底) */
|
|
199
|
+
type: string;
|
|
200
|
+
/** 多附件;type 为 file 时由 IM 填至少一项 */
|
|
201
|
+
files?: MsgFileVO[];
|
|
202
|
+
ext?: Record<string, any>;
|
|
203
|
+
}
|
|
204
|
+
|
|
116
205
|
/** robotMention 消息的 params */
|
|
117
206
|
export interface WsMessageParams {
|
|
118
207
|
msgId: string;
|
|
@@ -129,12 +218,7 @@ export interface WsMessageParams {
|
|
|
129
218
|
*/
|
|
130
219
|
background?: string;
|
|
131
220
|
};
|
|
132
|
-
msgContent:
|
|
133
|
-
text: string;
|
|
134
|
-
type: string;
|
|
135
|
-
url?: string;
|
|
136
|
-
ext?: Record<string, any>;
|
|
137
|
-
};
|
|
221
|
+
msgContent: WsMessageContent;
|
|
138
222
|
/** 服务端可能返回 msgSendTime 或 timestamp */
|
|
139
223
|
msgSendTime?: number;
|
|
140
224
|
timestamp?: number;
|
|
@@ -156,29 +240,40 @@ export interface SendMessageReply {
|
|
|
156
240
|
previewText: string;
|
|
157
241
|
}
|
|
158
242
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*/
|
|
171
|
-
msgId?: string;
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 被回复消息信息。
|
|
175
|
-
*
|
|
176
|
-
* - 若不传:视为普通消息。
|
|
177
|
-
* - 若传入:服务端可按 targetMsgId 建立“回复某条消息”的关联。
|
|
178
|
-
*/
|
|
179
|
-
reply?: SendMessageReply;
|
|
243
|
+
/** IM 发送 FILE 类型消息时的附件项(与发消息接口约定一致) */
|
|
244
|
+
export interface SendMessageFileAttachment {
|
|
245
|
+
fileId: string;
|
|
246
|
+
/** 固定为 FILE */
|
|
247
|
+
fileType: "FILE";
|
|
248
|
+
/** 小写扩展名,如 md、pdf */
|
|
249
|
+
format: string;
|
|
250
|
+
name: string;
|
|
251
|
+
size: number;
|
|
252
|
+
/** 固定为 resource */
|
|
253
|
+
source: "resource";
|
|
180
254
|
}
|
|
181
255
|
|
|
256
|
+
export type SendMessageBody =
|
|
257
|
+
| {
|
|
258
|
+
type: "TEXT" | "RICH_TEXT" | "VOICE";
|
|
259
|
+
groupId?: string;
|
|
260
|
+
toUserId?: string;
|
|
261
|
+
text: string;
|
|
262
|
+
atUserIds?: string[];
|
|
263
|
+
msgId?: string;
|
|
264
|
+
reply?: SendMessageReply;
|
|
265
|
+
}
|
|
266
|
+
| {
|
|
267
|
+
type: "FILE";
|
|
268
|
+
groupId?: string;
|
|
269
|
+
toUserId?: string;
|
|
270
|
+
text: string;
|
|
271
|
+
attachments: SendMessageFileAttachment[];
|
|
272
|
+
atUserIds?: string[];
|
|
273
|
+
msgId?: string;
|
|
274
|
+
reply?: SendMessageReply;
|
|
275
|
+
};
|
|
276
|
+
|
|
182
277
|
/** GET /im/message/getLatestMsgListForAI 响应 */
|
|
183
278
|
export interface GetLatestMsgListResponse {
|
|
184
279
|
data?: WsMessageParams[];
|