diff --git a/skills/openrouter-images/scripts/edit.ts b/skills/openrouter-images/scripts/edit.ts index 37199b1..2d5d96d 100644 --- a/skills/openrouter-images/scripts/edit.ts +++ b/skills/openrouter-images/scripts/edit.ts @@ -6,6 +6,7 @@ import { readImageAsDataUrl, saveImage, defaultOutputPath, + toDataUrl, } from "./lib.js"; const apiKey = requireApiKey(); @@ -57,7 +58,7 @@ if (message.content) { console.error(`Model: ${message.content}`); } -const images: string[] = message.images ?? []; +const images: unknown[] = message.images ?? []; if (images.length === 0) { console.error("Error: No images returned by model."); process.exit(1); @@ -65,7 +66,7 @@ if (images.length === 0) { const saved: string[] = []; for (let i = 0; i < images.length; i++) { - const img = images[i].startsWith("data:") ? images[i] : `data:image/png;base64,${images[i]}`; + const img = toDataUrl(images[i]); let outPath: string; if (images.length === 1) { outPath = outputBase; diff --git a/skills/openrouter-images/scripts/generate.ts b/skills/openrouter-images/scripts/generate.ts index 50e7f72..9eb488a 100644 --- a/skills/openrouter-images/scripts/generate.ts +++ b/skills/openrouter-images/scripts/generate.ts @@ -5,6 +5,7 @@ import { postChatCompletion, saveImage, defaultOutputPath, + toDataUrl, } from "./lib.js"; const apiKey = requireApiKey(); @@ -44,7 +45,7 @@ if (message.content) { console.error(`Model: ${message.content}`); } -const images: string[] = message.images ?? []; +const images: unknown[] = message.images ?? []; if (images.length === 0) { console.error("Error: No images returned by model."); process.exit(1); @@ -52,7 +53,7 @@ if (images.length === 0) { const saved: string[] = []; for (let i = 0; i < images.length; i++) { - const dataUrl = images[i].startsWith("data:") ? images[i] : `data:image/png;base64,${images[i]}`; + const dataUrl = toDataUrl(images[i]); let outPath: string; if (images.length === 1) { outPath = outputBase; diff --git a/skills/openrouter-images/scripts/lib.ts b/skills/openrouter-images/scripts/lib.ts index 143aea4..c22daff 100644 --- a/skills/openrouter-images/scripts/lib.ts +++ b/skills/openrouter-images/scripts/lib.ts @@ -103,3 +103,33 @@ export function defaultOutputPath(): string { `-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`; return `image-${stamp}.png`; } + +/** + * Normalize an entry from `message.images` into a data-URL string. + * + * The chat-completions response shape for `images` varies by model: + * - some models return a raw base64 string, + * - some return a data URL string, + * - others (e.g. openai/gpt-5.4-image-2, recent google/gemini-*-image + * variants) return an object: `{ type: "image_url", image_url: { url } }` + * or `{ b64_json: "..." }`. + * + * This helper accepts any of those and returns a `data:...;base64,...` URL + * suitable for `saveImage()`. It exits with a clean error on unrecognized + * shapes rather than throwing a `TypeError`. + */ +export function toDataUrl(entry: unknown): string { + let str: string | undefined; + if (typeof entry === "string") { + str = entry; + } else if (entry && typeof entry === "object") { + const e = entry as { image_url?: { url?: string }; url?: string; b64_json?: string; data?: string }; + str = e.image_url?.url ?? e.url ?? e.b64_json ?? e.data; + } + if (typeof str !== "string" || str.length === 0) { + const preview = JSON.stringify(entry).slice(0, 200); + console.error(`Error: Unrecognized image payload shape in response: ${preview}`); + process.exit(1); + } + return str.startsWith("data:") ? str : `data:image/png;base64,${str}`; +}