Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/feishu/tools/__tests__/doc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,39 @@ describe('feishu_doc tool', () => {
expect(result.isError).toBeUndefined();
});

it('should create a document with content when provided', async () => {
mockDocxDocumentCreate.mockResolvedValue({
code: 0,
data: { document: { document_id: 'NEW_TOKEN2', title: '带内容文档' } },
});
mockDocxDocumentBlockChildrenCreate.mockResolvedValue({ code: 0 });

const result = await capturedHandler({
action: 'create', title: '带内容文档', content: '# 标题\n\n正文内容',
});
expect(result.content[0].text).toContain('NEW_TOKEN2');
expect(result.isError).toBeUndefined();
// Should have called blockChildren.create to write content
expect(mockDocxDocumentBlockChildrenCreate).toHaveBeenCalled();
const call = mockDocxDocumentBlockChildrenCreate.mock.calls[0][0];
expect(call.path.document_id).toBe('NEW_TOKEN2');
});

it('should still succeed if content write fails after create', async () => {
mockDocxDocumentCreate.mockResolvedValue({
code: 0,
data: { document: { document_id: 'NEW_TOKEN3', title: '写入失败' } },
});
mockDocxDocumentBlockChildrenCreate.mockResolvedValue({ code: 99999, msg: 'write failed' });

const result = await capturedHandler({
action: 'create', title: '写入失败', content: '内容',
});
// Document was created, just content write failed — not a hard error
expect(result.content[0].text).toContain('NEW_TOKEN3');
expect(result.isError).toBeUndefined();
});

it('should return error when title is missing', async () => {
const result = await capturedHandler({ action: 'create' });
expect(result.isError).toBe(true);
Expand Down
19 changes: 17 additions & 2 deletions src/feishu/tools/doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function feishuDocTool(chatId?: string) {
'- read: 读取文档纯文本内容',
'- write: 覆盖写入文档 (清空后写入 Markdown,自动转换为飞书富文本格式)',
'- append: 在文档末尾追加内容 (支持 Markdown 格式)',
'- create: 创建新文档',
'- create: 创建新文档 (可同时传 content 写入内容,避免创建空文档)',
'- list_blocks: 列出文档的 block 结构',
'- update_block: 更新指定 block 的文本内容 (仅支持行内 Markdown: 加粗/斜体/删除线/行内代码/链接,不支持标题/列表等块级语法。需要 block_id,通过 list_blocks 获取)',
'- insert_blocks: 在指定位置插入新 block (需要 block_id 作为父 block,index 指定位置)',
Expand All @@ -36,7 +36,7 @@ export function feishuDocTool(chatId?: string) {
{
action: z.enum(['read', 'write', 'append', 'create', 'list_blocks', 'update_block', 'insert_blocks', 'delete_blocks']).describe('操作类型'),
doc_token: z.string().optional().describe('文档 token (read/write/append/list_blocks/update_block/insert_blocks/delete_blocks 时必填)'),
content: z.string().optional().describe('写入/追加的 Markdown 内容 (write/append/update_block/insert_blocks 时必填,自动转换为飞书富文本)'),
content: z.string().optional().describe('Markdown 内容 (write/append/update_block/insert_blocks 时必填;create 时可选,传入则创建后自动写入,避免空文档)'),
title: z.string().optional().describe('新文档标题 (create 时必填)'),
folder_token: z.string().optional().describe('目标文件夹 token (create 时可选)'),
block_id: z.string().optional().describe('目标 block ID (update_block/insert_blocks/delete_blocks 时必填,通过 list_blocks 获取)'),
Expand Down Expand Up @@ -140,6 +140,21 @@ export function feishuDocTool(chatId?: string) {
if (doc?.document_id) {
await grantOwnerPermission(doc.document_id, 'docx');
await grantChatMembersPermission(doc.document_id, 'docx', chatId);

// 如果提供了 content,创建后自动写入,避免空文档
if (args.content) {
const blocks = markdownToBlocks(args.content);
const batches = batchBlocks(blocks);
for (const batch of batches) {
const writeResp = await client.docx.documentBlockChildren.create({
path: { document_id: doc.document_id, block_id: doc.document_id },
data: { children: batch },
});
if (writeResp.code !== 0) {
logger.warn({ code: writeResp.code, msg: writeResp.msg }, 'create: 写入内容失败,文档已创建但为空');
}
}
}
}
return {
content: [{
Expand Down
Loading