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
This commit is contained in:
Andrew Beal 2025-10-15 21:06:23 +01:00
parent 9ab0897c57
commit 76afac7ffc
8 changed files with 99 additions and 42 deletions

View file

@ -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
]);
}

View file

@ -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"]
}
}

View file

@ -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"]
}
}

View file

@ -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"]
}
}

View file

@ -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",

View file

@ -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"

View file

@ -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<object> {
const content = await this.fileSystemService.readFile(filePath);
if (content === null) {
return { error: `File not found: ${filePath}` };
}
return { content };
private async readVaultFiles(filePaths: string[]): Promise<object> {
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<object> {
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<object> {
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 };
}
}

View file

@ -47,7 +47,7 @@ export class FileSystemService {
}
}
public async deleteFile(filePath: string, allowAccessToPluginRoot: boolean = false): Promise<boolean> {
public async deleteFile(filePath: string, allowAccessToPluginRoot: boolean = false): Promise<boolean | any> {
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;
}
}