@bobotu/feishu-fork 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +922 -0
  3. package/index.ts +65 -0
  4. package/openclaw.plugin.json +10 -0
  5. package/package.json +72 -0
  6. package/skills/feishu-doc/SKILL.md +161 -0
  7. package/skills/feishu-doc/references/block-types.md +102 -0
  8. package/skills/feishu-drive/SKILL.md +96 -0
  9. package/skills/feishu-perm/SKILL.md +90 -0
  10. package/skills/feishu-task/SKILL.md +210 -0
  11. package/skills/feishu-wiki/SKILL.md +96 -0
  12. package/src/accounts.ts +140 -0
  13. package/src/bitable-tools/actions.ts +199 -0
  14. package/src/bitable-tools/common.ts +90 -0
  15. package/src/bitable-tools/index.ts +1 -0
  16. package/src/bitable-tools/meta.ts +80 -0
  17. package/src/bitable-tools/register.ts +195 -0
  18. package/src/bitable-tools/schemas.ts +221 -0
  19. package/src/bot.ts +1125 -0
  20. package/src/channel.ts +334 -0
  21. package/src/client.ts +114 -0
  22. package/src/config-schema.ts +237 -0
  23. package/src/dedup.ts +54 -0
  24. package/src/directory.ts +165 -0
  25. package/src/doc-tools/actions.ts +341 -0
  26. package/src/doc-tools/common.ts +33 -0
  27. package/src/doc-tools/index.ts +2 -0
  28. package/src/doc-tools/register.ts +90 -0
  29. package/src/doc-tools/schemas.ts +85 -0
  30. package/src/doc-write-service.ts +711 -0
  31. package/src/drive-tools/actions.ts +182 -0
  32. package/src/drive-tools/common.ts +18 -0
  33. package/src/drive-tools/index.ts +2 -0
  34. package/src/drive-tools/register.ts +71 -0
  35. package/src/drive-tools/schemas.ts +67 -0
  36. package/src/dynamic-agent.ts +135 -0
  37. package/src/external-keys.ts +19 -0
  38. package/src/media.ts +510 -0
  39. package/src/mention.ts +121 -0
  40. package/src/monitor.ts +323 -0
  41. package/src/onboarding.ts +449 -0
  42. package/src/outbound.ts +40 -0
  43. package/src/perm-tools/actions.ts +111 -0
  44. package/src/perm-tools/common.ts +18 -0
  45. package/src/perm-tools/index.ts +2 -0
  46. package/src/perm-tools/register.ts +65 -0
  47. package/src/perm-tools/schemas.ts +52 -0
  48. package/src/policy.ts +117 -0
  49. package/src/probe.ts +147 -0
  50. package/src/reactions.ts +160 -0
  51. package/src/reply-dispatcher.ts +240 -0
  52. package/src/runtime.ts +14 -0
  53. package/src/send.ts +391 -0
  54. package/src/streaming-card.ts +211 -0
  55. package/src/targets.ts +58 -0
  56. package/src/task-tools/actions.ts +590 -0
  57. package/src/task-tools/common.ts +18 -0
  58. package/src/task-tools/constants.ts +13 -0
  59. package/src/task-tools/index.ts +1 -0
  60. package/src/task-tools/register.ts +263 -0
  61. package/src/task-tools/schemas.ts +567 -0
  62. package/src/text/markdown-links.ts +104 -0
  63. package/src/tools-common/feishu-api.ts +184 -0
  64. package/src/tools-common/tool-context.ts +23 -0
  65. package/src/tools-common/tool-exec.ts +73 -0
  66. package/src/tools-config.ts +22 -0
  67. package/src/types.ts +79 -0
  68. package/src/typing.ts +75 -0
  69. package/src/wiki-tools/actions.ts +166 -0
  70. package/src/wiki-tools/common.ts +18 -0
  71. package/src/wiki-tools/index.ts +2 -0
  72. package/src/wiki-tools/register.ts +66 -0
  73. package/src/wiki-tools/schemas.ts +55 -0
@@ -0,0 +1,590 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { TaskClient } from "./common.js";
5
+ import {
6
+ TASKLIST_UPDATE_FIELD_VALUES,
7
+ TASK_UPDATE_FIELD_VALUES,
8
+ type AddTaskToTasklistParams,
9
+ type AddTasklistMembersParams,
10
+ type CreateTasklistParams,
11
+ CreateSubtaskParams,
12
+ CreateTaskParams,
13
+ DeleteTaskAttachmentParams,
14
+ GetTaskAttachmentParams,
15
+ GetTaskParams,
16
+ type GetTasklistParams,
17
+ ListTaskAttachmentsParams,
18
+ type ListTasklistsParams,
19
+ type RemoveTaskFromTasklistParams,
20
+ type RemoveTasklistMembersParams,
21
+ TaskUpdateTask,
22
+ type TasklistPatchTasklist,
23
+ UploadTaskAttachmentParams,
24
+ type UpdateTasklistParams,
25
+ UpdateTaskParams,
26
+ } from "./schemas.js";
27
+ import {
28
+ DEFAULT_TASK_ATTACHMENT_FILENAME,
29
+ DEFAULT_TASK_ATTACHMENT_MAX_BYTES,
30
+ BYTES_PER_MEGABYTE,
31
+ HEX_RADIX,
32
+ RANDOM_TOKEN_PREFIX_LENGTH,
33
+ SIZE_DISPLAY_FRACTION_DIGITS,
34
+ } from "./constants.js";
35
+ import { getFeishuRuntime } from "../runtime.js";
36
+ import { runTaskApiCall } from "./common.js";
37
+
38
+ const SUPPORTED_PATCH_FIELDS = new Set<string>(TASK_UPDATE_FIELD_VALUES);
39
+ const SUPPORTED_TASKLIST_PATCH_FIELDS = new Set<string>(TASKLIST_UPDATE_FIELD_VALUES);
40
+
41
+ function omitUndefined<T extends Record<string, unknown>>(obj: T): T {
42
+ return Object.fromEntries(
43
+ Object.entries(obj).filter(([, value]) => value !== undefined),
44
+ ) as T;
45
+ }
46
+
47
+ function inferUpdateFields(task: TaskUpdateTask): string[] {
48
+ return Object.keys(task).filter((field) =>
49
+ SUPPORTED_PATCH_FIELDS.has(field),
50
+ );
51
+ }
52
+
53
+ function ensureSupportedUpdateFields(
54
+ updateFields: string[],
55
+ supported: Set<string>,
56
+ resource: "task" | "tasklist",
57
+ ) {
58
+ const invalid = updateFields.filter((field) => !supported.has(field));
59
+ if (invalid.length > 0) {
60
+ throw new Error(`unsupported ${resource} update_fields: ${invalid.join(", ")}`);
61
+ }
62
+ }
63
+
64
+ function inferTasklistUpdateFields(tasklist: TasklistPatchTasklist): string[] {
65
+ return Object.keys(tasklist).filter((field) =>
66
+ SUPPORTED_TASKLIST_PATCH_FIELDS.has(field),
67
+ );
68
+ }
69
+
70
+ function formatTask(task: Record<string, unknown> | undefined) {
71
+ if (!task) return undefined;
72
+ return {
73
+ guid: task.guid,
74
+ task_id: task.task_id,
75
+ summary: task.summary,
76
+ description: task.description,
77
+ status: task.status,
78
+ url: task.url,
79
+ created_at: task.created_at,
80
+ updated_at: task.updated_at,
81
+ completed_at: task.completed_at,
82
+ due: task.due,
83
+ start: task.start,
84
+ is_milestone: task.is_milestone,
85
+ members: task.members,
86
+ tasklists: task.tasklists,
87
+ };
88
+ }
89
+
90
+ function formatTasklist(tasklist: Record<string, unknown> | undefined) {
91
+ if (!tasklist) return undefined;
92
+ return {
93
+ guid: tasklist.guid,
94
+ name: tasklist.name,
95
+ creator: tasklist.creator,
96
+ owner: tasklist.owner,
97
+ members: tasklist.members,
98
+ url: tasklist.url,
99
+ created_at: tasklist.created_at,
100
+ updated_at: tasklist.updated_at,
101
+ archive_msec: tasklist.archive_msec,
102
+ };
103
+ }
104
+
105
+ function formatAttachment(attachment: Record<string, unknown> | undefined) {
106
+ if (!attachment) return undefined;
107
+ return {
108
+ guid: attachment.guid,
109
+ file_token: attachment.file_token,
110
+ name: attachment.name,
111
+ size: attachment.size,
112
+ uploader: attachment.uploader,
113
+ is_cover: attachment.is_cover,
114
+ uploaded_at: attachment.uploaded_at,
115
+ url: attachment.url,
116
+ resource: attachment.resource,
117
+ };
118
+ }
119
+
120
+ function sanitizeUploadFilename(input: string) {
121
+ const base = path.basename(input.trim());
122
+ return base.length > 0 ? base : DEFAULT_TASK_ATTACHMENT_FILENAME;
123
+ }
124
+
125
+ async function ensureUploadableLocalFile(filePath: string, maxBytes: number) {
126
+ let stat: fs.Stats;
127
+ try {
128
+ stat = await fs.promises.stat(filePath);
129
+ } catch {
130
+ throw new Error(`file_path not found: ${filePath}`);
131
+ }
132
+
133
+ if (!stat.isFile()) {
134
+ throw new Error(`file_path is not a regular file: ${filePath}`);
135
+ }
136
+
137
+ if (stat.size > maxBytes) {
138
+ throw new Error(
139
+ `file_path exceeds ${(maxBytes / BYTES_PER_MEGABYTE).toFixed(SIZE_DISPLAY_FRACTION_DIGITS)}MB limit: ${filePath}`,
140
+ );
141
+ }
142
+ }
143
+
144
+ async function saveBufferToTempFile(buffer: Buffer, fileName: string) {
145
+ const safeName = sanitizeUploadFilename(fileName);
146
+ const tempPath = path.join(
147
+ os.tmpdir(),
148
+ `feishu-task-attachment-${Date.now()}-${Math.random().toString(HEX_RADIX).slice(RANDOM_TOKEN_PREFIX_LENGTH)}-${safeName}`,
149
+ );
150
+
151
+ await fs.promises.writeFile(tempPath, buffer);
152
+
153
+ return {
154
+ tempPath,
155
+ cleanup: async () => {
156
+ await fs.promises.unlink(tempPath).catch(() => undefined);
157
+ },
158
+ };
159
+ }
160
+
161
+ async function downloadToTempFile(fileUrl: string, filename: string | undefined, maxBytes: number) {
162
+ const loaded = await getFeishuRuntime().media.loadWebMedia(fileUrl, {
163
+ maxBytes,
164
+ optimizeImages: false,
165
+ });
166
+
167
+ const parsedPath = (() => {
168
+ try {
169
+ return new URL(fileUrl).pathname;
170
+ } catch {
171
+ return "";
172
+ }
173
+ })();
174
+
175
+ const fallbackName = path.basename(parsedPath) || DEFAULT_TASK_ATTACHMENT_FILENAME;
176
+ const preferredName = filename?.trim() ? filename : loaded.fileName ?? fallbackName;
177
+ return saveBufferToTempFile(loaded.buffer, preferredName);
178
+ }
179
+
180
+ export async function createTask(client: TaskClient, params: CreateTaskParams) {
181
+ const res = await runTaskApiCall("task.v2.task.create", () =>
182
+ client.task.v2.task.create({
183
+ data: omitUndefined({
184
+ summary: params.summary,
185
+ description: params.description,
186
+ due: params.due,
187
+ start: params.start,
188
+ extra: params.extra,
189
+ completed_at: params.completed_at,
190
+ members: params.members,
191
+ repeat_rule: params.repeat_rule,
192
+ tasklists: params.tasklists,
193
+ mode: params.mode,
194
+ is_milestone: params.is_milestone,
195
+ }),
196
+ params: omitUndefined({
197
+ user_id_type: params.user_id_type,
198
+ }),
199
+ }),
200
+ );
201
+
202
+ return {
203
+ task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
204
+ };
205
+ }
206
+
207
+ export async function createSubtask(client: TaskClient, params: CreateSubtaskParams) {
208
+ const res = await runTaskApiCall("task.v2.taskSubtask.create", () =>
209
+ client.task.v2.taskSubtask.create({
210
+ path: { task_guid: params.task_guid },
211
+ data: omitUndefined({
212
+ summary: params.summary,
213
+ description: params.description,
214
+ due: params.due,
215
+ start: params.start,
216
+ extra: params.extra,
217
+ completed_at: params.completed_at,
218
+ members: params.members,
219
+ repeat_rule: params.repeat_rule,
220
+ tasklists: params.tasklists,
221
+ mode: params.mode,
222
+ is_milestone: params.is_milestone,
223
+ }),
224
+ params: omitUndefined({
225
+ user_id_type: params.user_id_type,
226
+ }),
227
+ }),
228
+ );
229
+
230
+ return {
231
+ subtask: formatTask((res.data?.subtask ?? undefined) as Record<string, unknown> | undefined),
232
+ };
233
+ }
234
+
235
+ export async function createTasklist(client: TaskClient, params: CreateTasklistParams) {
236
+ const res = await runTaskApiCall("task.v2.tasklist.create", () =>
237
+ client.task.v2.tasklist.create({
238
+ data: omitUndefined({
239
+ name: params.name,
240
+ members: params.members,
241
+ archive_tasklist: params.archive_tasklist,
242
+ }),
243
+ params: omitUndefined({
244
+ user_id_type: params.user_id_type,
245
+ }),
246
+ }),
247
+ );
248
+
249
+ return {
250
+ tasklist: formatTasklist(
251
+ (res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
252
+ ),
253
+ };
254
+ }
255
+
256
+ export async function deleteTaskAttachment(client: TaskClient, params: DeleteTaskAttachmentParams) {
257
+ await runTaskApiCall("task.v2.attachment.delete", () =>
258
+ client.task.v2.attachment.delete({
259
+ path: { attachment_guid: params.attachment_guid },
260
+ }),
261
+ );
262
+
263
+ return {
264
+ success: true,
265
+ attachment_guid: params.attachment_guid,
266
+ };
267
+ }
268
+
269
+ export async function deleteTask(client: TaskClient, taskGuid: string) {
270
+ await runTaskApiCall("task.v2.task.delete", () =>
271
+ client.task.v2.task.delete({
272
+ path: { task_guid: taskGuid },
273
+ }),
274
+ );
275
+
276
+ return {
277
+ success: true,
278
+ task_guid: taskGuid,
279
+ };
280
+ }
281
+
282
+ export async function deleteTasklist(client: TaskClient, tasklistGuid: string) {
283
+ await runTaskApiCall("task.v2.tasklist.delete", () =>
284
+ client.task.v2.tasklist.delete({
285
+ path: { tasklist_guid: tasklistGuid },
286
+ }),
287
+ );
288
+
289
+ return {
290
+ success: true,
291
+ tasklist_guid: tasklistGuid,
292
+ };
293
+ }
294
+
295
+ export async function getTask(client: TaskClient, params: GetTaskParams) {
296
+ const res = await runTaskApiCall("task.v2.task.get", () =>
297
+ client.task.v2.task.get({
298
+ path: { task_guid: params.task_guid },
299
+ params: omitUndefined({
300
+ user_id_type: params.user_id_type,
301
+ }),
302
+ }),
303
+ );
304
+
305
+ return {
306
+ task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
307
+ };
308
+ }
309
+
310
+ export async function getTasklist(client: TaskClient, params: GetTasklistParams) {
311
+ const res = await runTaskApiCall("task.v2.tasklist.get", () =>
312
+ client.task.v2.tasklist.get({
313
+ path: { tasklist_guid: params.tasklist_guid },
314
+ params: omitUndefined({
315
+ user_id_type: params.user_id_type,
316
+ }),
317
+ }),
318
+ );
319
+
320
+ return {
321
+ tasklist: formatTasklist(
322
+ (res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
323
+ ),
324
+ };
325
+ }
326
+
327
+ export async function listTasklists(client: TaskClient, params: ListTasklistsParams) {
328
+ const res = await runTaskApiCall("task.v2.tasklist.list", () =>
329
+ client.task.v2.tasklist.list({
330
+ params: omitUndefined({
331
+ page_size: params.page_size,
332
+ page_token: params.page_token,
333
+ user_id_type: params.user_id_type,
334
+ }),
335
+ }),
336
+ );
337
+
338
+ const items = (res.data?.items ?? []) as Record<string, unknown>[];
339
+
340
+ return {
341
+ items: items.map((item) => formatTasklist(item)),
342
+ page_token: res.data?.page_token,
343
+ has_more: res.data?.has_more,
344
+ };
345
+ }
346
+
347
+ export async function getTaskAttachment(client: TaskClient, params: GetTaskAttachmentParams) {
348
+ const res = await runTaskApiCall("task.v2.attachment.get", () =>
349
+ client.task.v2.attachment.get({
350
+ path: { attachment_guid: params.attachment_guid },
351
+ params: omitUndefined({
352
+ user_id_type: params.user_id_type,
353
+ }),
354
+ }),
355
+ );
356
+
357
+ return {
358
+ attachment: formatAttachment(
359
+ (res.data?.attachment ?? undefined) as Record<string, unknown> | undefined,
360
+ ),
361
+ };
362
+ }
363
+
364
+ export async function listTaskAttachments(client: TaskClient, params: ListTaskAttachmentsParams) {
365
+ const res = await runTaskApiCall("task.v2.attachment.list", () =>
366
+ client.task.v2.attachment.list({
367
+ params: omitUndefined({
368
+ resource_type: "task",
369
+ resource_id: params.task_guid,
370
+ page_size: params.page_size,
371
+ page_token: params.page_token,
372
+ updated_mesc: params.updated_mesc,
373
+ user_id_type: params.user_id_type,
374
+ }),
375
+ }),
376
+ );
377
+
378
+ const items = (res.data?.items ?? []) as Record<string, unknown>[];
379
+
380
+ return {
381
+ items: items.map((item) => formatAttachment(item)),
382
+ page_token: res.data?.page_token,
383
+ has_more: res.data?.has_more,
384
+ };
385
+ }
386
+
387
+ export async function updateTask(client: TaskClient, params: UpdateTaskParams) {
388
+ const task = omitUndefined(params.task as Record<string, unknown>) as TaskUpdateTask;
389
+ const updateFields = params.update_fields?.length ? params.update_fields : inferUpdateFields(task);
390
+
391
+ if (params.update_fields?.length) {
392
+ ensureSupportedUpdateFields(updateFields, SUPPORTED_PATCH_FIELDS, "task");
393
+ }
394
+
395
+ if (Object.keys(task).length === 0) {
396
+ throw new Error("task update payload is empty");
397
+ }
398
+ if (updateFields.length === 0) {
399
+ throw new Error("no valid update_fields provided or inferred from task payload");
400
+ }
401
+
402
+ const res = await runTaskApiCall("task.v2.task.patch", () =>
403
+ client.task.v2.task.patch({
404
+ path: { task_guid: params.task_guid },
405
+ data: {
406
+ task,
407
+ update_fields: updateFields,
408
+ },
409
+ params: omitUndefined({
410
+ user_id_type: params.user_id_type,
411
+ }),
412
+ }),
413
+ );
414
+
415
+ return {
416
+ task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
417
+ update_fields: updateFields,
418
+ };
419
+ }
420
+
421
+ export async function addTaskToTasklist(client: TaskClient, params: AddTaskToTasklistParams) {
422
+ const res = await runTaskApiCall("task.v2.task.add_tasklist", () =>
423
+ client.task.v2.task.addTasklist({
424
+ path: { task_guid: params.task_guid },
425
+ data: omitUndefined({
426
+ tasklist_guid: params.tasklist_guid,
427
+ section_guid: params.section_guid,
428
+ }),
429
+ params: omitUndefined({
430
+ user_id_type: params.user_id_type,
431
+ }),
432
+ }),
433
+ );
434
+
435
+ return {
436
+ task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
437
+ };
438
+ }
439
+
440
+ export async function removeTaskFromTasklist(
441
+ client: TaskClient,
442
+ params: RemoveTaskFromTasklistParams,
443
+ ) {
444
+ const res = await runTaskApiCall("task.v2.task.remove_tasklist", () =>
445
+ client.task.v2.task.removeTasklist({
446
+ path: { task_guid: params.task_guid },
447
+ data: omitUndefined({
448
+ tasklist_guid: params.tasklist_guid,
449
+ }),
450
+ params: omitUndefined({
451
+ user_id_type: params.user_id_type,
452
+ }),
453
+ }),
454
+ );
455
+
456
+ return {
457
+ task: formatTask((res.data?.task ?? undefined) as Record<string, unknown> | undefined),
458
+ };
459
+ }
460
+
461
+ export async function updateTasklist(client: TaskClient, params: UpdateTasklistParams) {
462
+ const tasklist = omitUndefined(params.tasklist as Record<string, unknown>) as TasklistPatchTasklist;
463
+ const updateFields = params.update_fields?.length
464
+ ? params.update_fields
465
+ : inferTasklistUpdateFields(tasklist);
466
+
467
+ if (params.update_fields?.length) {
468
+ ensureSupportedUpdateFields(updateFields, SUPPORTED_TASKLIST_PATCH_FIELDS, "tasklist");
469
+ }
470
+
471
+ if (Object.keys(tasklist).length === 0) {
472
+ throw new Error("tasklist update payload is empty");
473
+ }
474
+ if (updateFields.length === 0) {
475
+ throw new Error("no valid update_fields provided or inferred from tasklist payload");
476
+ }
477
+
478
+ const res = await runTaskApiCall("task.v2.tasklist.patch", () =>
479
+ client.task.v2.tasklist.patch({
480
+ path: { tasklist_guid: params.tasklist_guid },
481
+ data: omitUndefined({
482
+ tasklist,
483
+ update_fields: updateFields,
484
+ origin_owner_to_role: params.origin_owner_to_role,
485
+ }),
486
+ params: omitUndefined({
487
+ user_id_type: params.user_id_type,
488
+ }),
489
+ }),
490
+ );
491
+
492
+ return {
493
+ tasklist: formatTasklist(
494
+ (res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
495
+ ),
496
+ update_fields: updateFields,
497
+ };
498
+ }
499
+
500
+ export async function addTasklistMembers(client: TaskClient, params: AddTasklistMembersParams) {
501
+ const res = await runTaskApiCall("task.v2.tasklist.addMembers", () =>
502
+ client.task.v2.tasklist.addMembers({
503
+ path: { tasklist_guid: params.tasklist_guid },
504
+ data: {
505
+ members: params.members,
506
+ },
507
+ params: omitUndefined({
508
+ user_id_type: params.user_id_type,
509
+ }),
510
+ }),
511
+ );
512
+
513
+ return {
514
+ tasklist: formatTasklist(
515
+ (res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
516
+ ),
517
+ };
518
+ }
519
+
520
+ export async function removeTasklistMembers(
521
+ client: TaskClient,
522
+ params: RemoveTasklistMembersParams,
523
+ ) {
524
+ const res = await runTaskApiCall("task.v2.tasklist.removeMembers", () =>
525
+ client.task.v2.tasklist.removeMembers({
526
+ path: { tasklist_guid: params.tasklist_guid },
527
+ data: {
528
+ members: params.members,
529
+ },
530
+ params: omitUndefined({
531
+ user_id_type: params.user_id_type,
532
+ }),
533
+ }),
534
+ );
535
+
536
+ return {
537
+ tasklist: formatTasklist(
538
+ (res.data?.tasklist ?? undefined) as Record<string, unknown> | undefined,
539
+ ),
540
+ };
541
+ }
542
+
543
+ export async function uploadTaskAttachment(
544
+ client: TaskClient,
545
+ params: UploadTaskAttachmentParams,
546
+ options?: { maxBytes?: number },
547
+ ) {
548
+ const maxBytes =
549
+ typeof options?.maxBytes === "number" && options.maxBytes > 0
550
+ ? options.maxBytes
551
+ : DEFAULT_TASK_ATTACHMENT_MAX_BYTES;
552
+
553
+ let tempCleanup: (() => Promise<void>) | undefined;
554
+ let filePath: string;
555
+
556
+ if ("file_path" in params) {
557
+ filePath = params.file_path;
558
+ await ensureUploadableLocalFile(filePath, maxBytes);
559
+ } else {
560
+ const download = await downloadToTempFile(params.file_url, params.filename, maxBytes);
561
+ filePath = download.tempPath;
562
+ tempCleanup = download.cleanup;
563
+ }
564
+
565
+ try {
566
+ const res = await runTaskApiCall("task.v2.attachment.upload", async () => {
567
+ const data = await client.task.v2.attachment.upload({
568
+ data: {
569
+ resource_type: "task",
570
+ resource_id: params.task_guid,
571
+ file: fs.createReadStream(filePath),
572
+ },
573
+ params: omitUndefined({
574
+ user_id_type: params.user_id_type,
575
+ }),
576
+ });
577
+ return { code: 0, data } as { code: number; data: typeof data };
578
+ });
579
+
580
+ const items = (res.data?.items ?? []) as Record<string, unknown>[];
581
+
582
+ return {
583
+ items: items.map((item) => formatAttachment(item)),
584
+ };
585
+ } finally {
586
+ if (tempCleanup) {
587
+ await tempCleanup();
588
+ }
589
+ }
590
+ }
@@ -0,0 +1,18 @@
1
+ import { createFeishuClient } from "../client.js";
2
+ import {
3
+ errorResult,
4
+ json,
5
+ runFeishuApiCall,
6
+ type FeishuApiResponse,
7
+ } from "../tools-common/feishu-api.js";
8
+
9
+ export type TaskClient = ReturnType<typeof createFeishuClient>;
10
+
11
+ export { json, errorResult };
12
+
13
+ export async function runTaskApiCall<T extends FeishuApiResponse>(
14
+ context: string,
15
+ fn: () => Promise<T>,
16
+ ): Promise<T> {
17
+ return runFeishuApiCall(context, fn);
18
+ }
@@ -0,0 +1,13 @@
1
+ export const BYTES_PER_MEGABYTE = 1024 * 1024;
2
+
3
+ export const DEFAULT_TASK_MEDIA_MAX_MB = 30;
4
+
5
+ export const DEFAULT_TASK_ATTACHMENT_MAX_BYTES = DEFAULT_TASK_MEDIA_MAX_MB * BYTES_PER_MEGABYTE;
6
+
7
+ export const DEFAULT_TASK_ATTACHMENT_FILENAME = "attachment";
8
+
9
+ export const HEX_RADIX = 16;
10
+
11
+ export const RANDOM_TOKEN_PREFIX_LENGTH = 2;
12
+
13
+ export const SIZE_DISPLAY_FRACTION_DIGITS = 0;
@@ -0,0 +1 @@
1
+ export { registerFeishuTaskTools } from "./register.js";