@max1874/feishu 0.2.6 → 0.2.8
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/package.json +1 -1
- package/src/docx.ts +283 -142
package/package.json
CHANGED
package/src/docx.ts
CHANGED
|
@@ -64,101 +64,115 @@ const BLOCK_TYPE_NAMES: Record<number, string> = {
|
|
|
64
64
|
32: "TableCell",
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
// Block types that
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const cleaned = blocks
|
|
77
|
-
.filter((block) => {
|
|
78
|
-
if (UNSUPPORTED_CREATE_TYPES.has(block.block_type)) {
|
|
79
|
-
const typeName = BLOCK_TYPE_NAMES[block.block_type] || `type_${block.block_type}`;
|
|
80
|
-
skipped.push(typeName);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
})
|
|
85
|
-
.map((block) => {
|
|
86
|
-
// Remove any read-only fields that might slip through
|
|
87
|
-
if (block.block_type === 31 && block.table?.merge_info) {
|
|
88
|
-
const { merge_info, ...tableRest } = block.table;
|
|
89
|
-
return { ...block, table: tableRest };
|
|
90
|
-
}
|
|
91
|
-
return block;
|
|
92
|
-
});
|
|
93
|
-
return { cleaned, skipped };
|
|
67
|
+
// Block types that need special handling (not via standard documentBlockChildren.create)
|
|
68
|
+
const TABLE_BLOCK_TYPE = 31;
|
|
69
|
+
const TABLE_CELL_BLOCK_TYPE = 32;
|
|
70
|
+
|
|
71
|
+
/** Extracted table data from convert API result */
|
|
72
|
+
interface TableData {
|
|
73
|
+
rowSize: number;
|
|
74
|
+
colSize: number;
|
|
75
|
+
cells: string[][]; // 2D array of cell text content
|
|
94
76
|
}
|
|
95
77
|
|
|
96
|
-
|
|
78
|
+
/** Extract table data from converted blocks (flat list with ID references) */
|
|
79
|
+
function extractTableFromBlocks(blocks: any[]): { tables: TableData[]; otherBlocks: any[] } {
|
|
80
|
+
const tables: TableData[] = [];
|
|
81
|
+
const otherBlocks: any[] = [];
|
|
97
82
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const result: string[] = [];
|
|
106
|
-
let tableLines: string[] = [];
|
|
107
|
-
let inTable = false;
|
|
108
|
-
|
|
109
|
-
const isTableLine = (line: string): boolean => {
|
|
110
|
-
const trimmed = line.trim();
|
|
111
|
-
// Table line starts with | or is a separator line like |---|---|
|
|
112
|
-
return trimmed.startsWith("|") && trimmed.endsWith("|");
|
|
113
|
-
};
|
|
83
|
+
// Build a map of block_id -> block for quick lookup
|
|
84
|
+
const blockMap = new Map<string, any>();
|
|
85
|
+
for (const block of blocks) {
|
|
86
|
+
if (block.block_id) {
|
|
87
|
+
blockMap.set(block.block_id, block);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
114
90
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
91
|
+
// Track which blocks are part of tables (to exclude from otherBlocks)
|
|
92
|
+
const tableRelatedIds = new Set<string>();
|
|
93
|
+
|
|
94
|
+
for (const block of blocks) {
|
|
95
|
+
if (block.block_type === TABLE_BLOCK_TYPE) {
|
|
96
|
+
tableRelatedIds.add(block.block_id);
|
|
97
|
+
|
|
98
|
+
const tableInfo = block.table;
|
|
99
|
+
if (tableInfo) {
|
|
100
|
+
const rowSize = tableInfo.property?.row_size ?? 0;
|
|
101
|
+
const colSize = tableInfo.property?.column_size ?? 0;
|
|
102
|
+
|
|
103
|
+
// Initialize cells array
|
|
104
|
+
const cells: string[][] = Array.from({ length: rowSize }, () =>
|
|
105
|
+
Array.from({ length: colSize }, () => ""),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// table.cells is a flat array of cell block IDs in row-major order
|
|
109
|
+
const cellIds: string[] = tableInfo.cells ?? [];
|
|
110
|
+
|
|
111
|
+
let cellIndex = 0;
|
|
112
|
+
for (let row = 0; row < rowSize; row++) {
|
|
113
|
+
for (let col = 0; col < colSize; col++) {
|
|
114
|
+
if (cellIndex >= cellIds.length) break;
|
|
115
|
+
|
|
116
|
+
const cellId = cellIds[cellIndex];
|
|
117
|
+
tableRelatedIds.add(cellId);
|
|
118
|
+
|
|
119
|
+
// Find the TableCell block
|
|
120
|
+
const cellBlock = blockMap.get(cellId);
|
|
121
|
+
if (cellBlock?.block_type === TABLE_CELL_BLOCK_TYPE) {
|
|
122
|
+
// Get text content from cell's children (Text blocks)
|
|
123
|
+
const childIds: string[] = cellBlock.children ?? [];
|
|
124
|
+
const textParts: string[] = [];
|
|
125
|
+
|
|
126
|
+
for (const childId of childIds) {
|
|
127
|
+
tableRelatedIds.add(childId);
|
|
128
|
+
const textBlock = blockMap.get(childId);
|
|
129
|
+
if (textBlock?.block_type === 2 && textBlock.text?.elements) {
|
|
130
|
+
const text = textBlock.text.elements
|
|
131
|
+
.filter((e: any) => e.text_run)
|
|
132
|
+
.map((e: any) => e.text_run.content)
|
|
133
|
+
.join("");
|
|
134
|
+
textParts.push(text);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
cells[row][col] = textParts.join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
cellIndex++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
120
144
|
|
|
121
|
-
|
|
122
|
-
if (tableLines.length >= 2) {
|
|
123
|
-
// Check if we have a valid table (at least header + separator)
|
|
124
|
-
const hasSeparator = tableLines.some(isSeparatorLine);
|
|
125
|
-
if (hasSeparator) {
|
|
126
|
-
// Wrap table in code block to preserve formatting
|
|
127
|
-
result.push("```");
|
|
128
|
-
result.push(...tableLines);
|
|
129
|
-
result.push("```");
|
|
130
|
-
} else {
|
|
131
|
-
// Not a valid table, keep as-is
|
|
132
|
-
result.push(...tableLines);
|
|
145
|
+
tables.push({ rowSize, colSize, cells });
|
|
133
146
|
}
|
|
134
|
-
} else {
|
|
135
|
-
// Single line with |, keep as-is
|
|
136
|
-
result.push(...tableLines);
|
|
137
147
|
}
|
|
138
|
-
|
|
139
|
-
};
|
|
148
|
+
}
|
|
140
149
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else {
|
|
146
|
-
if (inTable) {
|
|
147
|
-
flushTable();
|
|
148
|
-
inTable = false;
|
|
149
|
-
}
|
|
150
|
-
result.push(line);
|
|
150
|
+
// Collect non-table blocks
|
|
151
|
+
for (const block of blocks) {
|
|
152
|
+
if (!tableRelatedIds.has(block.block_id)) {
|
|
153
|
+
otherBlocks.push(block);
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
flushTable();
|
|
157
|
-
}
|
|
157
|
+
return { tables, otherBlocks };
|
|
158
|
+
}
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
/** Clean blocks for insertion (remove read-only fields) */
|
|
161
|
+
function cleanBlocksForInsert(blocks: any[]): { cleaned: any[]; skipped: string[] } {
|
|
162
|
+
const skipped: string[] = [];
|
|
163
|
+
const cleaned = blocks.map((block) => {
|
|
164
|
+
// Remove any read-only fields that might slip through
|
|
165
|
+
if (block.block_type === TABLE_BLOCK_TYPE && block.table?.merge_info) {
|
|
166
|
+
const { merge_info, ...tableRest } = block.table;
|
|
167
|
+
return { ...block, table: tableRest };
|
|
168
|
+
}
|
|
169
|
+
return block;
|
|
170
|
+
});
|
|
171
|
+
return { cleaned, skipped };
|
|
160
172
|
}
|
|
161
173
|
|
|
174
|
+
// ============ Core Functions ============
|
|
175
|
+
|
|
162
176
|
/** Convert markdown to Feishu blocks using the Convert API */
|
|
163
177
|
async function convertMarkdown(client: Lark.Client, markdown: string) {
|
|
164
178
|
const res = await client.docx.document.convert({
|
|
@@ -185,19 +199,12 @@ async function insertBlocks(
|
|
|
185
199
|
return { children: [], skipped };
|
|
186
200
|
}
|
|
187
201
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return { children: res.data?.children ?? [], skipped };
|
|
195
|
-
} catch (err) {
|
|
196
|
-
// Log block types for debugging
|
|
197
|
-
const blockTypes = cleaned.map((b) => BLOCK_TYPE_NAMES[b.block_type] || `type_${b.block_type}`);
|
|
198
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
199
|
-
throw new Error(`insertBlocks failed: ${errMsg}. Block types: [${blockTypes.join(", ")}]. Count: ${cleaned.length}`);
|
|
200
|
-
}
|
|
202
|
+
const res = await client.docx.documentBlockChildren.create({
|
|
203
|
+
path: { document_id: docToken, block_id: blockId },
|
|
204
|
+
data: { children: cleaned },
|
|
205
|
+
});
|
|
206
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
207
|
+
return { children: res.data?.children ?? [], skipped };
|
|
201
208
|
}
|
|
202
209
|
|
|
203
210
|
/** Delete all child blocks from a parent */
|
|
@@ -256,6 +263,140 @@ async function downloadImage(url: string): Promise<Buffer> {
|
|
|
256
263
|
return Buffer.from(await response.arrayBuffer());
|
|
257
264
|
}
|
|
258
265
|
|
|
266
|
+
/** Create an empty table and return its block ID */
|
|
267
|
+
async function createEmptyTable(
|
|
268
|
+
client: Lark.Client,
|
|
269
|
+
docToken: string,
|
|
270
|
+
parentBlockId: string,
|
|
271
|
+
rowSize: number,
|
|
272
|
+
colSize: number,
|
|
273
|
+
): Promise<string | null> {
|
|
274
|
+
const tableBlock = {
|
|
275
|
+
block_type: TABLE_BLOCK_TYPE,
|
|
276
|
+
table: {
|
|
277
|
+
property: {
|
|
278
|
+
row_size: rowSize,
|
|
279
|
+
column_size: colSize,
|
|
280
|
+
header_row: true,
|
|
281
|
+
},
|
|
282
|
+
cells: [], // Empty cells, will be filled later
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const res = await client.docx.documentBlockChildren.create({
|
|
287
|
+
path: { document_id: docToken, block_id: parentBlockId },
|
|
288
|
+
data: { children: [tableBlock] },
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (res.code !== 0) {
|
|
292
|
+
console.error(`Failed to create table: ${res.msg}`);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const created = res.data?.children ?? [];
|
|
297
|
+
return created[0]?.block_id ?? null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Get children blocks of a parent block */
|
|
301
|
+
async function getBlockChildren(
|
|
302
|
+
client: Lark.Client,
|
|
303
|
+
docToken: string,
|
|
304
|
+
blockId: string,
|
|
305
|
+
): Promise<any[]> {
|
|
306
|
+
const res = await client.docx.documentBlockChildren.get({
|
|
307
|
+
path: { document_id: docToken, block_id: blockId },
|
|
308
|
+
});
|
|
309
|
+
if (res.code !== 0) {
|
|
310
|
+
throw new Error(`Failed to get block children: ${res.msg}`);
|
|
311
|
+
}
|
|
312
|
+
return res.data?.items ?? [];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Update a text block's content */
|
|
316
|
+
async function updateTextBlock(
|
|
317
|
+
client: Lark.Client,
|
|
318
|
+
docToken: string,
|
|
319
|
+
blockId: string,
|
|
320
|
+
content: string,
|
|
321
|
+
): Promise<void> {
|
|
322
|
+
const res = await client.docx.documentBlock.patch({
|
|
323
|
+
path: { document_id: docToken, block_id: blockId },
|
|
324
|
+
data: {
|
|
325
|
+
update_text_elements: {
|
|
326
|
+
elements: [{ text_run: { content } }],
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
if (res.code !== 0) {
|
|
331
|
+
console.error(`Failed to update text block ${blockId}: ${res.msg}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Fill table cells with content */
|
|
336
|
+
async function fillTableContent(
|
|
337
|
+
client: Lark.Client,
|
|
338
|
+
docToken: string,
|
|
339
|
+
tableBlockId: string,
|
|
340
|
+
cells: string[][],
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
// Get table's cell blocks (they are direct children of the table)
|
|
343
|
+
const cellBlocks = await getBlockChildren(client, docToken, tableBlockId);
|
|
344
|
+
|
|
345
|
+
// Cells are in row-major order
|
|
346
|
+
const colSize = cells[0]?.length ?? 0;
|
|
347
|
+
let cellIndex = 0;
|
|
348
|
+
|
|
349
|
+
for (let row = 0; row < cells.length; row++) {
|
|
350
|
+
for (let col = 0; col < colSize; col++) {
|
|
351
|
+
if (cellIndex >= cellBlocks.length) break;
|
|
352
|
+
|
|
353
|
+
const cellBlock = cellBlocks[cellIndex];
|
|
354
|
+
const cellBlockId = cellBlock?.block_id;
|
|
355
|
+
const cellText = cells[row]?.[col] ?? "";
|
|
356
|
+
|
|
357
|
+
if (cellBlockId && cellText) {
|
|
358
|
+
// Each cell block contains text blocks as children
|
|
359
|
+
const cellChildren = await getBlockChildren(client, docToken, cellBlockId);
|
|
360
|
+
if (cellChildren.length > 0) {
|
|
361
|
+
// Update the first text block in the cell
|
|
362
|
+
const textBlockId = cellChildren[0]?.block_id;
|
|
363
|
+
if (textBlockId) {
|
|
364
|
+
await updateTextBlock(client, docToken, textBlockId, cellText);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
cellIndex++;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Create and fill a table */
|
|
375
|
+
async function createAndFillTable(
|
|
376
|
+
client: Lark.Client,
|
|
377
|
+
docToken: string,
|
|
378
|
+
parentBlockId: string,
|
|
379
|
+
tableData: TableData,
|
|
380
|
+
): Promise<boolean> {
|
|
381
|
+
// 1. Create empty table
|
|
382
|
+
const tableBlockId = await createEmptyTable(
|
|
383
|
+
client,
|
|
384
|
+
docToken,
|
|
385
|
+
parentBlockId,
|
|
386
|
+
tableData.rowSize,
|
|
387
|
+
tableData.colSize,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (!tableBlockId) {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 2. Fill table content
|
|
395
|
+
await fillTableContent(client, docToken, tableBlockId, tableData.cells);
|
|
396
|
+
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
259
400
|
/** Process images in markdown: download from URL, upload to Feishu, update blocks */
|
|
260
401
|
async function processImages(
|
|
261
402
|
client: Lark.Client,
|
|
@@ -487,74 +628,74 @@ async function createDoc(
|
|
|
487
628
|
}
|
|
488
629
|
|
|
489
630
|
async function writeDoc(client: Lark.Client, docToken: string, markdown: string) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// 1. Clear existing content
|
|
493
|
-
step = "clear_content";
|
|
494
|
-
const deleted = await clearDocumentContent(client, docToken);
|
|
495
|
-
|
|
496
|
-
// 2. Pre-process markdown (convert tables to code blocks)
|
|
497
|
-
step = "preprocess_markdown";
|
|
498
|
-
const processedMarkdown = convertTablesToCodeBlocks(markdown);
|
|
499
|
-
|
|
500
|
-
// 3. Convert markdown to blocks
|
|
501
|
-
step = "convert_markdown";
|
|
502
|
-
const res = await client.docx.document.convert({
|
|
503
|
-
data: { content_type: "markdown", content: processedMarkdown },
|
|
504
|
-
});
|
|
505
|
-
if (res.code !== 0) throw new Error(`convert failed: ${res.msg}`);
|
|
506
|
-
const blocks = res.data?.blocks ?? [];
|
|
631
|
+
// 1. Clear existing content
|
|
632
|
+
const deleted = await clearDocumentContent(client, docToken);
|
|
507
633
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
634
|
+
// 2. Convert markdown to blocks
|
|
635
|
+
const { blocks } = await convertMarkdown(client, markdown);
|
|
636
|
+
if (blocks.length === 0) {
|
|
637
|
+
return { success: true, blocks_deleted: deleted, blocks_added: 0, images_processed: 0, tables_created: 0 };
|
|
638
|
+
}
|
|
511
639
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const { children: inserted, skipped } = await insertBlocks(client, docToken, blocks);
|
|
640
|
+
// 3. Separate tables from other blocks
|
|
641
|
+
const { tables, otherBlocks } = extractTableFromBlocks(blocks);
|
|
515
642
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
|
643
|
+
// 4. Insert non-table blocks
|
|
644
|
+
const { children: inserted, skipped } = await insertBlocks(client, docToken, otherBlocks);
|
|
519
645
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
...(skipped.length > 0 && {
|
|
526
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}. Tables are not supported via this API.`,
|
|
527
|
-
}),
|
|
528
|
-
};
|
|
529
|
-
} catch (err) {
|
|
530
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
531
|
-
throw new Error(`writeDoc failed at step '${step}': ${errMsg}`);
|
|
646
|
+
// 5. Create and fill tables
|
|
647
|
+
let tablesCreated = 0;
|
|
648
|
+
for (const tableData of tables) {
|
|
649
|
+
const success = await createAndFillTable(client, docToken, docToken, tableData);
|
|
650
|
+
if (success) tablesCreated++;
|
|
532
651
|
}
|
|
652
|
+
|
|
653
|
+
// 6. Process images
|
|
654
|
+
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
success: true,
|
|
658
|
+
blocks_deleted: deleted,
|
|
659
|
+
blocks_added: inserted.length,
|
|
660
|
+
tables_created: tablesCreated,
|
|
661
|
+
images_processed: imagesProcessed,
|
|
662
|
+
...(skipped.length > 0 && {
|
|
663
|
+
warning: `Skipped unsupported block types: ${skipped.join(", ")}.`,
|
|
664
|
+
}),
|
|
665
|
+
};
|
|
533
666
|
}
|
|
534
667
|
|
|
535
668
|
async function appendDoc(client: Lark.Client, docToken: string, markdown: string) {
|
|
536
|
-
// 1.
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
// 2. Convert markdown to blocks
|
|
540
|
-
const { blocks } = await convertMarkdown(client, processedMarkdown);
|
|
669
|
+
// 1. Convert markdown to blocks
|
|
670
|
+
const { blocks } = await convertMarkdown(client, markdown);
|
|
541
671
|
if (blocks.length === 0) {
|
|
542
672
|
throw new Error("Content is empty");
|
|
543
673
|
}
|
|
544
674
|
|
|
545
|
-
//
|
|
546
|
-
const {
|
|
675
|
+
// 2. Separate tables from other blocks
|
|
676
|
+
const { tables, otherBlocks } = extractTableFromBlocks(blocks);
|
|
677
|
+
|
|
678
|
+
// 3. Insert non-table blocks
|
|
679
|
+
const { children: inserted, skipped } = await insertBlocks(client, docToken, otherBlocks);
|
|
680
|
+
|
|
681
|
+
// 4. Create and fill tables
|
|
682
|
+
let tablesCreated = 0;
|
|
683
|
+
for (const tableData of tables) {
|
|
684
|
+
const success = await createAndFillTable(client, docToken, docToken, tableData);
|
|
685
|
+
if (success) tablesCreated++;
|
|
686
|
+
}
|
|
547
687
|
|
|
548
|
-
//
|
|
688
|
+
// 5. Process images
|
|
549
689
|
const imagesProcessed = await processImages(client, docToken, markdown, inserted);
|
|
550
690
|
|
|
551
691
|
return {
|
|
552
692
|
success: true,
|
|
553
693
|
blocks_added: inserted.length,
|
|
694
|
+
tables_created: tablesCreated,
|
|
554
695
|
images_processed: imagesProcessed,
|
|
555
696
|
block_ids: inserted.map((b: any) => b.block_id),
|
|
556
697
|
...(skipped.length > 0 && {
|
|
557
|
-
warning: `Skipped unsupported block types: ${skipped.join(", ")}
|
|
698
|
+
warning: `Skipped unsupported block types: ${skipped.join(", ")}.`,
|
|
558
699
|
}),
|
|
559
700
|
};
|
|
560
701
|
}
|
|
@@ -793,7 +934,7 @@ export function registerFeishuDocTools(api: OpenClawPluginApi) {
|
|
|
793
934
|
name: "feishu_doc_write",
|
|
794
935
|
label: "Feishu Doc Write",
|
|
795
936
|
description:
|
|
796
|
-
"Write markdown content to a Feishu document (replaces all content). Supports headings, lists, code blocks, quotes, links, images, and text styling.
|
|
937
|
+
"Write markdown content to a Feishu document (replaces all content). Supports headings, lists, code blocks, quotes, links, images, tables, and text styling.",
|
|
797
938
|
parameters: WriteDocSchema,
|
|
798
939
|
async execute(_toolCallId, params) {
|
|
799
940
|
const { doc_token, content } = params as { doc_token: string; content: string };
|