Spaces:
Sleeping
Sleeping
File size: 10,426 Bytes
95eb75a | 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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | // ─── fileToolsAdvanced.ts ─────────────────────────────────────────────────────
// S742: estratto da fileTools.ts — handler per 6 tool avanzati S706:
// copy_file · file_info · file_diff · append_to_file · clone_url_to_vfs · batch_file_op
// Ogni tool restituisce string | null.
// Importato da fileTools.ts tramite tryExecuteAdvanced().
// ─────────────────────────────────────────────────────────────────────────────
import { vfsAsync, fileBackupsDb } from "../vfsDb";
export type AdvancedToolArgs = Record<string, unknown>;
export async function tryExecuteAdvanced(
name: string,
args: AdvancedToolArgs,
signal?: AbortSignal
): Promise<string | null> {
// ── S706: copy_file ──────────────────────────────────────────────────────────
if (name === "copy_file") {
const { from, to } = args as { from: string; to: string };
const content = await vfsAsync.read(from);
if (content === null) {
const all = await vfsAsync.list() as Array<{ path: string }>;
return `❌ copy_file: sorgente "${from}" non trovata.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`;
}
const existing = await vfsAsync.read(to);
if (existing !== null) return `❌ copy_file: destinazione "${to}" esiste già. Cancellala con delete_file prima.`;
const files2 = await vfsAsync.list() as Array<{ path: string; type?: string }>;
const mime2 = (files2.find(f => f.path === from) as { type?: string } | undefined)?.type ?? "text/plain";
await vfsAsync.write(to, content, mime2);
return `✅ Copiato: \`${from}\` → \`${to}\` (${content.length} chars)`;
}
// ── S706: file_info ──────────────────────────────────────────────────────────
if (name === "file_info") {
const { path: fiPath } = args as { path: string };
const content = await vfsAsync.read(fiPath);
if (content === null) {
const all = await vfsAsync.list() as Array<{ path: string }>;
return `❌ file_info: "${fiPath}" non trovato.\nDisponibili: ${all.map(f => f.path).join(", ") || "nessuno"}`;
}
const allF = await vfsAsync.list() as Array<{ path: string; type?: string; size?: number }>;
const meta = allF.find(f => f.path === fiPath);
const isText = !content.startsWith("data:");
const lines = isText ? content.split("\n").length : null;
const words = isText ? content.split(/\s+/).filter(Boolean).length : null;
const ext = fiPath.split(".").pop()?.toLowerCase() ?? "";
const info: string[] = [
`📄 **Info: \`${fiPath}\`**`,
`- **Dimensione**: ${content.length.toLocaleString()} chars${meta?.size ? ` (${meta.size.toLocaleString()} bytes)` : ""}`,
`- **Tipo MIME**: ${meta?.type ?? "text/plain"}`,
`- **Estensione**: .${ext}`,
];
if (lines !== null) info.push(`- **Righe**: ${lines.toLocaleString()}`);
if (words !== null) info.push(`- **Parole**: ${words.toLocaleString()}`);
if (content.startsWith("data:")) {
const mimeFromUrl = content.match(/^data:([^;]+)/)?.[1] ?? "unknown";
info.push(`- **Contenuto**: file binario/media (${mimeFromUrl})`);
} else {
const hasNonAscii = /[^\x00-\x7F]/.test(content.slice(0, 1000));
info.push(`- **Encoding**: ${hasNonAscii ? "UTF-8 (con caratteri non-ASCII)" : "ASCII/UTF-8"}`);
}
const backups = await fileBackupsDb.list(fiPath);
if (backups.length) info.push(`- **Backup disponibili**: ${backups.length}`);
return info.join("\n");
}
// ── S706: file_diff ──────────────────────────────────────────────────────────
if (name === "file_diff") {
const { path_a, path_b } = args as { path_a: string; path_b: string };
const [ca, cb2] = await Promise.all([vfsAsync.read(path_a), vfsAsync.read(path_b)]);
if (ca === null) return `❌ file_diff: "${path_a}" non trovato nel VFS.`;
if (cb2 === null) return `❌ file_diff: "${path_b}" non trovato nel VFS.`;
const linesA = ca.split("\n"), linesB = cb2.split("\n");
const patch: string[] = [];
let i = 0, j = 0;
while (i < linesA.length || j < linesB.length) {
if (i < linesA.length && j < linesB.length && linesA[i] === linesB[j]) {
patch.push(` ${linesA[i]}`); i++; j++;
} else if (j >= linesB.length || (i < linesA.length && linesA[i] !== linesB[j])) {
patch.push(`-${linesA[i] ?? ""}`); i++;
} else {
patch.push(`+${linesB[j]}`); j++;
}
}
const added = patch.filter(l => l.startsWith("+")).length;
const removed = patch.filter(l => l.startsWith("-")).length;
if (added === 0 && removed === 0) return `✅ file_diff: **"${path_a}"** e **"${path_b}"** sono identici (${linesA.length} righe).`;
const preview = patch.slice(0, 100).join("\n");
return `📊 **Diff: \`${path_a}\` ↔ \`${path_b}\`**\n+${added} righe aggiunte / -${removed} rimosse\n\n\`\`\`diff\n${preview}${patch.length > 100 ? `\n…(+${patch.length - 100} righe)` : ""}\n\`\`\``;
}
// ── S706: append_to_file ─────────────────────────────────────────────────────
if (name === "append_to_file") {
const { path: apPath, content: apContent, separator = "\n" } = args as { path: string; content: string; separator?: string };
const existing = await vfsAsync.read(apPath);
const newContent = existing !== null ? existing + separator + apContent : apContent;
await vfsAsync.write(apPath, newContent);
const _wr = await vfsAsync.read(apPath);
if (_wr === null) return `⚠️ append_to_file: file non salvato (quota localStorage esaurita).`;
return `✅ Append a \`${apPath}\` — +${apContent.length} chars, totale ${newContent.length} chars`;
}
// ── S706: clone_url_to_vfs ───────────────────────────────────────────────────
if (name === "clone_url_to_vfs") {
const { url: cuUrl, save_path: cuPath, format: cuFmt = "auto" } = args as { url: string; save_path: string; format?: string };
if (!cuUrl || !cuPath) return "❌ clone_url_to_vfs: url e save_path sono obbligatori.";
try {
const PROXIES = [
(u: string) => `https://api.allorigins.win/raw?url=${encodeURIComponent(u)}`,
(u: string) => `https://corsproxy.io/?${encodeURIComponent(u)}`,
(u: string) => u,
];
let fetchedOk = false, mimeType = "text/plain", savedAs = "";
for (const proxy of PROXIES) {
try {
const resp = await fetch(proxy(cuUrl), { signal });
if (!resp.ok) continue;
const ct = resp.headers.get("content-type") ?? "";
const isBinary = cuFmt === "binary" || (!ct.includes("text") && !ct.includes("json") && !ct.includes("xml") && !ct.includes("svg") && cuFmt !== "text");
if (isBinary) {
const blob = await resp.blob();
const dr: string = await new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result as string); fr.onerror = rej; fr.readAsDataURL(blob); });
mimeType = ct.split(";")[0].trim() || "application/octet-stream";
await vfsAsync.write(cuPath, dr, mimeType);
savedAs = `binario (${blob.size} bytes, ${mimeType})`;
} else {
const text = await resp.text();
mimeType = ct.split(";")[0].trim() || "text/plain";
await vfsAsync.write(cuPath, text, mimeType);
savedAs = `testo (${text.length} chars)`;
}
fetchedOk = true; break;
} catch { continue; }
}
if (!fetchedOk) return `❌ clone_url_to_vfs: impossibile scaricare "${cuUrl}" (tutti i proxy CORS falliti).`;
return `✅ Download → \`${cuPath}\` ← ${cuUrl}\n${savedAs}`;
} catch (e) { return `❌ clone_url_to_vfs: ${e instanceof Error ? e.message : String(e)}`; }
}
// ── S706: batch_file_op ──────────────────────────────────────────────────────
if (name === "batch_file_op") {
const { operation: bOp, pattern: bPat, replacement: bRep, paths: bPaths, dry_run = false } = args as {
operation: "copy" | "delete" | "rename" | "list_matches";
pattern: string; replacement?: string; paths?: string[]; dry_run?: boolean;
};
const allBatch = await vfsAsync.list() as Array<{ path: string }>;
let bRx: RegExp;
try { bRx = new RegExp(bPat, "i"); } catch { return `❌ batch_file_op: pattern regex non valido: "${bPat}"`; }
const bTargets = (bPaths ? allBatch.filter(f => bPaths.includes(f.path)) : allBatch).filter(f => bRx.test(f.path));
if (!bTargets.length) return `📂 batch_file_op: nessun file corrisponde a \`${bPat}\`.`;
if (bOp === "list_matches") return `📂 **${bTargets.length} file corrispondono a \`${bPat}\`:**\n${bTargets.map(f => `• \`${f.path}\``).join("\n")}`;
const bResults: string[] = [];
for (const f of bTargets) {
if (bOp === "delete") {
if (!dry_run) await vfsAsync.delete(f.path);
bResults.push(`${dry_run ? "🔍" : "🗑️"} ${f.path}`);
} else if ((bOp === "copy" || bOp === "rename") && bRep !== undefined) {
const dest = f.path.replace(bRx, bRep);
if (!dry_run) {
const c = await vfsAsync.read(f.path);
if (c !== null) {
await vfsAsync.write(dest, c);
if (bOp === "rename") await vfsAsync.delete(f.path);
}
}
bResults.push(`${dry_run ? "🔍" : bOp === "copy" ? "📋" : "🔄"} ${f.path} → ${dest}`);
}
}
return `✅ **batch_file_op (${bOp})${dry_run ? " — DRY RUN" : ""}**\n${bResults.join("\n")}`;
}
return null;
}
|