HeyPuter/puter · error · HttpError

unknown_error

unknown_error

Error message

Failed to extract image URL from Gemini response

What it means

Thrown by GeminiImageProvider.generate() (GeminiImageProvider.ts:262) after a successful generateContent API call when #extractImageUrl() returns undefined. That helper walks response.candidates[0].content.parts looking for a part with inlineData.data; if no such part exists (no image modality in the response), the provider cannot return an image. Note billing has already been recorded at this point. HTTP 400 with legacyCode unknown_error — the response shape was unexpected.

Source

Thrown at src/backend/drivers/ai-image/providers/gemini/GeminiImageProvider.ts:262

                usageAmount: Math.max(inputTokenCount, 1),
                costOverride: this.#toMicroCents(inputCostInCents),
            },
            {
                usageType: `${usagePrefix}:output:text`,
                usageAmount: Math.max(outputTextTokenCount, 1),
                costOverride: this.#toMicroCents(outputTextCostInCents),
            },
            {
                usageType: `${usagePrefix}:output:image`,
                usageAmount: Math.max(outputImageTokenCount, 1),
                costOverride: this.#toMicroCents(outputImageCostInCents),
            },
        ]);

        const url = this.#extractImageUrl(response);

        if (!url) {
            throw new HttpError(
                400,
                'Failed to extract image URL from Gemini response',
                { legacyCode: 'unknown_error' },
            );
        }

        return url;
    }

    async #generateWithImagen(
        prompt: string,
        selectedModel: IGeminiImageModel,
        params: IGenerateParams,
    ): Promise<string> {
        const actor = Context.get('actor');
        if (!actor) {
            throw new HttpError(401, 'actor not found in context', {
                legacyCode: 'unauthorized',

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Rephrase the prompt to avoid content likely to trip safety filters (violence, explicit, copyright, public figures).
  2. Retry the request — transient non-image responses can occur; if it persists, the prompt/model is the issue.
  3. Inspect the full Gemini response (candidates[0].content.parts, finishReason, safetyRatings) in logs to see why no image was returned.
  4. If using a preview model, switch to a stable model id or check for API deprecation.
Defensive patterns

Strategy: retry

Try / catch

let lastErr;
for (let attempt = 0; attempt < 2; attempt++) {
  try {
    return await provider.generate(params);
  } catch (e) {
    lastErr = e;
    if (e instanceof HttpError && e.status_code === 400 && e.message.includes('extract image URL')) {
      continue; // retry once for transient non-image responses
    }
    throw e;
  }
}
throw lastErr;

Prevention

When it happens

Trigger: Gemini returned a text-only response (image generation implicitly declined); content was filtered by Gemini's safety settings without an explicit error; the model returned candidates with no parts or parts without inlineData; an API version change altered the response envelope.

Common situations: Prompt tripped Gemini's safety filters (violence, NSFW, etc.) producing text refusal instead of an image; model quirk where it returns reasoning text but no image; transient provider behavior; SDK version mismatch changing response structure.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/0ec77cd713f48720. Report an issue: GitHub.