UNPKG

@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
"use strict"; 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; }