Skip to content

Commit 0b28132

Browse files
committed
address comments
1 parent c02675b commit 0b28132

5 files changed

Lines changed: 94 additions & 19 deletions

File tree

apps/sim/app/api/files/parse/route.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,43 @@ describe('File Parse API Route', () => {
445445
expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).toHaveBeenCalledTimes(1)
446446
})
447447

448+
it('should include successful multi-file parse results when a later file exceeds the cap', async () => {
449+
inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValue({
450+
isValid: true,
451+
resolvedIP: '203.0.113.10',
452+
})
453+
inputValidationMockFns.mockSecureFetchWithPinnedIP.mockResolvedValue(
454+
new Response('file content', {
455+
status: 200,
456+
headers: { 'content-type': 'text/plain' },
457+
})
458+
)
459+
460+
mockParseBuffer
461+
.mockResolvedValueOnce({
462+
content: 'first file',
463+
metadata: { pageCount: 1 },
464+
})
465+
.mockResolvedValueOnce({
466+
content: 'a'.repeat(5 * 1024 * 1024),
467+
metadata: { pageCount: 1 },
468+
})
469+
470+
const req = createMockRequest('POST', {
471+
filePath: ['https://example.com/file1.txt', 'https://example.com/file2.txt'],
472+
})
473+
474+
const response = await POST(req)
475+
const data = await response.json()
476+
477+
expect(response.status).toBe(413)
478+
expect(data.success).toBe(false)
479+
expect(data.error).toContain('too large')
480+
expect(data.results).toHaveLength(1)
481+
expect(data.results[0].output.content).toBe('first file')
482+
expect(inputValidationMockFns.mockSecureFetchWithPinnedIP).toHaveBeenCalledTimes(2)
483+
})
484+
448485
it('should pass custom headers when fetching external URLs', async () => {
449486
inputValidationMockFns.mockValidateUrlWithDNS.mockResolvedValue({
450487
isValid: true,

apps/sim/app/api/files/parse/route.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
162162

163163
const remainingOutputBytes = MAX_MULTI_FILE_PARSE_OUTPUT_BYTES - totalOutputBytes
164164
if (remainingOutputBytes <= 0) {
165-
return parsedOutputTooLargeResponse()
165+
return parsedOutputTooLargeResponse(results)
166166
}
167167

168168
const result = await parseFileSingle(
@@ -183,7 +183,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
183183
if (result.success) {
184184
totalOutputBytes += getContentBytes(result.content)
185185
if (totalOutputBytes > MAX_MULTI_FILE_PARSE_OUTPUT_BYTES) {
186-
return parsedOutputTooLargeResponse()
186+
return parsedOutputTooLargeResponse(results)
187187
}
188188

189189
const displayName =
@@ -205,7 +205,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
205205
}
206206

207207
if (result.error?.startsWith('Parsed file output is too large')) {
208-
return parsedOutputTooLargeResponse()
208+
return parsedOutputTooLargeResponse(results)
209209
}
210210

211211
results.push(result)
@@ -396,13 +396,14 @@ function validateFileReferenceShape(filePath: string): { isValid: boolean; error
396396
return { isValid: true }
397397
}
398398

399-
function parsedOutputTooLargeResponse(): NextResponse {
399+
function parsedOutputTooLargeResponse(results?: unknown[]): NextResponse {
400400
return NextResponse.json(
401401
{
402402
success: false,
403403
error: `Parsed file output is too large to return safely. Maximum combined parsed output is ${prettySize(
404404
MAX_MULTI_FILE_PARSE_OUTPUT_BYTES
405405
)}.`,
406+
...(results && results.length > 0 ? { results } : {}),
406407
},
407408
{ status: 413 }
408409
)

apps/sim/lib/execution/payloads/store.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ vi.mock('@/lib/uploads', () => ({
2929
},
3030
}))
3131

32+
vi.mock('@/lib/uploads/core/storage-service', () => ({
33+
uploadFile: mockUploadFile,
34+
downloadFile: mockDownloadFile,
35+
}))
36+
3237
vi.mock('@/app/api/files/authorization', () => ({
3338
verifyFileAccess: mockVerifyFileAccess,
3439
}))

apps/sim/tools/file/parser.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,37 @@ describe('fileParserTool', () => {
8181
},
8282
})
8383
})
84+
85+
it('preserves partial multi-file parse successes from an oversized response', async () => {
86+
const result = await fileParserTool.transformResponse?.(
87+
Response.json(
88+
{
89+
success: false,
90+
error: 'Parsed file output is too large to return safely.',
91+
results: [
92+
{
93+
success: true,
94+
output: {
95+
content: 'ok',
96+
fileType: 'text/plain',
97+
size: 2,
98+
name: 'ok.txt',
99+
binary: false,
100+
},
101+
},
102+
],
103+
},
104+
{ status: 413 }
105+
)
106+
)
107+
108+
expect(result).toMatchObject({
109+
success: true,
110+
error: 'Parsed file output is too large to return safely.',
111+
output: {
112+
files: [{ name: 'ok.txt', content: 'ok' }],
113+
combinedContent: 'ok',
114+
},
115+
})
116+
})
84117
})

apps/sim/tools/file/parser.ts

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,6 @@ const parseFileParserResponse = async (response: Response): Promise<FileParserOu
8989
const result: unknown = await response.json()
9090
logger.info('Response parsed successfully')
9191

92-
if (isRecord(result) && result.success === false) {
93-
return {
94-
success: false,
95-
output: {
96-
files: [],
97-
combinedContent: '',
98-
},
99-
error: typeof result.error === 'string' ? result.error : 'Failed to parse file',
100-
}
101-
}
102-
10392
// Handle multiple files response
10493
if (isRecord(result) && Array.isArray(result.results)) {
10594
logger.info('Processing multiple files response')
@@ -129,10 +118,7 @@ const parseFileParserResponse = async (response: Response): Promise<FileParserOu
129118
.filter((fileResult) => !(isRecord(fileResult) && fileResult.success === false))
130119
.map((fileResult) => normalizeFileParseResult(fileResult))
131120

132-
// Collect UserFile objects from results
133-
const processedFiles: UserFile[] = fileResults
134-
.filter((file): file is FileParseResult & { file: UserFile } => Boolean(file.file))
135-
.map((file) => file.file)
121+
const processedFiles = fileResults.flatMap((file) => (file.file ? [file.file] : []))
136122

137123
// Combine all file contents with clear dividers
138124
const combinedContent = fileResults
@@ -149,10 +135,23 @@ const parseFileParserResponse = async (response: Response): Promise<FileParserOu
149135
combinedContent,
150136
...(processedFiles.length > 0 && { processedFiles }),
151137
}
138+
const error = typeof result.error === 'string' ? result.error : undefined
152139

153140
return {
154141
success: true,
155142
output,
143+
...(error ? { error } : {}),
144+
}
145+
}
146+
147+
if (isRecord(result) && result.success === false) {
148+
return {
149+
success: false,
150+
output: {
151+
files: [],
152+
combinedContent: '',
153+
},
154+
error: typeof result.error === 'string' ? result.error : 'Failed to parse file',
156155
}
157156
}
158157

0 commit comments

Comments
 (0)