summaryrefslogtreecommitdiffhomepage
path: root/src/ollama-client.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-03-28 04:52:55 +0900
committerAdam Malczewski <[email protected]>2026-03-28 04:52:55 +0900
commitbe2dee39e5bf907dfdd3462817203a08cf3c345f (patch)
tree54861c738e8762d9313ec4431ea6f4464dec1900 /src/ollama-client.ts
parent9ccf03b1172dec8c4b9ed2701fcff1935ae63b67 (diff)
downloadai-pulse-obsidian-plugin-be2dee39e5bf907dfdd3462817203a08cf3c345f.tar.gz
ai-pulse-obsidian-plugin-be2dee39e5bf907dfdd3462817203a08cf3c345f.zip
improve ai context
Diffstat (limited to 'src/ollama-client.ts')
-rw-r--r--src/ollama-client.ts193
1 files changed, 138 insertions, 55 deletions
diff --git a/src/ollama-client.ts b/src/ollama-client.ts
index 0eaae74..4a184f6 100644
--- a/src/ollama-client.ts
+++ b/src/ollama-client.ts
@@ -2,6 +2,8 @@ import { Platform, requestUrl } from "obsidian";
import type { App } from "obsidian";
import type { OllamaToolDefinition } from "./tools";
import { findToolByName } from "./tools";
+import systemPromptData from "./context/system-prompt.json";
+import markdownRulesData from "./context/obsidian-markdown-rules.json";
export interface ChatMessage {
role: "system" | "user" | "assistant" | "tool";
@@ -103,62 +105,143 @@ interface AgentLoopOptions {
}
/**
- * System prompt injected when tools are available.
+ * Build the Obsidian Markdown rules section from the structured JSON context.
+ * Only includes Obsidian-specific syntax (wikilinks, embeds, callouts,
+ * frontmatter, tags, etc.) — standard Markdown is omitted since the model
+ * already knows it. This keeps the prompt compact.
*/
-const TOOL_SYSTEM_PROMPT =
- "You are a helpful assistant with access to tools for interacting with an Obsidian vault. " +
- "When you use the search_files tool, the results contain exact file paths. " +
- "You MUST use these exact paths when calling read_file, edit_file, or referencing files. " +
- "NEVER guess or modify file paths — always use the paths returned by search_files or get_current_note verbatim.\n\n" +
- "LINKING TO NOTES — MANDATORY FORMAT:\n" +
- "When referencing any note that exists in the vault, you MUST use Obsidian wiki-link syntax.\n" +
- "FORMAT: [[exact file path without .md extension]]\n" +
- "RULES:\n" +
- "1. ALWAYS use the full vault-relative path minus the .md extension.\n" +
- " Example: a file at 'projects/2024/my-note.md' MUST be linked as [[projects/2024/my-note]].\n" +
- "2. NEVER use just the basename when the file is inside a subfolder.\n" +
- " WRONG: [[my-note]] CORRECT: [[projects/2024/my-note]]\n" +
- "3. For files in the vault root (no folder), use just the name: [[my-note]].\n" +
- "4. NEVER include the .md extension in the link: WRONG: [[my-note.md]] CORRECT: [[my-note]]\n" +
- "5. To show different display text, use a pipe: [[projects/2024/my-note|My Note]].\n" +
- "6. Get the exact path from search_files, read_file, or get_current_note output, strip the .md extension, and use that as the link target.\n" +
- "7. Link to notes whenever helpful — search results, related notes, files you read or edited. Links let the user click to navigate directly.\n\n" +
- "EDITING FILES — MANDATORY WORKFLOW:\n" +
- "The edit_file tool performs a find-and-replace. You provide old_text (the exact text currently in the file) and new_text (what to replace it with). " +
- "If old_text does not match the file contents exactly, the edit WILL FAIL.\n" +
- "Therefore you MUST follow this sequence every time you edit a file:\n" +
- "1. Get the file path (use search_files or get_current_note).\n" +
- "2. Call read_file to see the CURRENT content of the file.\n" +
- "3. Copy the exact text you want to change from the read_file output and use it as old_text.\n" +
- "4. Call edit_file with the correct old_text and your new_text.\n" +
- "NEVER skip step 2. NEVER guess what the file contains — always read it first.\n" +
- "If the file is empty (read_file returned no content), you may set old_text to an empty string to write initial content.\n" +
- "If the file is NOT empty, old_text MUST NOT be empty — copy the exact passage you want to change from the read_file output.\n" +
- "old_text must include enough surrounding context (a few lines) to uniquely identify the location in the file. " +
- "Preserve the exact whitespace, indentation, and newlines from the read_file output.\n\n" +
- "CREATING FILES:\n" +
- "Use create_file to make new notes. It will fail if the file already exists — use edit_file for existing files. " +
- "Parent folders are created automatically.\n\n" +
- "MOVING/RENAMING FILES:\n" +
- "Use move_file to move or rename a file. All [[wiki-links]] across the vault are automatically updated.\n\n" +
- "SEARCHING FILE CONTENTS:\n" +
- "Use grep_search to find text inside file contents (like grep). " +
- "Use search_files to find files by name/path. Use grep_search to find files containing specific text.\n\n" +
- "FRONTMATTER MANAGEMENT:\n" +
- "When you read a file with read_file, its YAML frontmatter is automatically included as a parsed JSON block at the top of the output. " +
- "Use set_frontmatter to add, update, or remove frontmatter properties (tags, aliases, categories, etc.). " +
- "set_frontmatter is MUCH safer than edit_file for metadata changes \u2014 it preserves YAML formatting. " +
- "ALWAYS prefer set_frontmatter over edit_file when modifying tags, aliases, or other frontmatter fields. " +
- "RECOMMENDED: Read the file first to see existing frontmatter before calling set_frontmatter.\n\n" +
- "Some tools (such as delete_file, edit_file, create_file, and move_file) require user approval before they execute. " +
- "If the user declines an action, ask them why so you can better assist them.\n\n" +
- "BATCH TOOLS:\n" +
- "When you need to perform the same type of operation on multiple files, prefer batch tools over calling individual tools repeatedly. " +
- "Available batch tools: batch_search_files, batch_grep_search, batch_delete_file, batch_move_file, batch_set_frontmatter, batch_edit_file. " +
- "Batch tools accept an array of operations and execute them all in one call, reporting per-item success/failure. " +
- "Batch tools that modify files (delete, move, edit, set_frontmatter) require a single user approval for the entire batch. " +
- "The parameters for batch tools use JSON arrays passed as strings. " +
- "IMPORTANT: For batch_edit_file, you MUST still read each file first to get exact content before editing.";
+function buildMarkdownRulesPrompt(): string {
+ const r = markdownRulesData.obsidianMarkdownRules;
+ const sections: string[] = [];
+
+ sections.push(`${r.header}\n${r.description}`);
+
+ const fmtList = (items: string[]): string =>
+ items.map((item) => ` - ${item}`).join("\n");
+
+ const fmtMistakes = (items: string[]): string =>
+ items.map((m, i) => ` ${i + 1}. ${m}`).join("\n");
+
+ // Internal Links
+ const il = r.internalLinks;
+ sections.push(
+ `${il.header}\n${fmtList(il.syntax)}\n` +
+ `Common mistakes:\n${fmtMistakes(il.commonMistakes)}`,
+ );
+
+ // Embeds
+ const em = r.embeds;
+ sections.push(
+ `${em.header}\n${em.description}\n${fmtList(em.syntax)}\n` +
+ `Block identifiers:\n${fmtList(em.blockIdentifiers)}\n` +
+ `Common mistakes:\n${fmtMistakes(em.commonMistakes)}`,
+ );
+
+ // Frontmatter
+ const fm = r.frontmatter;
+ sections.push(
+ `${fm.header}\n${fm.description}\n` +
+ `Key rules:\n${fmtList(fm.keyRules)}\n` +
+ `Example:\n${fm.example}`,
+ );
+
+ // Tags
+ sections.push(`${r.tags.header}\n${fmtList(r.tags.rules)}`);
+
+ // Callouts
+ const co = r.callouts;
+ sections.push(
+ `${co.header}\n${co.description}\n${fmtList(co.syntax)}\n` +
+ `Types: ${co.types}\n` +
+ `Common mistakes:\n${fmtMistakes(co.commonMistakes)}`,
+ );
+
+ // Obsidian-only formatting
+ sections.push(`${r.obsidianOnlyFormatting.header}\n${fmtList(r.obsidianOnlyFormatting.syntax)}`);
+
+ // Numbered lists
+ sections.push(`${r.numberedLists.header}\n${fmtList(r.numberedLists.rules)}`);
+
+ // Task lists
+ sections.push(`${r.taskLists.header}\n${fmtList(r.taskLists.syntax)}`);
+
+ return sections.join("\n\n");
+}
+
+/**
+ * Build the system prompt from the structured JSON context.
+ */
+function buildToolSystemPrompt(): string {
+ const p = systemPromptData.toolSystemPrompt;
+ const sections: string[] = [];
+
+ sections.push(p.intro);
+
+ // Linking to notes
+ const linkRules = p.linkingToNotes.rules
+ .map((rule, i) => `${i + 1}. ${rule}`)
+ .join("\n");
+ sections.push(
+ `${p.linkingToNotes.header}\n` +
+ `${p.linkingToNotes.description}\n` +
+ `FORMAT: ${p.linkingToNotes.format}\n` +
+ `RULES:\n${linkRules}`,
+ );
+
+ // Editing files
+ const editSteps = p.editingFiles.steps
+ .map((step, i) => `${i + 1}. ${step}`)
+ .join("\n");
+ const editWarnings = p.editingFiles.warnings.join("\n");
+ sections.push(
+ `${p.editingFiles.header}\n` +
+ `${p.editingFiles.description}\n` +
+ `Therefore you MUST follow this sequence every time you edit a file:\n${editSteps}\n` +
+ editWarnings,
+ );
+
+ // Simple sections
+ sections.push(`CREATING FILES:\n${p.creatingFiles}`);
+ sections.push(`MOVING/RENAMING FILES:\n${p.movingFiles}`);
+ sections.push(`SEARCHING FILE CONTENTS:\n${p.searchingContents}`);
+ sections.push(`FRONTMATTER MANAGEMENT:\n${p.frontmatterManagement}`);
+ sections.push(p.approvalNote);
+ sections.push(`BATCH TOOLS:\n${p.batchTools}`);
+
+ // Confirmation messages with wiki-links
+ const cl = p.confirmationLinks;
+ const clRules = cl.rules
+ .map((rule, i) => `${i + 1}. ${rule}`)
+ .join("\n");
+ sections.push(
+ `${cl.header}\n` +
+ `${cl.description}\n` +
+ `RULES:\n${clRules}\n` +
+ `WRONG: ${cl.examples.wrong}\n` +
+ `CORRECT: ${cl.examples.correct}`,
+ );
+
+ // Embed vs Copy distinction
+ const ev = p.embedVsCopy;
+ const evRules = ev.rules
+ .map((rule, i) => `${i + 1}. ${rule}`)
+ .join("\n");
+ sections.push(
+ `${ev.header}\n` +
+ `${ev.description}\n` +
+ `RULES:\n${evRules}\n` +
+ `Example: User says: "${ev.examples.userSays}"\n` +
+ `WRONG: ${ev.examples.wrong}\n` +
+ `CORRECT: ${ev.examples.correct}`,
+ );
+
+ // Obsidian Markdown rules
+ sections.push(buildMarkdownRulesPrompt());
+
+ return sections.join("\n\n");
+}
+
+const TOOL_SYSTEM_PROMPT = buildToolSystemPrompt();
/**
* Shared agent loop: injects the system prompt, calls the strategy for each