import { execSync } from "child_process"; import { readFileSync, writeFileSync, mkdirSync } from "fs"; import { dirname } from "path"; export interface ToolDefinition { name: string; description: string; input_schema: Record; } export const toolDefs: ToolDefinition[] = [ { name: "bash", description: "Execute a bash command. Returns stdout and stderr. Use for running commands, installing packages, git, etc. Timeout: 120s.", input_schema: { type: "object", properties: { command: { type: "string", description: "The bash command to execute", }, }, required: ["command"], }, }, { name: "read_file", description: "Read the contents of a file. Returns the text content. Use for examining code, configs, etc.", input_schema: { type: "object", properties: { path: { type: "string", description: "Path to the file" }, limit: { type: "number", description: "Max lines to read (default: all)", }, offset: { type: "number", description: "Line to start from, 1-indexed (default: 1)", }, }, required: ["path"], }, }, { name: "write_file", description: "Write content to a file. Creates parent directories if needed. Overwrites existing files.", input_schema: { type: "object", properties: { path: { type: "string", description: "Path to the file" }, content: { type: "string", description: "Content to write", }, }, required: ["path", "content"], }, }, { name: "edit_file", description: "Edit a file by replacing exact text. oldText must match exactly including whitespace.", input_schema: { type: "object", properties: { path: { type: "string", description: "Path to the file" }, old_text: { type: "string", description: "Exact text to find", }, new_text: { type: "string", description: "Replacement text", }, }, required: ["path", "old_text", "new_text"], }, }, { name: "done", description: "Call this when the task is fully complete. Provide a short summary of what was accomplished.", input_schema: { type: "object", properties: { summary: { type: "string", description: "Short summary of what was done", }, }, required: ["summary"], }, }, { name: "ask_user", description: "Ask the user a question when you need clarification or a decision. The user will respond via Telegram.", input_schema: { type: "object", properties: { question: { type: "string", description: "The question to ask", }, }, required: ["question"], }, }, ]; export function executeTool( name: string, input: Record ): { result: string; isDone?: boolean; isQuestion?: boolean } { try { switch (name) { case "bash": { const cmd = input.command as string; try { const output = execSync(cmd, { encoding: "utf-8", timeout: 120_000, maxBuffer: 1024 * 1024, cwd: process.cwd(), }); const trimmed = output.length > 10000 ? output.slice(0, 10000) + "\n...(truncated)" : output; return { result: trimmed || "(no output)" }; } catch (e: any) { const stderr = e.stderr || ""; const stdout = e.stdout || ""; return { result: `Exit code: ${e.status}\nSTDOUT: ${stdout}\nSTDERR: ${stderr}`.slice( 0, 5000 ), }; } } case "read_file": { const path = input.path as string; const content = readFileSync(path, "utf-8"); const lines = content.split("\n"); const offset = ((input.offset as number) || 1) - 1; const limit = (input.limit as number) || lines.length; const slice = lines.slice(offset, offset + limit).join("\n"); return { result: slice.length > 10000 ? slice.slice(0, 10000) + "\n...(truncated)" : slice, }; } case "write_file": { const path = input.path as string; mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, input.content as string, "utf-8"); return { result: `Written ${(input.content as string).length} bytes to ${path}` }; } case "edit_file": { const path = input.path as string; const content = readFileSync(path, "utf-8"); const oldText = input.old_text as string; const newText = input.new_text as string; if (!content.includes(oldText)) { return { result: `ERROR: old_text not found in ${path}` }; } writeFileSync(path, content.replace(oldText, newText), "utf-8"); return { result: `Edited ${path}` }; } case "done": { return { result: input.summary as string, isDone: true }; } case "ask_user": { return { result: input.question as string, isQuestion: true }; } default: return { result: `Unknown tool: ${name}` }; } } catch (e: any) { return { result: `Tool error: ${e.message}` }; } }