| | 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 }; |
| | } |
| |
|