Skip to content

Commit 0d23057

Browse files
committed
fix(knowledge): enforce OCR request limits by bytes and pages
1 parent 590c9a3 commit 0d23057

7 files changed

Lines changed: 435 additions & 112 deletions

File tree

apps/sim/app/api/tools/mistral/parse/route.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import {
99
validateUrlWithDNS,
1010
} from '@/lib/core/security/input-validation.server'
1111
import { generateRequestId } from '@/lib/core/utils/request'
12+
import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits'
1213
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1314
import { 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'
1419
import {
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'
1923
import {
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

Comments
 (0)