mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
Add image generation for Antigravity provider via gemini-3.1-flash-image and gemini-3-pro-image, exposed through Text to Image UI and /v1/images/generations. - registry: serviceKinds ['llm','image'] + image model entries - executor: image model detection + image_gen request envelope - chatCore: force stream=false for image models (generateContent) - nonStreamingHandler: parse inlineData -> markdown image - imageGenerationCore: useExecutor fast-path for executor delegation - imageProviders/antigravity: image adapter with image input support - usage/google: image models in quota whitelist Co-authored-by: Cursor <cursoragent@cursor.com>
73 lines
2.4 KiB
JavaScript
73 lines
2.4 KiB
JavaScript
// Antigravity image adapter - delegates to the executor for correct request
|
|
// envelope (project, model, requestType, sessionId) and auth headers.
|
|
import { nowSec } from "./_base.js";
|
|
import { getExecutor } from "../../executors/index.js";
|
|
|
|
// Convert image input (data URI or raw base64) to Gemini inlineData part
|
|
function resolveImageInput(input) {
|
|
if (!input || typeof input !== "string") return null;
|
|
// data:image/png;base64,... format
|
|
const dataUriMatch = input.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
if (dataUriMatch) {
|
|
return { inlineData: { mimeType: dataUriMatch[1], data: dataUriMatch[2] } };
|
|
}
|
|
// Raw base64 string (assume PNG)
|
|
if (/^[A-Za-z0-9+/]/.test(input) && input.length > 100 && !input.startsWith("http")) {
|
|
return { inlineData: { mimeType: "image/png", data: input } };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export default {
|
|
// Delegate to executor instead of building URL/headers/body manually
|
|
useExecutor: true,
|
|
|
|
// Stubs - required by imageGenerationCore interface but unused with useExecutor
|
|
buildUrl: () => "",
|
|
buildHeaders: () => ({}),
|
|
buildBody: () => ({}),
|
|
|
|
async executeViaExecutor(model, body, credentials, log) {
|
|
const executor = getExecutor("antigravity");
|
|
if (!executor) throw new Error("Antigravity executor not found");
|
|
|
|
// Build parts: text prompt + optional input image for editing
|
|
const parts = [{ text: body.prompt }];
|
|
const imageInput = body.image || (Array.isArray(body.images) && body.images[0]);
|
|
if (imageInput) {
|
|
const inlineData = resolveImageInput(imageInput);
|
|
if (inlineData) parts.unshift(inlineData);
|
|
}
|
|
|
|
const chatBody = {
|
|
contents: [{ role: "user", parts }],
|
|
};
|
|
|
|
const result = await executor.execute({
|
|
model,
|
|
body: chatBody,
|
|
stream: false,
|
|
credentials,
|
|
log,
|
|
});
|
|
|
|
if (!result.response.ok) {
|
|
const text = await result.response.text();
|
|
throw new Error(text || `HTTP ${result.response.status}`);
|
|
}
|
|
|
|
return result.response.json();
|
|
},
|
|
|
|
normalize: (responseBody, prompt) => {
|
|
const candidates = responseBody.candidates || responseBody.response?.candidates || [];
|
|
const parts = candidates[0]?.content?.parts || [];
|
|
const images = parts.filter((p) => p.inlineData?.data).map((p) => ({
|
|
b64_json: p.inlineData.data,
|
|
}));
|
|
return {
|
|
created: nowSec(),
|
|
data: images.length > 0 ? images : [{ b64_json: "", revised_prompt: prompt }],
|
|
};
|
|
},
|
|
}; |