(contentBlocks)
| 101 | } |
| 102 | |
| 103 | export async function extractImages(contentBlocks) { |
| 104 | if (!Array.isArray(contentBlocks)) return { text: String(contentBlocks ?? ''), images: [] }; |
| 105 | |
| 106 | let text = ''; |
| 107 | const images = []; |
| 108 | |
| 109 | for (const block of contentBlocks) { |
| 110 | if (!block || typeof block === 'string') { text += block || ''; continue; } |
| 111 | |
| 112 | if (block.type === 'text') { |
| 113 | text += block.text || ''; |
| 114 | } else if (block.type === 'document') { |
| 115 | const src = block.source || {}; |
| 116 | const mime = (src.media_type || '').toLowerCase(); |
| 117 | if (mime === 'application/pdf' && src.data) { |
| 118 | const pdf = tryExtractPdf(src.data); |
| 119 | if (pdf?.text) { |
| 120 | text += `\n[PDF Document — ${pdf.pageCount} page(s)]\n${pdf.text}\n`; |
| 121 | log.info(`PDF extracted: ${pdf.pageCount} pages, ${pdf.text.length} chars`); |
| 122 | } else { |
| 123 | text += '\n[PDF Document — no extractable text (scanned/image-only PDF)]\n'; |
| 124 | } |
| 125 | } |
| 126 | } else if (block.type === 'image') { |
| 127 | const src = block.source || {}; |
| 128 | const mime = (src.media_type || '').toLowerCase(); |
| 129 | if (mime === 'application/pdf' && src.data) { |
| 130 | const pdf = tryExtractPdf(src.data); |
| 131 | if (pdf?.text) { |
| 132 | text += `\n[PDF Document — ${pdf.pageCount} page(s)]\n${pdf.text}\n`; |
| 133 | } |
| 134 | continue; |
| 135 | } |
| 136 | try { |
| 137 | if ((src.type === 'base64' || !src.type) && src.data) { |
| 138 | if (src.data.length > MAX_BASE64_LEN) { log.warn('Image base64 exceeds size limit, skipping'); continue; } |
| 139 | images.push({ base64_data: src.data, mime_type: src.media_type || 'image/png' }); |
| 140 | } else if (src.type === 'url' && src.url) { |
| 141 | images.push(await fetchImageUrl(src.url)); |
| 142 | } |
| 143 | } catch (e) { log.warn(`Image extraction failed: ${e.message}`); } |
| 144 | } else if (block.type === 'image_url') { |
| 145 | const url = block.image_url?.url || ''; |
| 146 | try { |
| 147 | if (url.startsWith('data:')) { |
| 148 | // PDF-as-data-URL: let the model "see" it via text extraction |
| 149 | // rather than treating it as an unsupported image type. |
| 150 | const lower = url.slice(0, 40).toLowerCase(); |
| 151 | if (lower.startsWith('data:application/pdf')) { |
| 152 | const g = parseGenericDataUrl(url); |
| 153 | if (g?.base64_data) { |
| 154 | const pdf = tryExtractPdf(g.base64_data); |
| 155 | if (pdf?.text) { |
| 156 | text += `\n[PDF Document — ${pdf.pageCount} page(s)]\n${pdf.text}\n`; |
| 157 | log.info(`PDF extracted (image_url data URL): ${pdf.pageCount} pages, ${pdf.text.length} chars`); |
| 158 | } else { |
| 159 | text += '\n[PDF Document — no extractable text (scanned/image-only PDF)]\n'; |
| 160 | } |
no test coverage detected