mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
feat: rename ReadFile to ReadVaultFile and add WriteVaultFile function
- Rename ReadFile to ReadVaultFile for consistency - Add WriteVaultFile function for destructive operations - Fix array concatenation bug in getQueryActions - Return all vault files when search yields no results - Update SearchVaultFiles description to clarify fallback behavior - Improve error handling in writeFile to return error details - Reorder system prompt sections for better clarity
This commit is contained in:
parent
86d59b18d2
commit
9f8d0b5ee8
9 changed files with 106 additions and 53 deletions
|
|
@ -1,17 +1,18 @@
|
|||
import type { IAIFunctionDefinition } from "./IAIFunctionDefinition";
|
||||
import { SearchVaultFiles } from "./Functions/SearchVaultFiles";
|
||||
import { ReadFile } from "./Functions/ReadFile";
|
||||
import { ReadVaultFile } from "./Functions/ReadVaultFile";
|
||||
import { WriteVaultFile } from "./Functions/WriteVaultFile";
|
||||
|
||||
export class AIFunctionDefinitions {
|
||||
public getQueryActions(destructive: boolean): IAIFunctionDefinition[] {
|
||||
const actions = [
|
||||
let actions = [
|
||||
SearchVaultFiles,
|
||||
ReadFile
|
||||
ReadVaultFile
|
||||
];
|
||||
|
||||
if (destructive) {
|
||||
actions.concat([
|
||||
|
||||
actions = actions.concat([
|
||||
WriteVaultFile
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import { AIFunction } from "Enums/AIFunction";
|
||||
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
|
||||
|
||||
export const ReadFile: IAIFunctionDefinition = {
|
||||
name: AIFunction.ReadFile,
|
||||
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 explaining why you're reading this file (e.g., 'Reading your daily note to check tasks')"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "user_message"]
|
||||
}
|
||||
}
|
||||
25
AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts
Normal file
25
AIClasses/FunctionDefinitions/Functions/ReadVaultFile.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
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"]
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,17 @@ import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
|
|||
|
||||
export const SearchVaultFiles: IAIFunctionDefinition = {
|
||||
name: AIFunction.SearchVaultFiles,
|
||||
description: `Searches through all files in the user's Obsidian vault for the given search term.
|
||||
Uses regex pattern matching to search file content.
|
||||
Returns matching vault files with metadata (names, paths) and contextual snippets showing matched content with surrounding text to enable relevance assessment.
|
||||
Call this whenever you need to know what files exist in the vault to answer questions,
|
||||
verify file presence, or to perform further agentic functions.
|
||||
Use proactively when vault contents would inform your response.`,
|
||||
description: `Searches the content of all vault files using regex pattern matching.
|
||||
Returns files containing the search term with contextual snippets showing where matches appear.
|
||||
|
||||
**IMPORTANT: When a search returns 0 results, a complete list of all vault files will be automatically returned.**
|
||||
This allows you to verify the search scope and attempt alternative search strategies.
|
||||
|
||||
Use this function when you need to:
|
||||
- Find files based on what's written INSIDE them
|
||||
- Search for specific concepts, keywords, or text within notes
|
||||
- Locate content that matches a pattern or phrase
|
||||
- Answer questions about what the user has written about a topic`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
|
|
|||
28
AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts
Normal file
28
AIClasses/FunctionDefinitions/Functions/WriteVaultFile.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { AIFunction } from "Enums/AIFunction";
|
||||
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
|
||||
|
||||
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.
|
||||
For simple updates or additions, reading first ensures you don't lose data.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file_path: {
|
||||
type: "string",
|
||||
description: "The full path to the file within the vault (e.g., 'folder/note.md')"
|
||||
},
|
||||
content: {
|
||||
type: "string",
|
||||
description: "The complete content to write to the file. This will replace any existing content."
|
||||
},
|
||||
user_message: {
|
||||
type: "string",
|
||||
description: "A short message to be displayed to the user explaining what you're writing and why (e.g., 'Creating your daily note for today' or 'Updating project plan with new tasks')"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "content", "user_message"]
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,12 @@ You are a specialized AI assistant with direct access to the user's Obsidian vau
|
|||
|
||||
## Critical Operating Principles
|
||||
|
||||
### 1. Wiki-Link Everything from the Vault
|
||||
### 1. Request Completion
|
||||
- Execute ALL necessary operations before concluding your turn
|
||||
- Ensure the user's complete request is fulfilled, not just the first step
|
||||
- For multi-step tasks, gather all information before presenting findings
|
||||
|
||||
### 2. Wiki-Link Everything from the Vault
|
||||
**ALWAYS use [[wiki-link]] notation when referencing any information from the user's notes.**
|
||||
- Every mention of a note, concept, person, or topic from the vault must be linked
|
||||
- This builds the knowledge graph and helps users navigate their information
|
||||
|
|
@ -18,12 +23,7 @@ Examples:
|
|||
- ✅ "This relates to your ideas about [[Machine Learning]] in [[Research Notes]]"
|
||||
- ❌ "Based on your Project Alpha notes, the deadline is next month" (missing links)
|
||||
|
||||
### 2. Request Completion
|
||||
- Execute ALL necessary operations before concluding your turn
|
||||
- Ensure the user's complete request is fulfilled, not just the first step
|
||||
- For multi-step tasks, gather all information before presenting findings
|
||||
|
||||
### 2. Communication Efficiency
|
||||
### 3. Communication Efficiency
|
||||
When performing research or multi-step operations:
|
||||
- Execute operations to completion
|
||||
- Present a single, comprehensive response with findings
|
||||
|
|
@ -31,7 +31,7 @@ When performing research or multi-step operations:
|
|||
- Focus on RESULTS, not process narration
|
||||
- Only mention your methodology when it adds essential context
|
||||
|
||||
### 3. Vault-First Decision Framework
|
||||
### 4. Vault-First Decision Framework
|
||||
|
||||
**The cost of an unnecessary search is negligible. Missing relevant user information is costly.**
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ When performing research or multi-step operations:
|
|||
Acknowledge the search, then provide general assistance:
|
||||
"I searched your vault but didn't find notes about [topic]. Here's what I can tell you: [general information]. Would you like me to create a note about this?"
|
||||
|
||||
### 4. Semantic Directory Architecture
|
||||
### 5. Semantic Directory Architecture
|
||||
|
||||
**Directory names are semantic filters, not just organizational containers.**
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ When a query contains qualifiers that match directory names, those directories d
|
|||
#### Anti-Pattern:
|
||||
Showing all files with keyword "template" when user asked for "important templates" and '/Important templates/' directory exists.
|
||||
|
||||
### 5. Progressive Search Strategy
|
||||
### 6. Progressive Search Strategy
|
||||
|
||||
**NEVER accept a failed search as final. Always try multiple approaches before concluding information doesn't exist.**
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
export enum AIFunction {
|
||||
SearchVaultFiles = "search_vault_files",
|
||||
ReadFile = "read_file",
|
||||
ReadVaultFile = "read_vault_file",
|
||||
WriteVaultFile = "write_vault_file",
|
||||
|
||||
// used by gemini
|
||||
RequestWebSearch = "request_web_search"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ import { AIFunction } from "Enums/AIFunction";
|
|||
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
|
||||
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
|
||||
import type { SearchMatch } from "../Helpers/SearchTypes";
|
||||
import { normalizePath, TFile } from "obsidian";
|
||||
|
||||
export class AIFunctionService {
|
||||
|
||||
|
||||
private fileSystemService: FileSystemService = Resolve<FileSystemService>(Services.FileSystemService);
|
||||
|
||||
public async performAIFunction(functionCall: AIFunctionCall): Promise<AIFunctionResponse> {
|
||||
|
|
@ -15,8 +16,11 @@ export class AIFunctionService {
|
|||
case AIFunction.SearchVaultFiles:
|
||||
return new AIFunctionResponse(functionCall.name, await this.searchVaultFiles(functionCall.arguments.search_term));
|
||||
|
||||
case AIFunction.ReadFile:
|
||||
return new AIFunctionResponse(functionCall.name, await this.readFile(functionCall.arguments.file_path));
|
||||
case AIFunction.ReadVaultFile:
|
||||
return new AIFunctionResponse(functionCall.name, await this.readVaultFile(functionCall.arguments.file_path));
|
||||
|
||||
case AIFunction.WriteVaultFile:
|
||||
return new AIFunctionResponse(functionCall.name, await this.writeVaultFile(functionCall.arguments.file_path, functionCall.arguments.content));
|
||||
|
||||
// this is only used by gemini
|
||||
case AIFunction.RequestWebSearch:
|
||||
|
|
@ -34,6 +38,15 @@ export class AIFunctionService {
|
|||
|
||||
private async searchVaultFiles(searchTerm: string): Promise<object> {
|
||||
const matches: SearchMatch[] = await this.fileSystemService.searchVaultFiles(searchTerm);
|
||||
|
||||
if (matches.length === 0) {
|
||||
const files: TFile[] = await this.fileSystemService.listFilesInDirectory("/");
|
||||
return files.map((file) => ({
|
||||
name: file.basename,
|
||||
path: file.path
|
||||
}));
|
||||
}
|
||||
|
||||
return matches.map((match) => ({
|
||||
name: match.file.basename,
|
||||
path: match.file.path,
|
||||
|
|
@ -44,11 +57,16 @@ export class AIFunctionService {
|
|||
}));
|
||||
}
|
||||
|
||||
private async readFile(filePath: string): Promise<object> {
|
||||
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 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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ export class FileSystemService {
|
|||
return null;
|
||||
}
|
||||
|
||||
public async writeFile(filePath: string, content: string, allowAccessToPluginRoot: boolean = false): Promise<boolean> {
|
||||
public async writeFile(filePath: string, content: string, allowAccessToPluginRoot: boolean = false): Promise<boolean | any> {
|
||||
try {
|
||||
let file: TAbstractFile | null = this.vaultService.getAbstractFileByPath(filePath, allowAccessToPluginRoot);
|
||||
if (file == null || !(file instanceof TFile)) {
|
||||
|
|
@ -43,7 +43,7 @@ export class FileSystemService {
|
|||
}
|
||||
catch (error) {
|
||||
console.error("Error writing file:", error);
|
||||
return false;
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue