/** * Fetch a remote image URL and return it as a base64 data URI. * Used when upstream providers (Codex, etc.) require inline base64 images * instead of remote URLs they cannot fetch. * Returns null if fetch fails. * * @param {string} imageUrl - HTTP(S) URL of the image * @param {object} options - { signal, timeoutMs } * @returns {Promise<{url: string, mimeType: string}|null>} */ export async function fetchImageAsBase64(imageUrl, options = {}) { const { signal, timeoutMs = 10000 } = options; if (!imageUrl || (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://"))) { return null; } const controller = new AbortController(); const timeout = signal ? null : setTimeout(() => controller.abort(), timeoutMs); const fetchSignal = signal || controller.signal; try { const response = await fetch(imageUrl, { signal: fetchSignal }); if (!response.ok) return null; const mimeType = response.headers.get("Content-Type") || "image/jpeg"; const arrayBuffer = await response.arrayBuffer(); const base64 = Buffer.from(arrayBuffer).toString("base64"); return { url: `data:${mimeType};base64,${base64}`, mimeType }; } catch { return null; } finally { if (timeout) clearTimeout(timeout); } }