* Detect image file references in a message. * Returns array of { path, base64, mimeType }
(message, cwd)
| 17 | * Returns array of { path, base64, mimeType } |
| 18 | */ |
| 19 | function extractImages(message, cwd) { |
| 20 | const images = []; |
| 21 | |
| 22 | // Match @path references that end in image extensions |
| 23 | const FILE_REGEX = /(?<![\w`])@(\.?[^\s`,.]*(?:\.[^\s`,.]+)*)/g; |
| 24 | const matches = [...message.matchAll(FILE_REGEX)]; |
| 25 | |
| 26 | for (const match of matches) { |
| 27 | const rawPath = match[1]; |
| 28 | if (!rawPath) continue; |
| 29 | |
| 30 | const ext = path.extname(rawPath).toLowerCase(); |
| 31 | if (!IMAGE_EXTENSIONS.includes(ext)) continue; |
| 32 | |
| 33 | const safe = safeResolvePath(rawPath, cwd); |
| 34 | if (!safe.ok) continue; // Block out-of-tree images and sensitive paths |
| 35 | const fullPath = safe.fullPath; |
| 36 | if (!fs.existsSync(fullPath)) continue; |
| 37 | |
| 38 | let stat; |
| 39 | try { stat = fs.statSync(fullPath); } catch { continue; } |
| 40 | if (!stat.isFile() || stat.size === 0) continue; |
| 41 | if (stat.size > MAX_IMAGE_BYTES) continue; // Refuse oversized images |
| 42 | |
| 43 | try { |
| 44 | const buffer = fs.readFileSync(fullPath); |
| 45 | const base64 = buffer.toString('base64'); |
| 46 | const mimeType = getMimeType(ext); |
| 47 | images.push({ path: rawPath, base64, mimeType, size: buffer.length }); |
| 48 | } catch {} |
| 49 | } |
| 50 | |
| 51 | return images; |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Format images as OpenAI vision API content parts. |
no test coverage detected