nexu-io/open-design · error · Error

nano-banana image response missing candidates[].content.part

Error message

nano-banana image response missing candidates[].content.parts[].inlineData.data

What it means

Thrown by inlineImageBytesFromGenerateContent when the parsed Gemini response has no candidate whose content.parts contain an `inlineData.data` base64 string. The helper walks candidates→content→parts→inlineData.data and only succeeds if it finds a non-empty string; an empty candidates array, a finishReason-filtered response, or a prompt-blocked response all reach this throw.

Source

Thrown at apps/daemon/src/media/index.ts:1769

    || aspect === '3:4'
  ) {
    return aspect;
  }
  return '1:1';
}

function inlineImageBytesFromGenerateContent(data: any): Buffer {
  const candidates = Array.isArray(data?.candidates) ? data.candidates : [];
  for (const candidate of candidates) {
    const parts = Array.isArray(candidate?.content?.parts) ? candidate.content.parts : [];
    for (const part of parts) {
      const inline = part?.inlineData;
      if (typeof inline?.data === 'string' && inline.data) {
        return Buffer.from(inline.data, 'base64');
      }
    }
  }
  throw new Error('nano-banana image response missing candidates[].content.parts[].inlineData.data');
}

function sniffImageExt(bytes: Buffer): string {
  if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
    return '.jpg';
  }
  if (
    bytes.length >= 8
    && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47
  ) {
    return '.png';
  }
  if (
    bytes.length >= 12
    && bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46
    && bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50
  ) {
    return '.webp';

View on GitHub (pinned to 5be4028344)

Solutions

  1. Inspect the full Gemini response — look at promptFeedback.blockReason and candidates[].finishReason first.
  2. If blocked, reword the prompt to avoid flagged categories and retry.
  3. Confirm ctx.wireModel resolves to the image-capable Gemini slug (gemini-2.5-flash-image family), not the text variant.
  4. If finishReason is MAX_TOKENS, ensure the request body does not cap outputTokens below image-payload size.

Example fix

// before
throw new Error('nano-banana image response missing candidates[].content.parts[].inlineData.data');

// after — surface why nothing came back
const block = data?.promptFeedback?.blockReason;
const reasons = (data?.candidates || []).map(c => c?.finishReason).join(',');
throw new Error(
  `nano-banana image produced no inlineData (block=${block || 'none'}, finishReasons=[${reasons || 'none'}])`,
);
Defensive patterns

Strategy: type-guard

Validate before calling

// Inspect block/finish reasons before declaring 'missing inlineData'
function extractGeminiImageBytes(data: any): Buffer {
  const candidates = Array.isArray(data?.candidates) ? data.candidates : [];
  for (const c of candidates) {
    for (const p of c?.content?.parts || []) {
      if (typeof p?.inlineData?.data === 'string' && p.inlineData.data) {
        return Buffer.from(p.inlineData.data, 'base64');
      }
    }
  }
  const block = data?.promptFeedback?.blockReason;
  const finish = candidates.map((c: any) => c?.finishReason).join(',') || 'none';
  throw new Error(`nano-banana produced no image (block=${block || 'none'}, finish=[${finish}])`);
}

Type guard

interface GeminiImagePart { inlineData?: { data?: string } }
function hasGeminiInlineImage(data: any): boolean {
  return Array.isArray(data?.candidates) && data.candidates.some((c: any) =>
    Array.isArray(c?.content?.parts) && c.content.parts.some((p: GeminiImagePart) =>
      typeof p?.inlineData?.data === 'string' && p.inlineData.data.length > 0));
}

Try / catch

try {
  bytes = extractGeminiImageBytes(data);
} catch (e) {
  if (/block=/.test(String(e))) { /* content filter — surface to user as 'prompt blocked' */ }
  throw e;
}

Prevention

When it happens

Trigger: Gemini returned `candidates:[]` (nothing generated), `promptFeedback.blockReason` set (safety block with no candidate), candidates whose parts contain only text (a textual refusal), or an empty `inlineData.data` field.

Common situations: Prompt tripped Gemini safety filters; the model variant selected is the text-only flash and not the image flash; the key lacks the image generation scope; the response was truncated by maxOutputTokens before the image landed.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/5b8a0f3c89db5368. Report an issue: GitHub.