| import { Providers } from '@librechat/agents'; |
| import { mbToBytes, isOpenAILikeProvider } from 'librechat-data-provider'; |
|
|
| export interface PDFValidationResult { |
| isValid: boolean; |
| error?: string; |
| } |
|
|
| export interface VideoValidationResult { |
| isValid: boolean; |
| error?: string; |
| } |
|
|
| export interface AudioValidationResult { |
| isValid: boolean; |
| error?: string; |
| } |
|
|
| export interface ImageValidationResult { |
| isValid: boolean; |
| error?: string; |
| } |
|
|
| export async function validatePdf( |
| pdfBuffer: Buffer, |
| fileSize: number, |
| provider: Providers, |
| configuredFileSizeLimit?: number, |
| ): Promise<PDFValidationResult> { |
| if (provider === Providers.ANTHROPIC) { |
| return validateAnthropicPdf(pdfBuffer, fileSize, configuredFileSizeLimit); |
| } |
|
|
| if (isOpenAILikeProvider(provider)) { |
| return validateOpenAIPdf(fileSize, configuredFileSizeLimit); |
| } |
|
|
| if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { |
| return validateGooglePdf(fileSize, configuredFileSizeLimit); |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function validateAnthropicPdf( |
| pdfBuffer: Buffer, |
| fileSize: number, |
| configuredFileSizeLimit?: number, |
| ): Promise<PDFValidationResult> { |
| try { |
| const providerLimit = mbToBytes(32); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `PDF file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
|
|
| if (!pdfBuffer || pdfBuffer.length < 5) { |
| return { |
| isValid: false, |
| error: 'Invalid PDF file: too small or corrupted', |
| }; |
| } |
|
|
| const pdfHeader = pdfBuffer.subarray(0, 5).toString(); |
| if (!pdfHeader.startsWith('%PDF-')) { |
| return { |
| isValid: false, |
| error: 'Invalid PDF file: missing PDF header', |
| }; |
| } |
|
|
| const pdfContent = pdfBuffer.toString('binary'); |
| if ( |
| pdfContent.includes('/Encrypt ') || |
| pdfContent.includes('/U (') || |
| pdfContent.includes('/O (') |
| ) { |
| return { |
| isValid: false, |
| error: 'PDF is password-protected or encrypted. Anthropic requires unencrypted PDFs.', |
| }; |
| } |
|
|
| const pageMatches = pdfContent.match(/\/Type[\s]*\/Page[^s]/g); |
| const estimatedPages = pageMatches ? pageMatches.length : 1; |
|
|
| if (estimatedPages > 100) { |
| return { |
| isValid: false, |
| error: `PDF has approximately ${estimatedPages} pages, exceeding Anthropic's 100-page limit`, |
| }; |
| } |
|
|
| return { isValid: true }; |
| } catch (error) { |
| console.error('PDF validation error:', error); |
| return { |
| isValid: false, |
| error: 'Failed to validate PDF file', |
| }; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function validateOpenAIPdf( |
| fileSize: number, |
| configuredFileSizeLimit?: number, |
| ): Promise<PDFValidationResult> { |
| const providerLimit = mbToBytes(10); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `PDF file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function validateGooglePdf( |
| fileSize: number, |
| configuredFileSizeLimit?: number, |
| ): Promise<PDFValidationResult> { |
| const providerLimit = mbToBytes(20); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `PDF file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function validateVideo( |
| videoBuffer: Buffer, |
| fileSize: number, |
| provider: Providers, |
| configuredFileSizeLimit?: number, |
| ): Promise<VideoValidationResult> { |
| if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { |
| const providerLimit = mbToBytes(20); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `Video file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
| } |
|
|
| if (!videoBuffer || videoBuffer.length < 10) { |
| return { |
| isValid: false, |
| error: 'Invalid video file: too small or corrupted', |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function validateAudio( |
| audioBuffer: Buffer, |
| fileSize: number, |
| provider: Providers, |
| configuredFileSizeLimit?: number, |
| ): Promise<AudioValidationResult> { |
| if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { |
| const providerLimit = mbToBytes(20); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `Audio file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
| } |
|
|
| if (!audioBuffer || audioBuffer.length < 10) { |
| return { |
| isValid: false, |
| error: 'Invalid audio file: too small or corrupted', |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export async function validateImage( |
| imageBuffer: Buffer, |
| fileSize: number, |
| provider: Providers | string, |
| configuredFileSizeLimit?: number, |
| ): Promise<ImageValidationResult> { |
| if (provider === Providers.GOOGLE || provider === Providers.VERTEXAI) { |
| const providerLimit = mbToBytes(20); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `Image file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
| } |
|
|
| if (provider === Providers.ANTHROPIC) { |
| const providerLimit = mbToBytes(5); |
| const effectiveLimit = configuredFileSizeLimit ?? providerLimit; |
|
|
| if (fileSize > effectiveLimit) { |
| const limitMB = Math.round(effectiveLimit / (1024 * 1024)); |
| return { |
| isValid: false, |
| error: `Image file size (${Math.round(fileSize / (1024 * 1024))}MB) exceeds the ${limitMB}MB limit`, |
| }; |
| } |
| } |
|
|
| if (!imageBuffer || imageBuffer.length < 10) { |
| return { |
| isValid: false, |
| error: 'Invalid image file: too small or corrupted', |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|