feat(images): direct Antigravity image API for image tools with CLI fallback
Replace the agent/CLI image path with a direct call to the Antigravity
Cloud Code gateway (same OAuth token and project discovery already used by
the provider), keeping the CLI as an automatic fallback.
Protocol validated with 15 live probes on 2026-09-13:
- POST v1internal:generateContent (non-streaming), requestType "image_gen",
responseModalities ["IMAGE"], imageConfig {aspectRatio, imageSize}
- image returned as candidates[0].content.parts[].inlineData (base64, jpeg);
parts with thought=true are intermediate images and must be skipped
- 512/2K/4K verified (512x512 .. 5504x3072, 7-25s, 1.1-2.9K output tokens)
- image input works (single, multiple) and editing honours the input when
imageConfig.aspectRatio is supplied; HTTP 200 without images carries a
finishMessage explaining the refusal
- gemini-3-pro-image is not entitled on this account (HTTP 404)
- the image model quota is shared and can be exhausted (429 QUOTA_EXHAUSTED
with a "reset after" delay)
Changes:
- add antgGenerateImages(): direct client with token refresh retry, image
part parsing and finishMessage surfacing
- add antgSaveImages() and agyImageTask(): direct-first runner with automatic
CLI fallback, reporting engine, imagePaths, usage and fallback reason
- add antgParseQuotaDelayMs() + blocked-until guard so an exhausted image
quota skips the direct attempt instead of retrying it every time
- route agy_generate, agy_edit, agy_inpaint, agy_style_transfer, agy_compose
and agy_character through agyImageTask; add imageSize to agy_generate
- fix extractImagePath(): it matched the IMAGE_PATH placeholder echoed from
the prompt and returned a bogus path on CLI failures; now only existing
absolute image paths are accepted
Verified: module loads, tools invoke agy_generate through the direct engine
(8.6s, 1199 output tokens, file saved to outputDir), the CLI fallback fires
with an explicit reason on 429, and extractImagePath passes 5/5 cases tested
against the real function extracted from this file.
This commit is contained in:
+312
-38
@@ -229,10 +229,19 @@ async function runAgy(
|
|||||||
// Helper: estrarre il percorso immagine dall'output
|
// Helper: estrarre il percorso immagine dall'output
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function extractImagePath(text: string): string | undefined {
|
function extractImagePath(text: string): string | undefined {
|
||||||
const m =
|
// Candidati: prima la riga IMAGE_PATH, poi qualsiasi path immagine assoluto nel testo.
|
||||||
text.match(/IMAGE_PATH:\s*(\S+)/i) ||
|
// I placeholder ecoati dal prompt (es. "IMAGE_PATH: <percorso assoluto ...>") e i
|
||||||
text.match(/(\/[^\s]+\.(?:png|jpe?g|webp|gif))/i);
|
// path inesistenti vengono scartati: senza il filtro si ottenevano percorsi fasulli.
|
||||||
return m ? m[1] : undefined;
|
const candidates: string[] = [];
|
||||||
|
const m = text.match(/IMAGE_PATH:\s*(\S+)/i);
|
||||||
|
if (m?.[1]) candidates.push(m[1]);
|
||||||
|
for (const mm of text.matchAll(/(\/[^\s"'`]+?\.(?:png|jpe?g|webp|gif))/gi)) candidates.push(mm[1]);
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
const clean = candidate.replace(/[.,;:)\]}>]+$/, "");
|
||||||
|
if (!clean || clean.includes("<") || clean.includes(">")) continue;
|
||||||
|
if (fs.existsSync(clean)) return clean;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -643,6 +652,121 @@ async function executeAgy(opts: AgyExecOptions) {
|
|||||||
const IMG_SUFFIX =
|
const IMG_SUFFIX =
|
||||||
"\n\nAlla fine della risposta, scrivi su una riga esattamente: IMAGE_PATH: <percorso assoluto dell'immagine generata>";
|
"\n\nAlla fine della risposta, scrivi su una riga esattamente: IMAGE_PATH: <percorso assoluto dell'immagine generata>";
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Task immagine unificato: prima il protocollo diretto Antigravity (nessun
|
||||||
|
// subprocess, ~7-25s), con fallback automatico al percorso agy CLI se il
|
||||||
|
// modello non è disponibile o la risposta non contiene immagini.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const AGY_IMAGE_OUT_DIR = path.join(AGY_CHAT_DIR, "generated_images");
|
||||||
|
|
||||||
|
function antgSaveImages(images: AntgInlineImage[], outputDir: string | undefined, prefix: string): string[] {
|
||||||
|
const dir = outputDir ?? AGY_IMAGE_OUT_DIR;
|
||||||
|
const saved: string[] = [];
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
} catch {
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
const stamp = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
||||||
|
images.forEach((img, i) => {
|
||||||
|
const ext = img.mimeType.includes("jpeg") ? "jpg" : img.mimeType.includes("webp") ? "webp" : "png";
|
||||||
|
const file = path.join(dir, `${prefix}_${stamp}_${i}.${ext}`);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(file, Buffer.from(img.data, "base64"));
|
||||||
|
saved.push(file);
|
||||||
|
} catch {
|
||||||
|
/* ignora */
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return saved;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function agyImageTask(opts: {
|
||||||
|
prompt: string; // prompt in stile CLI (può contenere i percorsi + IMG_SUFFIX)
|
||||||
|
images?: string[];
|
||||||
|
aspectRatio?: string;
|
||||||
|
imageSize?: string;
|
||||||
|
model?: string;
|
||||||
|
outputDir?: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
label: string;
|
||||||
|
}): Promise<{
|
||||||
|
text: string;
|
||||||
|
imagePaths: string[];
|
||||||
|
engine: "direct" | "cli";
|
||||||
|
usage?: any;
|
||||||
|
exitCode?: number;
|
||||||
|
directError?: string;
|
||||||
|
}> {
|
||||||
|
const imageModel = opts.model && /image/i.test(opts.model) ? opts.model : ANTG_IMAGE_MODEL_DEFAULT;
|
||||||
|
let directError: string | undefined;
|
||||||
|
const quotaBlocked = Date.now() < antgImageBlockedUntilMs;
|
||||||
|
try {
|
||||||
|
if (quotaBlocked) {
|
||||||
|
const mins = Math.ceil((antgImageBlockedUntilMs - Date.now()) / 60000);
|
||||||
|
throw new Error(`quota API immagine esaurita (reset stimato tra ~${mins} min): ${antgImageBlockedReason}`);
|
||||||
|
}
|
||||||
|
// Prompt per il modello: niente IMG_SUFFIX, percorsi → riferimenti allegati,
|
||||||
|
// più un vincolo esplicito di composizione (migliora affidabilità su input).
|
||||||
|
let prompt = opts.prompt.split(IMG_SUFFIX).join("").trim();
|
||||||
|
for (const file of opts.images ?? []) prompt = prompt.split(file).join("(immagine allegata)");
|
||||||
|
if (opts.images?.length) {
|
||||||
|
prompt += "\nMantieni esattamente la composizione e l'aspect ratio dell'immagine di input; non ritagliare e non aggiungere testo.";
|
||||||
|
}
|
||||||
|
const res = await antgGenerateImages({
|
||||||
|
prompt,
|
||||||
|
images: opts.images,
|
||||||
|
aspectRatio: opts.aspectRatio,
|
||||||
|
imageSize: opts.imageSize,
|
||||||
|
model: imageModel,
|
||||||
|
signal: opts.signal,
|
||||||
|
});
|
||||||
|
if (!res.images.length) {
|
||||||
|
throw new Error(
|
||||||
|
res.finishMessage
|
||||||
|
? `il modello non ha prodotto immagini: ${res.finishMessage.slice(0, 200)}`
|
||||||
|
: `nessuna immagine nella risposta (finishReason=${res.finishReason ?? "?"})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const imagePaths = antgSaveImages(res.images, opts.outputDir, opts.label.replace(/[^a-z0-9]+/gi, "_"));
|
||||||
|
const outTok = res.usage?.candidatesTokenCount;
|
||||||
|
const text =
|
||||||
|
(res.text ? `${res.text}\n\n` : "") +
|
||||||
|
`✅ ${opts.label}: ${imagePaths.length} immagine/i via API diretta Antigravity (${res.model}${outTok ? `, ${outTok} token output` : ""}).` +
|
||||||
|
(imagePaths.length ? `\n${imagePaths.map((f) => `- ${f}`).join("\n")}` : "");
|
||||||
|
return { text, imagePaths, engine: "direct", usage: res.usage };
|
||||||
|
} catch (e: any) {
|
||||||
|
directError = e?.message ?? String(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: percorso CLI agy (comportamento precedente all'integrazione diretta)
|
||||||
|
const res = await executeAgy({
|
||||||
|
prompt: opts.prompt,
|
||||||
|
mode: "image",
|
||||||
|
model: opts.model,
|
||||||
|
stateless: true,
|
||||||
|
filePaths: opts.images,
|
||||||
|
outputDir: opts.outputDir,
|
||||||
|
yolo: true,
|
||||||
|
signal: opts.signal,
|
||||||
|
});
|
||||||
|
// Il path deve esistere davvero: se il CLI fallisce (es. quota) l'output può
|
||||||
|
// contenere il placeholder del prompt, che non è un'immagine.
|
||||||
|
const cliPath = res.imagePath && fs.existsSync(res.imagePath) ? res.imagePath : undefined;
|
||||||
|
const note = cliPath
|
||||||
|
? `\n\nℹ️ Fallback CLI agy (percorso diretto non disponibile): ${directError ?? "motivo non specificato"}`
|
||||||
|
: `\n\n⚠️ Nessuna immagine prodotta. API diretta: ${directError ?? "errore non specificato"}${
|
||||||
|
res.exitCode ? ` | CLI agy exitCode ${res.exitCode}` : ""
|
||||||
|
}`;
|
||||||
|
return {
|
||||||
|
text: res.text + note,
|
||||||
|
imagePaths: cliPath ? [cliPath] : [],
|
||||||
|
engine: "cli",
|
||||||
|
exitCode: res.exitCode,
|
||||||
|
directError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Registrazione microfono (F12) e trascrizione
|
// Registrazione microfono (F12) e trascrizione
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -1329,6 +1453,162 @@ async function antgLoadCodeAssist(token: string, base: string): Promise<string>
|
|||||||
return String(pid);
|
return String(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Generazione immagini via protocollo diretto Antigravity (stesso token OAuth
|
||||||
|
// del canale account). Validato con probe il 13/09/2026:
|
||||||
|
// POST v1internal:generateContent (NON streaming), requestType "image_gen",
|
||||||
|
// responseModalities ["IMAGE"], imageConfig {aspectRatio, imageSize};
|
||||||
|
// immagine in candidates[0].content.parts[].inlineData (base64, image/jpeg).
|
||||||
|
// I parts con thought=true sono immagini intermedie di ragionamento: ignorarli.
|
||||||
|
// HTTP 200 può non contenere immagini: in tal caso candidate.finishMessage
|
||||||
|
// spiega il rifiuto (nessun addebito) → si ricade sul percorso CLI.
|
||||||
|
// gemini-3-pro-image NON è disponibile su questo account (HTTP 404).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
const ANTG_IMAGE_MODEL_DEFAULT = "gemini-3.1-flash-image"; // Nano Banana 2
|
||||||
|
const ANTG_IMAGE_MAX_OUTPUT_TOKENS = 32768; // 4K richiede ~2.9K token di output
|
||||||
|
|
||||||
|
// Quota immagine: l'API diretta risponde 429 RESOURCE_EXHAUSTED con
|
||||||
|
// "quota will reset after XhYmZs" quando la capacità del modello è satura.
|
||||||
|
// Finché il blocco è attivo si va diretti al percorso CLI, evitando round-trip
|
||||||
|
// inutili.
|
||||||
|
let antgImageBlockedUntilMs = 0;
|
||||||
|
let antgImageBlockedReason = "";
|
||||||
|
|
||||||
|
function antgParseQuotaDelayMs(message: string): number {
|
||||||
|
const m = message.match(/reset after\s*(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/i);
|
||||||
|
if (!m) return 0;
|
||||||
|
const h = Number(m[1] ?? 0);
|
||||||
|
const min = Number(m[2] ?? 0);
|
||||||
|
const sec = Number(m[3] ?? 0);
|
||||||
|
return ((h * 60 + min) * 60 + sec) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AntgInlineImage {
|
||||||
|
mimeType: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AntgImageCallResult {
|
||||||
|
images: AntgInlineImage[];
|
||||||
|
text: string;
|
||||||
|
finishReason?: string;
|
||||||
|
finishMessage?: string;
|
||||||
|
usage?: any;
|
||||||
|
model: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function antgMimeFromPath(file: string): string {
|
||||||
|
switch (path.extname(file).toLowerCase()) {
|
||||||
|
case ".png":
|
||||||
|
return "image/png";
|
||||||
|
case ".webp":
|
||||||
|
return "image/webp";
|
||||||
|
case ".gif":
|
||||||
|
return "image/gif";
|
||||||
|
case ".jpg":
|
||||||
|
case ".jpeg":
|
||||||
|
return "image/jpeg";
|
||||||
|
default:
|
||||||
|
return "image/png";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function antgGenerateImages(opts: {
|
||||||
|
prompt: string;
|
||||||
|
images?: string[];
|
||||||
|
aspectRatio?: string;
|
||||||
|
imageSize?: string;
|
||||||
|
model?: string;
|
||||||
|
signal?: AbortSignal;
|
||||||
|
}): Promise<AntgImageCallResult> {
|
||||||
|
const model = opts.model ?? ANTG_IMAGE_MODEL_DEFAULT;
|
||||||
|
const { pid, base } = await antgGetProject();
|
||||||
|
const parts: any[] = [];
|
||||||
|
for (const file of opts.images ?? []) {
|
||||||
|
try {
|
||||||
|
parts.push({
|
||||||
|
inlineData: { mimeType: antgMimeFromPath(file), data: fs.readFileSync(file).toString("base64") },
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
throw new Error(`Impossibile leggere l'immagine ${file}: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parts.push({ text: opts.prompt });
|
||||||
|
|
||||||
|
const generationConfig: any = {
|
||||||
|
temperature: 1.0,
|
||||||
|
topP: 0.95,
|
||||||
|
topK: 40,
|
||||||
|
maxOutputTokens: ANTG_IMAGE_MAX_OUTPUT_TOKENS,
|
||||||
|
responseModalities: ["IMAGE"], // 1 sola immagine (TEXT+IMAGE può duplicarla)
|
||||||
|
};
|
||||||
|
const imageConfig: any = {};
|
||||||
|
if (opts.aspectRatio) imageConfig.aspectRatio = opts.aspectRatio;
|
||||||
|
if (opts.imageSize) imageConfig.imageSize = opts.imageSize;
|
||||||
|
if (Object.keys(imageConfig).length) generationConfig.imageConfig = imageConfig;
|
||||||
|
|
||||||
|
const envelope = {
|
||||||
|
project: pid,
|
||||||
|
model,
|
||||||
|
requestType: "image_gen",
|
||||||
|
userAgent: "antigravity",
|
||||||
|
requestId: `agent-${crypto.randomUUID().replace(/-/g, "")}`,
|
||||||
|
request: { contents: [{ role: "user", parts }], generationConfig },
|
||||||
|
};
|
||||||
|
|
||||||
|
const call = (tok: string) =>
|
||||||
|
fetch(`${base}/v1internal:generateContent`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: antgHeaders(tok),
|
||||||
|
body: JSON.stringify(envelope),
|
||||||
|
signal: antgAbortSignal(opts.signal, 300_000),
|
||||||
|
});
|
||||||
|
let resp = await call(await antgGetAccessToken());
|
||||||
|
if (resp.status === 401 || resp.status === 403) {
|
||||||
|
antgToken = null;
|
||||||
|
resp = await call(await antgGetAccessToken());
|
||||||
|
}
|
||||||
|
const raw = await resp.text();
|
||||||
|
if (!resp.ok) {
|
||||||
|
if (resp.status === 429 || /QUOTA_EXHAUSTED|RESOURCE_EXHAUSTED/.test(raw)) {
|
||||||
|
const delay = antgParseQuotaDelayMs(raw);
|
||||||
|
antgImageBlockedUntilMs = Date.now() + (delay || 30 * 60 * 1000);
|
||||||
|
antgImageBlockedReason = raw.replace(/\s+/g, " ").slice(0, 200);
|
||||||
|
}
|
||||||
|
throw new Error(`Antigravity image HTTP ${resp.status}: ${raw.slice(0, 300)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload: any;
|
||||||
|
try {
|
||||||
|
payload = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Risposta immagine non JSON: ${raw.slice(0, 200)}`);
|
||||||
|
}
|
||||||
|
const gem = payload?.response ?? payload;
|
||||||
|
const candidate = gem?.candidates?.[0];
|
||||||
|
const responseParts: any[] = candidate?.content?.parts ?? [];
|
||||||
|
const images: AntgInlineImage[] = [];
|
||||||
|
const texts: string[] = [];
|
||||||
|
for (const part of responseParts) {
|
||||||
|
const inline = part?.inlineData ?? part?.inline_data;
|
||||||
|
if (inline?.data) {
|
||||||
|
if (part.thought === true) continue; // immagine intermedia di ragionamento
|
||||||
|
images.push({ mimeType: inline.mimeType ?? inline.mime_type ?? "image/png", data: inline.data });
|
||||||
|
} else if (typeof part?.text === "string" && part.thought !== true) {
|
||||||
|
texts.push(part.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
images,
|
||||||
|
text: texts.join("\n").trim(),
|
||||||
|
finishReason: candidate?.finishReason,
|
||||||
|
finishMessage: candidate?.finishMessage,
|
||||||
|
usage: gem?.usageMetadata,
|
||||||
|
model,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function antgGetProject(): Promise<{ pid: string; base: string }> {
|
async function antgGetProject(): Promise<{ pid: string; base: string }> {
|
||||||
if (antgProject) return antgProject;
|
if (antgProject) return antgProject;
|
||||||
const token = await antgGetAccessToken();
|
const token = await antgGetAccessToken();
|
||||||
@@ -2122,6 +2402,9 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
text: Type.Optional(Type.String({ description: "Testo da includere nell'immagine (tra virgolette)." })),
|
text: Type.Optional(Type.String({ description: "Testo da includere nell'immagine (tra virgolette)." })),
|
||||||
negative: Type.Optional(Type.String({ description: "Cosa evitare, in framing positivo (es. 'nessun testo')." })),
|
negative: Type.Optional(Type.String({ description: "Cosa evitare, in framing positivo (es. 'nessun testo')." })),
|
||||||
model: Type.Optional(Type.String({ description: "Modello agy." })),
|
model: Type.Optional(Type.String({ description: "Modello agy." })),
|
||||||
|
imageSize: Type.Optional(
|
||||||
|
Type.String({ description: 'Risoluzione immagine via API diretta: "512", "1K", "2K", "4K" (default 1K).' }),
|
||||||
|
),
|
||||||
outputDir: Type.Optional(Type.String({ description: "Cartella dove copiare l'immagine generata." })),
|
outputDir: Type.Optional(Type.String({ description: "Cartella dove copiare l'immagine generata." })),
|
||||||
}),
|
}),
|
||||||
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
||||||
@@ -2138,17 +2421,18 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
const prompt = parts.join(". ") + IMG_SUFFIX;
|
const prompt = parts.join(". ") + IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_generate: generazione immagine...");
|
toolUpdate(onUpdate, "agy_generate: generazione immagine...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
aspectRatio: p.aspectRatio,
|
||||||
|
imageSize: p.imageSize ?? "1K",
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_generate",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2185,19 +2469,17 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
prompt += IMG_SUFFIX;
|
prompt += IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_edit: modifica immagine...");
|
toolUpdate(onUpdate, "agy_edit: modifica immagine...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
images: [p.baseImage],
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
filePaths: [p.baseImage],
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
yolo: true,
|
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_edit",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, baseImage: p.baseImage, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, baseImage: p.baseImage, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2230,19 +2512,17 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
IMG_SUFFIX;
|
IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_inpaint: modifica zona specifica...");
|
toolUpdate(onUpdate, "agy_inpaint: modifica zona specifica...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
images: [p.baseImage],
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
filePaths: [p.baseImage],
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
yolo: true,
|
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_inpaint",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, baseImage: p.baseImage, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, baseImage: p.baseImage, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2274,19 +2554,17 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
IMG_SUFFIX;
|
IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_style_transfer: applica stile...");
|
toolUpdate(onUpdate, "agy_style_transfer: applica stile...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
images: [p.baseImage],
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
filePaths: [p.baseImage],
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
yolo: true,
|
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_style_transfer",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, baseImage: p.baseImage, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, baseImage: p.baseImage, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2319,19 +2597,17 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
IMG_SUFFIX;
|
IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_compose: combina immagini...");
|
toolUpdate(onUpdate, "agy_compose: combina immagini...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
images: p.images,
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
filePaths: p.images,
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
yolo: true,
|
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_compose",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, images: p.images, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, images: p.images, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -2362,19 +2638,17 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
IMG_SUFFIX;
|
IMG_SUFFIX;
|
||||||
|
|
||||||
toolUpdate(onUpdate, "agy_character: mantieni consistenza personaggio...");
|
toolUpdate(onUpdate, "agy_character: mantieni consistenza personaggio...");
|
||||||
const res = await executeAgy({
|
const res = await agyImageTask({
|
||||||
prompt,
|
prompt,
|
||||||
mode: "image",
|
images: [p.referenceImage],
|
||||||
model: p.model,
|
model: p.model,
|
||||||
stateless: true,
|
|
||||||
filePaths: [p.referenceImage],
|
|
||||||
outputDir: p.outputDir,
|
outputDir: p.outputDir,
|
||||||
yolo: true,
|
|
||||||
signal,
|
signal,
|
||||||
|
label: "agy_character",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text", text: res.text }],
|
content: [{ type: "text", text: res.text }],
|
||||||
details: { imagePath: res.imagePath, referenceImage: p.referenceImage, exitCode: res.exitCode },
|
details: { imagePath: res.imagePaths[0], imagePaths: res.imagePaths, referenceImage: p.referenceImage, engine: res.engine, directError: res.directError, exitCode: res.exitCode },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user