import { z } from "zod"; import axios from "axios"; import fs from "fs"; import path from "path"; import { withAuth } from "../../auth/index.js"; import { handleToolError } from "../helpers/errorHandler.js"; import { withAuthParams } from "../helpers/authSchema.js"; import { pythonPost } from "../helpers/pythonServerClient.js"; import { resolveCurrentProjectDir } from "../files/helpers.js"; // --- Verificacion de creditos --- const WS_BASE = "https://ws.cocosolution.com/api/handler_acaicode.php"; function getAcaiToken() { const projectDir = resolveCurrentProjectDir(); if (!projectDir) return null; try { const acaiFile = path.join(projectDir, ".acai"); const data = JSON.parse(fs.readFileSync(acaiFile, "utf-8")); return data.token || null; } catch { return null; } } async function checkCredits() { const token = getAcaiToken(); if (!token) return false; // Si no hay token, no bloquear const testParam = process.env.STRIPE_MODE === "test" ? "&test" : ""; try { const resp = await axios.put(`${WS_BASE}?action=getUsageLimits${testParam}`, {}, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, timeout: 10000, }); return resp.data?.data?.exceeded === true; } catch { return false; } } export function registerGenerateImageTool(server) { server.tool( "generate_image", `Generate an AI image and save it to the project's uploads folder. Returns preview URLs plus the recommended upload URL for upload_record_image. In Forge environments, prefer uploadUrl (or fullUrl if uploadUrl is absent) over dockerUrl when assigning the image to a record field.`, withAuthParams({ prompt: z.string().describe("Description of the image to generate"), width: z.number().optional().describe("Image width in pixels (default: 1024)"), height: z.number().optional().describe("Image height in pixels (default: 1024)"), style: z.string().optional().describe("Image style hint to add to prompt (e.g., 'photographic', 'digital-art', 'minimalist')"), fileName: z.string().optional().describe("Custom filename (without extension). If not provided, auto-generated."), }), { readOnlyHint: false, destructiveHint: false }, withAuth(async ({ prompt, width = 1024, height = 1024, style, fileName }, extra) => { try { // Verificar creditos antes de generar const exceeded = await checkCredits(); if (exceeded) { return { content: [{ type: "text", text: "Error: No te quedan creditos. Mejora tu plan para seguir usando el asistente." }], isError: true, }; } const projectSlug = path.basename(resolveCurrentProjectDir()); const safeFileName = fileName || `generated-${Date.now()}`; const destRelativePath = `cms/uploads/generated/${safeFileName}.jpg`; const fullPrompt = style ? `${style} style: ${prompt}` : prompt; let result; try { result = await pythonPost("/api/generate-image", { project: projectSlug, prompt: fullPrompt, destRelativePath, }, 180000); // 3min timeout para generacion IA } catch (pyErr) { return handleToolError(new Error(`Python generate-image failed: ${pyErr.response?.data?.error || pyErr.message}`), 'generate_image', { prompt }); } if (!result?.success) { return { content: [{ type: "text", text: JSON.stringify({ error: result?.error || "Generation failed" }, null, 2) }], isError: true }; } return { content: [{ type: "text", text: JSON.stringify({ success: true, message: `Image generated and saved to ${result.relativePath}`, uploadUrl: result.fullUrl || result.dockerUrl, fullUrl: result.fullUrl || result.dockerUrl, relativePath: result.relativePath, fileName: result.fileName, size: result.size, }, null, 2), }], }; } catch (error) { return handleToolError(error, "generate_image", { prompt }); } }) ); }