@hauptsache.net/clickup-mcp
Version:
Transform your AI assistant into a powerful ClickUp integration for both agentic coding and productivity management. Enables seamless task context sharing, intelligent search, time tracking, and complete project management workflows.
127 lines (126 loc) • 5.96 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadImages = downloadImages;
const config_1 = require("./config");
const buffer_1 = require("buffer");
/**
* Downloads images from image_metadata blocks and applies smart size/count limiting
* Prioritizes keeping the most recent images (assumes content is ordered with newest items last)
* Uses intelligent size calculation accounting for text content
*
* @param content Array of content blocks that may contain image_metadata blocks
* @param maxImages Maximum number of images to keep (defaults to CONFIG.maxImages)
* @param maxSizeMB Maximum response size in MB (defaults to CONFIG.maxResponseSizeMB)
* @returns Promise resolving to content array with downloaded images or placeholders
*/
async function downloadImages(content, maxImages = config_1.CONFIG.maxImages, maxSizeMB = config_1.CONFIG.maxResponseSizeMB) {
// First apply count-based limiting to image_metadata blocks
const countLimitedContent = applyCountBasedLimitToImageMetadata(content, maxImages);
// Calculate text size to determine available image budget
const textContent = countLimitedContent.filter(block => block.type !== "image_metadata");
const textSizeBytes = JSON.stringify(textContent).length;
const maxSizeBytes = maxSizeMB * 1024 * 1024;
const availableImageBudget = Math.max(0, maxSizeBytes - textSizeBytes);
// Calculate per-image budget
const imageMetadataBlocks = countLimitedContent.filter(block => block.type === "image_metadata");
const perImageBudget = imageMetadataBlocks.length > 0 ? availableImageBudget / imageMetadataBlocks.length : 0;
// Download images in parallel and replace image_metadata blocks
const downloadPromises = countLimitedContent.map(async (block) => {
if (block.type === "image_metadata") {
return await downloadSingleImage(block, perImageBudget);
}
else {
return block;
}
});
return Promise.all(downloadPromises);
}
/**
* Apply count-based limiting to image_metadata blocks
* Keeps the most recent image_metadata blocks (newest last)
*/
function applyCountBasedLimitToImageMetadata(content, maxImages) {
// Find all image_metadata block indices
const imageMetadataIndices = [];
content.forEach((block, index) => {
if (block.type === "image_metadata") {
imageMetadataIndices.push(index);
}
});
// If we have fewer images than the limit, return the original content
if (imageMetadataIndices.length <= maxImages) {
return content;
}
// Determine which image_metadata blocks to remove (keep the most recent ones)
const imagesToRemove = imageMetadataIndices.slice(0, imageMetadataIndices.length - maxImages);
// Create a new content array with excess image_metadata blocks replaced by text placeholders
return content.map((block, index) => {
if (block.type === "image_metadata" && imagesToRemove.includes(index)) {
return {
type: "text",
text: "[Image removed due to count limitations. Only the most recent images are shown.]",
};
}
return block;
});
}
/**
* Download a single image from an image_metadata block, trying different sizes if needed
*/
async function downloadSingleImage(imageMetadata, perImageBudget) {
const fallbackText = {
type: "text",
text: `[Image "${imageMetadata.alt}" removed due to size limitations.]`,
};
// Try each URL in order (largest to smallest)
for (const url of imageMetadata.urls) {
const abortController = new AbortController();
try {
const response = await fetch(url, {
signal: abortController.signal
});
if (!response.ok) {
console.error(`Failed to fetch image from ${url}: ${response.status}`);
continue;
}
// Check Content-Length header first to avoid downloading large images
const contentLength = response.headers.get("Content-Length");
if (contentLength) {
const imageSizeBytes = parseInt(contentLength, 10);
if (imageSizeBytes > perImageBudget) {
// Cancel the request to stop any ongoing download
abortController.abort();
console.error(`Image from ${url} is ${imageSizeBytes} bytes (from Content-Length), exceeds budget of ${perImageBudget} bytes`);
// Continue to try smaller thumbnail
continue;
}
}
const imageBuffer = await response.arrayBuffer();
const actualSizeBytes = imageBuffer.byteLength;
// Double-check actual size (in case Content-Length was missing or incorrect)
if (actualSizeBytes <= perImageBudget) {
return {
type: "image",
mimeType: response.headers.get("Content-Type") || "image/png",
data: buffer_1.Buffer.from(imageBuffer).toString("base64"),
};
}
else {
// Cancel to clean up the connection
abortController.abort();
console.error(`Image from ${url} is ${actualSizeBytes} bytes (actual size), exceeds budget of ${perImageBudget} bytes`);
// Continue to try smaller thumbnail
}
}
catch (error) {
if (error.name === 'AbortError') {
// Request was cancelled, this is expected - continue to next URL
continue;
}
console.error(`Error fetching image from ${url}: ${error.message || "Unknown error"}`);
// Continue to try next URL
}
}
// If all URLs failed or were too large, return fallback text
return fallbackText;
}