/**
* 文档相关工具处理器
*/
import { BaseToolHandler } from './base.js';
import type { ExecutionContext, JSONSchema } from '../core/types.js';
import type { DocTreeNodeResponse } from '../../src/types/index.js';
/**
* 获取文档内容
*/
export class GetDocumentContentHandler extends BaseToolHandler<{ document_id: string }, string> {
readonly name = 'get_document_content';
readonly description = 'Get the markdown content of a document';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
document_id: {
type: 'string',
description: 'The document ID',
},
},
required: ['document_id'],
};
async execute(args: any, context: ExecutionContext): Promise<string> {
return await context.siyuan.getFileContent(args.document_id);
}
}
/**
* 创建文档
*/
export class CreateDocumentHandler extends BaseToolHandler<
{ notebook_id: string; path: string; content: string },
string
> {
readonly name = 'create_document';
readonly description = 'Create a new document in SiYuan Note';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
notebook_id: {
type: 'string',
description: 'The notebook ID',
},
path: {
type: 'string',
description: 'Document path (e.g., /folder/document)',
},
content: {
type: 'string',
description: 'Markdown content',
},
},
required: ['notebook_id', 'path', 'content'],
};
async execute(args: any, context: ExecutionContext): Promise<string> {
return await context.siyuan.createFile(args.notebook_id, args.path, args.content);
}
}
/**
* 追加到文档
*/
export class AppendToDocumentHandler extends BaseToolHandler<
{ document_id: string; content: string },
string
> {
readonly name = 'append_to_document';
readonly description = 'Append content to an existing document';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
document_id: {
type: 'string',
description: 'The document ID',
},
content: {
type: 'string',
description: 'Markdown content to append',
},
},
required: ['document_id', 'content'],
};
async execute(args: any, context: ExecutionContext): Promise<string> {
return await context.siyuan.appendToFile(args.document_id, args.content);
}
}
/**
* 更新文档
*/
export class UpdateDocumentHandler extends BaseToolHandler<
{ document_id: string; content: string },
{ success: boolean; document_id: string }
> {
readonly name = 'update_document';
readonly description = 'Update (overwrite) the content of a document';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
document_id: {
type: 'string',
description: 'The document ID to update',
},
content: {
type: 'string',
description: 'New markdown content',
},
},
required: ['document_id', 'content'],
};
async execute(args: any, context: ExecutionContext): Promise<{ success: boolean; document_id: string }> {
await context.siyuan.overwriteFile(args.document_id, args.content);
return { success: true, document_id: args.document_id };
}
}
/**
* 追加到今日笔记
*/
export class AppendToDailyNoteHandler extends BaseToolHandler<
{ notebook_id: string; content: string },
string
> {
readonly name = 'append_to_daily_note';
readonly description = "Append content to today's daily note (creates if not exists)";
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
notebook_id: {
type: 'string',
description: 'The notebook ID',
},
content: {
type: 'string',
description: 'Markdown content to append',
},
},
required: ['notebook_id', 'content'],
};
async execute(args: any, context: ExecutionContext): Promise<string> {
return await context.siyuan.appendToDailyNote(args.notebook_id, args.content);
}
}
/**
* 移动文档(通过ID)
*/
export class MoveDocumentsHandler extends BaseToolHandler<
{ from_ids: string | string[]; to_parent_id?: string; to_notebook_root?: string },
{ success: boolean; moved_count: number; from_ids: string[]; to_parent_id?: string; to_notebook_root?: string }
> {
readonly name = 'move_documents';
readonly description = 'Move one or more documents to a new location. Provide EXACTLY ONE destination: either to_parent_id (to nest under a document) OR to_notebook_root (to move to notebook top level).';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
from_ids: {
type: 'array',
items: { type: 'string' },
description: 'Array of document IDs to move. For a single document, use an array with one element: ["doc-id"]',
},
to_parent_id: {
type: 'string',
description: 'OPTION 1: Parent document ID. Documents will be moved under this document as children. Cannot be used together with to_notebook_root.',
},
to_notebook_root: {
type: 'string',
description: 'OPTION 2: Notebook ID. Documents will be moved to the root (top level) of this notebook. Cannot be used together with to_parent_id.',
},
},
required: ['from_ids'],
};
async execute(args: any, context: ExecutionContext): Promise<{ success: boolean; moved_count: number; from_ids: string[]; to_parent_id?: string; to_notebook_root?: string }> {
// 处理 from_ids,支持单个ID或数组
let fromIds: string[];
if (Array.isArray(args.from_ids)) {
fromIds = args.from_ids;
} else if (typeof args.from_ids === 'string') {
// 如果是JSON数组字符串,解析它
if (args.from_ids.startsWith('[')) {
try {
fromIds = JSON.parse(args.from_ids);
} catch {
fromIds = [args.from_ids];
}
} else {
fromIds = [args.from_ids];
}
} else {
throw new Error('from_ids must be a string or array of strings');
}
// 验证参数:必须提供其中一个,且只能提供一个
const hasParentId = !!args.to_parent_id;
const hasNotebookRoot = !!args.to_notebook_root;
if (!hasParentId && !hasNotebookRoot) {
throw new Error('Must provide exactly one of: to_parent_id (for nested placement) or to_notebook_root (for root placement)');
}
if (hasParentId && hasNotebookRoot) {
throw new Error('Cannot provide both to_parent_id and to_notebook_root - choose only one target location');
}
// 情况1: 移动到父文档下(嵌套)
if (hasParentId) {
await context.siyuan.document.moveDocumentsByIds(fromIds, args.to_parent_id);
}
// 情况2: 移动到笔记本根目录(顶级)
else {
await context.siyuan.document.moveDocumentsToNotebookRoot(fromIds, args.to_notebook_root);
}
return {
success: true,
moved_count: fromIds.length,
from_ids: fromIds,
to_parent_id: args.to_parent_id,
to_notebook_root: args.to_notebook_root
};
}
}
/**
* 获取文档树
*/
export class GetDocumentTreeHandler extends BaseToolHandler<
{ id: string; depth?: number },
DocTreeNodeResponse[]
> {
readonly name = 'get_document_tree';
readonly description = 'Get document tree structure with specified depth. Provide a document ID or notebook ID to get its children up to the specified depth.';
readonly inputSchema: JSONSchema = {
type: 'object',
properties: {
id: {
type: 'string',
description: 'Document ID or Notebook ID to start from',
},
depth: {
type: 'number',
description: 'Maximum depth to traverse (1 = direct children only, 2 = children and grandchildren, etc.). Default is 1.',
default: 1,
},
},
required: ['id'],
};
async execute(args: any, context: ExecutionContext): Promise<DocTreeNodeResponse[]> {
const depth = args.depth || 1;
return await context.siyuan.document.getDocumentTree(args.id, depth);
}
}