@holon-run/agentinbox 1.0.4 → 1.0.5
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 +13 -6
- package/dist/src/adapters.js +12 -0
- package/dist/src/cli.js +87 -17
- package/dist/src/http.js +58 -1
- package/dist/src/service.js +17 -3
- package/dist/src/source_hosts.js +2 -4
- package/dist/src/source_schema.js +1 -3
- package/dist/src/sources/feishu.js +1030 -31
- package/dist/src/sources/remote.js +24 -0
- package/dist/src/sources/remote_modules.js +15 -3
- package/package.json +2 -1
- package/skills/README.md +2 -2
- package/skills/agentinbox/SKILL.md +59 -14
|
@@ -3,12 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FeishuDeliveryAdapter = exports.FeishuUxcClient = exports.DEFAULT_FEISHU_EVENT_TYPES = exports.FEISHU_IM_SCHEMA_URL = exports.FEISHU_OPENAPI_ENDPOINT = void 0;
|
|
4
4
|
exports.feishuDeliveryOperationsForHandle = feishuDeliveryOperationsForHandle;
|
|
5
5
|
exports.invokeFeishuDeliveryOperation = invokeFeishuDeliveryOperation;
|
|
6
|
+
exports.feishuFollowTemplateSpec = feishuFollowTemplateSpec;
|
|
7
|
+
exports.expandFeishuFollowTemplate = expandFeishuFollowTemplate;
|
|
8
|
+
exports.feishuSourceOperations = feishuSourceOperations;
|
|
9
|
+
exports.invokeFeishuSourceOperation = invokeFeishuSourceOperation;
|
|
6
10
|
exports.normalizeFeishuBotEvent = normalizeFeishuBotEvent;
|
|
7
11
|
exports.parseFeishuSourceConfig = parseFeishuSourceConfig;
|
|
8
12
|
const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const node_os_1 = require("node:os");
|
|
9
16
|
exports.FEISHU_OPENAPI_ENDPOINT = "https://open.feishu.cn/open-apis";
|
|
10
17
|
exports.FEISHU_IM_SCHEMA_URL = "https://raw.githubusercontent.com/holon-run/uxc/main/skills/feishu-openapi-skill/references/feishu-im.openapi.json";
|
|
18
|
+
const FEISHU_BOT_SCHEMA_FILE = (0, node_path_1.join)((0, node_os_1.tmpdir)(), "agentinbox-feishu-bot.openapi.json");
|
|
11
19
|
exports.DEFAULT_FEISHU_EVENT_TYPES = ["im.message.receive_v1"];
|
|
20
|
+
const DEFAULT_CONTEXT_BOUND_SECONDS = 7 * 24 * 60 * 60;
|
|
12
21
|
class FeishuUxcClient {
|
|
13
22
|
client;
|
|
14
23
|
constructor(client = new uxc_daemon_client_1.UxcDaemonClient({ env: process.env })) {
|
|
@@ -18,13 +27,13 @@ class FeishuUxcClient {
|
|
|
18
27
|
await this.client.call({
|
|
19
28
|
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
20
29
|
operation: "post:/im/v1/messages",
|
|
21
|
-
payload: {
|
|
30
|
+
payload: compactPayload({
|
|
22
31
|
receive_id_type: "chat_id",
|
|
23
32
|
receive_id: input.chatId,
|
|
24
33
|
msg_type: input.msgType,
|
|
25
34
|
content: input.content,
|
|
26
|
-
uuid: input.uuid
|
|
27
|
-
},
|
|
35
|
+
uuid: input.uuid,
|
|
36
|
+
}),
|
|
28
37
|
options: {
|
|
29
38
|
auth: input.auth,
|
|
30
39
|
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
@@ -35,18 +44,127 @@ class FeishuUxcClient {
|
|
|
35
44
|
await this.client.call({
|
|
36
45
|
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
37
46
|
operation: "post:/im/v1/messages/{message_id}/reply",
|
|
38
|
-
payload: {
|
|
47
|
+
payload: compactPayload({
|
|
39
48
|
message_id: input.messageId,
|
|
40
49
|
msg_type: input.msgType,
|
|
41
50
|
content: input.content,
|
|
42
|
-
reply_in_thread: input.replyInThread
|
|
43
|
-
uuid: input.uuid
|
|
51
|
+
reply_in_thread: input.replyInThread,
|
|
52
|
+
uuid: input.uuid,
|
|
53
|
+
}),
|
|
54
|
+
options: {
|
|
55
|
+
auth: input.auth,
|
|
56
|
+
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async uploadFile(input) {
|
|
61
|
+
const response = await this.client.call({
|
|
62
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
63
|
+
operation: "post:/im/v1/files",
|
|
64
|
+
payload: {
|
|
65
|
+
file_type: input.fileType ?? "stream",
|
|
66
|
+
file_name: input.fileName,
|
|
67
|
+
file: input.filePath,
|
|
68
|
+
},
|
|
69
|
+
options: {
|
|
70
|
+
auth: input.auth,
|
|
71
|
+
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const fileKey = findStringField(response.data, "file_key");
|
|
75
|
+
if (!fileKey) {
|
|
76
|
+
throw new Error("Feishu file upload response did not include file_key");
|
|
77
|
+
}
|
|
78
|
+
return fileKey;
|
|
79
|
+
}
|
|
80
|
+
async getMessage(input) {
|
|
81
|
+
const response = await this.client.call({
|
|
82
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
83
|
+
operation: "get:/im/v1/messages/{message_id}",
|
|
84
|
+
payload: {
|
|
85
|
+
message_id: input.messageId,
|
|
86
|
+
},
|
|
87
|
+
options: {
|
|
88
|
+
auth: input.auth,
|
|
89
|
+
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
return response.data;
|
|
93
|
+
}
|
|
94
|
+
async listMessages(input) {
|
|
95
|
+
const payload = {
|
|
96
|
+
container_id_type: "chat",
|
|
97
|
+
container_id: input.chatId,
|
|
98
|
+
page_size: input.pageSize ?? 20,
|
|
99
|
+
sort_type: input.sort ?? "ByCreateTimeAsc",
|
|
100
|
+
card_msg_content_type: "raw_card_content",
|
|
101
|
+
};
|
|
102
|
+
if (input.startTime) {
|
|
103
|
+
payload.start_time = input.startTime;
|
|
104
|
+
}
|
|
105
|
+
if (input.endTime) {
|
|
106
|
+
payload.end_time = input.endTime;
|
|
107
|
+
}
|
|
108
|
+
if (input.pageToken) {
|
|
109
|
+
payload.page_token = input.pageToken;
|
|
110
|
+
}
|
|
111
|
+
const response = await this.client.call({
|
|
112
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
113
|
+
operation: "get:/im/v1/messages",
|
|
114
|
+
payload,
|
|
115
|
+
options: {
|
|
116
|
+
auth: input.auth,
|
|
117
|
+
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
return response.data;
|
|
121
|
+
}
|
|
122
|
+
async getBotOpenId(input) {
|
|
123
|
+
const response = await this.client.call({
|
|
124
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
125
|
+
operation: "get:/bot/v3/info",
|
|
126
|
+
payload: {},
|
|
127
|
+
options: {
|
|
128
|
+
auth: input.auth,
|
|
129
|
+
schema_url: ensureFeishuBotSchemaFile(),
|
|
130
|
+
refresh_schema: true,
|
|
131
|
+
no_cache: true,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const openId = findStringField(response.data, "open_id");
|
|
135
|
+
if (!openId) {
|
|
136
|
+
throw new Error("Feishu bot info response did not include bot.open_id");
|
|
137
|
+
}
|
|
138
|
+
return openId;
|
|
139
|
+
}
|
|
140
|
+
async listChats(input) {
|
|
141
|
+
const response = await this.client.call({
|
|
142
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
143
|
+
operation: "get:/im/v1/chats",
|
|
144
|
+
payload: compactPayload({
|
|
145
|
+
page_size: input.pageSize ?? 20,
|
|
146
|
+
page_token: input.pageToken,
|
|
147
|
+
}),
|
|
148
|
+
options: {
|
|
149
|
+
auth: input.auth,
|
|
150
|
+
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
return response.data;
|
|
154
|
+
}
|
|
155
|
+
async getChat(input) {
|
|
156
|
+
const response = await this.client.call({
|
|
157
|
+
endpoint: input.endpoint ?? exports.FEISHU_OPENAPI_ENDPOINT,
|
|
158
|
+
operation: "get:/im/v1/chats/{chat_id}",
|
|
159
|
+
payload: {
|
|
160
|
+
chat_id: input.chatId,
|
|
44
161
|
},
|
|
45
162
|
options: {
|
|
46
163
|
auth: input.auth,
|
|
47
164
|
schema_url: input.schemaUrl ?? exports.FEISHU_IM_SCHEMA_URL,
|
|
48
165
|
},
|
|
49
166
|
});
|
|
167
|
+
return response.data;
|
|
50
168
|
}
|
|
51
169
|
}
|
|
52
170
|
exports.FeishuUxcClient = FeishuUxcClient;
|
|
@@ -64,7 +182,9 @@ function feishuDeliveryOperationsForHandle(handle) {
|
|
|
64
182
|
if (handle.surface !== "message_reply" && handle.surface !== "chat_message") {
|
|
65
183
|
return [];
|
|
66
184
|
}
|
|
67
|
-
|
|
185
|
+
const commonProperties = commonDeliveryInputSchemaProperties();
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
68
188
|
name: "send_text",
|
|
69
189
|
title: handle.surface === "message_reply" ? "Reply With Text" : "Send Text Message",
|
|
70
190
|
inputSchema: {
|
|
@@ -73,29 +193,129 @@ function feishuDeliveryOperationsForHandle(handle) {
|
|
|
73
193
|
required: ["text"],
|
|
74
194
|
properties: {
|
|
75
195
|
text: { type: "string", minLength: 1 },
|
|
76
|
-
|
|
77
|
-
schemaUrl: { type: "string", minLength: 1 },
|
|
78
|
-
schema_url: { type: "string", minLength: 1 },
|
|
79
|
-
uxcAuth: { type: "string", minLength: 1 },
|
|
80
|
-
auth: { type: "string", minLength: 1 },
|
|
81
|
-
replyInThread: { type: "boolean" },
|
|
82
|
-
reply_in_thread: { type: "boolean" },
|
|
83
|
-
uuid: { type: "string", minLength: 1 },
|
|
196
|
+
...commonProperties,
|
|
84
197
|
},
|
|
85
198
|
},
|
|
86
199
|
canonicalTextAlias: true,
|
|
87
|
-
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "send_post",
|
|
203
|
+
title: handle.surface === "message_reply" ? "Reply With Rich Text" : "Send Rich Text Message",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
additionalProperties: false,
|
|
207
|
+
anyOf: [
|
|
208
|
+
{ required: ["blocks"] },
|
|
209
|
+
{ required: ["paragraphs"] },
|
|
210
|
+
{ required: ["content"] },
|
|
211
|
+
],
|
|
212
|
+
properties: {
|
|
213
|
+
title: { type: "string" },
|
|
214
|
+
locale: { type: "string", minLength: 1 },
|
|
215
|
+
blocks: {
|
|
216
|
+
type: "array",
|
|
217
|
+
items: { type: "object", additionalProperties: true },
|
|
218
|
+
},
|
|
219
|
+
paragraphs: {
|
|
220
|
+
type: "array",
|
|
221
|
+
items: {
|
|
222
|
+
type: "array",
|
|
223
|
+
items: { type: "object", additionalProperties: true },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
content: {
|
|
227
|
+
anyOf: [
|
|
228
|
+
{ type: "string", minLength: 1 },
|
|
229
|
+
{ type: "object", additionalProperties: true },
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
...commonProperties,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "send_file",
|
|
238
|
+
title: handle.surface === "message_reply" ? "Reply With File" : "Send File Message",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: "object",
|
|
241
|
+
additionalProperties: false,
|
|
242
|
+
anyOf: [
|
|
243
|
+
{ required: ["fileKey"] },
|
|
244
|
+
{ required: ["file_key"] },
|
|
245
|
+
],
|
|
246
|
+
properties: {
|
|
247
|
+
fileKey: { type: "string", minLength: 1 },
|
|
248
|
+
file_key: { type: "string", minLength: 1 },
|
|
249
|
+
...commonProperties,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "send_file_from_path",
|
|
255
|
+
title: handle.surface === "message_reply" ? "Upload And Reply With File" : "Upload And Send File",
|
|
256
|
+
inputSchema: {
|
|
257
|
+
type: "object",
|
|
258
|
+
additionalProperties: false,
|
|
259
|
+
anyOf: [
|
|
260
|
+
{ required: ["filePath"] },
|
|
261
|
+
{ required: ["file_path"] },
|
|
262
|
+
],
|
|
263
|
+
properties: {
|
|
264
|
+
filePath: { type: "string", minLength: 1 },
|
|
265
|
+
file_path: { type: "string", minLength: 1 },
|
|
266
|
+
fileName: { type: "string", minLength: 1 },
|
|
267
|
+
file_name: { type: "string", minLength: 1 },
|
|
268
|
+
fileType: { type: "string", minLength: 1 },
|
|
269
|
+
file_type: { type: "string", minLength: 1 },
|
|
270
|
+
...commonProperties,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
];
|
|
88
275
|
}
|
|
89
276
|
async function invokeFeishuDeliveryOperation(handle, operation, input, client = new FeishuUxcClient()) {
|
|
90
|
-
|
|
91
|
-
|
|
277
|
+
const config = parseDeliveryConfig(input);
|
|
278
|
+
if (operation === "send_text") {
|
|
279
|
+
const text = asString(input.text);
|
|
280
|
+
if (!text || text.trim().length === 0) {
|
|
281
|
+
throw new Error("send_text requires input.text");
|
|
282
|
+
}
|
|
283
|
+
await sendFeishuMessage(handle, normalizeDeliveryMessage({ text }), config, client);
|
|
284
|
+
return { status: "sent", note: sentNote(handle, "text") };
|
|
92
285
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
286
|
+
if (operation === "send_post") {
|
|
287
|
+
const message = normalizeFeishuPostMessage(input);
|
|
288
|
+
await sendFeishuMessage(handle, message, config, client);
|
|
289
|
+
return { status: "sent", note: sentNote(handle, "rich text") };
|
|
96
290
|
}
|
|
97
|
-
|
|
98
|
-
|
|
291
|
+
if (operation === "send_file") {
|
|
292
|
+
const fileKey = asString(input.fileKey) ?? asString(input.file_key);
|
|
293
|
+
if (!fileKey) {
|
|
294
|
+
throw new Error("send_file requires input.fileKey");
|
|
295
|
+
}
|
|
296
|
+
await sendFeishuMessage(handle, normalizeFeishuFileMessage(fileKey), config, client);
|
|
297
|
+
return { status: "sent", note: sentNote(handle, "file") };
|
|
298
|
+
}
|
|
299
|
+
if (operation === "send_file_from_path") {
|
|
300
|
+
const filePath = asString(input.filePath) ?? asString(input.file_path);
|
|
301
|
+
if (!filePath) {
|
|
302
|
+
throw new Error("send_file_from_path requires input.filePath");
|
|
303
|
+
}
|
|
304
|
+
const fileName = asString(input.fileName) ?? asString(input.file_name) ?? basename(filePath);
|
|
305
|
+
const fileKey = await client.uploadFile({
|
|
306
|
+
endpoint: config.endpoint,
|
|
307
|
+
schemaUrl: config.schemaUrl,
|
|
308
|
+
auth: config.auth,
|
|
309
|
+
filePath,
|
|
310
|
+
fileName,
|
|
311
|
+
fileType: asString(input.fileType) ?? asString(input.file_type) ?? "stream",
|
|
312
|
+
});
|
|
313
|
+
await sendFeishuMessage(handle, normalizeFeishuFileMessage(fileKey), config, client);
|
|
314
|
+
return { status: "sent", note: `${sentNote(handle, "file")} after upload` };
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`unknown Feishu delivery operation: ${operation}`);
|
|
317
|
+
}
|
|
318
|
+
async function sendFeishuMessage(handle, message, config, client) {
|
|
99
319
|
if (handle.surface === "message_reply") {
|
|
100
320
|
await client.replyToMessage({
|
|
101
321
|
endpoint: config.endpoint,
|
|
@@ -107,7 +327,7 @@ async function invokeFeishuDeliveryOperation(handle, operation, input, client =
|
|
|
107
327
|
replyInThread: config.replyInThread,
|
|
108
328
|
uuid: config.uuid,
|
|
109
329
|
});
|
|
110
|
-
return
|
|
330
|
+
return;
|
|
111
331
|
}
|
|
112
332
|
if (handle.surface === "chat_message") {
|
|
113
333
|
await client.sendChatMessage({
|
|
@@ -119,25 +339,375 @@ async function invokeFeishuDeliveryOperation(handle, operation, input, client =
|
|
|
119
339
|
content: message.content,
|
|
120
340
|
uuid: config.uuid,
|
|
121
341
|
});
|
|
122
|
-
return
|
|
342
|
+
return;
|
|
123
343
|
}
|
|
124
344
|
throw new Error(`deliver send only supports canonical Feishu text surfaces; use deliver invoke for ${handle.surface}`);
|
|
125
345
|
}
|
|
346
|
+
function sentNote(handle, kind) {
|
|
347
|
+
return handle.surface === "message_reply"
|
|
348
|
+
? `sent Feishu ${kind} message reply`
|
|
349
|
+
: `sent Feishu ${kind} chat message`;
|
|
350
|
+
}
|
|
351
|
+
function feishuFollowTemplateSpec() {
|
|
352
|
+
return [
|
|
353
|
+
{
|
|
354
|
+
templateId: "feishu.chat",
|
|
355
|
+
providerOrKind: "feishu",
|
|
356
|
+
label: "Feishu Chat",
|
|
357
|
+
description: "Follow messages from one Feishu/Lark chat.",
|
|
358
|
+
argsSchema: [
|
|
359
|
+
{ name: "chatId", type: "string", required: true, description: "Feishu chat ID." },
|
|
360
|
+
],
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
templateId: "feishu.mention",
|
|
364
|
+
providerOrKind: "feishu",
|
|
365
|
+
label: "Feishu Mention",
|
|
366
|
+
description: "Follow messages in one Feishu/Lark chat that mention a user or bot.",
|
|
367
|
+
argsSchema: [
|
|
368
|
+
{ name: "chatId", type: "string", required: true, description: "Feishu chat ID." },
|
|
369
|
+
{ name: "openId", type: "string", required: true, description: "Mentioned user or bot open_id." },
|
|
370
|
+
],
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
templateId: "feishu.mentions",
|
|
374
|
+
providerOrKind: "feishu",
|
|
375
|
+
label: "Feishu Mentions",
|
|
376
|
+
description: "Follow messages across visible Feishu/Lark chats that mention a user or bot.",
|
|
377
|
+
argsSchema: [
|
|
378
|
+
{ name: "openId", type: "string", required: false, description: "Mentioned user or bot open_id. Defaults to the configured app bot open_id." },
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
async function expandFeishuFollowTemplate(input, client = new FeishuUxcClient()) {
|
|
384
|
+
if (input.template !== "chat" && input.template !== "mention" && input.template !== "mentions") {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const args = input.args ?? {};
|
|
388
|
+
const chatId = asString(args.chatId);
|
|
389
|
+
if (input.template !== "mentions" && !chatId) {
|
|
390
|
+
throw new Error(`follow template feishu.${input.template} requires argument chatId`);
|
|
391
|
+
}
|
|
392
|
+
const config = parseFeishuSourceConfig(input.source);
|
|
393
|
+
const sourceKey = `feishu:${config.uxcAuth ?? input.source.configRef ?? "default"}:messages`;
|
|
394
|
+
const filter = chatId ? { metadata: { chatId } } : {};
|
|
395
|
+
let trackedResourceRef = chatId ? `chat:${chatId}` : "mentions";
|
|
396
|
+
if (input.template === "mention" || input.template === "mentions") {
|
|
397
|
+
const openId = asString(args.openId) ?? await resolveFeishuMentionOpenId(input, config, client);
|
|
398
|
+
if (!openId) {
|
|
399
|
+
throw new Error(`follow template feishu.${input.template} requires argument openId`);
|
|
400
|
+
}
|
|
401
|
+
filter.expr = `contains(metadata.mentionOpenIds, ${JSON.stringify(openId)})`;
|
|
402
|
+
trackedResourceRef = chatId ? `chat:${chatId}:mention:${openId}` : `mention:${openId}`;
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
templateId: `feishu.${input.template}`,
|
|
406
|
+
sources: [
|
|
407
|
+
{
|
|
408
|
+
logicalName: "messages",
|
|
409
|
+
sourceType: "feishu_bot",
|
|
410
|
+
sourceKey,
|
|
411
|
+
configRef: input.source.configRef ?? null,
|
|
412
|
+
config: feishuFollowSourceConfig(input.source),
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
subscriptions: [
|
|
416
|
+
{
|
|
417
|
+
sourceLogicalName: "messages",
|
|
418
|
+
filter,
|
|
419
|
+
trackedResourceRef,
|
|
420
|
+
cleanupPolicy: { mode: "manual" },
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
async function resolveFeishuMentionOpenId(input, config, client) {
|
|
426
|
+
if (input.template !== "mentions") {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
return client.getBotOpenId({
|
|
430
|
+
endpoint: config.endpoint,
|
|
431
|
+
auth: config.uxcAuth ?? input.source.configRef ?? undefined,
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function feishuSourceOperations() {
|
|
435
|
+
return [
|
|
436
|
+
{
|
|
437
|
+
name: "get_message_context",
|
|
438
|
+
title: "Get Message Context",
|
|
439
|
+
inputSchema: {
|
|
440
|
+
type: "object",
|
|
441
|
+
additionalProperties: false,
|
|
442
|
+
required: ["messageId"],
|
|
443
|
+
properties: {
|
|
444
|
+
messageId: { type: "string", minLength: 1 },
|
|
445
|
+
chatId: { type: "string", minLength: 1 },
|
|
446
|
+
windowBefore: { type: "number", minimum: 0 },
|
|
447
|
+
windowAfter: { type: "number", minimum: 0 },
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
outputSchema: {
|
|
451
|
+
type: "object",
|
|
452
|
+
additionalProperties: true,
|
|
453
|
+
required: ["anchorMessage", "chatWindowMessages", "threadMessages", "warnings", "deliveryHandle"],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "list_recent_messages",
|
|
458
|
+
title: "List Recent Messages",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
additionalProperties: false,
|
|
462
|
+
required: ["chatId"],
|
|
463
|
+
properties: {
|
|
464
|
+
chatId: { type: "string", minLength: 1 },
|
|
465
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
466
|
+
before: { type: "string", minLength: 1 },
|
|
467
|
+
pageToken: { type: "string", minLength: 1 },
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
outputSchema: {
|
|
471
|
+
type: "object",
|
|
472
|
+
additionalProperties: true,
|
|
473
|
+
required: ["chatId", "messages", "warnings"],
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: "list_chats",
|
|
478
|
+
title: "List Chats",
|
|
479
|
+
inputSchema: {
|
|
480
|
+
type: "object",
|
|
481
|
+
additionalProperties: false,
|
|
482
|
+
properties: {
|
|
483
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
484
|
+
pageToken: { type: "string", minLength: 1 },
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
outputSchema: {
|
|
488
|
+
type: "object",
|
|
489
|
+
additionalProperties: true,
|
|
490
|
+
required: ["chats", "warnings"],
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "search_chats",
|
|
495
|
+
title: "Search Chats",
|
|
496
|
+
inputSchema: {
|
|
497
|
+
type: "object",
|
|
498
|
+
additionalProperties: false,
|
|
499
|
+
required: ["query"],
|
|
500
|
+
properties: {
|
|
501
|
+
query: { type: "string", minLength: 1 },
|
|
502
|
+
limit: { type: "number", minimum: 1, maximum: 50 },
|
|
503
|
+
pageToken: { type: "string", minLength: 1 },
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
outputSchema: {
|
|
507
|
+
type: "object",
|
|
508
|
+
additionalProperties: true,
|
|
509
|
+
required: ["chats", "warnings"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "get_chat",
|
|
514
|
+
title: "Get Chat",
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: "object",
|
|
517
|
+
additionalProperties: false,
|
|
518
|
+
required: ["chatId"],
|
|
519
|
+
properties: {
|
|
520
|
+
chatId: { type: "string", minLength: 1 },
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
outputSchema: {
|
|
524
|
+
type: "object",
|
|
525
|
+
additionalProperties: true,
|
|
526
|
+
required: ["chat", "warnings"],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
];
|
|
530
|
+
}
|
|
531
|
+
async function invokeFeishuSourceOperation(source, operation, input, client = new FeishuUxcClient()) {
|
|
532
|
+
if (operation === "list_recent_messages") {
|
|
533
|
+
return listRecentFeishuMessages(source, input, client);
|
|
534
|
+
}
|
|
535
|
+
if (operation === "list_chats") {
|
|
536
|
+
return listFeishuChats(source, input, client);
|
|
537
|
+
}
|
|
538
|
+
if (operation === "search_chats") {
|
|
539
|
+
return searchFeishuChats(source, input, client);
|
|
540
|
+
}
|
|
541
|
+
if (operation === "get_chat") {
|
|
542
|
+
return getFeishuChat(source, input, client);
|
|
543
|
+
}
|
|
544
|
+
if (operation !== "get_message_context") {
|
|
545
|
+
throw new Error(`unknown Feishu source operation: ${operation}`);
|
|
546
|
+
}
|
|
547
|
+
const messageId = asString(input.messageId);
|
|
548
|
+
if (!messageId) {
|
|
549
|
+
throw new Error("get_message_context requires input.messageId");
|
|
550
|
+
}
|
|
551
|
+
const config = parseFeishuSourceConfig(source);
|
|
552
|
+
const anchorRaw = await client.getMessage({
|
|
553
|
+
endpoint: config.endpoint,
|
|
554
|
+
schemaUrl: config.schemaUrl,
|
|
555
|
+
auth: config.uxcAuth,
|
|
556
|
+
messageId,
|
|
557
|
+
});
|
|
558
|
+
const anchorMessage = normalizeFeishuMessage(anchorRaw);
|
|
559
|
+
const chatId = asString(input.chatId) ?? anchorMessage?.chatId ?? null;
|
|
560
|
+
const warnings = [];
|
|
561
|
+
let chatWindowMessages = [];
|
|
562
|
+
if (chatId) {
|
|
563
|
+
const windowBefore = positiveInteger(input.windowBefore) ?? 5;
|
|
564
|
+
const windowAfter = positiveInteger(input.windowAfter) ?? 5;
|
|
565
|
+
try {
|
|
566
|
+
chatWindowMessages = await fetchFeishuChatWindow({
|
|
567
|
+
client,
|
|
568
|
+
config,
|
|
569
|
+
chatId,
|
|
570
|
+
anchorMessage,
|
|
571
|
+
windowBefore,
|
|
572
|
+
windowAfter,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
warnings.push({
|
|
577
|
+
code: "chat_window_unavailable",
|
|
578
|
+
message: error instanceof Error ? error.message : String(error),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
anchorMessage,
|
|
584
|
+
chatWindowMessages,
|
|
585
|
+
threadMessages: [],
|
|
586
|
+
warnings,
|
|
587
|
+
deliveryHandle: {
|
|
588
|
+
provider: "feishu",
|
|
589
|
+
surface: "message_reply",
|
|
590
|
+
targetRef: messageId,
|
|
591
|
+
threadRef: anchorMessage?.threadId ?? anchorMessage?.parentId ?? null,
|
|
592
|
+
replyMode: "reply",
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
async function listFeishuChats(source, input, client) {
|
|
597
|
+
const config = parseFeishuSourceConfig(source);
|
|
598
|
+
const raw = await withFeishuChatDiscoveryError("list_chats", () => client.listChats({
|
|
599
|
+
endpoint: config.endpoint,
|
|
600
|
+
schemaUrl: config.schemaUrl,
|
|
601
|
+
auth: config.uxcAuth,
|
|
602
|
+
pageSize: boundedPositiveInteger(input.limit, 20, 50),
|
|
603
|
+
pageToken: asString(input.pageToken) ?? undefined,
|
|
604
|
+
}));
|
|
605
|
+
return feishuChatsResult(raw);
|
|
606
|
+
}
|
|
607
|
+
async function searchFeishuChats(source, input, client) {
|
|
608
|
+
const query = asString(input.query)?.trim();
|
|
609
|
+
if (!query) {
|
|
610
|
+
throw new Error("search_chats requires input.query");
|
|
611
|
+
}
|
|
612
|
+
const config = parseFeishuSourceConfig(source);
|
|
613
|
+
const limit = boundedPositiveInteger(input.limit, 20, 50);
|
|
614
|
+
const raw = await withFeishuChatDiscoveryError("search_chats", () => client.listChats({
|
|
615
|
+
endpoint: config.endpoint,
|
|
616
|
+
schemaUrl: config.schemaUrl,
|
|
617
|
+
auth: config.uxcAuth,
|
|
618
|
+
pageSize: 50,
|
|
619
|
+
pageToken: asString(input.pageToken) ?? undefined,
|
|
620
|
+
}));
|
|
621
|
+
return feishuChatsResult(raw, searchFeishuChatResults(normalizeFeishuChats(raw), query).slice(0, limit));
|
|
622
|
+
}
|
|
623
|
+
async function getFeishuChat(source, input, client) {
|
|
624
|
+
const chatId = asString(input.chatId);
|
|
625
|
+
if (!chatId) {
|
|
626
|
+
throw new Error("get_chat requires input.chatId");
|
|
627
|
+
}
|
|
628
|
+
const config = parseFeishuSourceConfig(source);
|
|
629
|
+
const raw = await withFeishuChatDiscoveryError("get_chat", () => client.getChat({
|
|
630
|
+
endpoint: config.endpoint,
|
|
631
|
+
schemaUrl: config.schemaUrl,
|
|
632
|
+
auth: config.uxcAuth,
|
|
633
|
+
chatId,
|
|
634
|
+
}));
|
|
635
|
+
return {
|
|
636
|
+
chat: normalizeFeishuChat(raw),
|
|
637
|
+
warnings: [],
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
async function withFeishuChatDiscoveryError(operation, run) {
|
|
641
|
+
try {
|
|
642
|
+
return await run();
|
|
643
|
+
}
|
|
644
|
+
catch (error) {
|
|
645
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
646
|
+
throw new Error(`${operation} failed; ensure the configured Feishu UXC auth can read visible chats: ${message}`, { cause: error });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function feishuChatsResult(raw, chats = normalizeFeishuChats(raw)) {
|
|
650
|
+
const pageToken = feishuResponsePageToken(raw);
|
|
651
|
+
const hasMore = feishuResponseHasMore(raw);
|
|
652
|
+
return {
|
|
653
|
+
chats,
|
|
654
|
+
warnings: [],
|
|
655
|
+
...(pageToken ? { pageToken } : {}),
|
|
656
|
+
...(hasMore != null ? { hasMore } : {}),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function searchFeishuChatResults(chats, query) {
|
|
660
|
+
const needle = query.trim().toLowerCase();
|
|
661
|
+
if (!needle) {
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
return chats.filter((chat) => {
|
|
665
|
+
const fields = [chat.chatId, chat.name, chat.description, chat.type]
|
|
666
|
+
.filter((value) => Boolean(value))
|
|
667
|
+
.map((value) => value.toLowerCase());
|
|
668
|
+
return fields.some((value) => value.includes(needle));
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
async function listRecentFeishuMessages(source, input, client) {
|
|
672
|
+
const chatId = asString(input.chatId);
|
|
673
|
+
if (!chatId) {
|
|
674
|
+
throw new Error("list_recent_messages requires input.chatId");
|
|
675
|
+
}
|
|
676
|
+
const config = parseFeishuSourceConfig(source);
|
|
677
|
+
const limit = boundedPositiveInteger(input.limit, 20, 50);
|
|
678
|
+
const raw = await client.listMessages({
|
|
679
|
+
endpoint: config.endpoint,
|
|
680
|
+
schemaUrl: config.schemaUrl,
|
|
681
|
+
auth: config.uxcAuth,
|
|
682
|
+
chatId,
|
|
683
|
+
endTime: asString(input.before) ?? undefined,
|
|
684
|
+
pageToken: asString(input.pageToken) ?? undefined,
|
|
685
|
+
pageSize: limit,
|
|
686
|
+
sort: "ByCreateTimeDesc",
|
|
687
|
+
});
|
|
688
|
+
return {
|
|
689
|
+
chatId,
|
|
690
|
+
messages: normalizeFeishuMessages(raw),
|
|
691
|
+
warnings: [],
|
|
692
|
+
...(feishuResponsePageToken(raw) ? { pageToken: feishuResponsePageToken(raw) } : {}),
|
|
693
|
+
...(feishuResponseHasMore(raw) != null ? { hasMore: feishuResponseHasMore(raw) } : {}),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
126
696
|
function normalizeFeishuBotEvent(source, config, raw) {
|
|
127
697
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
128
698
|
return null;
|
|
129
699
|
}
|
|
130
700
|
const payload = raw;
|
|
131
701
|
const header = asRecord(payload.header);
|
|
132
|
-
const eventType = asString(payload.event_type) ?? asString(header.event_type);
|
|
702
|
+
const eventType = asString(payload.event_type) ?? asString(payload.type) ?? asString(header.event_type);
|
|
133
703
|
if (!eventType || !(config.eventTypes ?? exports.DEFAULT_FEISHU_EVENT_TYPES).includes(eventType)) {
|
|
134
704
|
return null;
|
|
135
705
|
}
|
|
136
706
|
const event = asRecord(payload.event);
|
|
137
|
-
const message = nonEmptyRecord(payload.message) ?? nonEmptyRecord(event.message) ??
|
|
138
|
-
const sender = nonEmptyRecord(payload.sender) ?? nonEmptyRecord(event.sender) ??
|
|
707
|
+
const message = nonEmptyRecord(payload.message) ?? nonEmptyRecord(event.message) ?? flatFeishuMessage(payload);
|
|
708
|
+
const sender = nonEmptyRecord(payload.sender) ?? nonEmptyRecord(event.sender) ?? flatFeishuSender(payload);
|
|
139
709
|
const messageId = asString(message.message_id);
|
|
140
|
-
const eventId = asString(header.event_id) ?? messageId;
|
|
710
|
+
const eventId = asString(payload.event_id) ?? asString(header.event_id) ?? messageId;
|
|
141
711
|
const chatId = asString(message.chat_id);
|
|
142
712
|
if (!eventId || !messageId || !chatId) {
|
|
143
713
|
return null;
|
|
@@ -148,7 +718,7 @@ function normalizeFeishuBotEvent(source, config, raw) {
|
|
|
148
718
|
const mentions = extractMentionNames(message.mentions);
|
|
149
719
|
const mentionOpenIds = extractMentionOpenIds(message.mentions);
|
|
150
720
|
const messageType = asString(message.message_type) ?? "unknown";
|
|
151
|
-
const senderId = asString(asRecord(sender.sender_id).open_id);
|
|
721
|
+
const senderId = asString(asRecord(sender.sender_id).open_id) ?? asString(sender.sender_id);
|
|
152
722
|
const senderType = asString(sender.sender_type);
|
|
153
723
|
const content = stringifyFeishuMessageContent(messageType, asString(message.content), message.mentions);
|
|
154
724
|
const threadId = asString(message.thread_id) ?? asString(message.root_id);
|
|
@@ -191,6 +761,26 @@ function normalizeFeishuBotEvent(source, config, raw) {
|
|
|
191
761
|
},
|
|
192
762
|
};
|
|
193
763
|
}
|
|
764
|
+
function flatFeishuMessage(payload) {
|
|
765
|
+
return {
|
|
766
|
+
message_id: payload.message_id,
|
|
767
|
+
chat_id: payload.chat_id,
|
|
768
|
+
chat_type: payload.chat_type,
|
|
769
|
+
message_type: payload.message_type ?? payload.msg_type,
|
|
770
|
+
content: payload.content,
|
|
771
|
+
create_time: payload.create_time,
|
|
772
|
+
mentions: payload.mentions,
|
|
773
|
+
thread_id: payload.thread_id,
|
|
774
|
+
root_id: payload.root_id,
|
|
775
|
+
parent_id: payload.parent_id,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function flatFeishuSender(payload) {
|
|
779
|
+
return {
|
|
780
|
+
sender_id: payload.sender_id,
|
|
781
|
+
sender_type: payload.sender_type,
|
|
782
|
+
};
|
|
783
|
+
}
|
|
194
784
|
function parseFeishuSourceConfig(source) {
|
|
195
785
|
const config = source.config ?? {};
|
|
196
786
|
return {
|
|
@@ -231,6 +821,351 @@ function normalizeDeliveryMessage(payload) {
|
|
|
231
821
|
content: JSON.stringify({ text: JSON.stringify(payload) }),
|
|
232
822
|
};
|
|
233
823
|
}
|
|
824
|
+
function commonDeliveryInputSchemaProperties() {
|
|
825
|
+
return {
|
|
826
|
+
endpoint: { type: "string", minLength: 1 },
|
|
827
|
+
schemaUrl: { type: "string", minLength: 1 },
|
|
828
|
+
schema_url: { type: "string", minLength: 1 },
|
|
829
|
+
uxcAuth: { type: "string", minLength: 1 },
|
|
830
|
+
auth: { type: "string", minLength: 1 },
|
|
831
|
+
replyInThread: { type: "boolean" },
|
|
832
|
+
reply_in_thread: { type: "boolean" },
|
|
833
|
+
uuid: { type: "string", minLength: 1 },
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function normalizeFeishuFileMessage(fileKey) {
|
|
837
|
+
return {
|
|
838
|
+
msgType: "file",
|
|
839
|
+
content: JSON.stringify({ file_key: fileKey }),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function normalizeFeishuPostMessage(input) {
|
|
843
|
+
const rawContent = input.content;
|
|
844
|
+
if (typeof rawContent === "string" && rawContent.trim().length > 0) {
|
|
845
|
+
return { msgType: "post", content: rawContent };
|
|
846
|
+
}
|
|
847
|
+
if (rawContent && typeof rawContent === "object" && !Array.isArray(rawContent)) {
|
|
848
|
+
return { msgType: "post", content: JSON.stringify(rawContent) };
|
|
849
|
+
}
|
|
850
|
+
const paragraphs = normalizePostParagraphs(input);
|
|
851
|
+
if (paragraphs.length === 0) {
|
|
852
|
+
throw new Error("send_post requires input.blocks, input.paragraphs, or input.content");
|
|
853
|
+
}
|
|
854
|
+
const locale = asString(input.locale) ?? "zh_cn";
|
|
855
|
+
return {
|
|
856
|
+
msgType: "post",
|
|
857
|
+
content: JSON.stringify({
|
|
858
|
+
[locale]: {
|
|
859
|
+
title: asString(input.title) ?? "",
|
|
860
|
+
content: paragraphs,
|
|
861
|
+
},
|
|
862
|
+
}),
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function normalizePostParagraphs(input) {
|
|
866
|
+
if (Array.isArray(input.paragraphs)) {
|
|
867
|
+
return input.paragraphs
|
|
868
|
+
.map((paragraph) => Array.isArray(paragraph) ? normalizePostBlocks(paragraph) : [])
|
|
869
|
+
.filter((paragraph) => paragraph.length > 0);
|
|
870
|
+
}
|
|
871
|
+
if (Array.isArray(input.blocks)) {
|
|
872
|
+
const paragraph = normalizePostBlocks(input.blocks);
|
|
873
|
+
return paragraph.length > 0 ? [paragraph] : [];
|
|
874
|
+
}
|
|
875
|
+
return [];
|
|
876
|
+
}
|
|
877
|
+
function normalizePostBlocks(blocks) {
|
|
878
|
+
return blocks.map((block) => normalizePostBlock(asRecord(block))).filter((block) => block !== null);
|
|
879
|
+
}
|
|
880
|
+
function normalizePostBlock(block) {
|
|
881
|
+
const type = asString(block.type) ?? "text";
|
|
882
|
+
if (type === "text") {
|
|
883
|
+
const text = asString(block.text);
|
|
884
|
+
if (!text) {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
return { tag: "text", text };
|
|
888
|
+
}
|
|
889
|
+
if (type === "link") {
|
|
890
|
+
const text = asString(block.text);
|
|
891
|
+
const url = asString(block.url) ?? asString(block.href);
|
|
892
|
+
if (!text || !url) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
return { tag: "a", text, href: url };
|
|
896
|
+
}
|
|
897
|
+
if (type === "mention") {
|
|
898
|
+
const openId = asString(block.openId) ?? asString(block.open_id) ?? asString(block.userId) ?? asString(block.user_id);
|
|
899
|
+
if (!openId) {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
return {
|
|
903
|
+
tag: "at",
|
|
904
|
+
user_id: openId,
|
|
905
|
+
...(asString(block.name) ? { user_name: asString(block.name) } : {}),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
function basename(filePath) {
|
|
911
|
+
const trimmed = filePath.replace(/\/+$/, "");
|
|
912
|
+
const parts = trimmed.split("/");
|
|
913
|
+
return parts[parts.length - 1] || "upload.bin";
|
|
914
|
+
}
|
|
915
|
+
function findStringField(value, field) {
|
|
916
|
+
if (!value || typeof value !== "object") {
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
const record = value;
|
|
920
|
+
const direct = asString(record[field]);
|
|
921
|
+
if (direct) {
|
|
922
|
+
return direct;
|
|
923
|
+
}
|
|
924
|
+
for (const child of Object.values(record)) {
|
|
925
|
+
const found = findStringField(child, field);
|
|
926
|
+
if (found) {
|
|
927
|
+
return found;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
function feishuFollowSourceConfig(source) {
|
|
933
|
+
const config = parseFeishuSourceConfig(source);
|
|
934
|
+
return {
|
|
935
|
+
...(config.endpoint && config.endpoint !== exports.FEISHU_OPENAPI_ENDPOINT ? { endpoint: config.endpoint } : {}),
|
|
936
|
+
...(config.schemaUrl && config.schemaUrl !== exports.FEISHU_IM_SCHEMA_URL ? { schemaUrl: config.schemaUrl } : {}),
|
|
937
|
+
...(config.uxcAuth ? { uxcAuth: config.uxcAuth } : {}),
|
|
938
|
+
...(config.eventTypes ? { eventTypes: config.eventTypes } : {}),
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
function normalizeFeishuMessage(raw) {
|
|
942
|
+
const message = firstFeishuMessage(raw);
|
|
943
|
+
if (!message) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
const sender = asRecord(message.sender);
|
|
947
|
+
const messageType = asString(message.msg_type) ?? asString(message.message_type) ?? "unknown";
|
|
948
|
+
return {
|
|
949
|
+
messageId: asString(message.message_id) ?? "",
|
|
950
|
+
chatId: asString(message.chat_id),
|
|
951
|
+
chatType: asString(message.chat_type),
|
|
952
|
+
messageType,
|
|
953
|
+
senderOpenId: senderOpenId(sender),
|
|
954
|
+
senderType: asString(sender.sender_type) ?? asString(asRecord(sender.id).user_id) ?? null,
|
|
955
|
+
mentions: extractMentionNames(message.mentions),
|
|
956
|
+
mentionOpenIds: extractMentionOpenIds(message.mentions),
|
|
957
|
+
content: stringifyFeishuMessageContent(messageType, messageContentString(message), message.mentions),
|
|
958
|
+
createdAt: fromUnixMillisString(asString(message.create_time)),
|
|
959
|
+
threadId: asString(message.thread_id) ?? asString(message.root_id),
|
|
960
|
+
parentId: asString(message.parent_id),
|
|
961
|
+
raw: message,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
function senderOpenId(sender) {
|
|
965
|
+
const nested = asString(asRecord(sender.id).open_id) ?? asString(asRecord(sender.sender_id).open_id);
|
|
966
|
+
if (nested) {
|
|
967
|
+
return nested;
|
|
968
|
+
}
|
|
969
|
+
const id = asString(sender.id) ?? asString(sender.sender_id);
|
|
970
|
+
const idType = asString(sender.id_type);
|
|
971
|
+
return id && (!idType || idType === "open_id") ? id : null;
|
|
972
|
+
}
|
|
973
|
+
function normalizeFeishuMessages(raw) {
|
|
974
|
+
const items = feishuMessageItems(raw);
|
|
975
|
+
return items
|
|
976
|
+
.map((item) => normalizeFeishuMessage(item))
|
|
977
|
+
.filter((item) => item !== null);
|
|
978
|
+
}
|
|
979
|
+
function normalizeFeishuChat(raw) {
|
|
980
|
+
const chat = firstFeishuChat(raw);
|
|
981
|
+
if (!chat) {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
chatId: asString(chat.chat_id) ?? "",
|
|
986
|
+
name: asString(chat.name) ?? asString(chat.chat_name),
|
|
987
|
+
description: asString(chat.description),
|
|
988
|
+
type: asString(chat.chat_type) ?? asString(chat.chat_mode) ?? asString(chat.type),
|
|
989
|
+
memberCount: asNumber(chat.member_count) ?? asNumber(chat.members_count) ?? asNumber(chat.user_count),
|
|
990
|
+
isBotMember: asBoolean(chat.is_bot_member) ?? asBoolean(chat.bot_member) ?? asBoolean(chat.is_member),
|
|
991
|
+
raw: chat,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function normalizeFeishuChats(raw) {
|
|
995
|
+
return feishuChatItems(raw)
|
|
996
|
+
.map((item) => normalizeFeishuChat(item))
|
|
997
|
+
.filter((item) => item !== null);
|
|
998
|
+
}
|
|
999
|
+
async function fetchFeishuChatWindow(input) {
|
|
1000
|
+
const anchorSeconds = feishuMessageCreateSeconds(input.anchorMessage);
|
|
1001
|
+
const pageSize = Math.max(1, Math.min(input.windowBefore + input.windowAfter + 1, 50));
|
|
1002
|
+
if (!anchorSeconds) {
|
|
1003
|
+
const fallbackRaw = await input.client.listMessages({
|
|
1004
|
+
endpoint: input.config.endpoint,
|
|
1005
|
+
schemaUrl: input.config.schemaUrl,
|
|
1006
|
+
auth: input.config.uxcAuth,
|
|
1007
|
+
chatId: input.chatId,
|
|
1008
|
+
pageSize,
|
|
1009
|
+
});
|
|
1010
|
+
return normalizeFeishuMessages(fallbackRaw);
|
|
1011
|
+
}
|
|
1012
|
+
const beforeRaw = input.windowBefore > 0
|
|
1013
|
+
? await input.client.listMessages({
|
|
1014
|
+
endpoint: input.config.endpoint,
|
|
1015
|
+
schemaUrl: input.config.schemaUrl,
|
|
1016
|
+
auth: input.config.uxcAuth,
|
|
1017
|
+
chatId: input.chatId,
|
|
1018
|
+
startTime: String(Math.max(0, anchorSeconds - DEFAULT_CONTEXT_BOUND_SECONDS)),
|
|
1019
|
+
endTime: String(anchorSeconds + 1),
|
|
1020
|
+
pageSize: Math.max(1, Math.min(input.windowBefore + 1, 50)),
|
|
1021
|
+
sort: "ByCreateTimeDesc",
|
|
1022
|
+
})
|
|
1023
|
+
: null;
|
|
1024
|
+
const afterRaw = input.windowAfter > 0
|
|
1025
|
+
? await input.client.listMessages({
|
|
1026
|
+
endpoint: input.config.endpoint,
|
|
1027
|
+
schemaUrl: input.config.schemaUrl,
|
|
1028
|
+
auth: input.config.uxcAuth,
|
|
1029
|
+
chatId: input.chatId,
|
|
1030
|
+
startTime: String(anchorSeconds),
|
|
1031
|
+
endTime: String(anchorSeconds + DEFAULT_CONTEXT_BOUND_SECONDS),
|
|
1032
|
+
pageSize: Math.max(1, Math.min(input.windowAfter + 1, 50)),
|
|
1033
|
+
sort: "ByCreateTimeAsc",
|
|
1034
|
+
})
|
|
1035
|
+
: null;
|
|
1036
|
+
return mergeFeishuMessages([
|
|
1037
|
+
...(beforeRaw ? normalizeFeishuMessages(beforeRaw) : []),
|
|
1038
|
+
...(input.anchorMessage ? [input.anchorMessage] : []),
|
|
1039
|
+
...(afterRaw ? normalizeFeishuMessages(afterRaw) : []),
|
|
1040
|
+
]);
|
|
1041
|
+
}
|
|
1042
|
+
function mergeFeishuMessages(messages) {
|
|
1043
|
+
const byId = new Map();
|
|
1044
|
+
for (const message of messages) {
|
|
1045
|
+
if (message.messageId) {
|
|
1046
|
+
byId.set(message.messageId, message);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return Array.from(byId.values()).sort((left, right) => {
|
|
1050
|
+
const leftTime = feishuMessageCreateMillis(left) ?? 0;
|
|
1051
|
+
const rightTime = feishuMessageCreateMillis(right) ?? 0;
|
|
1052
|
+
return leftTime - rightTime;
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
function feishuMessageCreateSeconds(message) {
|
|
1056
|
+
const millis = feishuMessageCreateMillis(message);
|
|
1057
|
+
return millis == null ? null : Math.floor(millis / 1000);
|
|
1058
|
+
}
|
|
1059
|
+
function feishuMessageCreateMillis(message) {
|
|
1060
|
+
if (!message) {
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
const rawMillis = Number(asString(message.raw.create_time));
|
|
1064
|
+
if (Number.isFinite(rawMillis)) {
|
|
1065
|
+
return rawMillis;
|
|
1066
|
+
}
|
|
1067
|
+
if (message.createdAt) {
|
|
1068
|
+
const parsed = Date.parse(message.createdAt);
|
|
1069
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
function firstFeishuMessage(raw) {
|
|
1074
|
+
const items = feishuMessageItems(raw);
|
|
1075
|
+
if (items[0]) {
|
|
1076
|
+
return items[0];
|
|
1077
|
+
}
|
|
1078
|
+
const record = asRecord(raw);
|
|
1079
|
+
if (asString(record.message_id)) {
|
|
1080
|
+
return record;
|
|
1081
|
+
}
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
function feishuMessageItems(raw) {
|
|
1085
|
+
const record = asRecord(raw);
|
|
1086
|
+
const data = asRecord(record.data);
|
|
1087
|
+
const nestedData = asRecord(data.data);
|
|
1088
|
+
const items = Array.isArray(nestedData.items)
|
|
1089
|
+
? nestedData.items
|
|
1090
|
+
: Array.isArray(data.items)
|
|
1091
|
+
? data.items
|
|
1092
|
+
: Array.isArray(record.items)
|
|
1093
|
+
? record.items
|
|
1094
|
+
: Array.isArray(raw)
|
|
1095
|
+
? raw
|
|
1096
|
+
: [];
|
|
1097
|
+
return items.map((item) => asRecord(item)).filter((item) => Object.keys(item).length > 0);
|
|
1098
|
+
}
|
|
1099
|
+
function firstFeishuChat(raw) {
|
|
1100
|
+
const items = feishuChatItems(raw);
|
|
1101
|
+
if (items[0]) {
|
|
1102
|
+
return items[0];
|
|
1103
|
+
}
|
|
1104
|
+
const record = asRecord(raw);
|
|
1105
|
+
const data = asRecord(record.data);
|
|
1106
|
+
const nestedData = asRecord(data.data);
|
|
1107
|
+
if (asString(nestedData.chat_id)) {
|
|
1108
|
+
return nestedData;
|
|
1109
|
+
}
|
|
1110
|
+
if (asString(data.chat_id)) {
|
|
1111
|
+
return data;
|
|
1112
|
+
}
|
|
1113
|
+
if (asString(record.chat_id)) {
|
|
1114
|
+
return record;
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
function feishuChatItems(raw) {
|
|
1119
|
+
const record = asRecord(raw);
|
|
1120
|
+
const data = asRecord(record.data);
|
|
1121
|
+
const nestedData = asRecord(data.data);
|
|
1122
|
+
const items = Array.isArray(nestedData.items)
|
|
1123
|
+
? nestedData.items
|
|
1124
|
+
: Array.isArray(data.items)
|
|
1125
|
+
? data.items
|
|
1126
|
+
: Array.isArray(record.items)
|
|
1127
|
+
? record.items
|
|
1128
|
+
: Array.isArray(raw)
|
|
1129
|
+
? raw
|
|
1130
|
+
: [];
|
|
1131
|
+
return items.map((item) => asRecord(item)).filter((item) => asString(item.chat_id));
|
|
1132
|
+
}
|
|
1133
|
+
function messageContentString(message) {
|
|
1134
|
+
const body = asRecord(message.body);
|
|
1135
|
+
return asString(body.content) ?? asString(message.content);
|
|
1136
|
+
}
|
|
1137
|
+
function positiveInteger(value) {
|
|
1138
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
return value;
|
|
1142
|
+
}
|
|
1143
|
+
function boundedPositiveInteger(value, fallback, max) {
|
|
1144
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
1145
|
+
return fallback;
|
|
1146
|
+
}
|
|
1147
|
+
return Math.min(value, max);
|
|
1148
|
+
}
|
|
1149
|
+
function feishuResponsePageToken(raw) {
|
|
1150
|
+
const record = asRecord(raw);
|
|
1151
|
+
const data = asRecord(record.data);
|
|
1152
|
+
const nestedData = asRecord(data.data);
|
|
1153
|
+
return asString(nestedData.page_token)
|
|
1154
|
+
?? asString(data.page_token)
|
|
1155
|
+
?? asString(record.page_token);
|
|
1156
|
+
}
|
|
1157
|
+
function feishuResponseHasMore(raw) {
|
|
1158
|
+
const record = asRecord(raw);
|
|
1159
|
+
const data = asRecord(record.data);
|
|
1160
|
+
const nestedData = asRecord(data.data);
|
|
1161
|
+
if (typeof nestedData.has_more === "boolean") {
|
|
1162
|
+
return nestedData.has_more;
|
|
1163
|
+
}
|
|
1164
|
+
if (typeof data.has_more === "boolean") {
|
|
1165
|
+
return data.has_more;
|
|
1166
|
+
}
|
|
1167
|
+
return typeof record.has_more === "boolean" ? record.has_more : null;
|
|
1168
|
+
}
|
|
234
1169
|
function extractMentionNames(raw) {
|
|
235
1170
|
if (!Array.isArray(raw)) {
|
|
236
1171
|
return [];
|
|
@@ -269,7 +1204,8 @@ function stringifyFeishuMessageContent(messageType, rawContent, mentions) {
|
|
|
269
1204
|
return rawContent;
|
|
270
1205
|
}
|
|
271
1206
|
if (messageType === "text") {
|
|
272
|
-
|
|
1207
|
+
const text = asString(asRecord(parsed).text) ?? rawContent;
|
|
1208
|
+
return replaceMentionKeys(text, mentions);
|
|
273
1209
|
}
|
|
274
1210
|
if (messageType === "post") {
|
|
275
1211
|
const mentionMap = buildMentionMap(mentions);
|
|
@@ -303,6 +1239,13 @@ function buildMentionMap(raw) {
|
|
|
303
1239
|
}
|
|
304
1240
|
return map;
|
|
305
1241
|
}
|
|
1242
|
+
function replaceMentionKeys(text, mentions) {
|
|
1243
|
+
let result = text;
|
|
1244
|
+
for (const [key, name] of buildMentionMap(mentions)) {
|
|
1245
|
+
result = result.split(key).join(name);
|
|
1246
|
+
}
|
|
1247
|
+
return result;
|
|
1248
|
+
}
|
|
306
1249
|
function flattenPostContent(raw, mentionMap) {
|
|
307
1250
|
const parsed = asRecord(raw);
|
|
308
1251
|
const localeEntries = Object.entries(parsed);
|
|
@@ -369,9 +1312,18 @@ function asRecord(value) {
|
|
|
369
1312
|
}
|
|
370
1313
|
return value;
|
|
371
1314
|
}
|
|
1315
|
+
function compactPayload(input) {
|
|
1316
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
|
|
1317
|
+
}
|
|
372
1318
|
function asString(value) {
|
|
373
1319
|
return typeof value === "string" ? value : null;
|
|
374
1320
|
}
|
|
1321
|
+
function asNumber(value) {
|
|
1322
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1323
|
+
}
|
|
1324
|
+
function asBoolean(value) {
|
|
1325
|
+
return typeof value === "boolean" ? value : null;
|
|
1326
|
+
}
|
|
375
1327
|
function asStringArray(value) {
|
|
376
1328
|
if (!Array.isArray(value)) {
|
|
377
1329
|
return null;
|
|
@@ -380,3 +1332,50 @@ function asStringArray(value) {
|
|
|
380
1332
|
.map((item) => asString(item))
|
|
381
1333
|
.filter((item) => Boolean(item));
|
|
382
1334
|
}
|
|
1335
|
+
function ensureFeishuBotSchemaFile() {
|
|
1336
|
+
const body = JSON.stringify(FEISHU_BOT_OPENAPI_SCHEMA);
|
|
1337
|
+
(0, node_fs_1.writeFileSync)(FEISHU_BOT_SCHEMA_FILE, body, "utf8");
|
|
1338
|
+
return FEISHU_BOT_SCHEMA_FILE;
|
|
1339
|
+
}
|
|
1340
|
+
const FEISHU_BOT_OPENAPI_SCHEMA = {
|
|
1341
|
+
openapi: "3.0.3",
|
|
1342
|
+
info: {
|
|
1343
|
+
title: "Feishu Bot Info",
|
|
1344
|
+
version: "1.0.0",
|
|
1345
|
+
},
|
|
1346
|
+
servers: [
|
|
1347
|
+
{
|
|
1348
|
+
url: exports.FEISHU_OPENAPI_ENDPOINT,
|
|
1349
|
+
},
|
|
1350
|
+
],
|
|
1351
|
+
// Feishu's docs site does not currently expose this as a stable
|
|
1352
|
+
// machine-readable OpenAPI document to UXC, so keep the tiny operation
|
|
1353
|
+
// schema needed for bot self-identification close to the adapter.
|
|
1354
|
+
paths: {
|
|
1355
|
+
"/bot/v3/info": {
|
|
1356
|
+
get: {
|
|
1357
|
+
operationId: "get:/bot/v3/info",
|
|
1358
|
+
summary: "Get bot information for the current app",
|
|
1359
|
+
responses: {
|
|
1360
|
+
"200": {
|
|
1361
|
+
description: "OK",
|
|
1362
|
+
},
|
|
1363
|
+
},
|
|
1364
|
+
security: [
|
|
1365
|
+
{
|
|
1366
|
+
bearerAuth: [],
|
|
1367
|
+
},
|
|
1368
|
+
],
|
|
1369
|
+
},
|
|
1370
|
+
},
|
|
1371
|
+
},
|
|
1372
|
+
components: {
|
|
1373
|
+
securitySchemes: {
|
|
1374
|
+
bearerAuth: {
|
|
1375
|
+
type: "http",
|
|
1376
|
+
scheme: "bearer",
|
|
1377
|
+
bearerFormat: "tenant_access_token",
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
},
|
|
1381
|
+
};
|