From c230e1aae2b98c4067f88eafbb7bf9c6f413f515 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 3 Feb 2026 11:38:14 -0800 Subject: [PATCH] normalize file input --- apps/sim/blocks/blocks/a2a.ts | 9 +++ apps/sim/blocks/blocks/confluence.ts | 6 +- apps/sim/blocks/blocks/dropbox.ts | 7 ++ apps/sim/blocks/blocks/file.ts | 82 +++++++++-------------- apps/sim/blocks/blocks/fireflies.ts | 24 ++----- apps/sim/blocks/blocks/gmail.ts | 8 ++- apps/sim/blocks/blocks/google_drive.ts | 7 ++ apps/sim/blocks/blocks/google_slides.ts | 21 ++---- apps/sim/blocks/blocks/linear.ts | 18 +++-- apps/sim/blocks/blocks/mistral_parse.ts | 22 ++---- apps/sim/blocks/blocks/onedrive.ts | 17 ++++- apps/sim/blocks/blocks/outlook.ts | 9 +++ apps/sim/blocks/blocks/pulse.ts | 20 ++---- apps/sim/blocks/blocks/reducto.ts | 20 ++---- apps/sim/blocks/blocks/s3.ts | 4 +- apps/sim/blocks/blocks/sendgrid.ts | 7 ++ apps/sim/blocks/blocks/sftp.ts | 3 +- apps/sim/blocks/blocks/sharepoint.ts | 7 +- apps/sim/blocks/blocks/smtp.ts | 3 +- apps/sim/blocks/blocks/spotify.ts | 5 ++ apps/sim/blocks/blocks/stt.ts | 59 ++++++++-------- apps/sim/blocks/blocks/supabase.ts | 12 ++++ apps/sim/blocks/blocks/telegram.ts | 24 +++---- apps/sim/blocks/blocks/textract.ts | 23 ++----- apps/sim/blocks/blocks/video_generator.ts | 3 +- apps/sim/blocks/blocks/vision.ts | 21 ++---- apps/sim/blocks/blocks/wordpress.ts | 3 +- 27 files changed, 223 insertions(+), 221 deletions(-) diff --git a/apps/sim/blocks/blocks/a2a.ts b/apps/sim/blocks/blocks/a2a.ts index 86c98ac9b..7426ea917 100644 --- a/apps/sim/blocks/blocks/a2a.ts +++ b/apps/sim/blocks/blocks/a2a.ts @@ -1,5 +1,6 @@ import { A2AIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { ToolResponse } from '@/tools/types' export interface A2AResponse extends ToolResponse { @@ -214,6 +215,14 @@ export const A2ABlock: BlockConfig = { ], config: { tool: (params) => params.operation as string, + params: (params) => { + const { fileUpload, fileReference, ...rest } = params + const normalizedFiles = normalizeFileInput(fileUpload || fileReference || params.files) + return { + ...rest, + ...(normalizedFiles && { files: normalizedFiles }), + } + }, }, }, inputs: { diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index 5f9436f5c..0fb46836c 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -1,6 +1,7 @@ import { ConfluenceIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { ConfluenceResponse } from '@/tools/confluence/types' export const ConfluenceBlock: BlockConfig = { @@ -651,14 +652,15 @@ export const ConfluenceV2Block: BlockConfig = { if (operation === 'upload_attachment') { const fileInput = attachmentFileUpload || attachmentFileReference || attachmentFile - if (!fileInput) { + const normalizedFile = normalizeFileInput(fileInput) + if (!normalizedFile) { throw new Error('File is required for upload attachment operation.') } return { credential, pageId: effectivePageId, operation, - file: fileInput, + file: normalizedFile, fileName: attachmentFileName, comment: attachmentComment, ...rest, diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index b08f6403d..8fbbb8cb3 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -1,6 +1,7 @@ import { DropboxIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { DropboxResponse } from '@/tools/dropbox/types' export const DropboxBlock: BlockConfig = { @@ -316,6 +317,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, params.maxResults = Number(params.maxResults) } + // Normalize file input for upload operation + // normalizeFileInput handles JSON stringified values from advanced mode + if (params.fileContent) { + params.fileContent = normalizeFileInput(params.fileContent) + } + switch (params.operation) { case 'dropbox_upload': return 'dropbox_upload' diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index 3b0f5ccb9..be14e6a58 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger' import { DocumentIcon } from '@/components/icons' import { inferContextFromKey } from '@/lib/uploads/utils/file-utils' import type { BlockConfig, SubBlockType } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { FileParserOutput, FileParserV3Output } from '@/tools/file/types' const logger = createLogger('FileBlock') @@ -200,7 +200,21 @@ export const FileV2Block: BlockConfig = { throw new Error('File is required') } - if (typeof fileInput === 'string') { + // First, try to normalize as file objects (handles JSON strings from advanced mode) + const normalizedFiles = normalizeFileInput(fileInput) + if (normalizedFiles) { + const filePaths = resolveFilePathsFromInput(normalizedFiles) + if (filePaths.length > 0) { + return { + filePath: filePaths.length === 1 ? filePaths[0] : filePaths, + fileType: params.fileType || 'auto', + workspaceId: params._context?.workspaceId, + } + } + } + + // If normalization fails, treat as direct URL string + if (typeof fileInput === 'string' && fileInput.trim()) { return { filePath: fileInput.trim(), fileType: params.fileType || 'auto', @@ -208,22 +222,6 @@ export const FileV2Block: BlockConfig = { } } - if (Array.isArray(fileInput) && fileInput.length > 0) { - const filePaths = resolveFilePathsFromInput(fileInput) - return { - filePath: filePaths.length === 1 ? filePaths[0] : filePaths, - fileType: params.fileType || 'auto', - } - } - - const resolvedSingle = resolveFilePathsFromInput(fileInput) - if (resolvedSingle.length > 0) { - return { - filePath: resolvedSingle[0], - fileType: params.fileType || 'auto', - } - } - logger.error('Invalid file input format') throw new Error('Invalid file input') }, @@ -292,7 +290,23 @@ export const FileV3Block: BlockConfig = { throw new Error('File input is required') } - if (typeof fileInput === 'string') { + // First, try to normalize as file objects (handles JSON strings from advanced mode) + const normalizedFiles = normalizeFileInput(fileInput) + if (normalizedFiles) { + const filePaths = resolveFilePathsFromInput(normalizedFiles) + if (filePaths.length > 0) { + return { + filePath: filePaths.length === 1 ? filePaths[0] : filePaths, + fileType: params.fileType || 'auto', + workspaceId: params._context?.workspaceId, + workflowId: params._context?.workflowId, + executionId: params._context?.executionId, + } + } + } + + // If normalization fails, treat as direct URL string + if (typeof fileInput === 'string' && fileInput.trim()) { return { filePath: fileInput.trim(), fileType: params.fileType || 'auto', @@ -302,36 +316,6 @@ export const FileV3Block: BlockConfig = { } } - if (Array.isArray(fileInput)) { - const filePaths = resolveFilePathsFromInput(fileInput) - if (filePaths.length === 0) { - logger.error('No valid file paths found in file input array') - throw new Error('File input is required') - } - return { - filePath: filePaths.length === 1 ? filePaths[0] : filePaths, - fileType: params.fileType || 'auto', - workspaceId: params._context?.workspaceId, - workflowId: params._context?.workflowId, - executionId: params._context?.executionId, - } - } - - if (typeof fileInput === 'object') { - const resolvedPaths = resolveFilePathsFromInput(fileInput) - if (resolvedPaths.length === 0) { - logger.error('File input object missing path, url, or key') - throw new Error('File input is required') - } - return { - filePath: resolvedPaths[0], - fileType: params.fileType || 'auto', - workspaceId: params._context?.workspaceId, - workflowId: params._context?.workflowId, - executionId: params._context?.executionId, - } - } - logger.error('Invalid file input format') throw new Error('File input is required') }, diff --git a/apps/sim/blocks/blocks/fireflies.ts b/apps/sim/blocks/blocks/fireflies.ts index 5e12df369..d9d919da5 100644 --- a/apps/sim/blocks/blocks/fireflies.ts +++ b/apps/sim/blocks/blocks/fireflies.ts @@ -2,6 +2,7 @@ import { FirefliesIcon } from '@/components/icons' import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { FirefliesResponse } from '@/tools/fireflies/types' import { getTrigger } from '@/triggers' @@ -619,26 +620,13 @@ export const FirefliesV2Block: BlockConfig = { } if (params.operation === 'fireflies_upload_audio') { - let audioInput = params.audioFile || params.audioFileReference - if (!audioInput) { + const audioFiles = + normalizeFileInput(params.audioFile) || normalizeFileInput(params.audioFileReference) + if (!audioFiles || audioFiles.length === 0) { throw new Error('Audio file is required.') } - if (typeof audioInput === 'string') { - try { - audioInput = JSON.parse(audioInput) - } catch { - throw new Error('Audio file must be a valid file reference.') - } - } - if (Array.isArray(audioInput)) { - throw new Error( - 'File reference must be a single file, not an array. Use to select one file.' - ) - } - if (typeof audioInput !== 'object' || audioInput === null) { - throw new Error('Audio file must be a file reference.') - } - const audioUrl = resolveHttpsUrlFromFileInput(audioInput) + const audioFile = audioFiles[0] + const audioUrl = resolveHttpsUrlFromFileInput(audioFile) if (!audioUrl) { throw new Error('Audio file must include a https URL.') } diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index e0138a88d..5f8ac25e1 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -1,7 +1,7 @@ import { GmailIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { GmailToolResponse } from '@/tools/gmail/types' import { getTrigger } from '@/triggers' @@ -418,6 +418,8 @@ Return ONLY the search query - no explanations, no extra text.`, labelActionMessageId, labelManagement, manualLabelManagement, + attachmentFiles, + attachments, ...rest } = params @@ -465,9 +467,13 @@ Return ONLY the search query - no explanations, no extra text.`, } } + // Normalize attachments for send/draft operations + const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + return { ...rest, credential, + ...(normalizedAttachments && { attachments: normalizedAttachments }), } }, }, diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts index 23660a238..841934731 100644 --- a/apps/sim/blocks/blocks/google_drive.ts +++ b/apps/sim/blocks/blocks/google_drive.ts @@ -1,6 +1,7 @@ import { GoogleDriveIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { GoogleDriveResponse } from '@/tools/google_drive/types' export const GoogleDriveBlock: BlockConfig = { @@ -782,6 +783,8 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr manualDestinationFolderId, fileSelector, manualFileId, + file, + fileUpload, mimeType, shareType, starred, @@ -789,6 +792,9 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr ...rest } = params + // Normalize file input - handles both basic (file-upload) and advanced (short-input) modes + const normalizedFile = normalizeFileInput(file ?? fileUpload) + // Use folderSelector if provided, otherwise use manualFolderId const effectiveFolderId = (folderSelector || manualFolderId || '').trim() @@ -813,6 +819,7 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr folderId: effectiveFolderId || undefined, fileId: effectiveFileId || undefined, destinationFolderId: effectiveDestinationFolderId || undefined, + file: normalizedFile, pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined, mimeType: mimeType, type: shareType, // Map shareType to type for share tool diff --git a/apps/sim/blocks/blocks/google_slides.ts b/apps/sim/blocks/blocks/google_slides.ts index 42bd78961..31d47ff74 100644 --- a/apps/sim/blocks/blocks/google_slides.ts +++ b/apps/sim/blocks/blocks/google_slides.ts @@ -2,6 +2,7 @@ import { GoogleSlidesIcon } from '@/components/icons' import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { GoogleSlidesResponse } from '@/tools/google_slides/types' export const GoogleSlidesBlock: BlockConfig = { @@ -960,26 +961,18 @@ export const GoogleSlidesV2Block: BlockConfig = { } if (params.operation === 'add_image') { - let imageInput = params.imageFile || params.imageFileReference || params.imageSource - if (!imageInput) { + const imageInput = params.imageFile || params.imageFileReference || params.imageSource + const normalizedFiles = normalizeFileInput(imageInput) + if (!normalizedFiles || normalizedFiles.length === 0) { throw new Error('Image file is required.') } - if (typeof imageInput === 'string') { - try { - imageInput = JSON.parse(imageInput) - } catch { - throw new Error('Image file must be a valid file reference.') - } - } - if (Array.isArray(imageInput)) { + if (normalizedFiles.length > 1) { throw new Error( 'File reference must be a single file, not an array. Use to select one file.' ) } - if (typeof imageInput !== 'object' || imageInput === null) { - throw new Error('Image file must be a file reference.') - } - const imageUrl = resolveHttpsUrlFromFileInput(imageInput) + const fileObject = normalizedFiles[0] + const imageUrl = resolveHttpsUrlFromFileInput(fileObject) if (!imageUrl) { throw new Error('Image file must include a https URL.') } diff --git a/apps/sim/blocks/blocks/linear.ts b/apps/sim/blocks/blocks/linear.ts index fefe38bc9..58f336700 100644 --- a/apps/sim/blocks/blocks/linear.ts +++ b/apps/sim/blocks/blocks/linear.ts @@ -1,6 +1,7 @@ import { LinearIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { LinearResponse } from '@/tools/linear/types' import { getTrigger } from '@/triggers' @@ -1773,16 +1774,21 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n if (!params.issueId?.trim()) { throw new Error('Issue ID is required.') } - if (Array.isArray(params.file)) { + // Normalize file inputs - handles JSON stringified values from advanced mode + const normalizedUpload = normalizeFileInput(params.attachmentFileUpload) + const normalizedFile = normalizeFileInput(params.file) + // Take the first file from whichever input has data (Linear only accepts single file) + const attachmentFile = normalizedUpload?.[0] || normalizedFile?.[0] + // Check for multiple files + if ( + (normalizedUpload && normalizedUpload.length > 1) || + (normalizedFile && normalizedFile.length > 1) + ) { throw new Error('Attachment file must be a single file.') } - if (Array.isArray(params.attachmentFileUpload)) { - throw new Error('Attachment file must be a single file.') - } - const attachmentFile = params.attachmentFileUpload || params.file const attachmentUrl = params.url?.trim() || - (attachmentFile && !Array.isArray(attachmentFile) ? attachmentFile.url : undefined) + (attachmentFile ? (attachmentFile as { url?: string }).url : undefined) if (!attachmentUrl) { throw new Error('URL or file is required.') } diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts index 4330f2b04..472d1ff35 100644 --- a/apps/sim/blocks/blocks/mistral_parse.ts +++ b/apps/sim/blocks/blocks/mistral_parse.ts @@ -1,6 +1,6 @@ import { MistralIcon } from '@/components/icons' import { AuthMode, type BlockConfig, type SubBlockType } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { MistralParserOutput } from '@/tools/mistral/types' export const MistralParseBlock: BlockConfig = { @@ -213,26 +213,18 @@ export const MistralParseV2Block: BlockConfig = { resultType: params.resultType || 'markdown', } - let documentInput = params.fileUpload || params.fileReference || params.document - if (!documentInput) { + const documentInput = normalizeFileInput( + params.fileUpload || params.fileReference || params.document + ) + if (!documentInput || documentInput.length === 0) { throw new Error('PDF document is required') } - if (typeof documentInput === 'string') { - try { - documentInput = JSON.parse(documentInput) - } catch { - throw new Error('PDF document must be a valid file reference') - } - } - if (Array.isArray(documentInput)) { + if (documentInput.length > 1) { throw new Error( 'File reference must be a single file, not an array. Use to select one file.' ) } - if (typeof documentInput !== 'object' || documentInput === null) { - throw new Error('PDF document must be a file reference') - } - parameters.file = documentInput + parameters.file = documentInput[0] let pagesArray: number[] | undefined if (params.pages && params.pages.trim() !== '') { diff --git a/apps/sim/blocks/blocks/onedrive.ts b/apps/sim/blocks/blocks/onedrive.ts index 494bfa3b0..605c90ca3 100644 --- a/apps/sim/blocks/blocks/onedrive.ts +++ b/apps/sim/blocks/blocks/onedrive.ts @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger' import { MicrosoftOneDriveIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { OneDriveResponse } from '@/tools/onedrive/types' import { normalizeExcelValuesForToolParams } from '@/tools/onedrive/utils' @@ -352,17 +353,31 @@ export const OneDriveBlock: BlockConfig = { } }, params: (params) => { - const { credential, folderId, fileId, mimeType, values, downloadFileName, ...rest } = params + const { + credential, + folderId, + fileId, + mimeType, + values, + downloadFileName, + file, + fileReference, + ...rest + } = params let normalizedValues: ReturnType if (values !== undefined) { normalizedValues = normalizeExcelValuesForToolParams(values) } + // Normalize file input from both basic (file-upload) and advanced (short-input) modes + const normalizedFile = normalizeFileInput(file || fileReference) + return { credential, ...rest, values: normalizedValues, + file: normalizedFile, folderId: folderId || undefined, fileId: fileId || undefined, pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined, diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts index cfbe25304..b626c20a4 100644 --- a/apps/sim/blocks/blocks/outlook.ts +++ b/apps/sim/blocks/blocks/outlook.ts @@ -1,6 +1,7 @@ import { OutlookIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { OutlookResponse } from '@/tools/outlook/types' import { getTrigger } from '@/triggers' @@ -335,12 +336,20 @@ export const OutlookBlock: BlockConfig = { copyMessageId, copyDestinationFolder, manualCopyDestinationFolder, + attachmentFiles, + attachments, ...rest } = params // Handle both selector and manual folder input const effectiveFolder = (folder || manualFolder || '').trim() + // Normalize file attachments from either basic (file-upload) or advanced (short-input) mode + const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + if (normalizedAttachments) { + rest.attachments = normalizedAttachments + } + if (rest.operation === 'read_outlook') { rest.folder = effectiveFolder || 'INBOX' } diff --git a/apps/sim/blocks/blocks/pulse.ts b/apps/sim/blocks/blocks/pulse.ts index 4edcfe649..76119d1b1 100644 --- a/apps/sim/blocks/blocks/pulse.ts +++ b/apps/sim/blocks/blocks/pulse.ts @@ -1,6 +1,6 @@ import { PulseIcon } from '@/components/icons' import { AuthMode, type BlockConfig, type SubBlockType } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { PulseParserOutput } from '@/tools/pulse/types' export const PulseBlock: BlockConfig = { @@ -178,26 +178,16 @@ export const PulseV2Block: BlockConfig = { apiKey: params.apiKey.trim(), } - let documentInput = params.fileUpload || params.document - if (!documentInput) { + const normalizedFiles = normalizeFileInput(params.fileUpload || params.document) + if (!normalizedFiles || normalizedFiles.length === 0) { throw new Error('Document file is required') } - if (typeof documentInput === 'string') { - try { - documentInput = JSON.parse(documentInput) - } catch { - throw new Error('Document file must be a valid file reference') - } - } - if (Array.isArray(documentInput)) { + if (normalizedFiles.length > 1) { throw new Error( 'File reference must be a single file, not an array. Use to select one file.' ) } - if (typeof documentInput !== 'object' || documentInput === null) { - throw new Error('Document file must be a file reference') - } - parameters.file = documentInput + parameters.file = normalizedFiles[0] if (params.pages && params.pages.trim() !== '') { parameters.pages = params.pages.trim() diff --git a/apps/sim/blocks/blocks/reducto.ts b/apps/sim/blocks/blocks/reducto.ts index 8688341dd..8e7c13b8e 100644 --- a/apps/sim/blocks/blocks/reducto.ts +++ b/apps/sim/blocks/blocks/reducto.ts @@ -1,6 +1,6 @@ import { ReductoIcon } from '@/components/icons' import { AuthMode, type BlockConfig, type SubBlockType } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { ReductoParserOutput } from '@/tools/reducto/types' export const ReductoBlock: BlockConfig = { @@ -182,26 +182,16 @@ export const ReductoV2Block: BlockConfig = { apiKey: params.apiKey.trim(), } - let documentInput = params.fileUpload || params.document - if (!documentInput) { + const documentInput = normalizeFileInput(params.fileUpload || params.document) + if (!documentInput || documentInput.length === 0) { throw new Error('PDF document file is required') } - if (typeof documentInput === 'string') { - try { - documentInput = JSON.parse(documentInput) - } catch { - throw new Error('PDF document file must be a valid file reference') - } - } - if (Array.isArray(documentInput)) { + if (documentInput.length > 1) { throw new Error( 'File reference must be a single file, not an array. Use to select one file.' ) } - if (typeof documentInput !== 'object' || documentInput === null) { - throw new Error('PDF document file must be a file reference') - } - parameters.file = documentInput + parameters.file = documentInput[0] let pagesArray: number[] | undefined if (params.pages && params.pages.trim() !== '') { diff --git a/apps/sim/blocks/blocks/s3.ts b/apps/sim/blocks/blocks/s3.ts index f364a7888..5681984fd 100644 --- a/apps/sim/blocks/blocks/s3.ts +++ b/apps/sim/blocks/blocks/s3.ts @@ -1,6 +1,7 @@ import { S3Icon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { S3Response } from '@/tools/s3/types' export const S3Block: BlockConfig = { @@ -271,7 +272,8 @@ export const S3Block: BlockConfig = { throw new Error('Object Key is required for upload') } // Use file from uploadFile if in basic mode, otherwise use file reference - const fileParam = params.uploadFile || params.file + // normalizeFileInput handles JSON stringified values from advanced mode + const fileParam = normalizeFileInput(params.uploadFile || params.file) return { accessKeyId: params.accessKeyId, diff --git a/apps/sim/blocks/blocks/sendgrid.ts b/apps/sim/blocks/blocks/sendgrid.ts index 422f9b57f..c55513026 100644 --- a/apps/sim/blocks/blocks/sendgrid.ts +++ b/apps/sim/blocks/blocks/sendgrid.ts @@ -1,5 +1,6 @@ import { SendgridIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { SendMailResult } from '@/tools/sendgrid/types' export const SendGridBlock: BlockConfig = { @@ -561,9 +562,14 @@ Return ONLY the HTML content.`, templateGenerations, listPageSize, templatePageSize, + attachmentFiles, + attachments, ...rest } = params + // Normalize attachments for send_mail operation + const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + // Map renamed fields back to tool parameter names return { ...rest, @@ -577,6 +583,7 @@ Return ONLY the HTML content.`, ...(templateGenerations && { generations: templateGenerations }), ...(listPageSize && { pageSize: listPageSize }), ...(templatePageSize && { pageSize: templatePageSize }), + ...(normalizedAttachments && { attachments: normalizedAttachments }), } }, }, diff --git a/apps/sim/blocks/blocks/sftp.ts b/apps/sim/blocks/blocks/sftp.ts index 3621ee5b4..c7afdb534 100644 --- a/apps/sim/blocks/blocks/sftp.ts +++ b/apps/sim/blocks/blocks/sftp.ts @@ -1,6 +1,7 @@ import { SftpIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { SftpUploadResult } from '@/tools/sftp/types' export const SftpBlock: BlockConfig = { @@ -222,7 +223,7 @@ export const SftpBlock: BlockConfig = { return { ...connectionConfig, remotePath: params.remotePath, - files: params.files, + files: normalizeFileInput(params.uploadFiles || params.files), overwrite: params.overwrite !== false, permissions: params.permissions, } diff --git a/apps/sim/blocks/blocks/sharepoint.ts b/apps/sim/blocks/blocks/sharepoint.ts index 5fe1dfb6d..e1a6aac2a 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -2,6 +2,7 @@ import { createLogger } from '@sim/logger' import { MicrosoftSharepointIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { SharepointResponse } from '@/tools/sharepoint/types' const logger = createLogger('SharepointBlock') @@ -449,7 +450,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, } // Handle file upload files parameter - const fileParam = uploadFiles || files + const normalizedFiles = normalizeFileInput(uploadFiles || files) const baseParams: Record = { credential, siteId: effectiveSiteId || undefined, @@ -463,8 +464,8 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, } // Add files if provided - if (fileParam) { - baseParams.files = fileParam + if (normalizedFiles) { + baseParams.files = normalizedFiles } if (columnDefinitions) { diff --git a/apps/sim/blocks/blocks/smtp.ts b/apps/sim/blocks/blocks/smtp.ts index c292281b6..640cdd680 100644 --- a/apps/sim/blocks/blocks/smtp.ts +++ b/apps/sim/blocks/blocks/smtp.ts @@ -1,6 +1,7 @@ import { SmtpIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { SmtpSendMailResult } from '@/tools/smtp/types' export const SmtpBlock: BlockConfig = { @@ -176,7 +177,7 @@ export const SmtpBlock: BlockConfig = { cc: params.cc, bcc: params.bcc, replyTo: params.replyTo, - attachments: params.attachments, + attachments: normalizeFileInput(params.attachmentFiles || params.attachments), }), }, }, diff --git a/apps/sim/blocks/blocks/spotify.ts b/apps/sim/blocks/blocks/spotify.ts index 417724f6e..57e75639e 100644 --- a/apps/sim/blocks/blocks/spotify.ts +++ b/apps/sim/blocks/blocks/spotify.ts @@ -1,6 +1,7 @@ import { SpotifyIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { ToolResponse } from '@/tools/types' export const SpotifyBlock: BlockConfig = { @@ -785,6 +786,10 @@ export const SpotifyBlock: BlockConfig = { if (params.playUris) { params.uris = params.playUris } + // Normalize file input for cover image + if (params.coverImage !== undefined) { + params.coverImage = normalizeFileInput(params.coverImage) + } return params.operation || 'spotify_search' }, }, diff --git a/apps/sim/blocks/blocks/stt.ts b/apps/sim/blocks/blocks/stt.ts index 344f14458..8896f1b61 100644 --- a/apps/sim/blocks/blocks/stt.ts +++ b/apps/sim/blocks/blocks/stt.ts @@ -1,6 +1,6 @@ import { STTIcon } from '@/components/icons' import { AuthMode, type BlockConfig } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { SttBlockResponse } from '@/tools/stt/types' export const SttBlock: BlockConfig = { @@ -258,22 +258,28 @@ export const SttBlock: BlockConfig = { return 'stt_whisper' } }, - params: (params) => ({ - provider: params.provider, - apiKey: params.apiKey, - model: params.model, - audioFile: params.audioFile, - audioFileReference: params.audioFileReference, - audioUrl: params.audioUrl, - language: params.language, - timestamps: params.timestamps, - diarization: params.diarization, - translateToEnglish: params.translateToEnglish, - sentiment: params.sentiment, - entityDetection: params.entityDetection, - piiRedaction: params.piiRedaction, - summarization: params.summarization, - }), + params: (params) => { + // Normalize file input from basic (file-upload) or advanced (short-input) mode + const normalizedFiles = normalizeFileInput(params.audioFile || params.audioFileReference) + const audioFile = normalizedFiles?.[0] + + return { + provider: params.provider, + apiKey: params.apiKey, + model: params.model, + audioFile, + audioFileReference: undefined, + audioUrl: params.audioUrl, + language: params.language, + timestamps: params.timestamps, + diarization: params.diarization, + translateToEnglish: params.translateToEnglish, + sentiment: params.sentiment, + entityDetection: params.entityDetection, + piiRedaction: params.piiRedaction, + summarization: params.summarization, + } + }, }, }, @@ -386,24 +392,15 @@ export const SttV2Block: BlockConfig = { fallbackToolId: 'stt_whisper_v2', }), params: (params) => { - let audioInput = params.audioFile || params.audioFileReference - if (audioInput && typeof audioInput === 'string') { - try { - audioInput = JSON.parse(audioInput) - } catch { - throw new Error('Audio file must be a valid file reference') - } - } - if (audioInput && Array.isArray(audioInput)) { - throw new Error( - 'File reference must be a single file, not an array. Use to select one file.' - ) - } + // Normalize file input from basic (file-upload) or advanced (short-input) mode + const normalizedFiles = normalizeFileInput(params.audioFile || params.audioFileReference) + const audioFile = normalizedFiles?.[0] + return { provider: params.provider, apiKey: params.apiKey, model: params.model, - audioFile: audioInput, + audioFile, audioFileReference: undefined, language: params.language, timestamps: params.timestamps, diff --git a/apps/sim/blocks/blocks/supabase.ts b/apps/sim/blocks/blocks/supabase.ts index 49c59cb35..07d3a4bae 100644 --- a/apps/sim/blocks/blocks/supabase.ts +++ b/apps/sim/blocks/blocks/supabase.ts @@ -1,6 +1,7 @@ import { createLogger } from '@sim/logger' import { SupabaseIcon } from '@/components/icons' import { AuthMode, type BlockConfig } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { SupabaseResponse } from '@/tools/supabase/types' const logger = createLogger('SupabaseBlock') @@ -973,9 +974,16 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e allowedMimeTypes, upsert, download, + file, + fileContent, + fileData, ...rest } = params + // Normalize file input for storage_upload operation + // normalizeFileInput handles JSON stringified values from advanced mode + const normalizedFileData = normalizeFileInput(file || fileContent || fileData) + // Parse JSON data if it's a string let parsedData if (data && typeof data === 'string' && data.trim()) { @@ -1102,6 +1110,10 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e result.isPublic = parsedIsPublic } + if (normalizedFileData !== undefined) { + result.fileData = normalizedFileData + } + return result }, }, diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts index 8cdb9ae72..bda5b617b 100644 --- a/apps/sim/blocks/blocks/telegram.ts +++ b/apps/sim/blocks/blocks/telegram.ts @@ -269,46 +269,46 @@ export const TelegramBlock: BlockConfig = { messageId: params.messageId, } case 'telegram_send_photo': { - const photoSource = params.photoFile || params.photo - if (!photoSource) { + const photoSource = normalizeFileInput(params.photoFile || params.photo) + if (!photoSource || photoSource.length === 0) { throw new Error('Photo is required.') } return { ...commonParams, - photo: photoSource, + photo: photoSource[0], caption: params.caption, } } case 'telegram_send_video': { - const videoSource = params.videoFile || params.video - if (!videoSource) { + const videoSource = normalizeFileInput(params.videoFile || params.video) + if (!videoSource || videoSource.length === 0) { throw new Error('Video is required.') } return { ...commonParams, - video: videoSource, + video: videoSource[0], caption: params.caption, } } case 'telegram_send_audio': { - const audioSource = params.audioFile || params.audio - if (!audioSource) { + const audioSource = normalizeFileInput(params.audioFile || params.audio) + if (!audioSource || audioSource.length === 0) { throw new Error('Audio is required.') } return { ...commonParams, - audio: audioSource, + audio: audioSource[0], caption: params.caption, } } case 'telegram_send_animation': { - const animationSource = params.animationFile || params.animation - if (!animationSource) { + const animationSource = normalizeFileInput(params.animationFile || params.animation) + if (!animationSource || animationSource.length === 0) { throw new Error('Animation is required.') } return { ...commonParams, - animation: animationSource, + animation: animationSource[0], caption: params.caption, } } diff --git a/apps/sim/blocks/blocks/textract.ts b/apps/sim/blocks/blocks/textract.ts index f3c12c40b..19f32b541 100644 --- a/apps/sim/blocks/blocks/textract.ts +++ b/apps/sim/blocks/blocks/textract.ts @@ -1,6 +1,6 @@ import { TextractIcon } from '@/components/icons' import { AuthMode, type BlockConfig, type SubBlockType } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { TextractParserOutput } from '@/tools/textract/types' export const TextractBlock: BlockConfig = { @@ -260,26 +260,11 @@ export const TextractV2Block: BlockConfig = { } parameters.s3Uri = params.s3Uri.trim() } else { - let documentInput = params.fileUpload || params.document - if (!documentInput) { + const files = normalizeFileInput(params.fileUpload || params.document) + if (!files || files.length === 0) { throw new Error('Document file is required') } - if (typeof documentInput === 'string') { - try { - documentInput = JSON.parse(documentInput) - } catch { - throw new Error('Document file must be a valid file reference') - } - } - if (Array.isArray(documentInput)) { - throw new Error( - 'File reference must be a single file, not an array. Use to select one file.' - ) - } - if (typeof documentInput !== 'object' || documentInput === null) { - throw new Error('Document file must be a file reference') - } - parameters.file = documentInput + parameters.file = files[0] } const featureTypes: string[] = [] diff --git a/apps/sim/blocks/blocks/video_generator.ts b/apps/sim/blocks/blocks/video_generator.ts index 2743cf2c1..cd874c6de 100644 --- a/apps/sim/blocks/blocks/video_generator.ts +++ b/apps/sim/blocks/blocks/video_generator.ts @@ -1,5 +1,6 @@ import { VideoIcon } from '@/components/icons' import { AuthMode, type BlockConfig } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { VideoBlockResponse } from '@/tools/video/types' export const VideoGeneratorBlock: BlockConfig = { @@ -745,7 +746,7 @@ export const VideoGeneratorV2Block: BlockConfig = { duration: params.duration ? Number(params.duration) : undefined, aspectRatio: params.aspectRatio, resolution: params.resolution, - visualReference: visualRef, + visualReference: normalizeFileInput(visualRef), consistencyMode: params.consistencyMode, stylePreset: params.stylePreset, promptOptimizer: params.promptOptimizer, diff --git a/apps/sim/blocks/blocks/vision.ts b/apps/sim/blocks/blocks/vision.ts index 96422c2be..244c4126b 100644 --- a/apps/sim/blocks/blocks/vision.ts +++ b/apps/sim/blocks/blocks/vision.ts @@ -1,7 +1,7 @@ import { EyeIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' -import { createVersionedToolSelector } from '@/blocks/utils' +import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils' import type { VisionResponse } from '@/tools/vision/types' const VISION_MODEL_OPTIONS = [ @@ -117,22 +117,13 @@ export const VisionV2Block: BlockConfig = { fallbackToolId: 'vision_tool_v2', }), params: (params) => { - let imageInput = params.imageFile || params.imageFileReference - if (imageInput && typeof imageInput === 'string') { - try { - imageInput = JSON.parse(imageInput) - } catch { - throw new Error('Image file must be a valid file reference') - } - } - if (imageInput && Array.isArray(imageInput)) { - throw new Error( - 'File reference must be a single file, not an array. Use to select one file.' - ) - } + // normalizeFileInput handles JSON stringified values from advanced mode + const normalizedFiles = normalizeFileInput(params.imageFile || params.imageFileReference) + // Vision expects a single file, take the first from the normalized array + const imageFile = normalizedFiles?.[0] return { ...params, - imageFile: imageInput, + imageFile, imageFileReference: undefined, } }, diff --git a/apps/sim/blocks/blocks/wordpress.ts b/apps/sim/blocks/blocks/wordpress.ts index eb19e776a..bcc7eba50 100644 --- a/apps/sim/blocks/blocks/wordpress.ts +++ b/apps/sim/blocks/blocks/wordpress.ts @@ -1,6 +1,7 @@ import { WordpressIcon } from '@/components/icons' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' import type { WordPressResponse } from '@/tools/wordpress/types' export const WordPressBlock: BlockConfig = { @@ -769,7 +770,7 @@ export const WordPressBlock: BlockConfig = { case 'wordpress_upload_media': return { ...baseParams, - file: params.fileUpload || params.file, + file: normalizeFileInput(params.fileUpload || params.file), filename: params.filename, title: params.mediaTitle, caption: params.caption,