src / tools / fileTools.ts
import { tool } from "@lmstudio/sdk";
import { readFile, writeFile, mkdir, rm, readdir } from "fs/promises";
import { dirname } from "path";
import { exec } from "child_process";
import { promisify } from "util";
import { z } from "zod";
import { resolvePath, getWorkspaceDir, truncateOutput } from "../utils.js";
const execAsync = promisify(exec);
// 1. Leer archivo
export const read_file = tool({
name: "read_file",
description: "Read the content of a file on the host. Supports relative paths to workspace (/home/arkantu/workspace), home (~/...), or absolute paths.",
parameters: {
filepath: z.string().describe("Path to the file (e.g. 'my_file.txt', '~/notes.md', or '/home/user/workspace/project/main.py').")
},
implementation: async ({ filepath }) => {
try {
const targetPath = resolvePath(filepath);
const content = await readFile(targetPath, "utf-8");
return {
success: true,
filepath: targetPath,
content: truncateOutput(content)
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 2. Escribir / Crear archivo
export const write_file = tool({
name: "write_file",
description: "Write or overwrite text in a file on the host. Automatically creates parent directories. Relative paths are written to the workspace root.",
parameters: {
filepath: z.string().describe("Path to the file (e.g. 'src/index.ts', 'plan.md', or absolute path)."),
content: z.string().describe("Text content to write to the file.")
},
implementation: async ({ filepath, content }) => {
try {
const targetPath = resolvePath(filepath);
await mkdir(dirname(targetPath), { recursive: true });
await writeFile(targetPath, content, "utf-8");
return {
success: true,
filepath: targetPath,
message: `OK: File saved at ${targetPath} (${content.length} chars)`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 3. Editar archivo (reemplazo puntual de texto)
export const edit_file = tool({
name: "edit_file",
description: "Replace exact text inside an existing file. Relative paths resolve against the workspace root.",
parameters: {
filepath: z.string().describe("Path to the file to edit."),
old_text: z.string().describe("Exact text substring to replace."),
new_text: z.string().describe("New replacement text.")
},
implementation: async ({ filepath, old_text, new_text }) => {
try {
const targetPath = resolvePath(filepath);
const content = await readFile(targetPath, "utf-8");
if (!content.includes(old_text)) {
return {
success: false,
filepath: targetPath,
error: "old_text not found in file."
};
}
const updated = content.replace(old_text, new_text);
await writeFile(targetPath, updated, "utf-8");
return {
success: true,
filepath: targetPath,
message: `OK: File text replaced successfully in ${targetPath}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 4. Listar directorio
export const list_dir = tool({
name: "list_dir",
description: "List files and subdirectories in a directory. Defaults to host workspace root if omitted or relative.",
parameters: {
dirpath: z.string().optional().describe("Directory path (defaults to host workspace root).")
},
implementation: async ({ dirpath }) => {
try {
const targetPath = resolvePath(dirpath || ".");
const entries = await readdir(targetPath, { withFileTypes: true });
const list = entries.map(e => (e.isDirectory() ? `[DIR] ${e.name}` : `[FILE] ${e.name}`));
return {
success: true,
dirpath: targetPath,
count: list.length,
items: list.slice(0, 100)
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 5. Crear directorio
export const make_dir = tool({
name: "make_dir",
description: "Create a directory and parent directories if needed. Relative paths are created in host workspace.",
parameters: {
dirpath: z.string().describe("Directory path to create.")
},
implementation: async ({ dirpath }) => {
try {
const targetPath = resolvePath(dirpath);
await mkdir(targetPath, { recursive: true });
return {
success: true,
dirpath: targetPath,
message: `OK: Directory created at ${targetPath}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 6. Eliminar archivo o carpeta
export const delete_path = tool({
name: "delete_path",
description: "Delete a file or directory (recursive). Supports relative to workspace, ~, or absolute.",
parameters: {
target_path: z.string().describe("File or directory path to delete.")
},
implementation: async ({ target_path }) => {
try {
const p = resolvePath(target_path);
await rm(p, { recursive: true, force: true });
return {
success: true,
path: p,
message: `OK: Deleted ${p}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 7. Ejecutar Bash
export const run_bash = tool({
name: "run_bash",
description: "Run a bash shell command in host workspace directory.",
parameters: {
command: z.string().describe("The bash command to execute.")
},
implementation: async ({ command }) => {
try {
const cwd = getWorkspaceDir();
await mkdir(cwd, { recursive: true });
const { stdout, stderr } = await execAsync(command, {
cwd,
timeout: 45000,
maxBuffer: 2 * 1024 * 1024
});
return {
exit_code: 0,
cwd,
stdout: truncateOutput(stdout.trim()),
stderr: truncateOutput(stderr.trim())
};
} catch (err: any) {
return {
exit_code: err.code ?? 1,
stdout: truncateOutput(err.stdout ? String(err.stdout).trim() : ""),
stderr: truncateOutput(err.stderr ? String(err.stderr).trim() : err.message)
};
}
}
});
// 8. Ejecutar Python
export const run_python = tool({
name: "run_python",
description: "Execute Python 3 code directly with host workspace as working directory.",
parameters: {
code: z.string().describe("Python code snippet to execute.")
},
implementation: async ({ code }) => {
try {
const cwd = getWorkspaceDir();
await mkdir(cwd, { recursive: true });
const { stdout, stderr } = await execAsync("python3 -c " + JSON.stringify(code), {
cwd,
timeout: 45000,
maxBuffer: 2 * 1024 * 1024
});
return {
exit_code: 0,
cwd,
stdout: truncateOutput(stdout.trim()),
stderr: truncateOutput(stderr.trim())
};
} catch (err: any) {
return {
exit_code: err.code ?? 1,
stdout: truncateOutput(err.stdout ? String(err.stdout).trim() : ""),
stderr: truncateOutput(err.stderr ? String(err.stderr).trim() : err.message)
};
}
}
});
src / tools / fileTools.ts
import { tool } from "@lmstudio/sdk";
import { readFile, writeFile, mkdir, rm, readdir } from "fs/promises";
import { dirname } from "path";
import { exec } from "child_process";
import { promisify } from "util";
import { z } from "zod";
import { resolvePath, getWorkspaceDir, truncateOutput } from "../utils.js";
const execAsync = promisify(exec);
// 1. Leer archivo
export const read_file = tool({
name: "read_file",
description: "Read the content of a file on the host. Supports relative paths to workspace (/home/arkantu/workspace), home (~/...), or absolute paths.",
parameters: {
filepath: z.string().describe("Path to the file (e.g. 'my_file.txt', '~/notes.md', or '/home/user/workspace/project/main.py').")
},
implementation: async ({ filepath }) => {
try {
const targetPath = resolvePath(filepath);
const content = await readFile(targetPath, "utf-8");
return {
success: true,
filepath: targetPath,
content: truncateOutput(content)
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 2. Escribir / Crear archivo
export const write_file = tool({
name: "write_file",
description: "Write or overwrite text in a file on the host. Automatically creates parent directories. Relative paths are written to the workspace root.",
parameters: {
filepath: z.string().describe("Path to the file (e.g. 'src/index.ts', 'plan.md', or absolute path)."),
content: z.string().describe("Text content to write to the file.")
},
implementation: async ({ filepath, content }) => {
try {
const targetPath = resolvePath(filepath);
await mkdir(dirname(targetPath), { recursive: true });
await writeFile(targetPath, content, "utf-8");
return {
success: true,
filepath: targetPath,
message: `OK: File saved at ${targetPath} (${content.length} chars)`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 3. Editar archivo (reemplazo puntual de texto)
export const edit_file = tool({
name: "edit_file",
description: "Replace exact text inside an existing file. Relative paths resolve against the workspace root.",
parameters: {
filepath: z.string().describe("Path to the file to edit."),
old_text: z.string().describe("Exact text substring to replace."),
new_text: z.string().describe("New replacement text.")
},
implementation: async ({ filepath, old_text, new_text }) => {
try {
const targetPath = resolvePath(filepath);
const content = await readFile(targetPath, "utf-8");
if (!content.includes(old_text)) {
return {
success: false,
filepath: targetPath,
error: "old_text not found in file."
};
}
const updated = content.replace(old_text, new_text);
await writeFile(targetPath, updated, "utf-8");
return {
success: true,
filepath: targetPath,
message: `OK: File text replaced successfully in ${targetPath}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 4. Listar directorio
export const list_dir = tool({
name: "list_dir",
description: "List files and subdirectories in a directory. Defaults to host workspace root if omitted or relative.",
parameters: {
dirpath: z.string().optional().describe("Directory path (defaults to host workspace root).")
},
implementation: async ({ dirpath }) => {
try {
const targetPath = resolvePath(dirpath || ".");
const entries = await readdir(targetPath, { withFileTypes: true });
const list = entries.map(e => (e.isDirectory() ? `[DIR] ${e.name}` : `[FILE] ${e.name}`));
return {
success: true,
dirpath: targetPath,
count: list.length,
items: list.slice(0, 100)
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 5. Crear directorio
export const make_dir = tool({
name: "make_dir",
description: "Create a directory and parent directories if needed. Relative paths are created in host workspace.",
parameters: {
dirpath: z.string().describe("Directory path to create.")
},
implementation: async ({ dirpath }) => {
try {
const targetPath = resolvePath(dirpath);
await mkdir(targetPath, { recursive: true });
return {
success: true,
dirpath: targetPath,
message: `OK: Directory created at ${targetPath}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 6. Eliminar archivo o carpeta
export const delete_path = tool({
name: "delete_path",
description: "Delete a file or directory (recursive). Supports relative to workspace, ~, or absolute.",
parameters: {
target_path: z.string().describe("File or directory path to delete.")
},
implementation: async ({ target_path }) => {
try {
const p = resolvePath(target_path);
await rm(p, { recursive: true, force: true });
return {
success: true,
path: p,
message: `OK: Deleted ${p}`
};
} catch (err: any) {
return { success: false, error: err.message };
}
}
});
// 7. Ejecutar Bash
export const run_bash = tool({
name: "run_bash",
description: "Run a bash shell command in host workspace directory.",
parameters: {
command: z.string().describe("The bash command to execute.")
},
implementation: async ({ command }) => {
try {
const cwd = getWorkspaceDir();
await mkdir(cwd, { recursive: true });
const { stdout, stderr } = await execAsync(command, {
cwd,
timeout: 45000,
maxBuffer: 2 * 1024 * 1024
});
return {
exit_code: 0,
cwd,
stdout: truncateOutput(stdout.trim()),
stderr: truncateOutput(stderr.trim())
};
} catch (err: any) {
return {
exit_code: err.code ?? 1,
stdout: truncateOutput(err.stdout ? String(err.stdout).trim() : ""),
stderr: truncateOutput(err.stderr ? String(err.stderr).trim() : err.message)
};
}
}
});
// 8. Ejecutar Python
export const run_python = tool({
name: "run_python",
description: "Execute Python 3 code directly with host workspace as working directory.",
parameters: {
code: z.string().describe("Python code snippet to execute.")
},
implementation: async ({ code }) => {
try {
const cwd = getWorkspaceDir();
await mkdir(cwd, { recursive: true });
const { stdout, stderr } = await execAsync("python3 -c " + JSON.stringify(code), {
cwd,
timeout: 45000,
maxBuffer: 2 * 1024 * 1024
});
return {
exit_code: 0,
cwd,
stdout: truncateOutput(stdout.trim()),
stderr: truncateOutput(stderr.trim())
};
} catch (err: any) {
return {
exit_code: err.code ?? 1,
stdout: truncateOutput(err.stdout ? String(err.stdout).trim() : ""),
stderr: truncateOutput(err.stderr ? String(err.stderr).trim() : err.message)
};
}
}
});