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
- Inspect images[0] — its keys reveal whether the field was renamed or whether it is a moderation/metadata object.
- Retry once for transient partial responses.
- If the field was renamed, update the dataUrl extraction at apps/daemon/src/media/index.ts:1890.
- 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
- Accept common field aliases when extracting the image URL to absorb minor schema drift.
- Log the unrecognized entry keys when extraction fails.
- Pin to a stable OpenRouter model slug rather than a fast-moving alias.
- Retry once for transient partial responses.
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
- openrouter image non-JSON response: ${truncate(text, 200)}
- openrouter image response contained no images for model ${wi
- grok image response had no data[0]
- grok image response missing b64_json/url
- nano-banana image non-JSON: ${truncate(text, 200)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/a14372a0419afb66.
Report an issue: GitHub.