openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
211 lines (210 loc) • 7.96 kB
JavaScript
import { w as parseStrictPositiveInteger } from "../../number-coercion-CLj0HTDM.js";
import { c as isRecord } from "../../record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "../../string-coerce-CIXf7egm.js";
import { m as readProviderJsonResponse, n as assertOkOrThrowHttpError } from "../../provider-http-errors-U-nhuk_f.js";
import { p as sanitizeConfiguredModelProviderRequest } from "../../provider-request-config-DIOYidiO.js";
import { t as isProviderApiKeyConfigured } from "../../provider-auth-availability-BUrlw84-.js";
import { n as resolveGeneratedMediaMaxBytes } from "../../configured-max-bytes-CTSUbFDK.js";
import "../../provider-auth-BeZ7NZUU.js";
import { l as postJsonRequest } from "../../shared-DCDpYrmy.js";
import "../../number-runtime-Cy4drVnh.js";
import "../../string-coerce-runtime-GQa0ehRA.js";
import "../../media-generation-runtime-Dd81xPqM.js";
import { a as resolveApiKeyForProvider } from "../../provider-auth-runtime-BqyR7mkf.js";
import "../../provider-http-k9RMI7iG.js";
import { l as resolveInlineImageJsonResponseMaxBytes, n as generatedImageAssetFromBase64 } from "../../image-generation-B61kkjvL.js";
import { n as normalizeGoogleModelId } from "../../model-id-CAmKILzd.js";
import { t as resolveGoogleGenerativeAiHttpRequestConfig } from "../../api-BIkqybAZ.js";
import { n as toStandardGoogleProviderBase64 } from "../../base64-L17PENqC.js";
//#region extensions/google/image-generation-provider.ts
const DEFAULT_GOOGLE_IMAGE_MODEL = "gemini-3.1-flash-image";
const DEFAULT_IMAGE_TIMEOUT_MS = 18e4;
const DEFAULT_OUTPUT_MIME = "image/png";
const GOOGLE_MAX_IMAGE_RESULTS = 4;
const GOOGLE_SUPPORTED_SIZES = [
"1024x1024",
"1024x1536",
"1536x1024",
"1024x1792",
"1792x1024"
];
const GOOGLE_SUPPORTED_ASPECT_RATIOS = [
"1:1",
"2:3",
"3:2",
"3:4",
"4:3",
"4:5",
"5:4",
"9:16",
"16:9",
"21:9"
];
const GOOGLE_IMAGE_MALFORMED_RESPONSE = "Google image generation response malformed";
function normalizeGoogleImageModel(model) {
const trimmed = model?.trim();
return normalizeGoogleModelId(trimmed || DEFAULT_GOOGLE_IMAGE_MODEL);
}
function mapSizeToImageConfig(size) {
const trimmed = size?.trim();
if (!trimmed) return;
const normalized = normalizeLowercaseStringOrEmpty(trimmed);
const aspectRatio = (/* @__PURE__ */ new Map([
["1024x1024", "1:1"],
["1024x1536", "2:3"],
["1536x1024", "3:2"],
["1024x1792", "9:16"],
["1792x1024", "16:9"]
])).get(normalized);
const [widthRaw, heightRaw] = normalized.split("x");
const width = parseStrictPositiveInteger(widthRaw);
const height = parseStrictPositiveInteger(heightRaw);
if (width === void 0 || height === void 0) return;
const longestEdge = Math.max(width, height);
const imageSize = longestEdge >= 3072 ? "4K" : longestEdge >= 1536 ? "2K" : void 0;
if (!aspectRatio && !imageSize) return;
return {
...aspectRatio ? { aspectRatio } : {},
...imageSize ? { imageSize } : {}
};
}
function googleResponseParts(payload) {
if (!isRecord(payload)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const candidates = payload.candidates;
if (candidates === void 0 || candidates === null) return [];
if (!Array.isArray(candidates)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const parts = [];
for (const candidate of candidates) {
if (!isRecord(candidate)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const content = candidate.content;
if (content === void 0 || content === null) continue;
if (!isRecord(content)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const candidateParts = content.parts;
if (candidateParts === void 0 || candidateParts === null) continue;
if (!Array.isArray(candidateParts)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
parts.push(...candidateParts);
}
return parts;
}
function googleInlineDataFromPart(part) {
if (!isRecord(part)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const inline = part.inlineData ?? part.inline_data;
if (inline === void 0 || inline === null) return;
if (!isRecord(inline)) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
return inline;
}
function buildGoogleImageGenerationProvider() {
return {
id: "google",
label: "Google",
defaultModel: DEFAULT_GOOGLE_IMAGE_MODEL,
models: [DEFAULT_GOOGLE_IMAGE_MODEL, "gemini-3-pro-image"],
isConfigured: (ctx) => isProviderApiKeyConfigured({
provider: "google",
...ctx
}),
capabilities: {
generate: {
maxCount: GOOGLE_MAX_IMAGE_RESULTS,
supportsSize: true,
supportsAspectRatio: true,
supportsResolution: true
},
edit: {
enabled: true,
maxCount: GOOGLE_MAX_IMAGE_RESULTS,
maxInputImages: 5,
supportsSize: true,
supportsAspectRatio: true,
supportsResolution: true
},
geometry: {
sizes: [...GOOGLE_SUPPORTED_SIZES],
aspectRatios: [...GOOGLE_SUPPORTED_ASPECT_RATIOS],
resolutions: [
"1K",
"2K",
"4K"
]
}
},
async generateImage(req) {
const auth = await resolveApiKeyForProvider({
provider: "google",
cfg: req.cfg,
agentDir: req.agentDir,
store: req.authStore
});
if (!auth.apiKey) throw new Error("Google API key missing");
const model = normalizeGoogleImageModel(req.model);
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } = resolveGoogleGenerativeAiHttpRequestConfig({
apiKey: auth.apiKey,
baseUrl: req.cfg?.models?.providers?.google?.baseUrl,
request: sanitizeConfiguredModelProviderRequest(req.cfg?.models?.providers?.google?.request),
capability: "image",
transport: "http"
});
const imageConfig = mapSizeToImageConfig(req.size);
const inputParts = (req.inputImages ?? []).map((image) => ({ inlineData: {
mimeType: image.mimeType,
data: image.buffer.toString("base64")
} }));
const resolvedImageConfig = {
...imageConfig,
...req.aspectRatio?.trim() ? { aspectRatio: req.aspectRatio.trim() } : {},
...req.resolution ? { imageSize: req.resolution } : {}
};
const { response: res, release } = await postJsonRequest({
url: `${baseUrl}/models/${model}:generateContent`,
headers,
body: {
contents: [{
role: "user",
parts: [...inputParts, { text: req.prompt }]
}],
generationConfig: {
responseModalities: ["TEXT", "IMAGE"],
...Object.keys(resolvedImageConfig).length > 0 ? { imageConfig: resolvedImageConfig } : {}
}
},
timeoutMs: req.timeoutMs ?? DEFAULT_IMAGE_TIMEOUT_MS,
fetchFn: fetch,
pinDns: false,
allowPrivateNetwork,
ssrfPolicy: req.ssrfPolicy,
dispatcherPolicy
});
try {
await assertOkOrThrowHttpError(res, "Google image generation failed");
const payload = await readProviderJsonResponse(res, "google.image-generation", { maxBytes: resolveInlineImageJsonResponseMaxBytes(GOOGLE_MAX_IMAGE_RESULTS, resolveGeneratedMediaMaxBytes(req.cfg, "image")) });
let imageIndex = 0;
const images = [];
for (const part of googleResponseParts(payload)) {
const inline = googleInlineDataFromPart(part);
if (!inline) continue;
const data = normalizeOptionalString(inline.data);
if (!data) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const standardData = toStandardGoogleProviderBase64(data);
if (!standardData) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
const image = generatedImageAssetFromBase64({
base64: standardData,
index: imageIndex,
mimeType: normalizeOptionalString(inline.mimeType) ?? normalizeOptionalString(inline.mime_type) ?? DEFAULT_OUTPUT_MIME
});
if (!image) throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
imageIndex += 1;
images.push(image);
}
if (images.length === 0) throw new Error("Google image generation response missing image data");
return {
images,
model
};
} finally {
await release();
}
}
};
}
//#endregion
export { buildGoogleImageGenerationProvider };