From 76afac7ffc8de44b4b6ba6fd27fbcde9e32f4161 Mon Sep 17 00:00:00 2001 From: Andrew Beal Date: Wed, 15 Oct 2025 21:06:23 +0100 Subject: [PATCH] refactor: enhance file operations to support batch reads and deletion - Rename ReadVaultFile to ReadVaultFiles with multi-file support - Add DeleteVaultFile function with confirmation parameter - Update function references and error handling - Return detailed error objects from FileSystemService --- .../AIFunctionDefinitions.ts | 8 ++-- .../Functions/DeleteVaultFile.ts | 31 ++++++++++++++++ .../Functions/ReadVaultFile.ts | 25 ------------- .../Functions/ReadVaultFiles.ts | 31 ++++++++++++++++ .../Functions/WriteVaultFile.ts | 2 +- Enums/AIFunction.ts | 3 +- Services/AIFunctionService.ts | 37 ++++++++++++++----- Services/FileSystemService.ts | 4 +- 8 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 AIClasses/FunctionDefinitions/Functions/DeleteVaultFile.ts delete mode 100644 AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts create mode 100644 AIClasses/FunctionDefinitions/Functions/ReadVaultFiles.ts diff --git a/AIClasses/FunctionDefinitions/AIFunctionDefinitions.ts b/AIClasses/FunctionDefinitions/AIFunctionDefinitions.ts index 17e51f8..8e10eb1 100644 --- a/AIClasses/FunctionDefinitions/AIFunctionDefinitions.ts +++ b/AIClasses/FunctionDefinitions/AIFunctionDefinitions.ts @@ -1,18 +1,20 @@ import type { IAIFunctionDefinition } from "./IAIFunctionDefinition"; import { SearchVaultFiles } from "./Functions/SearchVaultFiles"; -import { ReadVaultFile } from "./Functions/ReadVaultFile"; +import { ReadVaultFiles } from "./Functions/ReadVaultFiles"; import { WriteVaultFile } from "./Functions/WriteVaultFile"; +import { DeleteVaultFile } from "./Functions/DeleteVaultFile"; export class AIFunctionDefinitions { public getQueryActions(destructive: boolean): IAIFunctionDefinition[] { let actions = [ SearchVaultFiles, - ReadVaultFile + ReadVaultFiles ]; if (destructive) { actions = actions.concat([ - WriteVaultFile + WriteVaultFile, + DeleteVaultFile ]); } diff --git a/AIClasses/FunctionDefinitions/Functions/DeleteVaultFile.ts b/AIClasses/FunctionDefinitions/Functions/DeleteVaultFile.ts new file mode 100644 index 0000000..c903bdf --- /dev/null +++ b/AIClasses/FunctionDefinitions/Functions/DeleteVaultFile.ts @@ -0,0 +1,31 @@ +import { AIFunction } from "Enums/AIFunction"; +import type { IAIFunctionDefinition } from "../IAIFunctionDefinition"; + +export const DeleteVaultFile: IAIFunctionDefinition = { + name: AIFunction.DeleteVaultFile, + description: `Permanently removes a file from the vault. Use this when the user explicitly + requests to delete a file, when a file is no longer needed, or when removing outdated + content. IMPORTANT: This action is irreversible - always confirm the exact file path + before deletion. Prefer archiving or moving files to a trash folder over permanent + deletion when uncertain. Only call this after verifying the file exists and confirming + the user's intent to delete.`, + parameters: { + type: "object", + properties: { + file_path: { + type: "string", + description: "The full path to the file within the vault to be deleted (e.g., 'folder/note.md'). Must be an exact match to an existing file." + }, + user_message: { + type: "string", + description: "A short message to be displayed to the user explaining why this file is being deleted (e.g., 'Deleting completed task list as requested' or 'Removing duplicate note file')." + }, + confirm_deletion: { + type: "boolean", + description: "Safety flag that must be explicitly set to true to confirm the deletion is intentional. This prevents accidental deletions.", + default: false + } + }, + required: ["file_path", "user_message", "confirm_deletion"] + } +} \ No newline at end of file diff --git a/AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts b/AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts deleted file mode 100644 index 8793d53..0000000 --- a/AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AIFunction } from "Enums/AIFunction"; -import type { IAIFunctionDefinition } from "../IAIFunctionDefinition"; - -export const ReadVaultFile: IAIFunctionDefinition = { - name: AIFunction.ReadVaultFile, - description: `Reads and returns the complete content of a specific file from the vault. - Call this when you need to access existing note content to answer questions, - provide summaries, verify information, or gather context before making updates. - Use proactively before updating files to understand current content and avoid - data loss. Essential for any operation that references or builds upon existing notes.`, - parameters: { - type: "object", - properties: { - file_path: { - type: "string", - description: "The full path to the file within the vault (e.g., 'folder/note.md')" - }, - user_message: { - type: "string", - description: "A short message to be displayed to the user explaining why you're reading this file (e.g., 'Reading your daily note to check tasks')" - } - }, - required: ["file_path", "user_message"] - } -} \ No newline at end of file diff --git a/AIClasses/FunctionDefinitions/Functions/ReadVaultFiles.ts b/AIClasses/FunctionDefinitions/Functions/ReadVaultFiles.ts new file mode 100644 index 0000000..085c2c0 --- /dev/null +++ b/AIClasses/FunctionDefinitions/Functions/ReadVaultFiles.ts @@ -0,0 +1,31 @@ +import { AIFunction } from "Enums/AIFunction"; +import type { IAIFunctionDefinition } from "../IAIFunctionDefinition"; + +export const ReadVaultFiles: IAIFunctionDefinition = { + name: AIFunction.ReadVaultFiles, + description: `Reads and returns the complete content of one or more files from the vault. + Call this when you need to access existing note content to answer questions, + provide summaries, verify information, or gather context before making updates. + Use proactively before updating files to understand current content and avoid + data loss. Essential for any operation that references or builds upon existing notes. + + For multiple files: Use when comparing content, gathering related context, or + analyzing information across several documents.`, + parameters: { + type: "object", + properties: { + file_paths: { + type: "array", + items: { + type: "string" + }, + description: "Array of full paths to files within the vault. Can contain a single file path ['folder/note.md'] or multiple paths ['folder/note1.md', 'folder/note2.md']. Each path must be exact and point to an existing file.", + }, + user_message: { + type: "string", + description: "A short message to be displayed to the user explaining why you're reading these file(s). Examples: 'Reading your daily note to check tasks' (single file) or 'Reading your project notes to compile a summary' (multiple files)" + } + }, + required: ["file_paths", "user_message"] + } +} \ No newline at end of file diff --git a/AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts b/AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts index d9cefe3..982e30d 100644 --- a/AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts +++ b/AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts @@ -5,7 +5,7 @@ export const WriteVaultFile: IAIFunctionDefinition = { name: AIFunction.WriteVaultFile, description: `Writes content to a file, creating it if it doesn't exist or replacing its contents if it does. Use this for creating new notes or completely updating existing ones when you have the full content ready. - IMPORTANT: This replaces the entire file content - always read the file first with ${AIFunction.ReadVaultFile} if you need to preserve existing content and make partial changes. + IMPORTANT: This replaces the entire file content - always read the file first with ${AIFunction.ReadVaultFiles} if you need to preserve existing content and make partial changes. For simple updates or additions, reading first ensures you don't lose data.`, parameters: { type: "object", diff --git a/Enums/AIFunction.ts b/Enums/AIFunction.ts index 6328ddc..a54612b 100644 --- a/Enums/AIFunction.ts +++ b/Enums/AIFunction.ts @@ -1,7 +1,8 @@ export enum AIFunction { SearchVaultFiles = "search_vault_files", - ReadVaultFile = "read_vault_file", + ReadVaultFiles = "read_vault_files", WriteVaultFile = "write_vault_file", + DeleteVaultFile = "delete_vault_file", // used by gemini RequestWebSearch = "request_web_search" diff --git a/Services/AIFunctionService.ts b/Services/AIFunctionService.ts index b807c73..22cabe9 100644 --- a/Services/AIFunctionService.ts +++ b/Services/AIFunctionService.ts @@ -17,12 +17,15 @@ export class AIFunctionService { case AIFunction.SearchVaultFiles: return new AIFunctionResponse(functionCall.name, await this.searchVaultFiles(functionCall.arguments.search_term)); - case AIFunction.ReadVaultFile: - return new AIFunctionResponse(functionCall.name, await this.readVaultFile(functionCall.arguments.file_path)); + case AIFunction.ReadVaultFiles: + return new AIFunctionResponse(functionCall.name, await this.readVaultFiles(functionCall.arguments.file_paths)); case AIFunction.WriteVaultFile: return new AIFunctionResponse(functionCall.name, await this.writeVaultFile(functionCall.arguments.file_path, functionCall.arguments.content)); + case AIFunction.DeleteVaultFile: + return new AIFunctionResponse(functionCall.name, await this.deleteVaultFile(functionCall.arguments.file_path, functionCall.arguments.confirm_deletion)); + // this is only used by gemini case AIFunction.RequestWebSearch: return new AIFunctionResponse(functionCall.name, {}) @@ -58,16 +61,30 @@ export class AIFunctionService { })); } - private async readVaultFile(filePath: string): Promise { - const content = await this.fileSystemService.readFile(filePath); - if (content === null) { - return { error: `File not found: ${filePath}` }; - } - return { content }; + private async readVaultFiles(filePaths: string[]): Promise { + const files = await Promise.all( + filePaths.map(async (filePath) => { + const content = await this.fileSystemService.readFile(filePath); + if (content === null) { + return { path: filePath, error: `File not found: ${filePath}` }; + } + return { path: filePath, content }; + }) + ); + return { files }; } private async writeVaultFile(filePath: string, content: string): Promise { - const result: boolean = await this.fileSystemService.writeFile(normalizePath(filePath), content); - return isBoolean(result) ? { success: result } : { success: false, error: result } + const result: any = await this.fileSystemService.writeFile(normalizePath(filePath), content); + return isBoolean(result) ? { success: result } : { success: false, error: result }; + } + + private async deleteVaultFile(filepath: string, confirmation: boolean): Promise { + if (!confirmation) { + return { error: "Confirmation was false, no action taken" }; + } + + const result: any = await this.fileSystemService.deleteFile(filepath); + return isBoolean(result) ? { success: result } : { success: false, error: result }; } } \ No newline at end of file diff --git a/Services/FileSystemService.ts b/Services/FileSystemService.ts index bc5d0e9..a8670fc 100644 --- a/Services/FileSystemService.ts +++ b/Services/FileSystemService.ts @@ -47,7 +47,7 @@ export class FileSystemService { } } - public async deleteFile(filePath: string, allowAccessToPluginRoot: boolean = false): Promise { + public async deleteFile(filePath: string, allowAccessToPluginRoot: boolean = false): Promise { try { const file: TAbstractFile | null = this.vaultService.getAbstractFileByPath(filePath, allowAccessToPluginRoot); @@ -59,7 +59,7 @@ export class FileSystemService { return false; } catch (error) { console.error("Error deleting file:", error); - return false; + return error; } }