feat: chat uploads, vision turn isolation, and MindSpace agent improvements
Add chat file/image upload UX, attachment proxying, vision thumbnails, and per-turn image scoping so agents only use the current upload. Extend MindSpace asset context, billing token state, OA/scenario verify scripts, and related runtime config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import sharp from 'sharp';
|
||||
|
||||
export const VISION_THUMB_MAX_SIDE = 1280;
|
||||
export const VISION_THUMB_MAX_BYTES = 600 * 1024;
|
||||
export const VISION_THUMB_JPEG_QUALITY = 78;
|
||||
|
||||
/**
|
||||
* Build a smaller JPEG buffer for vision-model analysis only.
|
||||
* Page generation still uses the full public standard image elsewhere.
|
||||
*/
|
||||
export async function buildVisionThumbnailBuffer(
|
||||
buffer,
|
||||
mimeType = 'image/jpeg',
|
||||
{
|
||||
maxSide = VISION_THUMB_MAX_SIDE,
|
||||
maxBytes = VISION_THUMB_MAX_BYTES,
|
||||
quality = VISION_THUMB_JPEG_QUALITY,
|
||||
} = {},
|
||||
) {
|
||||
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
|
||||
throw new Error('vision thumb: empty buffer');
|
||||
}
|
||||
|
||||
const metadata = await sharp(buffer, { sequentialRead: true }).metadata();
|
||||
const width = Number(metadata.width ?? 0);
|
||||
const height = Number(metadata.height ?? 0);
|
||||
if (!width || !height) {
|
||||
throw new Error('vision thumb: invalid dimensions');
|
||||
}
|
||||
|
||||
const longest = Math.max(width, height);
|
||||
const resizeWidth = longest > maxSide ? Math.max(1, Math.round((width * maxSide) / longest)) : null;
|
||||
const resizeHeight = longest > maxSide ? Math.max(1, Math.round((height * maxSide) / longest)) : null;
|
||||
|
||||
let pipeline = sharp(buffer, { sequentialRead: true }).rotate();
|
||||
if (resizeWidth && resizeHeight) {
|
||||
pipeline = pipeline.resize({
|
||||
width: resizeWidth,
|
||||
height: resizeHeight,
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
});
|
||||
}
|
||||
|
||||
let nextQuality = quality;
|
||||
let candidate = await pipeline
|
||||
.flatten({ background: '#ffffff' })
|
||||
.jpeg({ quality: nextQuality, mozjpeg: true })
|
||||
.toBuffer();
|
||||
|
||||
while (candidate.length > maxBytes && nextQuality > 52) {
|
||||
nextQuality = Math.max(52, nextQuality - 8);
|
||||
candidate = await sharp(buffer, { sequentialRead: true })
|
||||
.rotate()
|
||||
.resize({
|
||||
width: resizeWidth ?? width,
|
||||
height: resizeHeight ?? height,
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true,
|
||||
})
|
||||
.flatten({ background: '#ffffff' })
|
||||
.jpeg({ quality: nextQuality, mozjpeg: true })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
export const visionThumbInternals = {
|
||||
VISION_THUMB_MAX_SIDE,
|
||||
VISION_THUMB_MAX_BYTES,
|
||||
VISION_THUMB_JPEG_QUALITY,
|
||||
};
|
||||
Reference in New Issue
Block a user