nexu-io/open-design · error · Error

openrouter image response missing image_url.url: ${truncate(

Error message

openrouter image response missing image_url.url: ${truncate(text, 200)}

What it means

Thrown when the images array has at least one entry but images[0].image_url.url is missing. OpenRouter's contract puts the data URL or hosted URL under `image_url.url`; a present image object without that field means the provider returned a partial or differently-shaped image entry.

Source

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

  try {
    data = JSON.parse(text);
  } catch {
    throw new Error(`openrouter image non-JSON response: ${truncate(text, 200)}`);
  }

  // Extract the first generated image from the response.
  const images: any[] | undefined =
    data?.choices?.[0]?.message?.images;
  if (!images || images.length === 0) {
    throw new Error(
      `openrouter image response contained no images for model ${wireModel}: `
      + truncate(text, 200),
    );
  }

  const dataUrl: string | undefined = images[0]?.image_url?.url;
  if (!dataUrl) {
    throw new Error(
      `openrouter image response missing image_url.url: ${truncate(text, 200)}`,
    );
  }

  // Strip the data URL prefix (e.g. "data:image/png;base64,") and
  // decode the remaining base64 payload.
  const b64Match = dataUrl.match(/^data:image\/[^;]+;base64,(.+)$/s);
  let bytes: Buffer;
  if (b64Match) {
    bytes = Buffer.from(b64Match[1]!, 'base64');
  } else if (dataUrl.startsWith('http')) {
    // Some models may return a plain URL instead of inline base64.
    const imgResp = await fetch(dataUrl, withMediaRequestInit(ctx));
    if (!imgResp.ok) throw new Error(`openrouter image download ${imgResp.status}`);
    bytes = Buffer.from(await imgResp.arrayBuffer());
  } else {
    // Assume raw base64 without prefix.
    bytes = Buffer.from(dataUrl, 'base64');

View on GitHub (pinned to 5be4028344)

Solutions

  1. Inspect images[0] — its keys reveal whether the field was renamed or whether it is a moderation/metadata object.
  2. Retry once for transient partial responses.
  3. If the field was renamed, update the dataUrl extraction at apps/daemon/src/media/index.ts:1890.
  4. Switch to a different image-capable model on OpenRouter if the chosen one consistently returns this shape.

Example fix

// before
const dataUrl = images[0]?.image_url?.url;
if (!dataUrl) {
  throw new Error(`openrouter image response missing image_url.url: ${truncate(text, 200)}`);
}

// after — expose the malformed entry
const first = images[0];
const dataUrl = first?.image_url?.url;
if (!dataUrl) {
  throw new Error(
    `openrouter image entry missing image_url.url: ${truncate(JSON.stringify(first), 200)}`,
  );
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Accept a few aliases and surface the malformed entry
function extractOpenRouterDataUrl(first: any, fullText: string): string {
  const url = first?.image_url?.url || first?.url || first?.image;
  if (typeof url === 'string' && url) return url;
  throw new Error(`openrouter image entry missing image_url.url: ${truncate(JSON.stringify(first), 200)}`);
}

Type guard

interface OpenRouterImageEntry { image_url?: { url?: string }; url?: string; image?: string }
function isOpenRouterImageEntry(v: unknown): v is OpenRouterImageEntry {
  return typeof v === 'object' && v !== null && (
    typeof (v as any).image_url?.url === 'string' ||
    typeof (v as any).url === 'string' ||
    typeof (v as any).image === 'string'
  );
}

Try / catch

const first = images[0];
let dataUrl: string;
try {
  dataUrl = extractOpenRouterDataUrl(first, text);
} catch (e) {
  ctx.onProviderRequestSettled?.({ providerId: 'openrouter', ok: false, error: String(e) });
  throw e;
}

Prevention

When it happens

Trigger: Underlying provider returned an image object with only metadata (e.g. a revised_prompt or a moderation flag), OpenRouter changed the field name, or the entry is a placeholder/error object inside the images array.

Common situations: Schema drift on OpenRouter's side, an image model returning a content-filter object in the images slot, or a half-finished response from a provider incident.

Related errors


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