Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
3.6 kB · 104 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import { API_IMAGE_MAX_BASE64_SIZE } from '../constants/apiLimits.js'import { logEvent } from '../services/analytics/index.js'import { formatFileSize } from './format.js'
/** * Information about an oversized image. */export type OversizedImage = { index: number size: number}
/** * Error thrown when one or more images exceed the API size limit. */export class ImageSizeError extends Error { constructor(oversizedImages: OversizedImage[], maxSize: number) { let message: string const firstImage = oversizedImages[0] if (oversizedImages.length === 1 && firstImage) { message = `Image base64 size (${formatFileSize(firstImage.size)}) exceeds API limit (${formatFileSize(maxSize)}). ` + `Please resize the image before sending.` } else { message = `${oversizedImages.length} images exceed the API limit (${formatFileSize(maxSize)}): ` + oversizedImages .map(img => `Image ${img.index}: ${formatFileSize(img.size)}`) .join(', ') + `. Please resize these images before sending.` } super(message) this.name = 'ImageSizeError' }}
/** * Type guard to check if a block is a base64 image block */function isBase64ImageBlock( block: unknown,): block is { type: 'image'; source: { type: 'base64'; data: string } } { if (typeof block !== 'object' || block === null) return false const b = block as Record<string, unknown> if (b.type !== 'image') return false if (typeof b.source !== 'object' || b.source === null) return false const source = b.source as Record<string, unknown> return source.type === 'base64' && typeof source.data === 'string'}
/** * Validates that all images in messages are within the API size limit. * This is a safety net at the API boundary to catch any oversized images * that may have slipped through upstream processing. * * Note: The API's 5MB limit applies to the base64-encoded string length, * not the decoded raw bytes. * * Works with both UserMessage/AssistantMessage types (which have { type, message }) * and raw MessageParam types (which have { role, content }). * * @param messages - Array of messages to validate * @throws ImageSizeError if any image exceeds the API limit */export function validateImagesForAPI(messages: unknown[]): void { const oversizedImages: OversizedImage[] = [] let imageIndex = 0
for (const msg of messages) { if (typeof msg !== 'object' || msg === null) continue
const m = msg as Record<string, unknown>
// Handle wrapped message format { type: 'user', message: { role, content } } // Only check user messages if (m.type !== 'user') continue
const innerMessage = m.message as Record<string, unknown> | undefined if (!innerMessage) continue
const content = innerMessage.content if (typeof content === 'string' || !Array.isArray(content)) continue
for (const block of content) { if (isBase64ImageBlock(block)) { imageIndex++ // Check the base64-encoded string length directly (not decoded bytes) // The API limit applies to the base64 payload size const base64Size = block.source.data.length if (base64Size > API_IMAGE_MAX_BASE64_SIZE) { logEvent('tengu_image_api_validation_failed', { base64_size_bytes: base64Size, max_bytes: API_IMAGE_MAX_BASE64_SIZE, }) oversizedImages.push({ index: imageIndex, size: base64Size }) } } } }
if (oversizedImages.length > 0) { throw new ImageSizeError(oversizedImages, API_IMAGE_MAX_BASE64_SIZE) }}