Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | 2x 2x 2x 2x 2x 63x 63x 23x 1x 22x 1x 21x 8x 13x 3x 10x 10x 1x 9x 9x 1x 8x 16x 16x 8x 28x 28x 28x 28x 28x 27x 27x 1x 1x 1x 27x 26x 1x 20x 20x 26x 26x 26x 2x 24x 1x 23x 15x 8x 8x 7x 1x 1x | // src/tool-executor.ts
import { Vault, App } from 'obsidian';
import type { ToolCall, ToolResult } from './types';
import { safeParseJson } from './utils';
// Disallow characters that are invalid in file paths
const INVALID_PATH_CHARS = /[<>:"|?*~]/;
const MAX_PATH_LENGTH = 200;
const FORBIDDEN_DIRS = ['.obsidian', '.git'];
export class ToolExecutor {
private vault: Vault;
private app: App;
constructor(vault: Vault, app: App) {
this.vault = vault;
this.app = app;
}
private isSafePath(path: string): boolean {
// Reject empty paths
if (!path || path.trim().length === 0) {
return false;
}
// Reject paths that are too long
if (path.length > MAX_PATH_LENGTH) {
return false;
}
// Reject paths with invalid characters
if (INVALID_PATH_CHARS.test(path)) {
return false;
}
// Reject absolute paths
if (path.startsWith('/') || path.startsWith('\\')) {
return false;
}
// Reject Windows drive letters (e.g., C:)
Iif (/^[a-zA-Z]:/.test(path)) {
return false;
}
// Reject paths containing backslashes (Windows-style path separators)
if (path.includes('\\')) {
return false;
}
// Reject paths that traverse to parent directories
const normalized = path.replace(/^(\.\/)+/, '');
if (normalized.split('/').includes('..')) {
return false;
}
// Reject forbidden directories
for (const dir of FORBIDDEN_DIRS) {
Iif (normalized.startsWith(`${dir}/`) || normalized.startsWith(`${dir}\\`)) {
return false;
}
Iif (normalized.includes(`/${dir}/`) || normalized.includes(`\\${dir}\\`)) {
return false;
}
}
return true;
}
async handleToolCall(toolCall: ToolCall): Promise<ToolResult> {
try {
const toolName = toolCall.function?.name;
const rawArgs = toolCall.function?.arguments;
Iif (!toolName) {
throw new Error('Tool name is required');
}
// Parse arguments whether they're a string or object
let parsedArgs: Record<string, unknown>;
if (typeof rawArgs === 'string') {
try {
parsedArgs = safeParseJson(rawArgs) as Record<string, unknown>;
} catch {
throw new Error('Invalid JSON arguments');
}
} else if (rawArgs && typeof rawArgs === 'object') {
parsedArgs = rawArgs;
} else E{
throw new Error('Arguments must be an object or JSON string');
}
// Process the tool call based on its type
switch (toolName) {
case 'create_file':
return await this.handleCreateFile(parsedArgs);
default:
return { success: false, message: `Unknown tool: ${toolName}` };
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(errorMessage);
}
}
private async handleCreateFile(args: Record<string, unknown>): Promise<ToolResult> {
const path = args.path;
const content = args.content;
if (typeof path !== 'string') {
throw new Error('Path must be a string');
}
if (typeof content !== 'string') {
throw new Error('Content must be a string');
}
if (!this.isSafePath(path)) {
throw new Error('Invalid file path detected');
}
try {
await this.vault.create(path, content);
return { success: true, message: 'File created successfully' };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(errorMessage);
}
}
}
|