normalize file input

This commit is contained in:
Vikhyath Mondreti
2026-02-03 11:38:14 -08:00
parent 6e5e8debc5
commit c230e1aae2
27 changed files with 223 additions and 221 deletions

View File

@@ -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<A2AResponse> = {
],
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: {

View File

@@ -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<ConfluenceResponse> = {
@@ -651,14 +652,15 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
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,

View File

@@ -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<DropboxResponse> = {
@@ -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'

View File

@@ -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<FileParserOutput> = {
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<FileParserOutput> = {
}
}
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<FileParserV3Output> = {
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<FileParserV3Output> = {
}
}
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')
},

View File

@@ -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<FirefliesResponse> = {
}
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 <block.files[0]> 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.')
}

View File

@@ -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 }),
}
},
},

View File

@@ -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<GoogleDriveResponse> = {
@@ -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

View File

@@ -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<GoogleSlidesResponse> = {
@@ -960,26 +961,18 @@ export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
}
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 <block.files[0]> 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.')
}

View File

@@ -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.')
}

View File

@@ -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<MistralParserOutput> = {
@@ -213,26 +213,18 @@ export const MistralParseV2Block: BlockConfig<MistralParserOutput> = {
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 <block.attachments[0]> 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() !== '') {

View File

@@ -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<OneDriveResponse> = {
}
},
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<typeof normalizeExcelValuesForToolParams>
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,

View File

@@ -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<OutlookResponse> = {
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'
}

View File

@@ -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<PulseParserOutput> = {
@@ -178,26 +178,16 @@ export const PulseV2Block: BlockConfig<PulseParserOutput> = {
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 <block.attachments[0]> 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()

View File

@@ -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<ReductoParserOutput> = {
@@ -182,26 +182,16 @@ export const ReductoV2Block: BlockConfig<ReductoParserOutput> = {
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 <block.attachments[0]> 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() !== '') {

View File

@@ -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<S3Response> = {
@@ -271,7 +272,8 @@ export const S3Block: BlockConfig<S3Response> = {
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,

View File

@@ -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<SendMailResult> = {
@@ -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 }),
}
},
},

View File

@@ -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<SftpUploadResult> = {
@@ -222,7 +223,7 @@ export const SftpBlock: BlockConfig<SftpUploadResult> = {
return {
...connectionConfig,
remotePath: params.remotePath,
files: params.files,
files: normalizeFileInput(params.uploadFiles || params.files),
overwrite: params.overwrite !== false,
permissions: params.permissions,
}

View File

@@ -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<string, any> = {
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) {

View File

@@ -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<SmtpSendMailResult> = {
@@ -176,7 +177,7 @@ export const SmtpBlock: BlockConfig<SmtpSendMailResult> = {
cc: params.cc,
bcc: params.bcc,
replyTo: params.replyTo,
attachments: params.attachments,
attachments: normalizeFileInput(params.attachmentFiles || params.attachments),
}),
},
},

View File

@@ -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<ToolResponse> = {
@@ -785,6 +786,10 @@ export const SpotifyBlock: BlockConfig<ToolResponse> = {
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'
},
},

View File

@@ -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<SttBlockResponse> = {
@@ -258,22 +258,28 @@ export const SttBlock: BlockConfig<SttBlockResponse> = {
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<SttBlockResponse> = {
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 <block.files[0]> 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,

View File

@@ -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
},
},

View File

@@ -269,46 +269,46 @@ export const TelegramBlock: BlockConfig<TelegramResponse> = {
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,
}
}

View File

@@ -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<TextractParserOutput> = {
@@ -260,26 +260,11 @@ export const TextractV2Block: BlockConfig<TextractParserOutput> = {
}
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 <block.attachments[0]> 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[] = []

View File

@@ -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<VideoBlockResponse> = {
@@ -745,7 +746,7 @@ export const VideoGeneratorV2Block: BlockConfig<VideoBlockResponse> = {
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,

View File

@@ -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<VisionResponse> = {
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 <block.files[0]> 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,
}
},

View File

@@ -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<WordPressResponse> = {
@@ -769,7 +770,7 @@ export const WordPressBlock: BlockConfig<WordPressResponse> = {
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,