


























@@ -1,13 +1,19 @@
1-import type { ImageGenerationProvider } from "openclaw/plugin-sdk/image-generation";
2-import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
1+import {
2+generatedImageAssetFromBase64,
3+type GeneratedImageAsset,
4+type ImageGenerationProvider,
5+} from "openclaw/plugin-sdk/image-generation";
36import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
47import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
58import {
69assertOkOrThrowHttpError,
710postJsonRequest,
811sanitizeConfiguredModelProviderRequest,
912} from "openclaw/plugin-sdk/provider-http";
10-import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
13+import {
14+normalizeLowercaseStringOrEmpty,
15+normalizeOptionalString,
16+} from "openclaw/plugin-sdk/string-coerce-runtime";
1117import { normalizeGoogleModelId, resolveGoogleGenerativeAiHttpRequestConfig } from "./api.js";
12181319const DEFAULT_GOOGLE_IMAGE_MODEL = "gemini-3.1-flash-image-preview";
@@ -32,23 +38,11 @@ const GOOGLE_SUPPORTED_ASPECT_RATIOS = [
3238"21:9",
3339] as const;
344035-type GoogleInlineDataPart = {
36-mimeType?: string;
37-mime_type?: string;
38-data?: string;
39-};
40-41-type GoogleGenerateImageResponse = {
42-candidates?: Array<{
43-content?: {
44-parts?: Array<{
45-text?: string;
46-inlineData?: GoogleInlineDataPart;
47-inline_data?: GoogleInlineDataPart;
48-}>;
49-};
50-}>;
51-};
41+const GOOGLE_IMAGE_MALFORMED_RESPONSE = "Google image generation response malformed";
42+43+function isRecord(value: unknown): value is Record<string, unknown> {
44+return Boolean(value && typeof value === "object" && !Array.isArray(value));
45+}
52465347function normalizeGoogleImageModel(model: string | undefined): string {
5448const trimmed = model?.trim();
@@ -89,6 +83,56 @@ function mapSizeToImageConfig(
8983};
9084}
918586+function googleResponseParts(payload: unknown): unknown[] {
87+if (!isRecord(payload)) {
88+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
89+}
90+const candidates = payload.candidates;
91+if (candidates === undefined || candidates === null) {
92+return [];
93+}
94+if (!Array.isArray(candidates)) {
95+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
96+}
97+98+const parts: unknown[] = [];
99+for (const candidate of candidates) {
100+if (!isRecord(candidate)) {
101+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
102+}
103+const content = candidate.content;
104+if (content === undefined || content === null) {
105+continue;
106+}
107+if (!isRecord(content)) {
108+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
109+}
110+const candidateParts = content.parts;
111+if (candidateParts === undefined || candidateParts === null) {
112+continue;
113+}
114+if (!Array.isArray(candidateParts)) {
115+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
116+}
117+parts.push(...candidateParts);
118+}
119+return parts;
120+}
121+122+function googleInlineDataFromPart(part: unknown): Record<string, unknown> | undefined {
123+if (!isRecord(part)) {
124+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
125+}
126+const inline = part.inlineData ?? part.inline_data;
127+if (inline === undefined || inline === null) {
128+return undefined;
129+}
130+if (!isRecord(inline)) {
131+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
132+}
133+return inline;
134+}
135+92136export function buildGoogleImageGenerationProvider(): ImageGenerationProvider {
93137return {
94138id: "google",
@@ -184,26 +228,32 @@ export function buildGoogleImageGenerationProvider(): ImageGenerationProvider {
184228try {
185229await assertOkOrThrowHttpError(res, "Google image generation failed");
186230187-const payload = (await res.json()) as GoogleGenerateImageResponse;
231+const payload = await res.json();
188232let imageIndex = 0;
189-const images = (payload.candidates ?? [])
190-.flatMap((candidate) => candidate.content?.parts ?? [])
191-.map((part) => {
192-const inline = part.inlineData ?? part.inline_data;
193-const data = inline?.data?.trim();
194-if (!data) {
195-return null;
196-}
197-const mimeType = inline?.mimeType ?? inline?.mime_type ?? DEFAULT_OUTPUT_MIME;
198-const extension = extensionForMime(mimeType)?.slice(1) ?? "png";
199-imageIndex += 1;
200-return {
201-buffer: Buffer.from(data, "base64"),
202- mimeType,
203-fileName: `image-${imageIndex}.${extension}`,
204-};
205-})
206-.filter((entry): entry is NonNullable<typeof entry> => entry !== null);
233+const images: GeneratedImageAsset[] = [];
234+for (const part of googleResponseParts(payload)) {
235+const inline = googleInlineDataFromPart(part);
236+if (!inline) {
237+continue;
238+}
239+const data = normalizeOptionalString(inline.data);
240+if (!data) {
241+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
242+}
243+const image = generatedImageAssetFromBase64({
244+base64: data,
245+index: imageIndex,
246+mimeType:
247+normalizeOptionalString(inline.mimeType) ??
248+normalizeOptionalString(inline.mime_type) ??
249+DEFAULT_OUTPUT_MIME,
250+});
251+if (!image) {
252+throw new Error(GOOGLE_IMAGE_MALFORMED_RESPONSE);
253+}
254+imageIndex += 1;
255+images.push(image);
256+}
207257208258if (images.length === 0) {
209259throw new Error("Google image generation response missing image data");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。