UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

98 lines (97 loc) • 5.02 kB
export const MCP_IMAGE_MAX_BASE64 = 5 * 1024 * 1024; export const IMAGE_TARGET_RAW_SIZE = (MCP_IMAGE_MAX_BASE64 * 3) / 4; export const IMAGE_MAX_WIDTH = 2000; export const IMAGE_MAX_HEIGHT = 2000; export function createImageDownsampler(sharp) { const BOX = { fit: "inside", withoutEnlargement: true }; return async (input, mimeType) => { if (input.length === 0) return undefined; try { const meta = await sharp(input).metadata(); const fmt = meta.format === "jpg" ? "jpeg" : meta.format; const passMime = fmt ? `image/${fmt}` : mimeType; const originalSize = input.length; if (!meta.width || !meta.height) { if (originalSize > IMAGE_TARGET_RAW_SIZE) { const buf = await sharp(input).jpeg({ quality: 80 }).toBuffer(); return { base64: buf.toString("base64"), mimeType: "image/jpeg" }; } return { base64: input.toString("base64"), mimeType: passMime }; } const originalWidth = meta.width; const originalHeight = meta.height; let width = originalWidth; let height = originalHeight; const dims = (dw, dh) => ({ originalWidth, originalHeight, displayWidth: dw, displayHeight: dh }); if (originalSize <= IMAGE_TARGET_RAW_SIZE && width <= IMAGE_MAX_WIDTH && height <= IMAGE_MAX_HEIGHT) { return { base64: input.toString("base64"), mimeType: passMime, dimensions: dims(width, height) }; } const needsDimensionResize = width > IMAGE_MAX_WIDTH || height > IMAGE_MAX_HEIGHT; const isPng = fmt === "png"; if (!needsDimensionResize) { if (isPng) { const png = await sharp(input).png({ compressionLevel: 9, palette: true }).toBuffer(); if (png.length <= IMAGE_TARGET_RAW_SIZE) return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) }; } for (const quality of [80, 60, 40, 20]) { const buf = await sharp(input).jpeg({ quality }).toBuffer(); if (buf.length <= IMAGE_TARGET_RAW_SIZE) return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) }; } } if (width > IMAGE_MAX_WIDTH) { height = Math.round((height * IMAGE_MAX_WIDTH) / width); width = IMAGE_MAX_WIDTH; } if (height > IMAGE_MAX_HEIGHT) { width = Math.round((width * IMAGE_MAX_HEIGHT) / height); height = IMAGE_MAX_HEIGHT; } const resized = await sharp(input).resize(width, height, BOX).toBuffer(); if (resized.length <= IMAGE_TARGET_RAW_SIZE) { return { base64: resized.toString("base64"), mimeType: passMime, dimensions: dims(width, height) }; } if (isPng) { const png = await sharp(input).resize(width, height, BOX).png({ compressionLevel: 9, palette: true }).toBuffer(); if (png.length <= IMAGE_TARGET_RAW_SIZE) return { base64: png.toString("base64"), mimeType: "image/png", dimensions: dims(width, height) }; } for (const quality of [80, 60, 40, 20]) { const buf = await sharp(input).resize(width, height, BOX).jpeg({ quality }).toBuffer(); if (buf.length <= IMAGE_TARGET_RAW_SIZE) return { base64: buf.toString("base64"), mimeType: "image/jpeg", dimensions: dims(width, height) }; } const smallerWidth = Math.min(width, 1000); const smallerHeight = Math.round((height * smallerWidth) / Math.max(width, 1)); const last = await sharp(input).resize(smallerWidth, smallerHeight, BOX).jpeg({ quality: 20 }).toBuffer(); return { base64: last.toString("base64"), mimeType: "image/jpeg", dimensions: dims(smallerWidth, smallerHeight) }; } catch { return undefined; } }; } const SHARP_LIMIT_INPUT_PIXELS = 100_000_000; export async function sharpImageDownsampler() { try { const mod = (await import("sharp")); const real = (mod.default ?? mod); return createImageDownsampler((input) => real(input, { limitInputPixels: SHARP_LIMIT_INPUT_PIXELS })); } catch { return undefined; } } export async function sharpImageResizer() { const down = await sharpImageDownsampler(); if (!down) return undefined; return async (base64, mimeType) => { const out = await down(Buffer.from(base64, "base64"), mimeType).catch(() => undefined); if (!out || out.base64.length > MCP_IMAGE_MAX_BASE64) return undefined; return { base64: out.base64, mimeType: out.mimeType }; }; }