diff --git a/extensions/index.ts b/extensions/index.ts index d074d0b..b7b6f8e 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -467,6 +467,77 @@ async function webSearchGemini(query: string, signal?: AbortSignal): Promise { + let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? ""; + if (!key) { + try { + key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim(); + } catch { + /* ignora */ + } + } + if (!key) return ""; + const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash"; + try { + const res = await fetch( + `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`, + { + method: "POST", + signal, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [{ role: "user", parts: [{ text: prompt }] }], + }), + }, + ); + if (!res.ok) return ""; + const data: any = await res.json(); + const text = data?.candidates?.[0]?.content?.parts?.map((p: any) => p.text ?? "").join(" ") ?? ""; + return text.trim(); + } catch { + return ""; + } +} + +// Genera DINAMICAMENTE uno script Python/OpenCV specifico per i requisiti, +// usando l'LLM. Lo script legge l'immagine da argv[1] e stampa su stdout un JSON +// standard: {"pass": bool, "score": 0..1, "findings": [...], "errors": [...]}. +async function generateOpenCVScript(requirements: string): Promise { + const prompt = + `Sei un esperto di computer vision con OpenCV. Scrivi UN SINGOLO script Python (solo codice, senza markdown) che verifichi un'immagine rispetto a questi requisiti:\n\n` + + `REQUISITI: ${requirements}\n\n` + + `Lo script:\n` + + `- legge il percorso immagine da sys.argv[1]\n` + + `- importa solo cv2, numpy, json, sys (e eventuali standard library)\n` + + `- esegue verifiche OGGETTIVE pertinenti ai requisiti (es. intervalli di colore HSV, forme/contorni, simmetria, testo, dimensioni, orientamento, ecc.)\n` + + `- stampa su stdout UN SOLO oggetto JSON: {"pass": true/false, "score": 0..1, "findings": [stringhe], "errors": [stringhe]}\n` + + ` - pass: true se l'immagine rispetta i requisiti\n` + + ` - score: grado di conformità da 0 a 1\n` + + ` - findings: brevi note oggettive misurate (es. "colore dominante rosso RGB(200,30,30)")\n` + + ` - errors: eventuali problemi di lettura/verifica\n` + + `- NON deve fallire se l'immagine è leggibile: gestisci con try/except e metti l'errore in errors\n` + + `- usa solo OpenCV, numpy e librerie standard (nessuna libreria esterna)\n\n` + + `Restituisci SOLO il codice Python, nessun testo aggiuntivo, nessun blocco markdown.`; + const code = await geminiText(prompt); + // rimuovi eventuali fence markdown + return code.replace(/```python|```/g, "").trim(); +} + +// Esegue lo script OpenCV generato sull'immagine e restituisce il JSON su stdout. +async function runOpenCVScript(script: string, imagePath: string): Promise { + if (!script) return ""; + try { + const { stdout } = await execFileAsync("python3", ["-c", script, imagePath], { + timeout: 20_000, + }); + return stdout.trim(); + } catch { + return ""; + } +} + // Cache anti-ridondanza: evita ricerche web ripetute sullo stesso topic in una // finestra breve (il multi-step reasoning lancia prompt simili in sequenza). let lastSearch = { topic: "", time: 0 }; @@ -1692,6 +1763,14 @@ export default function agyExtension(pi: ExtensionAPI) { }[] = []; let finalText = ""; + // Genera DINAMICAMENTE lo script OpenCV specifico per i requisiti (una volta, + // i requisiti sono fissi) e lo riusa in ogni iterazione del loop. + let cvScript = ""; + if (useCV) { + toolUpdate(onUpdate, "agy_create_verified: generazione script OpenCV dinamico..."); + cvScript = await generateOpenCVScript(requirements); + } + for (let iter = 1; iter <= maxIter; iter++) { toolUpdate( onUpdate, @@ -1734,15 +1813,33 @@ export default function agyExtension(pi: ExtensionAPI) { signal, }); const verdict = judgeRes.text.trim(); - const pass = /^\s*PASS:?/i.test(verdict); + const visionPass = /^\s*PASS:?/i.test(verdict); lastIssues = verdict; - // 3) Metriche OpenCV oggettive (opzionale) + // 3) Verifica OpenCV DINAMICA (script generato dall'LLM per i requisiti) let cvMetrics = ""; - if (useCV) { - cvMetrics = await runOpenCV(imgPath); + let ocvPass: boolean | null = null; + if (useCV && cvScript) { + const out = await runOpenCVScript(cvScript, imgPath); + cvMetrics = out; + try { + const parsed = JSON.parse(out); + ocvPass = typeof parsed.pass === "boolean" ? parsed.pass : null; + if (typeof parsed.score === "number") cvMetrics += `\nscore: ${parsed.score}`; + if (Array.isArray(parsed.findings) && parsed.findings.length) + cvMetrics += `\nfindings: ${parsed.findings.join("; ")}`; + } catch { + ocvPass = null; + } + // Se OpenCV rileva non conformità, le aggiunge alle issue per guidare la rigenerazione + if (ocvPass === false) { + lastIssues = `${verdict}\nVerifica OpenCV: ${cvMetrics}`; + } } + // Conforme solo se visione PASS e (se disponibile) anche OpenCV PASS + const pass = visionPass && (ocvPass === null || ocvPass === true); + report.push({ iteration: iter, pass, imagePath: imgPath, verdict, opencv: cvMetrics }); if (pass) {