Skip to content

Commit c230e1a

Browse files
committed
normalize file input
1 parent 6e5e8de commit c230e1a

27 files changed

+220
-218
lines changed

apps/sim/blocks/blocks/a2a.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { A2AIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
3+
import { normalizeFileInput } from '@/blocks/utils'
34
import type { ToolResponse } from '@/tools/types'
45

56
export interface A2AResponse extends ToolResponse {
@@ -214,6 +215,14 @@ export const A2ABlock: BlockConfig<A2AResponse> = {
214215
],
215216
config: {
216217
tool: (params) => params.operation as string,
218+
params: (params) => {
219+
const { fileUpload, fileReference, ...rest } = params
220+
const normalizedFiles = normalizeFileInput(fileUpload || fileReference || params.files)
221+
return {
222+
...rest,
223+
...(normalizedFiles && { files: normalizedFiles }),
224+
}
225+
},
217226
},
218227
},
219228
inputs: {

apps/sim/blocks/blocks/confluence.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ConfluenceIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { AuthMode } from '@/blocks/types'
4+
import { normalizeFileInput } from '@/blocks/utils'
45
import type { ConfluenceResponse } from '@/tools/confluence/types'
56

67
export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
@@ -651,14 +652,15 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
651652

652653
if (operation === 'upload_attachment') {
653654
const fileInput = attachmentFileUpload || attachmentFileReference || attachmentFile
654-
if (!fileInput) {
655+
const normalizedFile = normalizeFileInput(fileInput)
656+
if (!normalizedFile) {
655657
throw new Error('File is required for upload attachment operation.')
656658
}
657659
return {
658660
credential,
659661
pageId: effectivePageId,
660662
operation,
661-
file: fileInput,
663+
file: normalizedFile,
662664
fileName: attachmentFileName,
663665
comment: attachmentComment,
664666
...rest,

apps/sim/blocks/blocks/dropbox.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DropboxIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { AuthMode } from '@/blocks/types'
4+
import { normalizeFileInput } from '@/blocks/utils'
45
import type { DropboxResponse } from '@/tools/dropbox/types'
56

67
export const DropboxBlock: BlockConfig<DropboxResponse> = {
@@ -316,6 +317,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
316317
params.maxResults = Number(params.maxResults)
317318
}
318319

320+
// Normalize file input for upload operation
321+
// normalizeFileInput handles JSON stringified values from advanced mode
322+
if (params.fileContent) {
323+
params.fileContent = normalizeFileInput(params.fileContent)
324+
}
325+
319326
switch (params.operation) {
320327
case 'dropbox_upload':
321328
return 'dropbox_upload'

apps/sim/blocks/blocks/file.ts

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { DocumentIcon } from '@/components/icons'
33
import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
44
import type { BlockConfig, SubBlockType } from '@/blocks/types'
5-
import { createVersionedToolSelector } from '@/blocks/utils'
5+
import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils'
66
import type { FileParserOutput, FileParserV3Output } from '@/tools/file/types'
77

88
const logger = createLogger('FileBlock')
@@ -200,27 +200,25 @@ export const FileV2Block: BlockConfig<FileParserOutput> = {
200200
throw new Error('File is required')
201201
}
202202

203-
if (typeof fileInput === 'string') {
204-
return {
205-
filePath: fileInput.trim(),
206-
fileType: params.fileType || 'auto',
207-
workspaceId: params._context?.workspaceId,
208-
}
209-
}
210-
211-
if (Array.isArray(fileInput) && fileInput.length > 0) {
212-
const filePaths = resolveFilePathsFromInput(fileInput)
213-
return {
214-
filePath: filePaths.length === 1 ? filePaths[0] : filePaths,
215-
fileType: params.fileType || 'auto',
203+
// First, try to normalize as file objects (handles JSON strings from advanced mode)
204+
const normalizedFiles = normalizeFileInput(fileInput)
205+
if (normalizedFiles) {
206+
const filePaths = resolveFilePathsFromInput(normalizedFiles)
207+
if (filePaths.length > 0) {
208+
return {
209+
filePath: filePaths.length === 1 ? filePaths[0] : filePaths,
210+
fileType: params.fileType || 'auto',
211+
workspaceId: params._context?.workspaceId,
212+
}
216213
}
217214
}
218215

219-
const resolvedSingle = resolveFilePathsFromInput(fileInput)
220-
if (resolvedSingle.length > 0) {
216+
// If normalization fails, treat as direct URL string
217+
if (typeof fileInput === 'string' && fileInput.trim()) {
221218
return {
222-
filePath: resolvedSingle[0],
219+
filePath: fileInput.trim(),
223220
fileType: params.fileType || 'auto',
221+
workspaceId: params._context?.workspaceId,
224222
}
225223
}
226224

@@ -292,39 +290,25 @@ export const FileV3Block: BlockConfig<FileParserV3Output> = {
292290
throw new Error('File input is required')
293291
}
294292

295-
if (typeof fileInput === 'string') {
296-
return {
297-
filePath: fileInput.trim(),
298-
fileType: params.fileType || 'auto',
299-
workspaceId: params._context?.workspaceId,
300-
workflowId: params._context?.workflowId,
301-
executionId: params._context?.executionId,
302-
}
303-
}
304-
305-
if (Array.isArray(fileInput)) {
306-
const filePaths = resolveFilePathsFromInput(fileInput)
307-
if (filePaths.length === 0) {
308-
logger.error('No valid file paths found in file input array')
309-
throw new Error('File input is required')
310-
}
311-
return {
312-
filePath: filePaths.length === 1 ? filePaths[0] : filePaths,
313-
fileType: params.fileType || 'auto',
314-
workspaceId: params._context?.workspaceId,
315-
workflowId: params._context?.workflowId,
316-
executionId: params._context?.executionId,
293+
// First, try to normalize as file objects (handles JSON strings from advanced mode)
294+
const normalizedFiles = normalizeFileInput(fileInput)
295+
if (normalizedFiles) {
296+
const filePaths = resolveFilePathsFromInput(normalizedFiles)
297+
if (filePaths.length > 0) {
298+
return {
299+
filePath: filePaths.length === 1 ? filePaths[0] : filePaths,
300+
fileType: params.fileType || 'auto',
301+
workspaceId: params._context?.workspaceId,
302+
workflowId: params._context?.workflowId,
303+
executionId: params._context?.executionId,
304+
}
317305
}
318306
}
319307

320-
if (typeof fileInput === 'object') {
321-
const resolvedPaths = resolveFilePathsFromInput(fileInput)
322-
if (resolvedPaths.length === 0) {
323-
logger.error('File input object missing path, url, or key')
324-
throw new Error('File input is required')
325-
}
308+
// If normalization fails, treat as direct URL string
309+
if (typeof fileInput === 'string' && fileInput.trim()) {
326310
return {
327-
filePath: resolvedPaths[0],
311+
filePath: fileInput.trim(),
328312
fileType: params.fileType || 'auto',
329313
workspaceId: params._context?.workspaceId,
330314
workflowId: params._context?.workflowId,

apps/sim/blocks/blocks/fireflies.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { FirefliesIcon } from '@/components/icons'
22
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
33
import type { BlockConfig } from '@/blocks/types'
44
import { AuthMode } from '@/blocks/types'
5+
import { normalizeFileInput } from '@/blocks/utils'
56
import type { FirefliesResponse } from '@/tools/fireflies/types'
67
import { getTrigger } from '@/triggers'
78

@@ -619,26 +620,13 @@ export const FirefliesV2Block: BlockConfig<FirefliesResponse> = {
619620
}
620621

621622
if (params.operation === 'fireflies_upload_audio') {
622-
let audioInput = params.audioFile || params.audioFileReference
623-
if (!audioInput) {
623+
const audioFiles =
624+
normalizeFileInput(params.audioFile) || normalizeFileInput(params.audioFileReference)
625+
if (!audioFiles || audioFiles.length === 0) {
624626
throw new Error('Audio file is required.')
625627
}
626-
if (typeof audioInput === 'string') {
627-
try {
628-
audioInput = JSON.parse(audioInput)
629-
} catch {
630-
throw new Error('Audio file must be a valid file reference.')
631-
}
632-
}
633-
if (Array.isArray(audioInput)) {
634-
throw new Error(
635-
'File reference must be a single file, not an array. Use <block.files[0]> to select one file.'
636-
)
637-
}
638-
if (typeof audioInput !== 'object' || audioInput === null) {
639-
throw new Error('Audio file must be a file reference.')
640-
}
641-
const audioUrl = resolveHttpsUrlFromFileInput(audioInput)
628+
const audioFile = audioFiles[0]
629+
const audioUrl = resolveHttpsUrlFromFileInput(audioFile)
642630
if (!audioUrl) {
643631
throw new Error('Audio file must include a https URL.')
644632
}

apps/sim/blocks/blocks/gmail.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { GmailIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { AuthMode } from '@/blocks/types'
4-
import { createVersionedToolSelector } from '@/blocks/utils'
4+
import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils'
55
import type { GmailToolResponse } from '@/tools/gmail/types'
66
import { getTrigger } from '@/triggers'
77

@@ -418,6 +418,8 @@ Return ONLY the search query - no explanations, no extra text.`,
418418
labelActionMessageId,
419419
labelManagement,
420420
manualLabelManagement,
421+
attachmentFiles,
422+
attachments,
421423
...rest
422424
} = params
423425

@@ -465,9 +467,13 @@ Return ONLY the search query - no explanations, no extra text.`,
465467
}
466468
}
467469

470+
// Normalize attachments for send/draft operations
471+
const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments)
472+
468473
return {
469474
...rest,
470475
credential,
476+
...(normalizedAttachments && { attachments: normalizedAttachments }),
471477
}
472478
},
473479
},

apps/sim/blocks/blocks/google_drive.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GoogleDriveIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { AuthMode } from '@/blocks/types'
4+
import { normalizeFileInput } from '@/blocks/utils'
45
import type { GoogleDriveResponse } from '@/tools/google_drive/types'
56

67
export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
@@ -782,13 +783,18 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
782783
manualDestinationFolderId,
783784
fileSelector,
784785
manualFileId,
786+
file,
787+
fileUpload,
785788
mimeType,
786789
shareType,
787790
starred,
788791
sendNotification,
789792
...rest
790793
} = params
791794

795+
// Normalize file input - handles both basic (file-upload) and advanced (short-input) modes
796+
const normalizedFile = normalizeFileInput(file ?? fileUpload)
797+
792798
// Use folderSelector if provided, otherwise use manualFolderId
793799
const effectiveFolderId = (folderSelector || manualFolderId || '').trim()
794800

@@ -813,6 +819,7 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr
813819
folderId: effectiveFolderId || undefined,
814820
fileId: effectiveFileId || undefined,
815821
destinationFolderId: effectiveDestinationFolderId || undefined,
822+
file: normalizedFile,
816823
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
817824
mimeType: mimeType,
818825
type: shareType, // Map shareType to type for share tool

apps/sim/blocks/blocks/google_slides.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { GoogleSlidesIcon } from '@/components/icons'
22
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
33
import type { BlockConfig } from '@/blocks/types'
44
import { AuthMode } from '@/blocks/types'
5+
import { normalizeFileInput } from '@/blocks/utils'
56
import type { GoogleSlidesResponse } from '@/tools/google_slides/types'
67

78
export const GoogleSlidesBlock: BlockConfig<GoogleSlidesResponse> = {
@@ -960,26 +961,18 @@ export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
960961
}
961962

962963
if (params.operation === 'add_image') {
963-
let imageInput = params.imageFile || params.imageFileReference || params.imageSource
964-
if (!imageInput) {
964+
const imageInput = params.imageFile || params.imageFileReference || params.imageSource
965+
const normalizedFiles = normalizeFileInput(imageInput)
966+
if (!normalizedFiles || normalizedFiles.length === 0) {
965967
throw new Error('Image file is required.')
966968
}
967-
if (typeof imageInput === 'string') {
968-
try {
969-
imageInput = JSON.parse(imageInput)
970-
} catch {
971-
throw new Error('Image file must be a valid file reference.')
972-
}
973-
}
974-
if (Array.isArray(imageInput)) {
969+
if (normalizedFiles.length > 1) {
975970
throw new Error(
976971
'File reference must be a single file, not an array. Use <block.files[0]> to select one file.'
977972
)
978973
}
979-
if (typeof imageInput !== 'object' || imageInput === null) {
980-
throw new Error('Image file must be a file reference.')
981-
}
982-
const imageUrl = resolveHttpsUrlFromFileInput(imageInput)
974+
const fileObject = normalizedFiles[0]
975+
const imageUrl = resolveHttpsUrlFromFileInput(fileObject)
983976
if (!imageUrl) {
984977
throw new Error('Image file must include a https URL.')
985978
}

apps/sim/blocks/blocks/linear.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LinearIcon } from '@/components/icons'
22
import type { BlockConfig } from '@/blocks/types'
33
import { AuthMode } from '@/blocks/types'
4+
import { normalizeFileInput } from '@/blocks/utils'
45
import type { LinearResponse } from '@/tools/linear/types'
56
import { getTrigger } from '@/triggers'
67

@@ -1773,16 +1774,21 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
17731774
if (!params.issueId?.trim()) {
17741775
throw new Error('Issue ID is required.')
17751776
}
1776-
if (Array.isArray(params.file)) {
1777-
throw new Error('Attachment file must be a single file.')
1778-
}
1779-
if (Array.isArray(params.attachmentFileUpload)) {
1777+
// Normalize file inputs - handles JSON stringified values from advanced mode
1778+
const normalizedUpload = normalizeFileInput(params.attachmentFileUpload)
1779+
const normalizedFile = normalizeFileInput(params.file)
1780+
// Take the first file from whichever input has data (Linear only accepts single file)
1781+
const attachmentFile = normalizedUpload?.[0] || normalizedFile?.[0]
1782+
// Check for multiple files
1783+
if (
1784+
(normalizedUpload && normalizedUpload.length > 1) ||
1785+
(normalizedFile && normalizedFile.length > 1)
1786+
) {
17801787
throw new Error('Attachment file must be a single file.')
17811788
}
1782-
const attachmentFile = params.attachmentFileUpload || params.file
17831789
const attachmentUrl =
17841790
params.url?.trim() ||
1785-
(attachmentFile && !Array.isArray(attachmentFile) ? attachmentFile.url : undefined)
1791+
(attachmentFile ? (attachmentFile as { url?: string }).url : undefined)
17861792
if (!attachmentUrl) {
17871793
throw new Error('URL or file is required.')
17881794
}

apps/sim/blocks/blocks/mistral_parse.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MistralIcon } from '@/components/icons'
22
import { AuthMode, type BlockConfig, type SubBlockType } from '@/blocks/types'
3-
import { createVersionedToolSelector } from '@/blocks/utils'
3+
import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils'
44
import type { MistralParserOutput } from '@/tools/mistral/types'
55

66
export const MistralParseBlock: BlockConfig<MistralParserOutput> = {
@@ -213,26 +213,18 @@ export const MistralParseV2Block: BlockConfig<MistralParserOutput> = {
213213
resultType: params.resultType || 'markdown',
214214
}
215215

216-
let documentInput = params.fileUpload || params.fileReference || params.document
217-
if (!documentInput) {
216+
const documentInput = normalizeFileInput(
217+
params.fileUpload || params.fileReference || params.document
218+
)
219+
if (!documentInput || documentInput.length === 0) {
218220
throw new Error('PDF document is required')
219221
}
220-
if (typeof documentInput === 'string') {
221-
try {
222-
documentInput = JSON.parse(documentInput)
223-
} catch {
224-
throw new Error('PDF document must be a valid file reference')
225-
}
226-
}
227-
if (Array.isArray(documentInput)) {
222+
if (documentInput.length > 1) {
228223
throw new Error(
229224
'File reference must be a single file, not an array. Use <block.attachments[0]> to select one file.'
230225
)
231226
}
232-
if (typeof documentInput !== 'object' || documentInput === null) {
233-
throw new Error('PDF document must be a file reference')
234-
}
235-
parameters.file = documentInput
227+
parameters.file = documentInput[0]
236228

237229
let pagesArray: number[] | undefined
238230
if (params.pages && params.pages.trim() !== '') {

0 commit comments

Comments
 (0)