src / toolsProvider.ts
// src/toolsProvider.ts
import { text, tool, type Tool, type ToolsProviderController } from "@lmstudio/sdk";
import { spawn } from "child_process";
import { rm, writeFile, readFile, readdir, stat, mkdir, cp, unlink } from "fs/promises";
import { join } from "path";
import { z } from "zod";
import { findLMStudioHome } from "./findLMStudioHome";
import { existsSync } from "fs";
/* ---------- المسارات ---------- */
function getDenoPath() {
try {
const lmstudioHome = findLMStudioHome();
const utilPath = join(lmstudioHome, ".internal", "utils");
return process.platform === "win32" ? join(utilPath, "deno.exe") : join(utilPath, "deno");
} catch {
return "deno";
}
}
const targetBase = join(process.cwd(), "bido_files");
async function ensureDirectory(dirPath: string) {
if (!existsSync(dirPath)) {
await mkdir(dirPath, { recursive: true });
}
}
async function runCommand(options: {
cmd: string;
args?: string[];
cwd?: string;
timeoutMs?: number;
stageLabel?: string;
}) {
const { cmd, args = [], cwd = process.cwd(), timeoutMs = 0, stageLabel } = options;
return await new Promise<{ success: boolean; stdout: string; stderr: string; code: number | null }>((resolve) => {
const child = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
let stdout = '';
let stderr = '';
if (child.stdout) child.stdout.setEncoding('utf-8');
if (child.stderr) child.stderr.setEncoding('utf-8');
child.stdout?.on('data', (d: string) => (stdout += d));
child.stderr?.on('data', (d: string) => (stderr += d));
let killedByTimeout = false;
let timer: NodeJS.Timeout | undefined;
if (timeoutMs && timeoutMs > 0) {
timer = setTimeout(() => {
killedByTimeout = true;
try { child.kill(); } catch (e) {}
}, timeoutMs);
}
child.on('close', async (code: number) => {
if (timer) clearTimeout(timer);
const exitCode = code;
const timestamp = new Date().toISOString();
const header = stageLabel ? `--- ${stageLabel} ---` : `--- ${cmd} ${args.join(' ')} ---`;
const logEntry = `\n\n${header} ${timestamp}\nEXIT CODE: ${exitCode}${killedByTimeout ? ' (killed by timeout)' : ''}\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}\n`;
try {
await ensureDirectory(targetBase);
await writeFile(join(targetBase, 'execution_trace.md'), logEntry, { flag: 'a' });
} catch {}
resolve({ success: code === 0 && !killedByTimeout, stdout: stdout.trim(), stderr: stderr.trim(), code: exitCode });
});
child.on('error', async (err: any) => {
if (timer) clearTimeout(timer);
const timestamp = new Date().toISOString();
const header = stageLabel ? `--- ${stageLabel} ERROR ---` : `--- ${cmd} ERROR ---`;
const logEntry = `\n\n${header} ${timestamp}\nERROR: ${String(err)}\n`;
try { await ensureDirectory(targetBase); await writeFile(join(targetBase, 'execution_trace.md'), logEntry, { flag: 'a' }); } catch (_) {}
resolve({ success: false, stdout: '', stderr: String(err), code: null });
});
});
}
/* ---------- تعريف الأدوات ---------- */
export async function toolsProvider(ctl: ToolsProviderController): Promise<Tool[]> {
const tools: Tool[] = [];
await ensureDirectory(targetBase);
tools.push(tool({
name: "run_javascript",
description: text`تشغيل كود JavaScript باستخدام Deno.`,
parameters: {
javascript: z.string(),
timeout_seconds: z.number().min(1).max(60).optional().default(5)
},
implementation: async ({ javascript, timeout_seconds }) => {
const workingDirectory = ctl.getWorkingDirectory();
const scriptFileName = `temp_script_${Date.now()}.ts`;
const scriptFilePath = join(workingDirectory, scriptFileName);
await writeFile(scriptFilePath, javascript, "utf-8");
const denoPath = getDenoPath();
const res = await runCommand({
cmd: denoPath,
args: ["run", "--allow-read=.", "--allow-write=.", "--no-prompt", "--deny-net", "--deny-env", "--deny-sys", "--deny-run", "--deny-ffi", scriptFilePath],
cwd: workingDirectory,
timeoutMs: (timeout_seconds || 5) * 1000,
stageLabel: `run_javascript ${scriptFileName}`
});
try { await rm(scriptFilePath); } catch (_) {}
return { success: res.success, result: res.stdout, error: res.success ? undefined : res.stderr };
}
}));
tools.push(tool({
name: "save_chat_summary",
description: text`حفظ ملخص محادثة في bido_files/chats/chats.txt.`,
parameters: {
summary: z.string().min(10)
},
implementation: async ({ summary }) => {
const chatsDir = join(targetBase, "chats");
await ensureDirectory(chatsDir);
const timestamp = new Date().toISOString();
const filePath = join(chatsDir, "chats.txt");
const entry = `\n\n--- ${timestamp} ---\n${summary}\n`;
await writeFile(filePath, entry, { flag: "a" });
return { success: true, message: `تم حفظ ملخص المحادثة`, path: filePath, timestamp };
}
}));
tools.push(tool({
name: "generate_trace",
description: text`توليد سجل تنفيذ حيّ في execution_trace.md.`,
parameters: {
input: z.string().min(5)
},
implementation: async ({ input }) => {
const tracePath = join(targetBase, "execution_trace.md");
const timestamp = new Date().toISOString();
const entry = `\n\n--- ${timestamp} ---\n${input}\n`;
await writeFile(tracePath, entry, { flag: "a" });
return { success: true, message: "تم حفظ السجل", path: tracePath };
}
}));
tools.push(tool({
name: "sync_structure_map",
description: text`تشغيل سكربت update_structure.py لرسم هيكل المشروع في ملف workspace_structure.md.`,
parameters: {},
implementation: async () => {
const workingDirectory = ctl.getWorkingDirectory();
const res = await runCommand({ cmd: "python", args: ["update_structure.py"], cwd: workingDirectory, stageLabel: "sync_structure_map" });
return { success: res.success, result: res.stdout, error: res.success ? undefined : res.stderr };
}
}));
tools.push(tool({
name: "run_fragmented_awareness",
description: text`تشغيل سلسلة سكربتات من مجلد fragmented_awareness (المراحل 1..4).`,
parameters: {
stage: z.number().min(1).max(4)
},
implementation: async ({ stage }) => {
const scriptMap = {
1: "search_file.py",
2: "create_reminder_excel.py",
3: "scan_resources.py",
4: "awakening_test.py"
};
const scriptName = scriptMap[stage as keyof typeof scriptMap];
if (!scriptName) return { success: false, error: `unknown stage ${stage}` };
const workingDirectory = ctl.getWorkingDirectory();
const scriptPath = join(workingDirectory, "fragmented_awareness", scriptName);
const res = await runCommand({ cmd: "python", args: [scriptPath], cwd: workingDirectory, stageLabel: `stage_${stage}_${scriptName}` });
return { success: res.success, result: res.stdout, error: res.success ? undefined : res.stderr };
}
}));
tools.push(tool({
name: "log_tool_usage",
description: text`تسجيل استخدام أداة في tool_log.md.`,
parameters: {
tool_name: z.string().min(1),
context: z.string().min(5)
},
implementation: async ({ tool_name, context }) => {
const logPath = join(targetBase, "tool_log.md");
const timestamp = new Date().toISOString();
const entry = `\n\n--- ${timestamp} ---\nأداة: ${tool_name}\n${context}\n`;
await writeFile(logPath, entry, { flag: "a" });
return { success: true, message: "تم تسجيل استخدام الأداة", path: logPath };
}
}));
tools.push(tool({
name: "mark_awareness",
description: text`توثيق لحظة إدراك في model_reflection.md.`,
parameters: {
reflection: z.string().min(10)
},
implementation: async ({ reflection }) => {
const reflectionPath = join(targetBase, "model_reflection.md");
const timestamp = new Date().toISOString();
const entry = `\n\n--- ${timestamp} ---\n${reflection}\n`;
await writeFile(reflectionPath, entry, { flag: "a" });
return { success: true, message: "تم توثيق لحظة الإدراك", path: reflectionPath };
}
}));
tools.push(tool({
name: "summarize_file",
description: text`تلخيص محتوى ملف نصي.`,
parameters: {
filename: z.string().min(1)
},
implementation: async ({ filename }) => {
const filePath = join(targetBase, filename.endsWith(".txt") ? filename : `${filename}.txt`);
if (!existsSync(filePath)) {
return { success: false, error: `الملف ${filename} غير موجود.` };
}
const content = await readFile(filePath, "utf-8");
const summary = content.split("\n").slice(0, 3).join(" ");
return { success: true, summary };
}
}));
tools.push(tool({
name: "extract_insight",
description: text`استخراج لحظة إدراك من ملف نصي.`,
parameters: {
filename: z.string().min(1)
},
implementation: async ({ filename }) => {
const filePath = join(targetBase, filename.endsWith(".txt") ? filename : `${filename}.txt`);
if (!existsSync(filePath)) {
return { success: false, error: `الملف ${filename} غير موجود.` };
}
const content = await readFile(filePath, "utf-8");
const insight = content.includes("زنقة") ? "تم رصد زنقة إدراكية" : "لا توجد زنقات واضحة";
return { success: true, insight };
}
}));
tools.push(tool({
name: "compare_models",
description: text`مقارنة بين ردّي نموذجين على نفس المهمة وتحليل الفرق.`,
parameters: {
prompt: z.string().min(5),
modelA: z.string().min(1),
modelB: z.string().min(1)
},
implementation: async ({ prompt, modelA, modelB }) => {
const timestamp = new Date().toISOString();
const entry = `\n\n--- ${timestamp} ---\nالمهمة: ${prompt}\nالنموذج A: ${modelA}\nالنموذج B: ${modelB}\n`;
const filePath = join(targetBase, "model_comparison.md");
await writeFile(filePath, entry, { flag: "a" });
return {
success: true,
message: "تم تسجيل مقارنة النموذجين",
path: filePath,
timestamp
};
}
}));
tools.push(tool({
name: "suggest_next_action",
description: text`اقتراح خطوة إدراكية تالية بناءً على السياق الحالي.`,
parameters: {
last_tool: z.string().min(1),
context: z.string().min(5)
},
implementation: async ({ last_tool, context }) => {
let suggestion = "لا يوجد اقتراح واضح في السياق الحالي.";
if (last_tool === "run_javascript") {
suggestion = "هل تحب توليد سجل تنفيذ حيّ باستخدام generate_trace؟";
} else if (last_tool === "save_chat_summary") {
suggestion = "هل ترغب في أرشفة هذا التلخيص داخل مجلد الجلسة؟";
} else if (last_tool === "summarize_file") {
suggestion = "هل نربط هذا التلخيص بتحليل زنقة باستخدام extract_insight؟";
} else if (last_tool === "compare_models") {
suggestion = "هل ترغب في تسجيل هذه المقارنة داخل جدول الإدراك؟";
}
const timestamp = new Date().toISOString();
const entry = `\n\n--- ${timestamp} ---\nالأداة السابقة: ${last_tool}\nالسياق: ${context}\nالاقتراح: ${suggestion}\n`;
const filePath = join(targetBase, "next_action_suggestions.md");
await writeFile(filePath, entry, { flag: "a" });
return {
success: true,
suggestion,
path: filePath,
timestamp
};
}
}));
tools.push(tool({
name: "create_folder",
description: text`إنشاء مجلد جديد داخل bido_files.`,
parameters: {
folder_name: z.string().min(1)
},
implementation: async ({ folder_name }) => {
const folderPath = join(targetBase, folder_name);
await mkdir(folderPath, { recursive: true });
return { success: true, message: `تم إنشاء المجلد: ${folder_name}` };
}
}));
tools.push(tool({
name: "delete_file",
description: text`حذف ملف من bido_files.`,
parameters: {
filename: z.string().min(1)
},
implementation: async ({ filename }) => {
const filePath = join(targetBase, filename);
if (!existsSync(filePath)) {
return { success: false, error: `الملف ${filename} غير موجود.` };
}
await rm(filePath);
return { success: true, message: `تم حذف الملف: ${filename}` };
}
}));
tools.push(tool({
name: "delete_folder",
description: text`حذف مجلد بالكامل من bido_files.`,
parameters: {
folder_name: z.string().min(1)
},
implementation: async ({ folder_name }) => {
const folderPath = join(targetBase, folder_name);
if (!existsSync(folderPath)) {
return { success: false, error: `المجلد ${folder_name} غير موجود.` };
}
await rm(folderPath, { recursive: true, force: true });
return { success: true, message: `تم حذف المجلد: ${folder_name}` };
}
}));
tools.push(tool({
name: "backup_file",
description: text`إنشاء نسخة احتياطية من ملف داخل bido_files.`,
parameters: {
filename: z.string().min(1)
},
implementation: async ({ filename }) => {
const originalPath = join(targetBase, filename);
const backupPath = join(targetBase, `${filename}.bak`);
if (!existsSync(originalPath)) {
return { success: false, error: `الملف ${filename} غير موجود.` };
}
await cp(originalPath, backupPath);
return { success: true, message: `تم إنشاء نسخة احتياطية: ${filename}.bak` };
}
}));
tools.push(tool({
name: "get_current_time",
description: text`إرجاع الوقت الحالي.`,
parameters: {},
implementation: async () => {
const now = new Date();
return { time: now.toLocaleTimeString() };
}
}));
tools.push(tool({
name: "get_current_date",
description: text`إرجاع التاريخ الحالي.`,
parameters: {},
implementation: async () => {
const now = new Date();
return { date: now.toLocaleDateString() };
}
}));
tools.push(tool({
name: "delete_all_files",
description: text`حذف جميع الملفات داخل bido_files.`,
parameters: {},
implementation: async () => {
const entries = await readdir(targetBase, { withFileTypes: true });
const files = entries.filter(e => e.isFile());
for (const file of files) {
await unlink(join(targetBase, file.name));
}
return { success: true, message: `تم حذف ${files.length} ملف.` };
}
}));
tools.push(tool({
name: "delete_all_folders",
description: text`حذف جميع المجلدات داخل bido_files.`,
parameters: {},
implementation: async () => {
const entries = await readdir(targetBase, { withFileTypes: true });
const folders = entries.filter(e => e.isDirectory());
for (const folder of folders) {
await rm(join(targetBase, folder.name), { recursive: true, force: true });
}
return { success: true, message: `تم حذف ${folders.length} مجلد.` };
}
}));
return tools;
}