@m1heng-clawd/feishu 0.1.14 → 0.1.16

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 (43) hide show
  1. package/README.md +82 -2
  2. package/index.ts +7 -2
  3. package/package.json +5 -4
  4. package/skills/feishu-chat/SKILL.md +139 -0
  5. package/skills/feishu-urgent/SKILL.md +107 -0
  6. package/src/bot.ts +391 -45
  7. package/src/channel.ts +2 -0
  8. package/src/chat-tools/actions.ts +502 -0
  9. package/src/chat-tools/common.ts +13 -0
  10. package/src/chat-tools/index.ts +2 -0
  11. package/src/chat-tools/register.ts +72 -0
  12. package/src/chat-tools/schemas.ts +61 -0
  13. package/src/client.ts +13 -0
  14. package/src/config-schema.ts +6 -0
  15. package/src/doc-tools/actions.ts +63 -14
  16. package/src/doc-tools/schemas.ts +41 -77
  17. package/src/doc-write-service.ts +19 -0
  18. package/src/drive-tools/actions.ts +75 -27
  19. package/src/drive-tools/schemas.ts +45 -58
  20. package/src/media-duration.ts +190 -0
  21. package/src/media.ts +50 -27
  22. package/src/mention.ts +1 -1
  23. package/src/monitor.ts +32 -8
  24. package/src/outbound.ts +2 -2
  25. package/src/perm-tools/actions.ts +27 -3
  26. package/src/perm-tools/schemas.ts +39 -45
  27. package/src/policy.ts +7 -3
  28. package/src/reply-dispatcher.ts +97 -13
  29. package/src/send.ts +360 -42
  30. package/src/streaming-card.ts +32 -4
  31. package/src/task-tools/actions.ts +145 -10
  32. package/src/task-tools/register.ts +56 -1
  33. package/src/task-tools/schemas.ts +23 -18
  34. package/src/tools-common/tool-context.ts +1 -0
  35. package/src/tools-config.ts +9 -2
  36. package/src/types.ts +8 -1
  37. package/src/typing.ts +115 -5
  38. package/src/urgent-tools/actions.ts +84 -0
  39. package/src/urgent-tools/index.ts +3 -0
  40. package/src/urgent-tools/register.ts +64 -0
  41. package/src/urgent-tools/schemas.ts +25 -0
  42. package/src/wiki-tools/actions.ts +24 -6
  43. package/src/wiki-tools/schemas.ts +32 -51
@@ -4,6 +4,7 @@ export { z };
4
4
  const DmPolicySchema = z.enum(["open", "pairing", "allowlist"]);
5
5
  const GroupPolicySchema = z.enum(["open", "allowlist", "disabled"]);
6
6
  const GroupCommandMentionBypassSchema = z.enum(["never", "single_bot", "always"]).optional();
7
+ const AllowMentionlessInMultiBotGroupSchema = z.boolean().optional();
7
8
  const FeishuDomainSchema = z.union([
8
9
  z.enum(["feishu", "lark"]),
9
10
  z.string().url().startsWith("https://"),
@@ -88,6 +89,8 @@ const FeishuToolsConfigSchema = z
88
89
  perm: z.boolean().optional(), // Permission management (default: false, sensitive)
89
90
  scopes: z.boolean().optional(), // App scopes diagnostic (default: true)
90
91
  task: z.boolean().optional(), // Task operations (default: true)
92
+ chat: z.boolean().optional(), // Chat management operations (default: true)
93
+ urgent: z.boolean().optional(), // Buzz/urgent notifications (default: true)
91
94
  })
92
95
  .strict()
93
96
  .optional();
@@ -106,6 +109,7 @@ export const FeishuGroupSchema = z
106
109
  .object({
107
110
  requireMention: z.boolean().optional(),
108
111
  groupCommandMentionBypass: GroupCommandMentionBypassSchema,
112
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema,
109
113
  tools: ToolPolicySchema,
110
114
  skills: z.array(z.string()).optional(),
111
115
  enabled: z.boolean().optional(),
@@ -140,6 +144,7 @@ export const FeishuAccountConfigSchema = z
140
144
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
141
145
  requireMention: z.boolean().optional(),
142
146
  groupCommandMentionBypass: GroupCommandMentionBypassSchema,
147
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema,
143
148
  groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
144
149
  historyLimit: z.number().int().min(0).optional(),
145
150
  dmHistoryLimit: z.number().int().min(0).optional(),
@@ -177,6 +182,7 @@ export const FeishuConfigSchema = z
177
182
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
178
183
  requireMention: z.boolean().optional().default(true),
179
184
  groupCommandMentionBypass: GroupCommandMentionBypassSchema.default("single_bot"),
185
+ allowMentionlessInMultiBotGroup: AllowMentionlessInMultiBotGroupSchema.default(false),
180
186
  groups: z.record(z.string(), FeishuGroupSchema.optional()).optional(),
181
187
  topicSessionMode: TopicSessionModeSchema,
182
188
  historyLimit: z.number().int().min(0).optional(),
@@ -44,6 +44,13 @@ function normalizePageSize(pageSize?: number) {
44
44
  return pageSize;
45
45
  }
46
46
 
47
+ function requireString(value: unknown, field: string): string {
48
+ if (typeof value !== "string" || value.trim().length === 0) {
49
+ throw new Error(`${field} is required`);
50
+ }
51
+ return value;
52
+ }
53
+
47
54
  function omitUndefined<T extends Record<string, unknown>>(obj: T): T {
48
55
  return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) as T;
49
56
  }
@@ -304,37 +311,79 @@ export async function runDocAction(
304
311
  ) {
305
312
  switch (params.action) {
306
313
  case "read":
307
- return readDoc(client, params.doc_token);
314
+ return readDoc(client, requireString(params.doc_token, "doc_token"));
308
315
  case "write":
309
- return writeDoc(client, params.doc_token, params.content, mediaMaxBytes);
316
+ return writeDoc(
317
+ client,
318
+ requireString(params.doc_token, "doc_token"),
319
+ requireString(params.content, "content"),
320
+ mediaMaxBytes,
321
+ );
310
322
  case "append":
311
- return appendDoc(client, params.doc_token, params.content, mediaMaxBytes);
323
+ return appendDoc(
324
+ client,
325
+ requireString(params.doc_token, "doc_token"),
326
+ requireString(params.content, "content"),
327
+ mediaMaxBytes,
328
+ );
312
329
  case "create":
313
- return createDoc(client, params.title, params.folder_token);
330
+ return createDoc(client, requireString(params.title, "title"), params.folder_token);
314
331
  case "create_and_write":
315
332
  return createAndWriteDoc(
316
333
  client,
317
- params.title,
318
- params.content,
334
+ requireString(params.title, "title"),
335
+ requireString(params.content, "content"),
319
336
  mediaMaxBytes,
320
337
  params.folder_token,
321
338
  );
322
339
  case "list_blocks":
323
- return listBlocks(client, params.doc_token);
340
+ return listBlocks(client, requireString(params.doc_token, "doc_token"));
324
341
  case "get_block":
325
- return getBlock(client, params.doc_token, params.block_id);
342
+ return getBlock(
343
+ client,
344
+ requireString(params.doc_token, "doc_token"),
345
+ requireString(params.block_id, "block_id"),
346
+ );
326
347
  case "update_block":
327
- return updateBlock(client, params.doc_token, params.block_id, params.content);
348
+ return updateBlock(
349
+ client,
350
+ requireString(params.doc_token, "doc_token"),
351
+ requireString(params.block_id, "block_id"),
352
+ requireString(params.content, "content"),
353
+ );
328
354
  case "delete_block":
329
- return deleteBlock(client, params.doc_token, params.block_id);
355
+ return deleteBlock(
356
+ client,
357
+ requireString(params.doc_token, "doc_token"),
358
+ requireString(params.block_id, "block_id"),
359
+ );
330
360
  case "list_comments":
331
- return listComments(client, params.doc_token, params.page_token, params.page_size);
361
+ return listComments(
362
+ client,
363
+ requireString(params.doc_token, "doc_token"),
364
+ params.page_token,
365
+ params.page_size,
366
+ );
332
367
  case "create_comment":
333
- return createComment(client, params.doc_token, params.content);
368
+ return createComment(
369
+ client,
370
+ requireString(params.doc_token, "doc_token"),
371
+ requireString(params.content, "content"),
372
+ );
334
373
  case "get_comment":
335
- return getComment(client, params.doc_token, params.comment_id);
374
+ return getComment(
375
+ client,
376
+ requireString(params.doc_token, "doc_token"),
377
+ requireString(params.comment_id, "comment_id"),
378
+ );
336
379
  case "list_comment_replies":
337
- return listCommentReplies(client, params.doc_token, params.comment_id, params.page_token, params.page_size);
380
+ return listCommentReplies(
381
+ client,
382
+ requireString(params.doc_token, "doc_token"),
383
+ requireString(params.comment_id, "comment_id"),
384
+ params.page_token,
385
+ params.page_size,
386
+ );
338
387
  default:
339
388
  return { error: `Unknown action: ${(params as any).action}` };
340
389
  }
@@ -1,85 +1,49 @@
1
1
  import { Type, type Static } from "@sinclair/typebox";
2
2
 
3
- export const FeishuDocSchema = Type.Union([
4
- Type.Object({
5
- action: Type.Literal("read"),
6
- doc_token: Type.String({
3
+ function stringEnum<T extends readonly string[]>(
4
+ values: T,
5
+ options: { description?: string; default?: T[number] } = {},
6
+ ) {
7
+ return Type.Unsafe<T[number]>({ type: "string", enum: [...values], ...options });
8
+ }
9
+
10
+ const DOC_ACTION_VALUES = [
11
+ "read",
12
+ "write",
13
+ "append",
14
+ "create",
15
+ "create_and_write",
16
+ "list_blocks",
17
+ "get_block",
18
+ "update_block",
19
+ "delete_block",
20
+ "list_comments",
21
+ "create_comment",
22
+ "get_comment",
23
+ "list_comment_replies",
24
+ ] as const;
25
+
26
+ export const FeishuDocSchema = Type.Object({
27
+ action: stringEnum(DOC_ACTION_VALUES, { description: "Document action" }),
28
+ doc_token: Type.Optional(
29
+ Type.String({
7
30
  description:
8
31
  "Document token (extract from URL /docx/XXX or /docs/XXX). Supports both new (docx) and legacy (doc) formats.",
9
32
  }),
10
- }),
11
- Type.Object({
12
- action: Type.Literal("write"),
13
- doc_token: Type.String({ description: "Document token" }),
14
- content: Type.String({
15
- description: "Markdown content to write (replaces entire document content)",
16
- }),
17
- }),
18
- Type.Object({
19
- action: Type.Literal("append"),
20
- doc_token: Type.String({ description: "Document token" }),
21
- content: Type.String({ description: "Markdown content to append to end of document" }),
22
- }),
23
- Type.Object({
24
- action: Type.Literal("create"),
25
- title: Type.String({ description: "Document title" }),
26
- folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
27
- }),
28
- Type.Object({
29
- action: Type.Literal("create_and_write"),
30
- title: Type.String({ description: "Document title" }),
31
- content: Type.String({
32
- description: "Markdown content to write immediately after document creation",
33
+ ),
34
+ content: Type.Optional(
35
+ Type.String({
36
+ description: "Markdown content for write/append/comment/update operations",
33
37
  }),
34
- folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
35
- }),
36
- Type.Object({
37
- action: Type.Literal("list_blocks"),
38
- doc_token: Type.String({ description: "Document token" }),
39
- }),
40
- Type.Object({
41
- action: Type.Literal("get_block"),
42
- doc_token: Type.String({ description: "Document token" }),
43
- block_id: Type.String({ description: "Block ID (from list_blocks)" }),
44
- }),
45
- Type.Object({
46
- action: Type.Literal("update_block"),
47
- doc_token: Type.String({ description: "Document token" }),
48
- block_id: Type.String({ description: "Block ID (from list_blocks)" }),
49
- content: Type.String({ description: "New text content" }),
50
- }),
51
- Type.Object({
52
- action: Type.Literal("delete_block"),
53
- doc_token: Type.String({ description: "Document token" }),
54
- block_id: Type.String({ description: "Block ID" }),
55
- }),
56
- Type.Object({
57
- action: Type.Literal("list_comments"),
58
- doc_token: Type.String({ description: "Document token" }),
59
- page_token: Type.Optional(Type.String({ description: "Page token for pagination" })),
60
- page_size: Type.Optional(
61
- Type.Integer({ minimum: 1, description: "Page size, default 50 (positive integer)" }),
62
- ),
63
- }),
64
- Type.Object({
65
- action: Type.Literal("create_comment"),
66
- doc_token: Type.String({ description: "Document token" }),
67
- content: Type.String({ description: "Comment content" }),
68
- }),
69
- Type.Object({
70
- action: Type.Literal("get_comment"),
71
- doc_token: Type.String({ description: "Document token" }),
72
- comment_id: Type.String({ description: "Comment ID" }),
73
- }),
74
- Type.Object({
75
- action: Type.Literal("list_comment_replies"),
76
- doc_token: Type.String({ description: "Document token" }),
77
- comment_id: Type.String({ description: "Comment ID" }),
78
- page_token: Type.Optional(Type.String({ description: "Page token for pagination" })),
79
- page_size: Type.Optional(
80
- Type.Integer({ minimum: 1, description: "Page size, default 50 (positive integer)" }),
81
- ),
82
- }),
83
- ]);
38
+ ),
39
+ title: Type.Optional(Type.String({ description: "Document title (for create/create_and_write)" })),
40
+ folder_token: Type.Optional(Type.String({ description: "Target folder token (optional)" })),
41
+ block_id: Type.Optional(Type.String({ description: "Block ID (from list_blocks)" })),
42
+ comment_id: Type.Optional(Type.String({ description: "Comment ID" })),
43
+ page_token: Type.Optional(Type.String({ description: "Page token for pagination" })),
44
+ page_size: Type.Optional(
45
+ Type.Integer({ minimum: 1, description: "Page size, default 50 (positive integer)" }),
46
+ ),
47
+ });
84
48
 
85
49
  export type FeishuDocParams = Static<typeof FeishuDocSchema>;
@@ -1,6 +1,7 @@
1
1
  import type * as Lark from "@larksuiteoapi/node-sdk";
2
2
  import { Readable } from "stream";
3
3
  import { getFeishuRuntime } from "./runtime.js";
4
+ import { getCurrentFeishuToolContext } from "./tools-common/tool-context.js";
4
5
 
5
6
  const BLOCK_TYPE_NAMES: Record<number, string> = {
6
7
  1: "Page",
@@ -572,6 +573,24 @@ export async function createDoc(
572
573
  });
573
574
  if (res.code !== 0) throw new Error(res.msg);
574
575
  const doc = res.data?.document;
576
+
577
+ // Auto-share with the requesting user so they can edit the document.
578
+ const senderOpenId = getCurrentFeishuToolContext()?.senderOpenId;
579
+ if (doc?.document_id && senderOpenId) {
580
+ try {
581
+ const permRes = await client.drive.permissionMember.create({
582
+ path: { token: doc.document_id },
583
+ params: { type: "docx", need_notification: false },
584
+ data: { member_type: "openid", member_id: senderOpenId, perm: "edit" },
585
+ });
586
+ if (permRes.code !== 0) {
587
+ console.warn(`[feishu_doc] Failed to auto-share doc ${doc.document_id}: ${permRes.msg}`);
588
+ }
589
+ } catch (err) {
590
+ console.warn(`[feishu_doc] Failed to auto-share doc ${doc.document_id}:`, err);
591
+ }
592
+ }
593
+
575
594
  return {
576
595
  document_id: doc?.document_id,
577
596
  title: doc?.title,
@@ -5,6 +5,37 @@ import type { FeishuDriveParams } from "./schemas.js";
5
5
  type DriveMoveType = "doc" | "docx" | "sheet" | "bitable" | "folder" | "file" | "mindnote" | "slides";
6
6
  type DriveDeleteType = DriveMoveType | "shortcut";
7
7
 
8
+ function requireString(value: unknown, field: string): string {
9
+ if (typeof value !== "string" || value.trim().length === 0) {
10
+ throw new Error(`${field} is required`);
11
+ }
12
+ return value;
13
+ }
14
+
15
+ async function getFileType(client: DriveClient, fileToken: string): Promise<string> {
16
+ let pageToken: string | undefined;
17
+
18
+ do {
19
+ const listRes = await runDriveApiCall("drive.file.list", () =>
20
+ client.drive.file.list({
21
+ params: pageToken ? { page_token: pageToken } : {},
22
+ }),
23
+ );
24
+
25
+ const file = listRes.data?.files?.find((f: any) => f.token === fileToken);
26
+ if (file) {
27
+ if (!file.type) {
28
+ throw new Error(`File found but no type for ${fileToken}. Please provide the 'type' parameter explicitly.`);
29
+ }
30
+ return file.type;
31
+ }
32
+
33
+ pageToken = listRes.data?.next_page_token;
34
+ } while (pageToken);
35
+
36
+ throw new Error(`File not found: ${fileToken}. Please provide the 'type' parameter explicitly.`);
37
+ }
38
+
8
39
  async function getRootFolderToken(client: DriveClient): Promise<string> {
9
40
  // Use generic HTTP client to call the root folder meta API
10
41
  // as it's not directly exposed in the SDK.
@@ -46,27 +77,32 @@ async function listFolder(client: DriveClient, folderToken?: string) {
46
77
  }
47
78
 
48
79
  async function getFileInfo(client: DriveClient, fileToken: string, folderToken?: string) {
49
- // Use list with folder_token to find file info.
50
- const res = await runDriveApiCall("drive.file.list", () =>
51
- client.drive.file.list({
52
- params: folderToken ? { folder_token: folderToken } : {},
53
- }),
54
- );
80
+ let pageToken: string | undefined;
81
+
82
+ do {
83
+ const res = await runDriveApiCall("drive.file.list", () =>
84
+ client.drive.file.list({
85
+ params: folderToken ? { folder_token: folderToken, page_token: pageToken } : { page_token: pageToken },
86
+ }),
87
+ );
88
+
89
+ const file = res.data?.files?.find((f: any) => f.token === fileToken);
90
+ if (file) {
91
+ return {
92
+ token: file.token,
93
+ name: file.name,
94
+ type: file.type,
95
+ url: file.url,
96
+ created_time: file.created_time,
97
+ modified_time: file.modified_time,
98
+ owner_id: file.owner_id,
99
+ };
100
+ }
55
101
 
56
- const file = res.data?.files?.find((f) => f.token === fileToken);
57
- if (!file) {
58
- throw new Error(`File not found: ${fileToken}`);
59
- }
102
+ pageToken = res.data?.next_page_token;
103
+ } while (pageToken);
60
104
 
61
- return {
62
- token: file.token,
63
- name: file.name,
64
- type: file.type,
65
- url: file.url,
66
- created_time: file.created_time,
67
- modified_time: file.modified_time,
68
- owner_id: file.owner_id,
69
- };
105
+ throw new Error(`File not found: ${fileToken}`);
70
106
  }
71
107
 
72
108
  async function createFolder(client: DriveClient, name: string, folderToken?: string) {
@@ -119,12 +155,18 @@ async function moveFile(
119
155
  };
120
156
  }
121
157
 
122
- async function deleteFile(client: DriveClient, fileToken: string, type: string) {
158
+ async function deleteFile(client: DriveClient, fileToken: string, type?: string) {
159
+ let effectiveType = type;
160
+
161
+ if (!effectiveType) {
162
+ effectiveType = await getFileType(client, fileToken);
163
+ }
164
+
123
165
  const res = await runDriveApiCall("drive.file.delete", () =>
124
166
  client.drive.file.delete({
125
167
  path: { file_token: fileToken },
126
168
  params: {
127
- type: type as DriveDeleteType,
169
+ type: effectiveType as DriveDeleteType,
128
170
  },
129
171
  }),
130
172
  );
@@ -132,6 +174,7 @@ async function deleteFile(client: DriveClient, fileToken: string, type: string)
132
174
  return {
133
175
  success: true,
134
176
  task_id: res.data?.task_id,
177
+ type_used: effectiveType,
135
178
  };
136
179
  }
137
180
 
@@ -160,18 +203,23 @@ export async function runDriveAction(
160
203
  case "list":
161
204
  return listFolder(client, params.folder_token);
162
205
  case "info":
163
- return getFileInfo(client, params.file_token);
206
+ return getFileInfo(client, requireString(params.file_token, "file_token"));
164
207
  case "create_folder":
165
- return createFolder(client, params.name, params.folder_token);
208
+ return createFolder(client, requireString(params.name, "name"), params.folder_token);
166
209
  case "move":
167
- return moveFile(client, params.file_token, params.type, params.folder_token);
210
+ return moveFile(
211
+ client,
212
+ requireString(params.file_token, "file_token"),
213
+ requireString(params.type, "type"),
214
+ requireString(params.folder_token, "folder_token"),
215
+ );
168
216
  case "delete":
169
- return deleteFile(client, params.file_token, params.type);
217
+ return deleteFile(client, requireString(params.file_token, "file_token"), params.type);
170
218
  case "import_document":
171
219
  return importDocument(
172
220
  client,
173
- params.title,
174
- params.content,
221
+ requireString(params.title, "title"),
222
+ requireString(params.content, "content"),
175
223
  mediaMaxBytes,
176
224
  params.folder_token,
177
225
  params.doc_type || "docx",
@@ -1,67 +1,54 @@
1
1
  import { Type, type Static } from "@sinclair/typebox";
2
2
 
3
- const FileType = Type.Union([
4
- Type.Literal("doc"),
5
- Type.Literal("docx"),
6
- Type.Literal("sheet"),
7
- Type.Literal("bitable"),
8
- Type.Literal("folder"),
9
- Type.Literal("file"),
10
- Type.Literal("mindnote"),
11
- Type.Literal("shortcut"),
12
- ]);
3
+ function stringEnum<T extends readonly string[]>(
4
+ values: T,
5
+ options: { description?: string; default?: T[number] } = {},
6
+ ) {
7
+ return Type.Unsafe<T[number]>({ type: "string", enum: [...values], ...options });
8
+ }
13
9
 
14
- const DocType = Type.Union([
15
- Type.Literal("docx", { description: "New generation document (default)" }),
16
- Type.Literal("doc", { description: "Legacy document" }),
17
- ]);
10
+ const FILE_TYPE_VALUES = [
11
+ "doc",
12
+ "docx",
13
+ "sheet",
14
+ "bitable",
15
+ "folder",
16
+ "file",
17
+ "mindnote",
18
+ "shortcut",
19
+ ] as const;
18
20
 
19
- export const FeishuDriveSchema = Type.Union([
20
- Type.Object({
21
- action: Type.Literal("list"),
22
- folder_token: Type.Optional(
23
- Type.String({ description: "Folder token (optional, omit for root directory)" }),
24
- ),
25
- }),
26
- Type.Object({
27
- action: Type.Literal("info"),
28
- file_token: Type.String({ description: "File or folder token" }),
29
- type: FileType,
30
- }),
31
- Type.Object({
32
- action: Type.Literal("create_folder"),
33
- name: Type.String({ description: "Folder name" }),
34
- folder_token: Type.Optional(
35
- Type.String({ description: "Parent folder token (optional, omit for root)" }),
36
- ),
37
- }),
38
- Type.Object({
39
- action: Type.Literal("move"),
40
- file_token: Type.String({ description: "File token to move" }),
41
- type: FileType,
42
- folder_token: Type.String({ description: "Target folder token" }),
43
- }),
44
- Type.Object({
45
- action: Type.Literal("delete"),
46
- file_token: Type.String({ description: "File token to delete" }),
47
- type: FileType,
48
- }),
49
- Type.Object({
50
- action: Type.Literal("import_document"),
51
- title: Type.String({
52
- description: "Document title",
53
- }),
54
- content: Type.String({
21
+ const DOC_TYPE_VALUES = ["docx", "doc"] as const;
22
+
23
+ const DRIVE_ACTION_VALUES = [
24
+ "list",
25
+ "info",
26
+ "create_folder",
27
+ "move",
28
+ "delete",
29
+ "import_document",
30
+ ] as const;
31
+
32
+ export const FeishuDriveSchema = Type.Object({
33
+ action: stringEnum(DRIVE_ACTION_VALUES, { description: "Drive action" }),
34
+ folder_token: Type.Optional(
35
+ Type.String({ description: "Folder token (optional, omit for root directory)" }),
36
+ ),
37
+ file_token: Type.Optional(Type.String({ description: "File or folder token" })),
38
+ type: Type.Optional(stringEnum(FILE_TYPE_VALUES, { description: "File type" })),
39
+ name: Type.Optional(Type.String({ description: "Folder name (for create_folder)" })),
40
+ title: Type.Optional(Type.String({ description: "Document title (for import_document)" })),
41
+ content: Type.Optional(
42
+ Type.String({
55
43
  description:
56
44
  "Markdown content to import. Supports full Markdown syntax including tables, lists, code blocks, etc.",
57
45
  }),
58
- folder_token: Type.Optional(
59
- Type.String({
60
- description: "Target folder token (optional, defaults to root). Use 'list' to find folder tokens.",
61
- }),
62
- ),
63
- doc_type: Type.Optional(DocType),
64
- }),
65
- ]);
46
+ ),
47
+ doc_type: Type.Optional(
48
+ stringEnum(DOC_TYPE_VALUES, {
49
+ description: "Document type for import_document (docx default, doc legacy)",
50
+ }),
51
+ ),
52
+ });
66
53
 
67
54
  export type FeishuDriveParams = Static<typeof FeishuDriveSchema>;