@kodelyth/feishu 2026.5.39 → 2026.6.1

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.
Files changed (47) hide show
  1. package/dist/accounts-D0ow-lRb.js +429 -0
  2. package/dist/api.js +2308 -0
  3. package/dist/app-registration-DBSnysKJ.js +184 -0
  4. package/dist/audio-preflight.runtime-Dpjbn-7r.js +7 -0
  5. package/dist/channel-13WQvQ0u.js +2115 -0
  6. package/dist/channel-entry.js +22 -0
  7. package/dist/channel-plugin-api.js +2 -0
  8. package/dist/channel.runtime-JMJonrJ4.js +729 -0
  9. package/dist/client-D1pzbBGo.js +157 -0
  10. package/dist/contract-api.js +9 -0
  11. package/dist/conversation-id-_58ecqlx.js +139 -0
  12. package/dist/drive-CgHOluXx.js +883 -0
  13. package/dist/index.js +68 -0
  14. package/dist/monitor-oWptK0zL.js +60 -0
  15. package/dist/monitor.account-DHaWlslg.js +5207 -0
  16. package/dist/monitor.state-C211a4tX.js +100 -0
  17. package/dist/probe-CF4duEpK.js +149 -0
  18. package/dist/rolldown-runtime-DUslC3ob.js +14 -0
  19. package/dist/runtime-DSh5rL_d.js +8 -0
  20. package/dist/runtime-api.js +14 -0
  21. package/dist/secret-contract-NSee-WzN.js +119 -0
  22. package/dist/secret-contract-api.js +2 -0
  23. package/dist/security-audit-DWVC0vSK.js +11 -0
  24. package/dist/security-audit-shared-Dpcwxeft.js +38 -0
  25. package/dist/security-contract-api.js +2 -0
  26. package/dist/send-DfZuV4Fi.js +1212 -0
  27. package/dist/session-conversation-Duaukbnl.js +27 -0
  28. package/dist/session-key-api.js +2 -0
  29. package/dist/setup-api.js +2 -0
  30. package/dist/setup-entry.js +15 -0
  31. package/dist/subagent-hooks-Dtegs0kh.js +235 -0
  32. package/dist/subagent-hooks-api.js +23 -0
  33. package/dist/targets-DFskxX4p.js +48 -0
  34. package/dist/thread-bindings-DI7lVSOE.js +222 -0
  35. package/package.json +20 -7
  36. package/api.js +0 -7
  37. package/channel-entry.js +0 -7
  38. package/channel-plugin-api.js +0 -7
  39. package/contract-api.js +0 -7
  40. package/index.js +0 -7
  41. package/runtime-api.js +0 -7
  42. package/secret-contract-api.js +0 -7
  43. package/security-contract-api.js +0 -7
  44. package/session-key-api.js +0 -7
  45. package/setup-api.js +0 -7
  46. package/setup-entry.js +0 -7
  47. package/subagent-hooks-api.js +0 -7
@@ -0,0 +1,883 @@
1
+ import { d as formatFeishuApiError, f as isRecord$1, h as readString, i as listFeishuAccountIds, l as encodeQuery, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount, u as extractReplyText, y as parseFeishuCommentTarget } from "./accounts-D0ow-lRb.js";
2
+ import { r as createFeishuClient } from "./client-D1pzbBGo.js";
3
+ import { normalizeOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
4
+ import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
5
+ import { Type } from "typebox";
6
+ //#region extensions/feishu/src/tools-config.ts
7
+ /**
8
+ * Default tool configuration.
9
+ * - doc, chat, wiki, drive, scopes: enabled by default
10
+ * - perm: disabled by default (sensitive operation)
11
+ */
12
+ const DEFAULT_TOOLS_CONFIG = {
13
+ doc: true,
14
+ chat: true,
15
+ wiki: true,
16
+ drive: true,
17
+ perm: false,
18
+ scopes: true
19
+ };
20
+ /**
21
+ * Resolve tools config with defaults.
22
+ */
23
+ function resolveToolsConfig(cfg) {
24
+ return {
25
+ ...DEFAULT_TOOLS_CONFIG,
26
+ ...cfg
27
+ };
28
+ }
29
+ //#endregion
30
+ //#region extensions/feishu/src/tool-account.ts
31
+ function resolveImplicitToolAccountId(params) {
32
+ const explicitAccountId = normalizeOptionalString(params.executeParams?.accountId);
33
+ if (explicitAccountId) return explicitAccountId;
34
+ const contextualAccountId = normalizeOptionalString(params.defaultAccountId);
35
+ if (contextualAccountId && listFeishuAccountIds(params.api.config).includes(contextualAccountId)) {
36
+ if (resolveFeishuAccount({
37
+ cfg: params.api.config,
38
+ accountId: contextualAccountId
39
+ }).enabled) return contextualAccountId;
40
+ }
41
+ const configuredDefaultAccountId = normalizeOptionalString((params.api.config?.channels?.feishu)?.defaultAccount);
42
+ if (configuredDefaultAccountId) return configuredDefaultAccountId;
43
+ }
44
+ function resolveFeishuToolAccount(params) {
45
+ if (!params.api.config) throw new Error("Feishu config unavailable");
46
+ return resolveFeishuRuntimeAccount({
47
+ cfg: params.api.config,
48
+ accountId: resolveImplicitToolAccountId(params)
49
+ });
50
+ }
51
+ function createFeishuToolClient(params) {
52
+ return createFeishuClient(resolveFeishuToolAccount(params));
53
+ }
54
+ function resolveAnyEnabledFeishuToolsConfig(accounts) {
55
+ const merged = {
56
+ doc: false,
57
+ chat: false,
58
+ wiki: false,
59
+ drive: false,
60
+ perm: false,
61
+ scopes: false
62
+ };
63
+ for (const account of accounts) {
64
+ const cfg = resolveToolsConfig(account.config.tools);
65
+ merged.doc = merged.doc || cfg.doc;
66
+ merged.chat = merged.chat || cfg.chat;
67
+ merged.wiki = merged.wiki || cfg.wiki;
68
+ merged.drive = merged.drive || cfg.drive;
69
+ merged.perm = merged.perm || cfg.perm;
70
+ merged.scopes = merged.scopes || cfg.scopes;
71
+ }
72
+ return merged;
73
+ }
74
+ //#endregion
75
+ //#region extensions/feishu/src/chat-schema.ts
76
+ const CHAT_ACTION_VALUES = [
77
+ "members",
78
+ "info",
79
+ "member_info"
80
+ ];
81
+ const MEMBER_ID_TYPE_VALUES = [
82
+ "open_id",
83
+ "user_id",
84
+ "union_id"
85
+ ];
86
+ const FeishuChatSchema = Type.Object({
87
+ action: Type.Unsafe({
88
+ type: "string",
89
+ enum: [...CHAT_ACTION_VALUES],
90
+ description: "Action to run: members | info | member_info"
91
+ }),
92
+ chat_id: Type.Optional(Type.String({ description: "Chat ID (from URL or event payload)" })),
93
+ member_id: Type.Optional(Type.String({ description: "Member ID for member_info lookups" })),
94
+ page_size: Type.Optional(Type.Number({ description: "Page size (1-100, default 50)" })),
95
+ page_token: Type.Optional(Type.String({ description: "Pagination token" })),
96
+ member_id_type: Type.Optional(Type.Unsafe({
97
+ type: "string",
98
+ enum: [...MEMBER_ID_TYPE_VALUES],
99
+ description: "Member ID type (default: open_id)"
100
+ }))
101
+ });
102
+ //#endregion
103
+ //#region extensions/feishu/src/chat.ts
104
+ function json(data) {
105
+ return {
106
+ content: [{
107
+ type: "text",
108
+ text: JSON.stringify(data, null, 2)
109
+ }],
110
+ details: data
111
+ };
112
+ }
113
+ async function getChatInfo(client, chatId) {
114
+ const res = await client.im.chat.get({ path: { chat_id: chatId } });
115
+ if (res.code !== 0) throw new Error(res.msg);
116
+ const chat = res.data;
117
+ return {
118
+ chat_id: chatId,
119
+ name: chat?.name,
120
+ description: chat?.description,
121
+ owner_id: chat?.owner_id,
122
+ tenant_key: chat?.tenant_key,
123
+ user_count: chat?.user_count,
124
+ chat_mode: chat?.chat_mode,
125
+ chat_type: chat?.chat_type,
126
+ join_message_visibility: chat?.join_message_visibility,
127
+ leave_message_visibility: chat?.leave_message_visibility,
128
+ membership_approval: chat?.membership_approval,
129
+ moderation_permission: chat?.moderation_permission,
130
+ avatar: chat?.avatar
131
+ };
132
+ }
133
+ async function getChatMembers(client, chatId, pageSize, pageToken, memberIdType) {
134
+ const page_size = pageSize ? Math.max(1, Math.min(100, pageSize)) : 50;
135
+ const res = await client.im.chatMembers.get({
136
+ path: { chat_id: chatId },
137
+ params: {
138
+ page_size,
139
+ page_token: pageToken,
140
+ member_id_type: memberIdType ?? "open_id"
141
+ }
142
+ });
143
+ if (res.code !== 0) throw new Error(res.msg);
144
+ return {
145
+ chat_id: chatId,
146
+ has_more: res.data?.has_more,
147
+ page_token: res.data?.page_token,
148
+ members: res.data?.items?.map((item) => ({
149
+ member_id: item.member_id,
150
+ name: item.name,
151
+ tenant_key: item.tenant_key,
152
+ member_id_type: item.member_id_type
153
+ })) ?? []
154
+ };
155
+ }
156
+ async function getFeishuMemberInfo(client, memberId, memberIdType = "open_id") {
157
+ const res = await client.contact.user.get({
158
+ path: { user_id: memberId },
159
+ params: {
160
+ user_id_type: memberIdType,
161
+ department_id_type: "open_department_id"
162
+ }
163
+ });
164
+ if (res.code !== 0) throw new Error(res.msg);
165
+ const user = res.data?.user;
166
+ return {
167
+ member_id: memberId,
168
+ member_id_type: memberIdType,
169
+ open_id: user?.open_id,
170
+ user_id: user?.user_id,
171
+ union_id: user?.union_id,
172
+ name: user?.name,
173
+ en_name: user?.en_name,
174
+ nickname: user?.nickname,
175
+ email: user?.email,
176
+ enterprise_email: user?.enterprise_email,
177
+ mobile: user?.mobile,
178
+ mobile_visible: user?.mobile_visible,
179
+ status: user?.status,
180
+ avatar: user?.avatar,
181
+ department_ids: user?.department_ids,
182
+ department_path: user?.department_path,
183
+ leader_user_id: user?.leader_user_id,
184
+ city: user?.city,
185
+ country: user?.country,
186
+ work_station: user?.work_station,
187
+ join_time: user?.join_time,
188
+ is_tenant_manager: user?.is_tenant_manager,
189
+ employee_no: user?.employee_no,
190
+ employee_type: user?.employee_type,
191
+ description: user?.description,
192
+ job_title: user?.job_title,
193
+ geo: user?.geo
194
+ };
195
+ }
196
+ function registerFeishuChatTools(api) {
197
+ if (!api.config) return;
198
+ const accounts = listEnabledFeishuAccounts(api.config);
199
+ if (accounts.length === 0) return;
200
+ const firstAccount = accounts[0];
201
+ if (!resolveToolsConfig(firstAccount.config.tools).chat) return;
202
+ const getClient = () => createFeishuClient(firstAccount);
203
+ api.registerTool({
204
+ name: "feishu_chat",
205
+ label: "Feishu Chat",
206
+ description: "Feishu chat operations. Actions: members, info, member_info",
207
+ parameters: FeishuChatSchema,
208
+ async execute(_toolCallId, params) {
209
+ const p = params;
210
+ try {
211
+ const client = getClient();
212
+ switch (p.action) {
213
+ case "members":
214
+ if (!p.chat_id) return json({ error: "chat_id is required for action members" });
215
+ return json(await getChatMembers(client, p.chat_id, p.page_size, p.page_token, p.member_id_type));
216
+ case "info":
217
+ if (!p.chat_id) return json({ error: "chat_id is required for action info" });
218
+ return json(await getChatInfo(client, p.chat_id));
219
+ case "member_info":
220
+ if (!p.member_id) return json({ error: "member_id is required for action member_info" });
221
+ return json(await getFeishuMemberInfo(client, p.member_id, p.member_id_type ?? "open_id"));
222
+ default: return json({ error: `Unknown action: ${String(p.action)}` });
223
+ }
224
+ } catch (err) {
225
+ return json({ error: formatFeishuApiError(err, { includeNestedErrorLogId: true }) });
226
+ }
227
+ }
228
+ }, { name: "feishu_chat" });
229
+ }
230
+ //#endregion
231
+ //#region extensions/feishu/src/tool-result.ts
232
+ function jsonToolResult(data) {
233
+ return {
234
+ content: [{
235
+ type: "text",
236
+ text: JSON.stringify(data, null, 2)
237
+ }],
238
+ details: data
239
+ };
240
+ }
241
+ function unknownToolActionResult(action) {
242
+ return jsonToolResult({ error: `Unknown action: ${String(action)}` });
243
+ }
244
+ function toolExecutionErrorResult(error) {
245
+ return jsonToolResult({ error: formatErrorMessage(error) });
246
+ }
247
+ //#endregion
248
+ //#region extensions/feishu/src/comment-reaction.ts
249
+ const COMMENT_TYPING_REACTION_TYPE = "Typing";
250
+ const COMMENT_REACTION_TIMEOUT_MS = 3e4;
251
+ const commentTypingReactionState = /* @__PURE__ */ new Map();
252
+ function buildCommentTypingReactionKey(params) {
253
+ return `${params.fileType}:${params.fileToken}:${params.replyId}`;
254
+ }
255
+ function ensureCommentTypingReactionState(key) {
256
+ const existing = commentTypingReactionState.get(key);
257
+ if (existing) return existing;
258
+ const created = {
259
+ active: false,
260
+ cleaned: false,
261
+ cleanupPromise: void 0
262
+ };
263
+ commentTypingReactionState.set(key, created);
264
+ return created;
265
+ }
266
+ async function requestCommentTypingReactionWithClient(params) {
267
+ try {
268
+ const response = await params.client.request({
269
+ method: "POST",
270
+ url: `/open-apis/drive/v2/files/${encodeURIComponent(params.fileToken)}/comments/reaction` + encodeQuery({ file_type: params.fileType }),
271
+ data: {
272
+ action: params.action,
273
+ reply_id: params.replyId,
274
+ reaction_type: COMMENT_TYPING_REACTION_TYPE
275
+ },
276
+ timeout: COMMENT_REACTION_TIMEOUT_MS
277
+ });
278
+ if (response.code === 0) return true;
279
+ params.runtime?.log?.(`${params.logPrefix ?? "[feishu]"}: comment typing reaction ${params.action} failed reply=${params.replyId} file=${params.fileType}:${params.fileToken} code=${response.code ?? "unknown"} msg=${response.msg ?? "unknown"} log_id=${response.log_id ?? response.error?.log_id ?? "unknown"}`);
280
+ } catch (error) {
281
+ params.runtime?.log?.(`${params.logPrefix ?? "[feishu]"}: comment typing reaction ${params.action} threw reply=${params.replyId} file=${params.fileType}:${params.fileToken} error=${formatCommentReactionFailure(error)}`);
282
+ }
283
+ return false;
284
+ }
285
+ function formatCommentReactionFailure(error) {
286
+ return formatFeishuApiError(error, { includeNestedErrorLogId: true });
287
+ }
288
+ async function requestCommentTypingReaction(params) {
289
+ const account = resolveFeishuRuntimeAccount({
290
+ cfg: params.cfg,
291
+ accountId: params.accountId
292
+ });
293
+ if (!account.configured || !(account.config.typingIndicator ?? true)) return false;
294
+ return requestCommentTypingReactionWithClient({
295
+ client: createFeishuClient(account),
296
+ fileToken: params.fileToken,
297
+ fileType: params.fileType,
298
+ replyId: params.replyId,
299
+ action: params.action,
300
+ runtime: params.runtime,
301
+ logPrefix: `feishu[${account.accountId}]`
302
+ });
303
+ }
304
+ async function cleanupCommentTypingReactionByKey(params) {
305
+ const state = ensureCommentTypingReactionState(params.key);
306
+ if (state.cleaned) return false;
307
+ if (state.cleanupPromise) return await state.cleanupPromise;
308
+ const cleanupPromise = (async () => {
309
+ if (!state.active) {
310
+ state.cleaned = true;
311
+ return false;
312
+ }
313
+ const deleted = await params.performDelete();
314
+ if (deleted) {
315
+ state.cleaned = true;
316
+ state.active = false;
317
+ }
318
+ return deleted;
319
+ })();
320
+ state.cleanupPromise = cleanupPromise;
321
+ try {
322
+ return await cleanupPromise;
323
+ } finally {
324
+ state.cleanupPromise = void 0;
325
+ if (state.cleaned) {
326
+ state.active = false;
327
+ commentTypingReactionState.delete(params.key);
328
+ }
329
+ }
330
+ }
331
+ async function cleanupAmbientCommentTypingReaction(params) {
332
+ const deliveryContext = params.deliveryContext;
333
+ if (deliveryContext?.channel && deliveryContext.channel !== "feishu" && deliveryContext.channel !== "feishu-comment") return false;
334
+ const target = parseFeishuCommentTarget(deliveryContext?.to);
335
+ const replyId = typeof deliveryContext?.threadId === "string" || typeof deliveryContext?.threadId === "number" ? String(deliveryContext.threadId).trim() : "";
336
+ if (!target || !replyId) return false;
337
+ return cleanupCommentTypingReactionByKey({
338
+ key: buildCommentTypingReactionKey({
339
+ fileToken: target.fileToken,
340
+ fileType: target.fileType,
341
+ replyId
342
+ }),
343
+ performDelete: () => requestCommentTypingReactionWithClient({
344
+ client: params.client,
345
+ fileToken: target.fileToken,
346
+ fileType: target.fileType,
347
+ replyId,
348
+ action: "delete",
349
+ runtime: params.runtime,
350
+ logPrefix: "[feishu]"
351
+ })
352
+ });
353
+ }
354
+ function createCommentTypingReactionLifecycle(params) {
355
+ const key = params.replyId?.trim() ? buildCommentTypingReactionKey({
356
+ fileToken: params.fileToken,
357
+ fileType: params.fileType,
358
+ replyId: params.replyId.trim()
359
+ }) : void 0;
360
+ const state = key ? ensureCommentTypingReactionState(key) : void 0;
361
+ return {
362
+ start: async () => {
363
+ const replyId = params.replyId?.trim();
364
+ if (!state || state.cleaned || state.active || !replyId) return;
365
+ state.active = await requestCommentTypingReaction({
366
+ cfg: params.cfg,
367
+ fileToken: params.fileToken,
368
+ fileType: params.fileType,
369
+ replyId,
370
+ action: "add",
371
+ accountId: params.accountId,
372
+ runtime: params.runtime
373
+ });
374
+ },
375
+ cleanup: async () => {
376
+ const replyId = params.replyId?.trim();
377
+ if (!key || !replyId) return;
378
+ await cleanupCommentTypingReactionByKey({
379
+ key,
380
+ performDelete: () => requestCommentTypingReaction({
381
+ cfg: params.cfg,
382
+ fileToken: params.fileToken,
383
+ fileType: params.fileType,
384
+ replyId,
385
+ action: "delete",
386
+ accountId: params.accountId,
387
+ runtime: params.runtime
388
+ })
389
+ });
390
+ }
391
+ };
392
+ }
393
+ //#endregion
394
+ //#region extensions/feishu/src/drive-schema.ts
395
+ const FileType = Type.Union([
396
+ Type.Literal("doc"),
397
+ Type.Literal("docx"),
398
+ Type.Literal("sheet"),
399
+ Type.Literal("bitable"),
400
+ Type.Literal("folder"),
401
+ Type.Literal("file"),
402
+ Type.Literal("mindnote"),
403
+ Type.Literal("shortcut")
404
+ ]);
405
+ const CommentFileType = Type.Union([
406
+ Type.Literal("doc"),
407
+ Type.Literal("docx"),
408
+ Type.Literal("sheet"),
409
+ Type.Literal("file"),
410
+ Type.Literal("slides")
411
+ ]);
412
+ const FeishuDriveSchema = Type.Union([
413
+ Type.Object({
414
+ action: Type.Literal("list"),
415
+ folder_token: Type.Optional(Type.String({ description: "Folder token (optional, omit for root directory)" }))
416
+ }),
417
+ Type.Object({
418
+ action: Type.Literal("info"),
419
+ file_token: Type.String({ description: "File or folder token" }),
420
+ type: FileType
421
+ }),
422
+ Type.Object({
423
+ action: Type.Literal("create_folder"),
424
+ name: Type.String({ description: "Folder name" }),
425
+ folder_token: Type.Optional(Type.String({ description: "Parent folder token (optional, omit for root)" }))
426
+ }),
427
+ Type.Object({
428
+ action: Type.Literal("move"),
429
+ file_token: Type.String({ description: "File token to move" }),
430
+ type: FileType,
431
+ folder_token: Type.String({ description: "Target folder token" })
432
+ }),
433
+ Type.Object({
434
+ action: Type.Literal("delete"),
435
+ file_token: Type.String({ description: "File token to delete" }),
436
+ type: FileType
437
+ }),
438
+ Type.Object({
439
+ action: Type.Literal("list_comments"),
440
+ file_token: Type.String({ description: "Document token" }),
441
+ file_type: Type.Optional(CommentFileType),
442
+ page_size: Type.Optional(Type.Integer({
443
+ minimum: 1,
444
+ maximum: 100,
445
+ description: "Page size"
446
+ })),
447
+ page_token: Type.Optional(Type.String({ description: "Comment page token" }))
448
+ }),
449
+ Type.Object({
450
+ action: Type.Literal("list_comment_replies"),
451
+ file_token: Type.String({ description: "Document token" }),
452
+ file_type: Type.Optional(CommentFileType),
453
+ comment_id: Type.String({ description: "Comment id" }),
454
+ page_size: Type.Optional(Type.Integer({
455
+ minimum: 1,
456
+ maximum: 100,
457
+ description: "Page size"
458
+ })),
459
+ page_token: Type.Optional(Type.String({ description: "Reply page token" }))
460
+ }),
461
+ Type.Object({
462
+ action: Type.Literal("add_comment"),
463
+ file_token: Type.String({ description: "Document token" }),
464
+ file_type: Type.Optional(Type.Union([Type.Literal("doc"), Type.Literal("docx")], { description: "Document type. Defaults to docx when omitted." })),
465
+ content: Type.String({ description: "Comment text content" }),
466
+ block_id: Type.Optional(Type.String({ description: "Optional docx block id for a local comment. Omit to create a full-document comment." }))
467
+ }),
468
+ Type.Object({
469
+ action: Type.Literal("reply_comment"),
470
+ file_token: Type.String({ description: "Document token" }),
471
+ file_type: Type.Optional(CommentFileType),
472
+ comment_id: Type.String({ description: "Comment id" }),
473
+ content: Type.String({ description: "Reply text content" })
474
+ })
475
+ ]);
476
+ //#endregion
477
+ //#region extensions/feishu/src/drive.ts
478
+ var FeishuReplyCommentError = class extends Error {
479
+ constructor(params) {
480
+ super(params.message);
481
+ this.name = "FeishuReplyCommentError";
482
+ this.httpStatus = params.httpStatus;
483
+ this.feishuCode = params.feishuCode;
484
+ this.feishuMsg = params.feishuMsg;
485
+ this.feishuLogId = params.feishuLogId;
486
+ }
487
+ };
488
+ const FEISHU_DRIVE_REQUEST_TIMEOUT_MS = 3e4;
489
+ function getDriveInternalClient(client) {
490
+ return client;
491
+ }
492
+ function buildReplyElements(content) {
493
+ return [{
494
+ type: "text",
495
+ text: content
496
+ }];
497
+ }
498
+ async function requestDriveApi(params) {
499
+ return await getDriveInternalClient(params.client).request({
500
+ method: params.method,
501
+ url: params.url,
502
+ params: params.query ?? {},
503
+ data: params.data ?? {},
504
+ timeout: FEISHU_DRIVE_REQUEST_TIMEOUT_MS
505
+ });
506
+ }
507
+ function assertDriveApiSuccess(response) {
508
+ if (response.code !== 0) throw new Error(response.msg ?? "Feishu Drive API request failed");
509
+ return response;
510
+ }
511
+ function normalizeCommentReply(reply) {
512
+ return {
513
+ reply_id: reply.reply_id,
514
+ user_id: reply.user_id,
515
+ create_time: reply.create_time,
516
+ update_time: reply.update_time,
517
+ text: extractReplyText(reply)
518
+ };
519
+ }
520
+ function normalizeCommentCard(comment) {
521
+ const replies = comment.reply_list?.replies ?? [];
522
+ const rootReply = replies[0];
523
+ return {
524
+ comment_id: comment.comment_id,
525
+ user_id: comment.user_id,
526
+ create_time: comment.create_time,
527
+ update_time: comment.update_time,
528
+ is_solved: comment.is_solved,
529
+ is_whole: comment.is_whole,
530
+ quote: comment.quote,
531
+ text: extractReplyText(rootReply),
532
+ has_more_replies: comment.has_more,
533
+ replies_page_token: comment.page_token,
534
+ replies: replies.slice(1).map(normalizeCommentReply)
535
+ };
536
+ }
537
+ function normalizeCommentPageSize(pageSize) {
538
+ if (typeof pageSize !== "number" || !Number.isFinite(pageSize)) return;
539
+ return String(Math.min(Math.max(Math.floor(pageSize), 1), 100));
540
+ }
541
+ function resolveAmbientCommentTarget(context) {
542
+ const deliveryContext = context?.deliveryContext;
543
+ if (deliveryContext?.channel && deliveryContext.channel !== "feishu") return null;
544
+ return parseFeishuCommentTarget(deliveryContext?.to);
545
+ }
546
+ function applyAmbientCommentDefaults(params, context) {
547
+ const ambient = resolveAmbientCommentTarget(context);
548
+ if (!ambient) return params;
549
+ return {
550
+ ...params,
551
+ file_token: params.file_token?.trim() || ambient.fileToken,
552
+ file_type: params.file_type ?? ambient.fileType,
553
+ comment_id: params.comment_id?.trim() || ambient.commentId
554
+ };
555
+ }
556
+ function applyAddCommentAmbientDefaults(params, context) {
557
+ const ambient = resolveAmbientCommentTarget(context);
558
+ if (!ambient || ambient.fileType !== "doc" && ambient.fileType !== "docx") return params;
559
+ return {
560
+ ...params,
561
+ file_token: params.file_token?.trim() || ambient.fileToken,
562
+ file_type: params.file_type ?? ambient.fileType
563
+ };
564
+ }
565
+ function applyAddCommentDefaults(params) {
566
+ const fileType = params.file_type ?? "docx";
567
+ if (!params.file_type) console.info(`[feishu_drive] add_comment missing file_type; defaulting to docx file_token=${params.file_token ?? "unknown"}`);
568
+ return {
569
+ ...params,
570
+ file_type: fileType
571
+ };
572
+ }
573
+ function applyCommentFileTypeDefault(params, action) {
574
+ const fileType = params.file_type ?? "docx";
575
+ if (!params.file_type) console.info(`[feishu_drive] ${action} missing file_type; defaulting to docx file_token=${params.file_token ?? "unknown"}`);
576
+ return {
577
+ ...params,
578
+ file_type: fileType
579
+ };
580
+ }
581
+ function formatDriveApiError(error) {
582
+ return formatFeishuApiError(error, { includeConfigParams: true });
583
+ }
584
+ function extractDriveApiErrorMeta(error) {
585
+ if (!isRecord$1(error)) return { message: typeof error === "string" ? error : JSON.stringify(error) };
586
+ const response = isRecord$1(error.response) ? error.response : void 0;
587
+ const responseData = isRecord$1(response?.data) ? response?.data : void 0;
588
+ return {
589
+ message: typeof error.message === "string" ? error.message : typeof error === "string" ? error : JSON.stringify(error),
590
+ httpStatus: typeof response?.status === "number" ? response.status : void 0,
591
+ feishuCode: typeof responseData?.code === "number" ? responseData.code : readString(responseData?.code),
592
+ feishuMsg: readString(responseData?.msg),
593
+ feishuLogId: readString(responseData?.log_id)
594
+ };
595
+ }
596
+ function isReplyNotAllowedError(error) {
597
+ if (!(error instanceof FeishuReplyCommentError)) return false;
598
+ return error.feishuCode === 1069302;
599
+ }
600
+ async function getRootFolderToken(client) {
601
+ const internalClient = getDriveInternalClient(client);
602
+ const domain = internalClient.domain ?? "https://open.feishu.cn";
603
+ const res = await internalClient.httpInstance.get(`${domain}/open-apis/drive/explorer/v2/root_folder/meta`);
604
+ if (res.code !== 0) throw new Error(res.msg ?? "Failed to get root folder");
605
+ const token = res.data?.token;
606
+ if (!token) throw new Error("Root folder token not found");
607
+ return token;
608
+ }
609
+ async function listFolder(client, folderToken) {
610
+ const validFolderToken = folderToken && folderToken !== "0" ? folderToken : void 0;
611
+ const res = await client.drive.file.list({ params: validFolderToken ? { folder_token: validFolderToken } : {} });
612
+ if (res.code !== 0) throw new Error(res.msg);
613
+ return {
614
+ files: res.data?.files?.map((f) => ({
615
+ token: f.token,
616
+ name: f.name,
617
+ type: f.type,
618
+ url: f.url,
619
+ created_time: f.created_time,
620
+ modified_time: f.modified_time,
621
+ owner_id: f.owner_id
622
+ })) ?? [],
623
+ next_page_token: res.data?.next_page_token
624
+ };
625
+ }
626
+ async function getFileInfo(client, fileToken, folderToken) {
627
+ const res = await client.drive.file.list({ params: folderToken ? { folder_token: folderToken } : {} });
628
+ if (res.code !== 0) throw new Error(res.msg);
629
+ const file = res.data?.files?.find((f) => f.token === fileToken);
630
+ if (!file) throw new Error(`File not found: ${fileToken}`);
631
+ return {
632
+ token: file.token,
633
+ name: file.name,
634
+ type: file.type,
635
+ url: file.url,
636
+ created_time: file.created_time,
637
+ modified_time: file.modified_time,
638
+ owner_id: file.owner_id
639
+ };
640
+ }
641
+ async function createFolder(client, name, folderToken) {
642
+ let effectiveToken = folderToken && folderToken !== "0" ? folderToken : "0";
643
+ if (effectiveToken === "0") try {
644
+ effectiveToken = await getRootFolderToken(client);
645
+ } catch {}
646
+ const res = await client.drive.file.createFolder({ data: {
647
+ name,
648
+ folder_token: effectiveToken
649
+ } });
650
+ if (res.code !== 0) throw new Error(res.msg);
651
+ return {
652
+ token: res.data?.token,
653
+ url: res.data?.url
654
+ };
655
+ }
656
+ async function moveFile(client, fileToken, type, folderToken) {
657
+ const res = await client.drive.file.move({
658
+ path: { file_token: fileToken },
659
+ data: {
660
+ type,
661
+ folder_token: folderToken
662
+ }
663
+ });
664
+ if (res.code !== 0) throw new Error(res.msg);
665
+ return {
666
+ success: true,
667
+ task_id: res.data?.task_id
668
+ };
669
+ }
670
+ async function deleteFile(client, fileToken, type) {
671
+ const res = await client.drive.file.delete({
672
+ path: { file_token: fileToken },
673
+ params: { type }
674
+ });
675
+ if (res.code !== 0) throw new Error(res.msg);
676
+ return {
677
+ success: true,
678
+ task_id: res.data?.task_id
679
+ };
680
+ }
681
+ async function listComments(client, params) {
682
+ const response = assertDriveApiSuccess(await requestDriveApi({
683
+ client,
684
+ method: "GET",
685
+ url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments` + encodeQuery({
686
+ file_type: params.file_type,
687
+ page_size: normalizeCommentPageSize(params.page_size),
688
+ page_token: params.page_token,
689
+ user_id_type: "open_id"
690
+ })
691
+ }));
692
+ return {
693
+ has_more: response.data?.has_more ?? false,
694
+ page_token: response.data?.page_token,
695
+ comments: (response.data?.items ?? []).map(normalizeCommentCard)
696
+ };
697
+ }
698
+ async function listCommentReplies(client, params) {
699
+ const response = assertDriveApiSuccess(await requestDriveApi({
700
+ client,
701
+ method: "GET",
702
+ url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments/${encodeURIComponent(params.comment_id)}/replies` + encodeQuery({
703
+ file_type: params.file_type,
704
+ page_size: normalizeCommentPageSize(params.page_size),
705
+ page_token: params.page_token,
706
+ user_id_type: "open_id"
707
+ })
708
+ }));
709
+ return {
710
+ has_more: response.data?.has_more ?? false,
711
+ page_token: response.data?.page_token,
712
+ replies: (response.data?.items ?? []).map(normalizeCommentReply)
713
+ };
714
+ }
715
+ async function addComment(client, params) {
716
+ if (params.block_id?.trim() && params.file_type !== "docx") throw new Error("block_id is only supported for docx comments");
717
+ return {
718
+ success: true,
719
+ ...assertDriveApiSuccess(await requestDriveApi({
720
+ client,
721
+ method: "POST",
722
+ url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/new_comments`,
723
+ data: {
724
+ file_type: params.file_type,
725
+ reply_elements: buildReplyElements(params.content),
726
+ ...params.block_id?.trim() ? { anchor: { block_id: params.block_id.trim() } } : {}
727
+ }
728
+ })).data
729
+ };
730
+ }
731
+ async function queryCommentById(client, params) {
732
+ return assertDriveApiSuccess(await requestDriveApi({
733
+ client,
734
+ method: "POST",
735
+ url: `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments/batch_query` + encodeQuery({
736
+ file_type: params.file_type,
737
+ user_id_type: "open_id"
738
+ }),
739
+ data: { comment_ids: [params.comment_id] }
740
+ })).data?.items?.find((comment) => comment.comment_id?.trim() === params.comment_id);
741
+ }
742
+ async function replyComment(client, params) {
743
+ const url = `/open-apis/drive/v1/files/${encodeURIComponent(params.file_token)}/comments/${encodeURIComponent(params.comment_id)}/replies`;
744
+ const query = { file_type: params.file_type };
745
+ try {
746
+ const response = await requestDriveApi({
747
+ client,
748
+ method: "POST",
749
+ url,
750
+ query,
751
+ data: { content: { elements: [{
752
+ type: "text_run",
753
+ text_run: { text: params.content }
754
+ }] } }
755
+ });
756
+ if (response.code === 0) return {
757
+ success: true,
758
+ ...response.data
759
+ };
760
+ console.warn(`[feishu_drive] replyComment failed comment=${params.comment_id} file_type=${params.file_type} code=${response.code ?? "unknown"} msg=${response.msg ?? "unknown"} log_id=${response.log_id ?? "unknown"}`);
761
+ throw new FeishuReplyCommentError({
762
+ message: response.msg ?? "Feishu Drive reply comment failed",
763
+ feishuCode: response.code,
764
+ feishuMsg: response.msg,
765
+ feishuLogId: response.log_id
766
+ });
767
+ } catch (error) {
768
+ if (error instanceof FeishuReplyCommentError) throw error;
769
+ const meta = extractDriveApiErrorMeta(error);
770
+ console.warn(`[feishu_drive] replyComment threw comment=${params.comment_id} file_type=${params.file_type} error=${formatDriveApiError(error)}`);
771
+ throw new FeishuReplyCommentError({
772
+ message: meta.message,
773
+ httpStatus: meta.httpStatus,
774
+ feishuCode: meta.feishuCode,
775
+ feishuMsg: meta.feishuMsg,
776
+ feishuLogId: meta.feishuLogId
777
+ });
778
+ }
779
+ }
780
+ async function deliverCommentThreadText(client, params) {
781
+ let isWholeComment = params.is_whole_comment;
782
+ if (isWholeComment === void 0) try {
783
+ isWholeComment = (await queryCommentById(client, params))?.is_whole === true;
784
+ } catch (error) {
785
+ console.warn(`[feishu_drive] comment metadata preflight failed comment=${params.comment_id} file_type=${params.file_type} error=${formatErrorMessage(error)}`);
786
+ isWholeComment = false;
787
+ }
788
+ if (isWholeComment) {
789
+ if (params.file_type !== "doc" && params.file_type !== "docx") throw new Error(`Whole-document comment follow-ups are only supported for doc/docx (got ${params.file_type})`);
790
+ const wholeCommentFileType = params.file_type;
791
+ console.info(`[feishu_drive] whole-comment compatibility path comment=${params.comment_id} file_type=${params.file_type} mode=add_comment`);
792
+ return {
793
+ delivery_mode: "add_comment",
794
+ ...await addComment(client, {
795
+ file_token: params.file_token,
796
+ file_type: wholeCommentFileType,
797
+ content: params.content
798
+ })
799
+ };
800
+ }
801
+ try {
802
+ return {
803
+ delivery_mode: "reply_comment",
804
+ ...await replyComment(client, params)
805
+ };
806
+ } catch (error) {
807
+ if (error instanceof FeishuReplyCommentError && isReplyNotAllowedError(error)) {
808
+ if (params.file_type !== "doc" && params.file_type !== "docx") throw error;
809
+ const fallbackFileType = params.file_type;
810
+ console.info(`[feishu_drive] reply-not-allowed compatibility path comment=${params.comment_id} file_type=${params.file_type} mode=add_comment log_id=${error.feishuLogId ?? "unknown"}`);
811
+ return {
812
+ delivery_mode: "add_comment",
813
+ ...await addComment(client, {
814
+ file_token: params.file_token,
815
+ file_type: fallbackFileType,
816
+ content: params.content
817
+ })
818
+ };
819
+ }
820
+ throw error;
821
+ }
822
+ }
823
+ function registerFeishuDriveTools(api) {
824
+ if (!api.config) return;
825
+ const accounts = listEnabledFeishuAccounts(api.config);
826
+ if (accounts.length === 0) return;
827
+ if (!resolveAnyEnabledFeishuToolsConfig(accounts).drive) return;
828
+ api.registerTool((ctx) => {
829
+ const defaultAccountId = ctx.agentAccountId;
830
+ return {
831
+ name: "feishu_drive",
832
+ label: "Feishu Drive",
833
+ description: "Feishu cloud storage operations. Actions: list, info, create_folder, move, delete, list_comments, list_comment_replies, add_comment, reply_comment",
834
+ parameters: FeishuDriveSchema,
835
+ async execute(_toolCallId, params) {
836
+ const p = params;
837
+ try {
838
+ const client = createFeishuToolClient({
839
+ api,
840
+ executeParams: p,
841
+ defaultAccountId
842
+ });
843
+ switch (p.action) {
844
+ case "list": return jsonToolResult(await listFolder(client, p.folder_token));
845
+ case "info": return jsonToolResult(await getFileInfo(client, p.file_token));
846
+ case "create_folder": return jsonToolResult(await createFolder(client, p.name, p.folder_token));
847
+ case "move": return jsonToolResult(await moveFile(client, p.file_token, p.type, p.folder_token));
848
+ case "delete": return jsonToolResult(await deleteFile(client, p.file_token, p.type));
849
+ case "list_comments": return jsonToolResult(await listComments(client, applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "list_comments")));
850
+ case "list_comment_replies": return jsonToolResult(await listCommentReplies(client, applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "list_comment_replies")));
851
+ case "add_comment": {
852
+ const resolved = applyAddCommentDefaults(applyAddCommentAmbientDefaults(p, ctx));
853
+ try {
854
+ return jsonToolResult(await addComment(client, resolved));
855
+ } finally {
856
+ cleanupAmbientCommentTypingReaction({
857
+ client: getDriveInternalClient(client),
858
+ deliveryContext: ctx.deliveryContext
859
+ });
860
+ }
861
+ }
862
+ case "reply_comment": {
863
+ const resolved = applyCommentFileTypeDefault(applyAmbientCommentDefaults(p, ctx), "reply_comment");
864
+ try {
865
+ return jsonToolResult(await deliverCommentThreadText(client, resolved));
866
+ } finally {
867
+ cleanupAmbientCommentTypingReaction({
868
+ client: getDriveInternalClient(client),
869
+ deliveryContext: ctx.deliveryContext
870
+ });
871
+ }
872
+ }
873
+ default: return unknownToolActionResult(p.action);
874
+ }
875
+ } catch (err) {
876
+ return toolExecutionErrorResult(err);
877
+ }
878
+ }
879
+ };
880
+ }, { name: "feishu_drive" });
881
+ }
882
+ //#endregion
883
+ export { jsonToolResult as a, getChatInfo as c, registerFeishuChatTools as d, createFeishuToolClient as f, createCommentTypingReactionLifecycle as i, getChatMembers as l, resolveFeishuToolAccount as m, registerFeishuDriveTools as n, toolExecutionErrorResult as o, resolveAnyEnabledFeishuToolsConfig as p, cleanupAmbientCommentTypingReaction as r, unknownToolActionResult as s, deliverCommentThreadText as t, getFeishuMemberInfo as u };