@@ -9,13 +9,17 @@ import {
99 validateUrlWithDNS ,
1010} from '@/lib/core/security/input-validation.server'
1111import { generateRequestId } from '@/lib/core/utils/request'
12+ import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
1213import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1314import { validateOpaqueModelInputProvenance } from '@/lib/execution/model-input-provenance'
15+ import { decodeDataUriWithinLimit } from '@/lib/file-parsers/data-uri'
16+ import { isFileParserError } from '@/lib/file-parsers/errors'
17+ import { MISTRAL_OCR_REQUEST_POLICY } from '@/lib/knowledge/documents/ocr-request-policy'
18+ import { readBoundedHttpErrorBody } from '@/lib/knowledge/documents/utils'
1419import {
1520 isModelSafeWorkspaceFileKey ,
1621 MODEL_UNSAFE_WORKSPACE_FILE_ERROR_MESSAGE ,
1722} from '@/lib/uploads/contexts/workspace/workspace-file-secret-provenance'
18- import { MAX_BUFFERED_TRANSFER_BYTES } from '@/lib/uploads/shared/types'
1923import {
2024 extractStorageKey ,
2125 isInternalFileUrl ,
@@ -160,14 +164,43 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
160164 requestId ,
161165 logger ,
162166 {
163- maxBytes : MAX_BUFFERED_TRANSFER_BYTES ,
167+ maxBytes : MISTRAL_OCR_REQUEST_POLICY . maxBytes ,
164168 }
165169 )
166170 base64 = buffer . toString ( 'base64' )
167171 if ( contentType && contentType !== 'application/octet-stream' ) {
168172 mimeType = contentType
169173 }
170174 }
175+
176+ let inlineBytes : number
177+ try {
178+ inlineBytes = base64 . startsWith ( 'data:' )
179+ ? decodeDataUriWithinLimit ( base64 , MISTRAL_OCR_REQUEST_POLICY . maxBytes ) . buffer . length
180+ : Buffer . byteLength ( base64 , 'base64' )
181+ } catch ( error ) {
182+ const status = isFileParserError ( error ) && error . code === 'complexity_limit' ? 413 : 400
183+ return NextResponse . json (
184+ {
185+ success : false ,
186+ error :
187+ status === 413
188+ ? `File exceeds Mistral OCR's ${ MISTRAL_OCR_REQUEST_POLICY . maxBytes . toLocaleString ( ) } -byte request limit`
189+ : getErrorMessage ( error , 'Invalid inline file data' ) ,
190+ } ,
191+ { status }
192+ )
193+ }
194+ if ( inlineBytes > MISTRAL_OCR_REQUEST_POLICY . maxBytes ) {
195+ return NextResponse . json (
196+ {
197+ success : false ,
198+ error : `File exceeds Mistral OCR's ${ MISTRAL_OCR_REQUEST_POLICY . maxBytes . toLocaleString ( ) } -byte request limit` ,
199+ } ,
200+ { status : 413 }
201+ )
202+ }
203+
171204 const base64Payload = base64 . startsWith ( 'data:' )
172205 ? base64
173206 : `data:${ mimeType } ;base64,${ base64 } `
@@ -295,8 +328,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
295328 )
296329
297330 if ( ! mistralResponse . ok ) {
298- const errorText = await mistralResponse . text ( )
299- logger . error ( `[${ requestId } ] Mistral API error:` , errorText )
331+ const errorText = await readBoundedHttpErrorBody ( mistralResponse )
332+ logger . error ( `[${ requestId } ] Mistral API error` , {
333+ status : mistralResponse . status ,
334+ diagnostic : errorText ,
335+ } )
300336 return NextResponse . json (
301337 {
302338 success : false ,
@@ -318,6 +354,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
318354 const notReady = docNotReadyResponse ( error )
319355 if ( notReady ) return notReady
320356
357+ if ( isPayloadSizeLimitError ( error ) ) {
358+ return NextResponse . json (
359+ {
360+ success : false ,
361+ error : `File exceeds Mistral OCR's ${ MISTRAL_OCR_REQUEST_POLICY . maxBytes . toLocaleString ( ) } -byte request limit` ,
362+ } ,
363+ { status : 413 }
364+ )
365+ }
366+
321367 logger . error ( `[${ requestId } ] Error in Mistral parse:` , error )
322368
323369 return NextResponse . json (
0 commit comments