@openclaw/feishu 2026.5.2-beta.2 → 2026.5.3-beta.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.
- package/dist/accounts-Ba3-WP1z.js +423 -0
- package/dist/api.js +2280 -0
- package/dist/app-registration-B8qc1MCM.js +184 -0
- package/dist/audio-preflight.runtime-BPlzkO3l.js +7 -0
- package/dist/card-interaction-BfRLgvw_.js +96 -0
- package/dist/channel-CSD_Jt8I.js +1668 -0
- package/dist/channel-entry.js +22 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-DYsXcD36.js +700 -0
- package/dist/client-DBVoQL5w.js +157 -0
- package/dist/contract-api.js +9 -0
- package/dist/conversation-id-DWS3Ep2A.js +139 -0
- package/dist/directory.static-f3EeoRJd.js +44 -0
- package/dist/drive-C5eJLJr7.js +883 -0
- package/dist/index.js +68 -0
- package/dist/monitor-CT189QfR.js +60 -0
- package/dist/monitor.account-dJV2jO8C.js +4990 -0
- package/dist/monitor.state-DYM02ipp.js +100 -0
- package/dist/policy-D6c-wMPl.js +118 -0
- package/dist/probe-BNzzU_uR.js +149 -0
- package/dist/rolldown-runtime-DUslC3ob.js +14 -0
- package/dist/runtime-CG0DuRCy.js +8 -0
- package/dist/runtime-api.js +14 -0
- package/dist/secret-contract-Dm4Z_zQN.js +119 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-DqJdocrN.js +11 -0
- package/dist/security-audit-shared-ByuMx9cJ.js +38 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-DowxxbpH.js +1218 -0
- package/dist/session-conversation-B4nrW-vo.js +27 -0
- package/dist/session-key-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +15 -0
- package/dist/subagent-hooks-C3UhPVLV.js +227 -0
- package/dist/subagent-hooks-api.js +23 -0
- package/dist/targets-JMFJRKSe.js +48 -0
- package/dist/thread-bindings-BmS6TLes.js +222 -0
- package/package.json +15 -6
- package/api.ts +0 -31
- package/channel-entry.ts +0 -20
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -16
- package/index.ts +0 -82
- package/runtime-api.ts +0 -55
- package/secret-contract-api.ts +0 -5
- package/security-contract-api.ts +0 -1
- package/session-key-api.ts +0 -1
- package/setup-api.ts +0 -3
- package/setup-entry.test.ts +0 -14
- package/setup-entry.ts +0 -13
- package/src/accounts.test.ts +0 -459
- package/src/accounts.ts +0 -326
- package/src/app-registration.ts +0 -331
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -25
- package/src/async.test.ts +0 -35
- package/src/async.ts +0 -104
- package/src/audio-preflight.runtime.ts +0 -9
- package/src/bitable.test.ts +0 -131
- package/src/bitable.ts +0 -762
- package/src/bot-content.ts +0 -474
- package/src/bot-group-name.test.ts +0 -108
- package/src/bot-runtime-api.ts +0 -12
- package/src/bot-sender-name.ts +0 -125
- package/src/bot.broadcast.test.ts +0 -463
- package/src/bot.card-action.test.ts +0 -577
- package/src/bot.checkBotMentioned.test.ts +0 -265
- package/src/bot.helpers.test.ts +0 -118
- package/src/bot.stripBotMention.test.ts +0 -126
- package/src/bot.test.ts +0 -3040
- package/src/bot.ts +0 -1559
- package/src/card-action.ts +0 -447
- package/src/card-interaction.test.ts +0 -129
- package/src/card-interaction.ts +0 -159
- package/src/card-test-helpers.ts +0 -47
- package/src/card-ux-approval.ts +0 -65
- package/src/card-ux-launcher.test.ts +0 -99
- package/src/card-ux-launcher.ts +0 -121
- package/src/card-ux-shared.ts +0 -33
- package/src/channel-runtime-api.ts +0 -16
- package/src/channel.runtime.ts +0 -47
- package/src/channel.test.ts +0 -959
- package/src/channel.ts +0 -1313
- package/src/chat-schema.ts +0 -25
- package/src/chat.test.ts +0 -196
- package/src/chat.ts +0 -188
- package/src/client.test.ts +0 -433
- package/src/client.ts +0 -290
- package/src/comment-dispatcher-runtime-api.ts +0 -6
- package/src/comment-dispatcher.test.ts +0 -169
- package/src/comment-dispatcher.ts +0 -107
- package/src/comment-handler-runtime-api.ts +0 -3
- package/src/comment-handler.test.ts +0 -486
- package/src/comment-handler.ts +0 -309
- package/src/comment-reaction.test.ts +0 -166
- package/src/comment-reaction.ts +0 -259
- package/src/comment-shared.test.ts +0 -182
- package/src/comment-shared.ts +0 -406
- package/src/comment-target.ts +0 -44
- package/src/config-schema.test.ts +0 -309
- package/src/config-schema.ts +0 -333
- package/src/conversation-id.test.ts +0 -18
- package/src/conversation-id.ts +0 -199
- package/src/dedup-runtime-api.ts +0 -1
- package/src/dedup.ts +0 -141
- package/src/directory.static.ts +0 -61
- package/src/directory.test.ts +0 -136
- package/src/directory.ts +0 -124
- package/src/doc-schema.ts +0 -182
- package/src/docx-batch-insert.test.ts +0 -91
- package/src/docx-batch-insert.ts +0 -223
- package/src/docx-color-text.ts +0 -154
- package/src/docx-table-ops.test.ts +0 -53
- package/src/docx-table-ops.ts +0 -316
- package/src/docx-types.ts +0 -38
- package/src/docx.account-selection.test.ts +0 -79
- package/src/docx.test.ts +0 -685
- package/src/docx.ts +0 -1616
- package/src/drive-schema.ts +0 -92
- package/src/drive.test.ts +0 -1219
- package/src/drive.ts +0 -829
- package/src/dynamic-agent.ts +0 -137
- package/src/event-types.ts +0 -45
- package/src/external-keys.test.ts +0 -20
- package/src/external-keys.ts +0 -19
- package/src/lifecycle.test-support.ts +0 -220
- package/src/media.test.ts +0 -900
- package/src/media.ts +0 -861
- package/src/mention-target.types.ts +0 -5
- package/src/mention.ts +0 -114
- package/src/message-action-contract.ts +0 -13
- package/src/monitor-state-runtime-api.ts +0 -7
- package/src/monitor-transport-runtime-api.ts +0 -7
- package/src/monitor.account.ts +0 -468
- package/src/monitor.acp-init-failure.lifecycle.test-support.ts +0 -219
- package/src/monitor.bot-identity.ts +0 -86
- package/src/monitor.bot-menu-handler.ts +0 -165
- package/src/monitor.bot-menu.lifecycle.test-support.ts +0 -224
- package/src/monitor.bot-menu.test.ts +0 -178
- package/src/monitor.broadcast.reply-once.lifecycle.test-support.ts +0 -264
- package/src/monitor.card-action.lifecycle.test-support.ts +0 -373
- package/src/monitor.cleanup.test.ts +0 -376
- package/src/monitor.comment-notice-handler.ts +0 -105
- package/src/monitor.comment.test.ts +0 -937
- package/src/monitor.comment.ts +0 -1386
- package/src/monitor.lifecycle.test.ts +0 -4
- package/src/monitor.message-handler.ts +0 -339
- package/src/monitor.reaction.lifecycle.test-support.ts +0 -68
- package/src/monitor.reaction.test.ts +0 -713
- package/src/monitor.startup.test.ts +0 -192
- package/src/monitor.startup.ts +0 -74
- package/src/monitor.state.defaults.test.ts +0 -46
- package/src/monitor.state.ts +0 -170
- package/src/monitor.synthetic-error.ts +0 -18
- package/src/monitor.test-mocks.ts +0 -45
- package/src/monitor.transport.ts +0 -424
- package/src/monitor.ts +0 -100
- package/src/monitor.webhook-e2e.test.ts +0 -272
- package/src/monitor.webhook-security.test.ts +0 -264
- package/src/monitor.webhook.test-helpers.ts +0 -116
- package/src/outbound-runtime-api.ts +0 -1
- package/src/outbound.test.ts +0 -935
- package/src/outbound.ts +0 -718
- package/src/perm-schema.ts +0 -52
- package/src/perm.ts +0 -170
- package/src/pins.ts +0 -108
- package/src/policy.test.ts +0 -334
- package/src/policy.ts +0 -236
- package/src/post.test.ts +0 -105
- package/src/post.ts +0 -275
- package/src/probe.test.ts +0 -275
- package/src/probe.ts +0 -166
- package/src/processing-claims.ts +0 -59
- package/src/qr-terminal.ts +0 -1
- package/src/reactions.ts +0 -123
- package/src/reasoning-preview.test.ts +0 -59
- package/src/reasoning-preview.ts +0 -20
- package/src/reply-dispatcher-runtime-api.ts +0 -7
- package/src/reply-dispatcher.test.ts +0 -1144
- package/src/reply-dispatcher.ts +0 -650
- package/src/runtime.ts +0 -9
- package/src/secret-contract.ts +0 -145
- package/src/secret-input.ts +0 -1
- package/src/security-audit-shared.ts +0 -69
- package/src/security-audit.test.ts +0 -61
- package/src/security-audit.ts +0 -1
- package/src/send-result.ts +0 -29
- package/src/send-target.test.ts +0 -80
- package/src/send-target.ts +0 -35
- package/src/send.reply-fallback.test.ts +0 -292
- package/src/send.test.ts +0 -550
- package/src/send.ts +0 -800
- package/src/sequential-key.test.ts +0 -72
- package/src/sequential-key.ts +0 -28
- package/src/sequential-queue.test.ts +0 -92
- package/src/sequential-queue.ts +0 -16
- package/src/session-conversation.ts +0 -42
- package/src/session-route.ts +0 -48
- package/src/setup-core.ts +0 -51
- package/src/setup-surface.test.ts +0 -174
- package/src/setup-surface.ts +0 -581
- package/src/streaming-card.test.ts +0 -190
- package/src/streaming-card.ts +0 -490
- package/src/subagent-hooks.test.ts +0 -603
- package/src/subagent-hooks.ts +0 -397
- package/src/targets.ts +0 -97
- package/src/test-support/lifecycle-test-support.ts +0 -453
- package/src/thread-bindings.test.ts +0 -143
- package/src/thread-bindings.ts +0 -330
- package/src/tool-account-routing.test.ts +0 -187
- package/src/tool-account.test.ts +0 -44
- package/src/tool-account.ts +0 -93
- package/src/tool-factory-test-harness.ts +0 -79
- package/src/tool-result.test.ts +0 -32
- package/src/tool-result.ts +0 -16
- package/src/tools-config.test.ts +0 -21
- package/src/tools-config.ts +0 -22
- package/src/types.ts +0 -104
- package/src/typing.test.ts +0 -144
- package/src/typing.ts +0 -214
- package/src/wiki-schema.ts +0 -55
- package/src/wiki.ts +0 -227
- package/subagent-hooks-api.ts +0 -31
- package/tsconfig.json +0 -16
|
@@ -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-Ba3-WP1z.js";
|
|
2
|
+
import { r as createFeishuClient } from "./client-DBVoQL5w.js";
|
|
3
|
+
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
4
|
+
import { formatErrorMessage } from "openclaw/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 };
|