diff --git a/.claude/commands/add-integration.md b/.claude/commands/add-integration.md index 12350ccd5..c3221b2e9 100644 --- a/.claude/commands/add-integration.md +++ b/.claude/commands/add-integration.md @@ -206,10 +206,15 @@ export const {Service}Block: BlockConfig = { } ``` -**Critical:** -- `canonicalParamId` must NOT match any other subblock's `id`, must be unique per block, and should only be used to link basic/advanced alternatives for the same parameter. -- `mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent. -- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions. +**Critical Canonical Param Rules:** +- `canonicalParamId` must NOT match any subblock's `id` in the block +- `canonicalParamId` must be unique per operation/condition context +- Only use `canonicalParamId` to link basic/advanced alternatives for the same logical parameter +- `mode` only controls UI visibility, NOT serialization. Without `canonicalParamId`, both basic and advanced field values would be sent +- Every subblock `id` must be unique within the block. Duplicate IDs cause conflicts even with different conditions +- **Required consistency:** If one subblock in a canonical group has `required: true`, ALL subblocks in that group must have `required: true` (prevents bypassing validation by switching modes) +- **Inputs section:** Must list canonical param IDs (e.g., `fileId`), NOT raw subblock IDs (e.g., `fileSelector`, `manualFileId`) +- **Params function:** Must use canonical param IDs, NOT raw subblock IDs (raw IDs are deleted after canonical transformation) ## Step 4: Add Icon diff --git a/.claude/rules/sim-integrations.md b/.claude/rules/sim-integrations.md index 825acce5d..7a1c70017 100644 --- a/.claude/rules/sim-integrations.md +++ b/.claude/rules/sim-integrations.md @@ -157,6 +157,36 @@ dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] } - `'both'` - Show in both modes (default) - `'trigger'` - Only when block is used as trigger +### `canonicalParamId` - Link basic/advanced alternatives + +Use to map multiple UI inputs to a single logical parameter: + +```typescript +// Basic mode: Visual selector +{ + id: 'fileSelector', + type: 'file-selector', + mode: 'basic', + canonicalParamId: 'fileId', + required: true, +}, +// Advanced mode: Manual input +{ + id: 'manualFileId', + type: 'short-input', + mode: 'advanced', + canonicalParamId: 'fileId', + required: true, +}, +``` + +**Critical Rules:** +- `canonicalParamId` must NOT match any subblock's `id` +- `canonicalParamId` must be unique per operation/condition context +- **Required consistency:** All subblocks in a canonical group must have the same `required` status +- **Inputs section:** Must list canonical param IDs (e.g., `fileId`), NOT raw subblock IDs +- **Params function:** Must use canonical param IDs (raw IDs are deleted after canonical transformation) + **Register in `blocks/registry.ts`:** ```typescript diff --git a/.cursor/rules/sim-integrations.mdc b/.cursor/rules/sim-integrations.mdc index 20edc82e1..309cd6a4e 100644 --- a/.cursor/rules/sim-integrations.mdc +++ b/.cursor/rules/sim-integrations.mdc @@ -155,6 +155,36 @@ dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] } - `'both'` - Show in both modes (default) - `'trigger'` - Only when block is used as trigger +### `canonicalParamId` - Link basic/advanced alternatives + +Use to map multiple UI inputs to a single logical parameter: + +```typescript +// Basic mode: Visual selector +{ + id: 'fileSelector', + type: 'file-selector', + mode: 'basic', + canonicalParamId: 'fileId', + required: true, +}, +// Advanced mode: Manual input +{ + id: 'manualFileId', + type: 'short-input', + mode: 'advanced', + canonicalParamId: 'fileId', + required: true, +}, +``` + +**Critical Rules:** +- `canonicalParamId` must NOT match any subblock's `id` +- `canonicalParamId` must be unique per operation/condition context +- **Required consistency:** All subblocks in a canonical group must have the same `required` status +- **Inputs section:** Must list canonical param IDs (e.g., `fileId`), NOT raw subblock IDs +- **Params function:** Must use canonical param IDs (raw IDs are deleted after canonical transformation) + **Register in `blocks/registry.ts`:** ```typescript diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx index 39ad880d9..b46467aa3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/file-upload/file-upload.tsx @@ -34,6 +34,103 @@ interface UploadedFile { type: string } +interface SingleFileSelectorProps { + file: UploadedFile + options: Array<{ label: string; value: string; disabled?: boolean }> + selectedValue: string + inputValue: string + onInputChange: (value: string) => void + onClear: (e: React.MouseEvent) => void + onOpenChange: (open: boolean) => void + disabled: boolean + isLoading: boolean + formatFileSize: (bytes: number) => string + truncateMiddle: (text: string, start?: number, end?: number) => string + isDeleting: boolean +} + +/** + * Single file selector component that shows the selected file with both + * a clear button (X) and a chevron to change the selection. + * Follows the same pattern as SelectorCombobox for consistency. + */ +function SingleFileSelector({ + file, + options, + selectedValue, + inputValue, + onInputChange, + onClear, + onOpenChange, + disabled, + isLoading, + formatFileSize, + truncateMiddle, + isDeleting, +}: SingleFileSelectorProps) { + const displayLabel = `${truncateMiddle(file.name, 20, 12)} (${formatFileSize(file.size)})` + const [localInputValue, setLocalInputValue] = useState(displayLabel) + const [isEditing, setIsEditing] = useState(false) + + // Sync display label when file changes + useEffect(() => { + if (!isEditing) { + setLocalInputValue(displayLabel) + } + }, [displayLabel, isEditing]) + + return ( +
+ { + // Check if user selected an option + const matched = options.find((opt) => opt.value === newValue || opt.label === newValue) + if (matched) { + setIsEditing(false) + setLocalInputValue(displayLabel) + onInputChange(matched.value) + return + } + // User is typing to search + setIsEditing(true) + setLocalInputValue(newValue) + }} + onOpenChange={(open) => { + if (!open) { + setIsEditing(false) + setLocalInputValue(displayLabel) + } + onOpenChange(open) + }} + placeholder={isLoading ? 'Loading files...' : 'Select or upload file'} + disabled={disabled || isDeleting} + editable={true} + filterOptions={isEditing} + isLoading={isLoading} + inputProps={{ + className: 'pr-[60px]', + }} + /> + +
+ ) +} + interface UploadingFile { id: string name: string @@ -500,6 +597,7 @@ export function FileUpload({ const hasFiles = filesArray.length > 0 const isUploading = uploadingFiles.length > 0 + // Options for multiple file mode (filters out already selected files) const comboboxOptions = useMemo( () => [ { label: 'Upload New File', value: '__upload_new__' }, @@ -516,10 +614,43 @@ export function FileUpload({ [availableWorkspaceFiles, acceptedTypes] ) + // Options for single file mode (includes all files, selected one will be highlighted) + const singleFileOptions = useMemo( + () => [ + { label: 'Upload New File', value: '__upload_new__' }, + ...workspaceFiles.map((file) => { + const isAccepted = + !acceptedTypes || acceptedTypes === '*' || isFileTypeAccepted(file.type, acceptedTypes) + return { + label: file.name, + value: file.id, + disabled: !isAccepted, + } + }), + ], + [workspaceFiles, acceptedTypes] + ) + + // Find the selected file's workspace ID for highlighting in single file mode + const selectedFileId = useMemo(() => { + if (!hasFiles || multiple) return '' + const currentFile = filesArray[0] + if (!currentFile) return '' + // Match by key or path + const matchedWorkspaceFile = workspaceFiles.find( + (wf) => + wf.key === currentFile.key || + wf.name === currentFile.name || + currentFile.path?.includes(wf.key) + ) + return matchedWorkspaceFile?.id || '' + }, [filesArray, workspaceFiles, hasFiles, multiple]) + const handleComboboxChange = (value: string) => { setInputValue(value) - const selectedFile = availableWorkspaceFiles.find((file) => file.id === value) + // Look in full workspaceFiles list (not filtered) to allow re-selecting same file in single mode + const selectedFile = workspaceFiles.find((file) => file.id === value) const isAcceptedType = selectedFile && (!acceptedTypes || @@ -559,16 +690,17 @@ export function FileUpload({ {/* Error message */} {uploadError &&
{uploadError}
} - {/* File list with consistent spacing */} - {(hasFiles || isUploading) && ( + {/* File list with consistent spacing - only show for multiple mode or when uploading */} + {((hasFiles && multiple) || isUploading) && (
- {/* Only show files that aren't currently uploading */} - {filesArray.map((file) => { - const isCurrentlyUploading = uploadingFiles.some( - (uploadingFile) => uploadingFile.name === file.name - ) - return !isCurrentlyUploading && renderFileItem(file) - })} + {/* Only show files that aren't currently uploading (for multiple mode only) */} + {multiple && + filesArray.map((file) => { + const isCurrentlyUploading = uploadingFiles.some( + (uploadingFile) => uploadingFile.name === file.name + ) + return !isCurrentlyUploading && renderFileItem(file) + })} {isUploading && ( <> {uploadingFiles.map(renderUploadingItem)} @@ -604,6 +736,26 @@ export function FileUpload({ /> )} + {/* Single file mode with file selected: show combobox-style UI with X and chevron */} + {hasFiles && !multiple && !isUploading && ( + handleRemoveFile(filesArray[0], e)} + onOpenChange={(open) => { + if (open) void loadWorkspaceFiles() + }} + disabled={disabled} + isLoading={loadingWorkspaceFiles} + formatFileSize={formatFileSize} + truncateMiddle={truncateMiddle} + isDeleting={deletingFiles[filesArray[0]?.path || '']} + /> + )} + {/* Show dropdown selector if no files and not uploading */} {!hasFiles && !isUploading && ( { + e.preventDefault() + e.stopPropagation() + if (readOnly || disabled) return + setStoreValue(null) + setInputValue('') + onOptionChange?.('') + }, + [setStoreValue, onOptionChange, readOnly, disabled] + ) + + const showClearButton = Boolean(activeValue) && !disabled && !readOnly + return (
{({ ref, onDrop, onDragOver }) => ( - { - const matched = optionMap.get(newValue) - if (matched) { - setInputValue(matched.label) - setIsEditing(false) - handleSelection(matched.id) - return - } - if (allowSearch) { - setInputValue(newValue) - setIsEditing(true) - setSearchTerm(newValue) - } - }} - placeholder={placeholder || subBlock.placeholder || 'Select an option'} - disabled={disabled || readOnly} - editable={allowSearch} - filterOptions={allowSearch} - inputRef={ref as React.RefObject} - inputProps={{ - onDrop: onDrop as (e: React.DragEvent) => void, - onDragOver: onDragOver as (e: React.DragEvent) => void, - }} - isLoading={isLoading} - error={error instanceof Error ? error.message : null} - /> +
+ { + const matched = optionMap.get(newValue) + if (matched) { + setInputValue(matched.label) + setIsEditing(false) + handleSelection(matched.id) + return + } + if (allowSearch) { + setInputValue(newValue) + setIsEditing(true) + setSearchTerm(newValue) + } + }} + placeholder={placeholder || subBlock.placeholder || 'Select an option'} + disabled={disabled || readOnly} + editable={allowSearch} + filterOptions={allowSearch} + inputRef={ref as React.RefObject} + inputProps={{ + onDrop: onDrop as (e: React.DragEvent) => void, + onDragOver: onDragOver as (e: React.DragEvent) => void, + className: showClearButton ? 'pr-[60px]' : undefined, + }} + isLoading={isLoading} + error={error instanceof Error ? error.message : null} + /> + {showClearButton && ( + + )} +
)}
diff --git a/apps/sim/blocks/blocks.test.ts b/apps/sim/blocks/blocks.test.ts index 5790ca6d0..47bf66097 100644 --- a/apps/sim/blocks/blocks.test.ts +++ b/apps/sim/blocks/blocks.test.ts @@ -649,4 +649,394 @@ describe('Blocks Module', () => { } }) }) + + describe('Canonical Param Validation', () => { + /** + * Helper to serialize a condition for comparison + */ + function serializeCondition(condition: unknown): string { + if (!condition) return '' + return JSON.stringify(condition) + } + + it('should not have canonicalParamId that matches any subBlock id within the same block', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + const allSubBlockIds = new Set(block.subBlocks.map((sb) => sb.id)) + const canonicalParamIds = new Set( + block.subBlocks.filter((sb) => sb.canonicalParamId).map((sb) => sb.canonicalParamId) + ) + + for (const canonicalId of canonicalParamIds) { + if (allSubBlockIds.has(canonicalId!)) { + // Check if the matching subBlock also has a canonicalParamId pointing to itself + const matchingSubBlock = block.subBlocks.find( + (sb) => sb.id === canonicalId && !sb.canonicalParamId + ) + if (matchingSubBlock) { + errors.push( + `Block "${block.type}": canonicalParamId "${canonicalId}" clashes with subBlock id "${canonicalId}"` + ) + } + } + } + } + + if (errors.length > 0) { + throw new Error(`Canonical param ID clashes detected:\n${errors.join('\n')}`) + } + }) + + it('should have unique subBlock IDs within the same condition context', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + // Group subBlocks by their condition (only for static/JSON conditions, not functions) + const subBlocksByCondition = new Map< + string, + Array<{ id: string; mode?: string; hasCanonical: boolean }> + >() + + for (const subBlock of block.subBlocks) { + // Skip subBlocks with function conditions - we can't evaluate them statically + // These are valid when the function returns different conditions at runtime + if (typeof subBlock.condition === 'function') { + continue + } + + const conditionKey = serializeCondition(subBlock.condition) + if (!subBlocksByCondition.has(conditionKey)) { + subBlocksByCondition.set(conditionKey, []) + } + subBlocksByCondition.get(conditionKey)!.push({ + id: subBlock.id, + mode: subBlock.mode, + hasCanonical: Boolean(subBlock.canonicalParamId), + }) + } + + // Check for duplicate IDs within the same condition (excluding canonical pairs and mode swaps) + for (const [conditionKey, subBlocks] of subBlocksByCondition) { + const idCounts = new Map() + for (const sb of subBlocks) { + idCounts.set(sb.id, (idCounts.get(sb.id) || 0) + 1) + } + + for (const [id, count] of idCounts) { + if (count > 1) { + const duplicates = subBlocks.filter((sb) => sb.id === id) + + // Categorize modes + const basicModes = duplicates.filter( + (sb) => !sb.mode || sb.mode === 'basic' || sb.mode === 'both' + ) + const advancedModes = duplicates.filter((sb) => sb.mode === 'advanced') + const triggerModes = duplicates.filter((sb) => sb.mode === 'trigger') + + // Valid pattern 1: basic/advanced mode swap (with or without canonicalParamId) + if ( + basicModes.length === 1 && + advancedModes.length === 1 && + triggerModes.length === 0 + ) { + continue // This is a valid basic/advanced mode swap pair + } + + // Valid pattern 2: basic/trigger mode separation (trigger version for trigger mode) + // One basic/both + one or more trigger versions is valid + if ( + basicModes.length <= 1 && + advancedModes.length === 0 && + triggerModes.length >= 1 + ) { + continue // This is a valid pattern where trigger mode has its own subBlock + } + + // Valid pattern 3: All duplicates have canonicalParamId (they form a canonical group) + const allHaveCanonical = duplicates.every((sb) => sb.hasCanonical) + if (allHaveCanonical) { + continue // Validated separately by canonical pair tests + } + + // Invalid: duplicates without proper pairing + const condition = conditionKey || '(no condition)' + const modeBreakdown = duplicates.map((d) => d.mode || 'basic/both').join(', ') + errors.push( + `Block "${block.type}": Duplicate subBlock id "${id}" with condition ${condition} (count: ${count}, modes: ${modeBreakdown})` + ) + } + } + } + } + + if (errors.length > 0) { + throw new Error(`Duplicate subBlock IDs detected:\n${errors.join('\n')}`) + } + }) + + it('should have properly formed canonical pairs (matching conditions)', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + // Group subBlocks by canonicalParamId + const canonicalGroups = new Map< + string, + Array<{ id: string; mode?: string; condition: unknown; isStaticCondition: boolean }> + >() + + for (const subBlock of block.subBlocks) { + if (subBlock.canonicalParamId) { + if (!canonicalGroups.has(subBlock.canonicalParamId)) { + canonicalGroups.set(subBlock.canonicalParamId, []) + } + canonicalGroups.get(subBlock.canonicalParamId)!.push({ + id: subBlock.id, + mode: subBlock.mode, + condition: subBlock.condition, + isStaticCondition: typeof subBlock.condition !== 'function', + }) + } + } + + // Validate each canonical group + for (const [canonicalId, members] of canonicalGroups) { + // Only validate condition matching for static conditions + const staticMembers = members.filter((m) => m.isStaticCondition) + if (staticMembers.length > 1) { + const conditions = staticMembers.map((m) => serializeCondition(m.condition)) + const uniqueConditions = new Set(conditions) + + if (uniqueConditions.size > 1) { + errors.push( + `Block "${block.type}": Canonical param "${canonicalId}" has members with different conditions: ${[...uniqueConditions].join(' vs ')}` + ) + } + } + + // Check for proper basic/advanced pairing + const basicMembers = members.filter((m) => !m.mode || m.mode === 'basic') + const advancedMembers = members.filter((m) => m.mode === 'advanced') + + if (basicMembers.length > 1) { + errors.push( + `Block "${block.type}": Canonical param "${canonicalId}" has ${basicMembers.length} basic mode members (should have at most 1)` + ) + } + + if (basicMembers.length === 0 && advancedMembers.length === 0) { + errors.push( + `Block "${block.type}": Canonical param "${canonicalId}" has no basic or advanced mode members` + ) + } + } + } + + if (errors.length > 0) { + throw new Error(`Canonical pair validation errors:\n${errors.join('\n')}`) + } + }) + + it('should have unique canonicalParamIds per operation/condition context', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + // Group by condition + canonicalParamId to detect same canonical used for different operations + const canonicalByCondition = new Map>() + + for (const subBlock of block.subBlocks) { + if (subBlock.canonicalParamId) { + // Skip function conditions - we can't evaluate them statically + if (typeof subBlock.condition === 'function') { + continue + } + const conditionKey = serializeCondition(subBlock.condition) + if (!canonicalByCondition.has(subBlock.canonicalParamId)) { + canonicalByCondition.set(subBlock.canonicalParamId, new Set()) + } + canonicalByCondition.get(subBlock.canonicalParamId)!.add(conditionKey) + } + } + + // Check that each canonicalParamId is only used for one condition + for (const [canonicalId, conditions] of canonicalByCondition) { + if (conditions.size > 1) { + errors.push( + `Block "${block.type}": Canonical param "${canonicalId}" is used across ${conditions.size} different conditions. Each operation should have its own unique canonicalParamId.` + ) + } + } + } + + if (errors.length > 0) { + throw new Error(`Canonical param reuse across conditions:\n${errors.join('\n')}`) + } + }) + + it('should have inputs containing canonical param IDs instead of raw subBlock IDs', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + if (!block.inputs) continue + + // Find all canonical groups (subBlocks with canonicalParamId) + const canonicalGroups = new Map() + for (const subBlock of block.subBlocks) { + if (subBlock.canonicalParamId) { + if (!canonicalGroups.has(subBlock.canonicalParamId)) { + canonicalGroups.set(subBlock.canonicalParamId, []) + } + canonicalGroups.get(subBlock.canonicalParamId)!.push(subBlock.id) + } + } + + const inputKeys = Object.keys(block.inputs) + + for (const [canonicalId, rawSubBlockIds] of canonicalGroups) { + // Check that the canonical param ID is in inputs + if (!inputKeys.includes(canonicalId)) { + errors.push( + `Block "${block.type}": inputs section is missing canonical param "${canonicalId}"` + ) + } + + // Check that raw subBlock IDs are NOT in inputs (they get deleted after transformation) + for (const rawId of rawSubBlockIds) { + if (rawId !== canonicalId && inputKeys.includes(rawId)) { + errors.push( + `Block "${block.type}": inputs section contains raw subBlock id "${rawId}" which should be replaced by canonical param "${canonicalId}"` + ) + } + } + } + } + + if (errors.length > 0) { + throw new Error(`Inputs section validation errors:\n${errors.join('\n')}`) + } + }) + + it('should have params function using canonical IDs instead of raw subBlock IDs', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + // Check if block has a params function + const paramsFunc = block.tools?.config?.params + if (!paramsFunc || typeof paramsFunc !== 'function') continue + + // Get the function source code, stripping comments to avoid false positives + const rawFuncSource = paramsFunc.toString() + // Remove single-line comments (// ...) and multi-line comments (/* ... */) + const funcSource = rawFuncSource + .replace(/\/\/[^\n]*/g, '') // Remove single-line comments + .replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments + + // Find all canonical groups (subBlocks with canonicalParamId) + const canonicalGroups = new Map() + for (const subBlock of block.subBlocks) { + if (subBlock.canonicalParamId) { + if (!canonicalGroups.has(subBlock.canonicalParamId)) { + canonicalGroups.set(subBlock.canonicalParamId, []) + } + canonicalGroups.get(subBlock.canonicalParamId)!.push(subBlock.id) + } + } + + // Check for raw subBlock IDs being used in the params function + for (const [canonicalId, rawSubBlockIds] of canonicalGroups) { + for (const rawId of rawSubBlockIds) { + // Skip if the rawId is the same as the canonicalId (self-referential, which is allowed in some cases) + if (rawId === canonicalId) continue + + // Check if the params function references the raw subBlock ID + // Look for patterns like: params.rawId, { rawId }, destructuring rawId + const patterns = [ + new RegExp(`params\\.${rawId}\\b`), // params.rawId + new RegExp(`\\{[^}]*\\b${rawId}\\b[^}]*\\}\\s*=\\s*params`), // { rawId } = params + new RegExp(`\\b${rawId}\\s*[,}]`), // rawId in destructuring + ] + + for (const pattern of patterns) { + if (pattern.test(funcSource)) { + errors.push( + `Block "${block.type}": params function references raw subBlock id "${rawId}" which is deleted after canonical transformation. Use canonical param "${canonicalId}" instead.` + ) + break + } + } + } + } + } + + if (errors.length > 0) { + throw new Error(`Params function validation errors:\n${errors.join('\n')}`) + } + }) + + it('should have consistent required status across canonical param groups', () => { + const blocks = getAllBlocks() + const errors: string[] = [] + + for (const block of blocks) { + // Find all canonical groups (subBlocks with canonicalParamId) + const canonicalGroups = new Map() + for (const subBlock of block.subBlocks) { + if (subBlock.canonicalParamId) { + if (!canonicalGroups.has(subBlock.canonicalParamId)) { + canonicalGroups.set(subBlock.canonicalParamId, []) + } + canonicalGroups.get(subBlock.canonicalParamId)!.push(subBlock) + } + } + + // For each canonical group, check that required status is consistent + for (const [canonicalId, subBlocks] of canonicalGroups) { + if (subBlocks.length < 2) continue // Single subblock, no consistency check needed + + // Get required status for each subblock (handling both boolean and condition object) + const requiredStatuses = subBlocks.map((sb) => { + // If required is a condition object or function, we can't statically determine it + // so we skip those cases + if (typeof sb.required === 'object' || typeof sb.required === 'function') { + return 'dynamic' + } + return sb.required === true ? 'required' : 'optional' + }) + + // Filter out dynamic cases + const staticStatuses = requiredStatuses.filter((s) => s !== 'dynamic') + if (staticStatuses.length < 2) continue // Not enough static statuses to compare + + // Check if all static statuses are the same + const hasRequired = staticStatuses.includes('required') + const hasOptional = staticStatuses.includes('optional') + + if (hasRequired && hasOptional) { + const requiredSubBlocks = subBlocks + .filter((sb, i) => requiredStatuses[i] === 'required') + .map((sb) => `${sb.id} (${sb.mode || 'both'})`) + const optionalSubBlocks = subBlocks + .filter((sb, i) => requiredStatuses[i] === 'optional') + .map((sb) => `${sb.id} (${sb.mode || 'both'})`) + + errors.push( + `Block "${block.type}": canonical param "${canonicalId}" has inconsistent required status. ` + + `Required: [${requiredSubBlocks.join(', ')}], Optional: [${optionalSubBlocks.join(', ')}]. ` + + `All subBlocks in a canonical group should have the same required status.` + ) + } + } + } + + if (errors.length > 0) { + throw new Error(`Required status consistency errors:\n${errors.join('\n')}`) + } + }) + }) }) diff --git a/apps/sim/blocks/blocks/a2a.ts b/apps/sim/blocks/blocks/a2a.ts index 7426ea917..b12905b4b 100644 --- a/apps/sim/blocks/blocks/a2a.ts +++ b/apps/sim/blocks/blocks/a2a.ts @@ -216,8 +216,8 @@ 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) + const { files, ...rest } = params + const normalizedFiles = normalizeFileInput(files) return { ...rest, ...(normalizedFiles && { files: normalizedFiles }), @@ -252,15 +252,7 @@ export const A2ABlock: BlockConfig = { }, files: { type: 'array', - description: 'Files to include with the message', - }, - fileUpload: { - type: 'array', - description: 'Uploaded files (basic mode)', - }, - fileReference: { - type: 'json', - description: 'File reference from previous blocks (advanced mode)', + description: 'Files to include with the message (canonical param)', }, historyLength: { type: 'number', diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index f3197bf27..4c8790928 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -94,6 +94,19 @@ export const ConfluenceBlock: BlockConfig = { placeholder: 'Select Confluence page', dependsOn: ['credential', 'domain'], mode: 'basic', + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'create_comment', + 'list_comments', + 'list_attachments', + 'list_labels', + 'upload_attachment', + ], + }, }, { id: 'manualPageId', @@ -102,14 +115,26 @@ export const ConfluenceBlock: BlockConfig = { canonicalParamId: 'pageId', placeholder: 'Enter Confluence page ID', mode: 'advanced', + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'create_comment', + 'list_comments', + 'list_attachments', + 'list_labels', + 'upload_attachment', + ], + }, }, { id: 'spaceId', title: 'Space ID', type: 'short-input', placeholder: 'Enter Confluence space ID', - required: true, - condition: { field: 'operation', value: ['create', 'get_space'] }, + required: { field: 'operation', value: ['create', 'get_space'] }, }, { id: 'title', @@ -264,7 +289,6 @@ export const ConfluenceBlock: BlockConfig = { const { credential, pageId, - manualPageId, operation, attachmentFile, attachmentFileName, @@ -272,28 +296,7 @@ export const ConfluenceBlock: BlockConfig = { ...rest } = params - const effectivePageId = (pageId || manualPageId || '').trim() - - const requiresPageId = [ - 'read', - 'update', - 'delete', - 'create_comment', - 'list_comments', - 'list_attachments', - 'list_labels', - 'upload_attachment', - ] - - const requiresSpaceId = ['create', 'get_space'] - - if (requiresPageId.includes(operation) && !effectivePageId) { - throw new Error('Page ID is required. Please select a page or enter a page ID manually.') - } - - if (requiresSpaceId.includes(operation) && !rest.spaceId) { - throw new Error('Space ID is required for this operation.') - } + const effectivePageId = pageId ? String(pageId).trim() : '' if (operation === 'upload_attachment') { return { @@ -320,8 +323,7 @@ export const ConfluenceBlock: BlockConfig = { operation: { type: 'string', description: 'Operation to perform' }, domain: { type: 'string', description: 'Confluence domain' }, credential: { type: 'string', description: 'Confluence access token' }, - pageId: { type: 'string', description: 'Page identifier' }, - manualPageId: { type: 'string', description: 'Manual page identifier' }, + pageId: { type: 'string', description: 'Page identifier (canonical param)' }, spaceId: { type: 'string', description: 'Space identifier' }, title: { type: 'string', description: 'Page title' }, content: { type: 'string', description: 'Page content' }, @@ -330,7 +332,7 @@ export const ConfluenceBlock: BlockConfig = { comment: { type: 'string', description: 'Comment text' }, commentId: { type: 'string', description: 'Comment identifier' }, attachmentId: { type: 'string', description: 'Attachment identifier' }, - attachmentFile: { type: 'json', description: 'File to upload as attachment' }, + attachmentFile: { type: 'json', description: 'File to upload as attachment (canonical param)' }, attachmentFileName: { type: 'string', description: 'Custom file name for attachment' }, attachmentComment: { type: 'string', description: 'Comment for the attachment' }, labelName: { type: 'string', description: 'Label name' }, @@ -486,6 +488,26 @@ export const ConfluenceV2Block: BlockConfig = { ], not: true, }, + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'create_comment', + 'list_comments', + 'list_attachments', + 'list_labels', + 'upload_attachment', + 'add_label', + 'get_page_children', + 'get_page_ancestors', + 'list_page_versions', + 'get_page_version', + 'list_page_properties', + 'create_page_property', + ], + }, }, { id: 'manualPageId', @@ -508,6 +530,26 @@ export const ConfluenceV2Block: BlockConfig = { ], not: true, }, + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'create_comment', + 'list_comments', + 'list_attachments', + 'list_labels', + 'upload_attachment', + 'add_label', + 'get_page_children', + 'get_page_ancestors', + 'list_page_versions', + 'get_page_version', + 'list_page_properties', + 'create_page_property', + ], + }, }, { id: 'spaceId', @@ -620,6 +662,7 @@ export const ConfluenceV2Block: BlockConfig = { placeholder: 'Select file to upload', condition: { field: 'operation', value: 'upload_attachment' }, mode: 'basic', + required: { field: 'operation', value: 'upload_attachment' }, }, { id: 'attachmentFileReference', @@ -629,6 +672,7 @@ export const ConfluenceV2Block: BlockConfig = { placeholder: 'Reference file from previous blocks', condition: { field: 'operation', value: 'upload_attachment' }, mode: 'advanced', + required: { field: 'operation', value: 'upload_attachment' }, }, { id: 'attachmentFileName', @@ -856,10 +900,7 @@ export const ConfluenceV2Block: BlockConfig = { const { credential, pageId, - manualPageId, operation, - attachmentFileUpload, - attachmentFileReference, attachmentFile, attachmentFileName, attachmentComment, @@ -875,50 +916,8 @@ export const ConfluenceV2Block: BlockConfig = { ...rest } = params - const effectivePageId = (pageId || manualPageId || '').trim() - - const requiresPageId = [ - 'read', - 'update', - 'delete', - 'create_comment', - 'list_comments', - 'list_attachments', - 'list_labels', - 'upload_attachment', - 'add_label', - 'get_page_children', - 'get_page_ancestors', - 'list_page_versions', - 'get_page_version', - 'list_page_properties', - 'create_page_property', - ] - - const requiresSpaceId = [ - 'create', - 'get_space', - 'list_pages_in_space', - 'search_in_space', - 'create_blogpost', - 'list_blogposts_in_space', - ] - - if (requiresPageId.includes(operation) && !effectivePageId) { - throw new Error('Page ID is required. Please select a page or enter a page ID manually.') - } - - if (requiresSpaceId.includes(operation) && !rest.spaceId) { - throw new Error('Space ID is required for this operation.') - } - - if (operation === 'get_blogpost' && !blogPostId) { - throw new Error('Blog Post ID is required for this operation.') - } - - if (operation === 'get_page_version' && !versionNumber) { - throw new Error('Version number is required for this operation.') - } + // Use canonical param (serializer already handles basic/advanced mode) + const effectivePageId = pageId ? String(pageId).trim() : '' if (operation === 'add_label') { return { @@ -998,8 +997,7 @@ export const ConfluenceV2Block: BlockConfig = { } if (operation === 'upload_attachment') { - const fileInput = attachmentFileUpload || attachmentFileReference || attachmentFile - const normalizedFile = normalizeFileInput(fileInput, { single: true }) + const normalizedFile = normalizeFileInput(attachmentFile, { single: true }) if (!normalizedFile) { throw new Error('File is required for upload attachment operation.') } @@ -1029,8 +1027,7 @@ export const ConfluenceV2Block: BlockConfig = { operation: { type: 'string', description: 'Operation to perform' }, domain: { type: 'string', description: 'Confluence domain' }, credential: { type: 'string', description: 'Confluence access token' }, - pageId: { type: 'string', description: 'Page identifier' }, - manualPageId: { type: 'string', description: 'Manual page identifier' }, + pageId: { type: 'string', description: 'Page identifier (canonical param)' }, spaceId: { type: 'string', description: 'Space identifier' }, blogPostId: { type: 'string', description: 'Blog post identifier' }, versionNumber: { type: 'number', description: 'Page version number' }, @@ -1043,9 +1040,7 @@ export const ConfluenceV2Block: BlockConfig = { comment: { type: 'string', description: 'Comment text' }, commentId: { type: 'string', description: 'Comment identifier' }, attachmentId: { type: 'string', description: 'Attachment identifier' }, - attachmentFile: { type: 'json', description: 'File to upload as attachment' }, - attachmentFileUpload: { type: 'json', description: 'Uploaded file (basic mode)' }, - attachmentFileReference: { type: 'json', description: 'File reference (advanced mode)' }, + attachmentFile: { type: 'json', description: 'File to upload as attachment (canonical param)' }, attachmentFileName: { type: 'string', description: 'Custom file name for attachment' }, attachmentComment: { type: 'string', description: 'Comment for the attachment' }, labelName: { type: 'string', description: 'Label name' }, diff --git a/apps/sim/blocks/blocks/discord.ts b/apps/sim/blocks/blocks/discord.ts index 79331eaac..0e245c9e8 100644 --- a/apps/sim/blocks/blocks/discord.ts +++ b/apps/sim/blocks/blocks/discord.ts @@ -584,7 +584,7 @@ export const DiscordBlock: BlockConfig = { ...commonParams, channelId: params.channelId, content: params.content, - files: normalizeFileInput(params.attachmentFiles || params.files), + files: normalizeFileInput(params.files), } } case 'discord_get_messages': @@ -773,8 +773,7 @@ export const DiscordBlock: BlockConfig = { nick: { type: 'string', description: 'Member nickname' }, reason: { type: 'string', description: 'Reason for moderation action' }, archived: { type: 'string', description: 'Archive status (true/false)' }, - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - files: { type: 'array', description: 'Files to attach (UserFile array)' }, + files: { type: 'array', description: 'Files to attach (canonical param)' }, limit: { type: 'number', description: 'Message limit' }, autoArchiveDuration: { type: 'number', description: 'Thread auto-archive duration in minutes' }, channelType: { type: 'number', description: 'Discord channel type (0=text, 2=voice, etc.)' }, diff --git a/apps/sim/blocks/blocks/dropbox.ts b/apps/sim/blocks/blocks/dropbox.ts index e7127c118..90be6e74f 100644 --- a/apps/sim/blocks/blocks/dropbox.ts +++ b/apps/sim/blocks/blocks/dropbox.ts @@ -317,12 +317,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, params.maxResults = Number(params.maxResults) } - // Normalize file input for upload operation - // Check all possible field IDs: uploadFile (basic), fileRef (advanced), fileContent (legacy) - const normalizedFile = normalizeFileInput( - params.uploadFile || params.fileRef || params.fileContent, - { single: true } - ) + // Normalize file input for upload operation - use canonical 'file' param + const normalizedFile = normalizeFileInput(params.file, { single: true }) if (normalizedFile) { params.file = normalizedFile } @@ -361,10 +357,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, path: { type: 'string', description: 'Path in Dropbox' }, autorename: { type: 'boolean', description: 'Auto-rename on conflict' }, // Upload inputs - uploadFile: { type: 'json', description: 'Uploaded file (UserFile)' }, - file: { type: 'json', description: 'File to upload (UserFile object)' }, - fileRef: { type: 'json', description: 'File reference from previous block' }, - fileContent: { type: 'string', description: 'Legacy: base64 encoded file content' }, + file: { type: 'json', description: 'File to upload (canonical param)' }, fileName: { type: 'string', description: 'Optional filename' }, mode: { type: 'string', description: 'Write mode: add or overwrite' }, mute: { type: 'boolean', description: 'Mute notifications' }, diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index 3db0c2d47..f9b3058f2 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -194,7 +194,8 @@ export const FileV2Block: BlockConfig = { fallbackToolId: 'file_parser_v2', }), params: (params) => { - const fileInput = params.file || params.filePath || params.fileInput + // Use canonical 'fileInput' param directly + const fileInput = params.fileInput if (!fileInput) { logger.error('No file input provided') throw new Error('File is required') @@ -228,9 +229,7 @@ export const FileV2Block: BlockConfig = { }, }, inputs: { - fileInput: { type: 'json', description: 'File input (upload or URL reference)' }, - filePath: { type: 'string', description: 'File URL (advanced mode)' }, - file: { type: 'json', description: 'Uploaded file data (basic mode)' }, + fileInput: { type: 'json', description: 'File input (canonical param)' }, fileType: { type: 'string', description: 'File type' }, }, outputs: { @@ -283,7 +282,8 @@ export const FileV3Block: BlockConfig = { config: { tool: () => 'file_parser_v3', params: (params) => { - const fileInput = params.fileInput ?? params.file ?? params.fileUrl ?? params.filePath + // Use canonical 'fileInput' param directly + const fileInput = params.fileInput if (!fileInput) { logger.error('No file input provided') throw new Error('File input is required') @@ -321,9 +321,7 @@ export const FileV3Block: BlockConfig = { }, }, inputs: { - fileInput: { type: 'json', description: 'File input (upload or URL)' }, - fileUrl: { type: 'string', description: 'External file URL (advanced mode)' }, - file: { type: 'json', description: 'Uploaded file data (basic mode)' }, + fileInput: { type: 'json', description: 'File input (canonical param)' }, fileType: { type: 'string', description: 'File type' }, }, outputs: { diff --git a/apps/sim/blocks/blocks/fireflies.ts b/apps/sim/blocks/blocks/fireflies.ts index 568cda788..308704f6e 100644 --- a/apps/sim/blocks/blocks/fireflies.ts +++ b/apps/sim/blocks/blocks/fireflies.ts @@ -461,12 +461,11 @@ Return ONLY the summary text - no quotes, no labels.`, return baseParams case 'fireflies_upload_audio': { - // Support both file upload and URL + // Support both file upload and URL - use canonical 'audioFile' param const audioUrl = params.audioUrl?.trim() const audioFile = params.audioFile - const audioFileReference = params.audioFileReference - if (!audioUrl && !audioFile && !audioFileReference) { + if (!audioUrl && !audioFile) { throw new Error('Either audio file or audio URL is required.') } @@ -474,7 +473,6 @@ Return ONLY the summary text - no quotes, no labels.`, ...baseParams, audioUrl: audioUrl || undefined, audioFile: audioFile || undefined, - audioFileReference: audioFileReference || undefined, title: params.title?.trim() || undefined, language: params.language?.trim() || undefined, attendees: params.attendees?.trim() || undefined, @@ -548,8 +546,7 @@ Return ONLY the summary text - no quotes, no labels.`, hostEmail: { type: 'string', description: 'Filter by host email' }, participants: { type: 'string', description: 'Filter by participants (comma-separated)' }, limit: { type: 'number', description: 'Maximum results to return' }, - audioFile: { type: 'json', description: 'Audio/video file (UserFile)' }, - audioFileReference: { type: 'json', description: 'Audio/video file reference' }, + audioFile: { type: 'json', description: 'Audio/video file (canonical param)' }, audioUrl: { type: 'string', description: 'Public URL to audio file' }, title: { type: 'string', description: 'Meeting title' }, language: { type: 'string', description: 'Language code for transcription' }, @@ -620,9 +617,8 @@ export const FirefliesV2Block: BlockConfig = { } if (params.operation === 'fireflies_upload_audio') { - const audioFile = normalizeFileInput(params.audioFile || params.audioFileReference, { - single: true, - }) + // Use canonical 'audioFile' param directly + const audioFile = normalizeFileInput(params.audioFile, { single: true }) if (!audioFile) { throw new Error('Audio file is required.') } @@ -635,7 +631,6 @@ export const FirefliesV2Block: BlockConfig = { ...params, audioUrl, audioFile: undefined, - audioFileReference: undefined, }) } @@ -643,8 +638,5 @@ export const FirefliesV2Block: BlockConfig = { }, }, }, - inputs: { - ...firefliesV2Inputs, - audioFileReference: { type: 'json', description: 'Audio/video file reference' }, - }, + inputs: firefliesV2Inputs, } diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index 5f8ac25e1..4a6d66e37 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -362,10 +362,10 @@ Return ONLY the search query - no explanations, no extra text.`, }, // Add/Remove Label - Label selector (basic mode) { - id: 'labelManagement', + id: 'labelSelector', title: 'Label', type: 'folder-selector', - canonicalParamId: 'labelIds', + canonicalParamId: 'manageLabelId', serviceId: 'gmail', requiredScopes: ['https://www.googleapis.com/auth/gmail.labels'], placeholder: 'Select label', @@ -376,10 +376,10 @@ Return ONLY the search query - no explanations, no extra text.`, }, // Add/Remove Label - Manual label input (advanced mode) { - id: 'manualLabelManagement', + id: 'manualLabelId', title: 'Label', type: 'short-input', - canonicalParamId: 'labelIds', + canonicalParamId: 'manageLabelId', placeholder: 'Enter label ID (e.g., INBOX, Label_123)', mode: 'advanced', condition: { field: 'operation', value: ['add_label_gmail', 'remove_label_gmail'] }, @@ -408,38 +408,33 @@ Return ONLY the search query - no explanations, no extra text.`, const { credential, folder, - manualFolder, - destinationLabel, - manualDestinationLabel, - sourceLabel, - manualSourceLabel, + addLabelIds, + removeLabelIds, moveMessageId, actionMessageId, labelActionMessageId, - labelManagement, - manualLabelManagement, - attachmentFiles, + manageLabelId, attachments, ...rest } = params - // Handle both selector and manual folder input - const effectiveFolder = (folder || manualFolder || '').trim() + // Use canonical 'folder' param directly + const effectiveFolder = folder ? String(folder).trim() : '' if (rest.operation === 'read_gmail') { rest.folder = effectiveFolder || 'INBOX' } - // Handle move operation + // Handle move operation - use canonical params addLabelIds and removeLabelIds if (rest.operation === 'move_gmail') { if (moveMessageId) { rest.messageId = moveMessageId } - if (!rest.addLabelIds) { - rest.addLabelIds = (destinationLabel || manualDestinationLabel || '').trim() + if (addLabelIds) { + rest.addLabelIds = String(addLabelIds).trim() } - if (!rest.removeLabelIds) { - rest.removeLabelIds = (sourceLabel || manualSourceLabel || '').trim() + if (removeLabelIds) { + rest.removeLabelIds = String(removeLabelIds).trim() } } @@ -462,13 +457,13 @@ Return ONLY the search query - no explanations, no extra text.`, if (labelActionMessageId) { rest.messageId = labelActionMessageId } - if (!rest.labelIds) { - rest.labelIds = (labelManagement || manualLabelManagement || '').trim() + if (manageLabelId) { + rest.labelIds = String(manageLabelId).trim() } } - // Normalize attachments for send/draft operations - const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + // Normalize attachments for send/draft operations - use canonical 'attachments' param + const normalizedAttachments = normalizeFileInput(attachments) return { ...rest, @@ -493,10 +488,9 @@ Return ONLY the search query - no explanations, no extra text.`, }, cc: { type: 'string', description: 'CC recipients (comma-separated)' }, bcc: { type: 'string', description: 'BCC recipients (comma-separated)' }, - attachments: { type: 'array', description: 'Files to attach (UserFile array)' }, + attachments: { type: 'array', description: 'Files to attach (canonical param)' }, // Read operation inputs - folder: { type: 'string', description: 'Gmail folder' }, - manualFolder: { type: 'string', description: 'Manual folder name' }, + folder: { type: 'string', description: 'Gmail folder (canonical param)' }, readMessageId: { type: 'string', description: 'Message identifier for reading specific email' }, unreadOnly: { type: 'boolean', description: 'Unread messages only' }, includeAttachments: { type: 'boolean', description: 'Include email attachments' }, @@ -505,18 +499,16 @@ Return ONLY the search query - no explanations, no extra text.`, maxResults: { type: 'number', description: 'Maximum results' }, // Move operation inputs moveMessageId: { type: 'string', description: 'Message ID to move' }, - destinationLabel: { type: 'string', description: 'Destination label ID' }, - manualDestinationLabel: { type: 'string', description: 'Manual destination label ID' }, - sourceLabel: { type: 'string', description: 'Source label ID to remove' }, - manualSourceLabel: { type: 'string', description: 'Manual source label ID' }, - addLabelIds: { type: 'string', description: 'Label IDs to add' }, - removeLabelIds: { type: 'string', description: 'Label IDs to remove' }, + addLabelIds: { type: 'string', description: 'Label IDs to add (canonical param)' }, + removeLabelIds: { type: 'string', description: 'Label IDs to remove (canonical param)' }, // Action operation inputs actionMessageId: { type: 'string', description: 'Message ID for actions' }, labelActionMessageId: { type: 'string', description: 'Message ID for label actions' }, - labelManagement: { type: 'string', description: 'Label ID for management' }, - manualLabelManagement: { type: 'string', description: 'Manual label ID' }, - labelIds: { type: 'string', description: 'Label IDs for add/remove operations' }, + manageLabelId: { + type: 'string', + description: 'Label ID for add/remove operations (canonical param)', + }, + labelIds: { type: 'string', description: 'Label IDs to monitor (trigger)' }, }, outputs: { // Tool outputs diff --git a/apps/sim/blocks/blocks/google_calendar.ts b/apps/sim/blocks/blocks/google_calendar.ts index db010d696..2c28ebeba 100644 --- a/apps/sim/blocks/blocks/google_calendar.ts +++ b/apps/sim/blocks/blocks/google_calendar.ts @@ -517,21 +517,17 @@ Return ONLY the natural language event text - no explanations.`, attendees, replaceExisting, calendarId, - manualCalendarId, - destinationCalendar, - manualDestinationCalendarId, + destinationCalendarId, ...rest } = params - // Handle calendar ID (selector or manual) - const effectiveCalendarId = (calendarId || manualCalendarId || '').trim() + // Use canonical 'calendarId' param directly + const effectiveCalendarId = calendarId ? String(calendarId).trim() : '' - // Handle destination calendar ID for move operation (selector or manual) - const effectiveDestinationCalendarId = ( - destinationCalendar || - manualDestinationCalendarId || - '' - ).trim() + // Use canonical 'destinationCalendarId' param directly + const effectiveDestinationCalendarId = destinationCalendarId + ? String(destinationCalendarId).trim() + : '' const processedParams: Record = { ...rest, @@ -589,8 +585,7 @@ Return ONLY the natural language event text - no explanations.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Calendar access token' }, - calendarId: { type: 'string', description: 'Calendar identifier' }, - manualCalendarId: { type: 'string', description: 'Manual calendar identifier' }, + calendarId: { type: 'string', description: 'Calendar identifier (canonical param)' }, // Create/Update operation inputs summary: { type: 'string', description: 'Event title' }, @@ -609,8 +604,10 @@ Return ONLY the natural language event text - no explanations.`, eventId: { type: 'string', description: 'Event identifier' }, // Move operation inputs - destinationCalendar: { type: 'string', description: 'Destination calendar selector' }, - manualDestinationCalendarId: { type: 'string', description: 'Manual destination calendar ID' }, + destinationCalendarId: { + type: 'string', + description: 'Destination calendar ID (canonical param)', + }, // List Calendars operation inputs minAccessRole: { type: 'string', description: 'Minimum access role filter' }, diff --git a/apps/sim/blocks/blocks/google_docs.ts b/apps/sim/blocks/blocks/google_docs.ts index 14e919e90..2a780fd78 100644 --- a/apps/sim/blocks/blocks/google_docs.ts +++ b/apps/sim/blocks/blocks/google_docs.ts @@ -157,11 +157,10 @@ Return ONLY the document content - no explanations, no extra text.`, } }, params: (params) => { - const { credential, documentId, manualDocumentId, folderSelector, folderId, ...rest } = - params + const { credential, documentId, folderId, ...rest } = params - const effectiveDocumentId = (documentId || manualDocumentId || '').trim() - const effectiveFolderId = (folderSelector || folderId || '').trim() + const effectiveDocumentId = documentId ? String(documentId).trim() : '' + const effectiveFolderId = folderId ? String(folderId).trim() : '' return { ...rest, @@ -175,11 +174,9 @@ Return ONLY the document content - no explanations, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Docs access token' }, - documentId: { type: 'string', description: 'Document identifier' }, - manualDocumentId: { type: 'string', description: 'Manual document identifier' }, + documentId: { type: 'string', description: 'Document identifier (canonical param)' }, title: { type: 'string', description: 'Document title' }, - folderSelector: { type: 'string', description: 'Selected folder' }, - folderId: { type: 'string', description: 'Folder identifier' }, + folderId: { type: 'string', description: 'Parent folder identifier (canonical param)' }, content: { type: 'string', description: 'Document content' }, }, outputs: { diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts index d14168d5a..3c44d8092 100644 --- a/apps/sim/blocks/blocks/google_drive.ts +++ b/apps/sim/blocks/blocks/google_drive.ts @@ -121,10 +121,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr required: false, }, { - id: 'folderSelector', + id: 'uploadFolderSelector', title: 'Select Parent Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'uploadFolderId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -137,10 +137,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr condition: { field: 'operation', value: ['create_file', 'upload'] }, }, { - id: 'manualFolderId', + id: 'uploadManualFolderId', title: 'Parent Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'uploadFolderId', placeholder: 'Enter parent folder ID (leave empty for root folder)', mode: 'advanced', condition: { field: 'operation', value: ['create_file', 'upload'] }, @@ -193,10 +193,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr required: true, }, { - id: 'folderSelector', + id: 'createFolderParentSelector', title: 'Select Parent Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'createFolderParentId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -210,20 +210,20 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr }, // Manual Folder ID input (advanced mode) { - id: 'manualFolderId', + id: 'createFolderManualParentId', title: 'Parent Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'createFolderParentId', placeholder: 'Enter parent folder ID (leave empty for root folder)', mode: 'advanced', condition: { field: 'operation', value: 'create_folder' }, }, // List Fields - Folder Selector (basic mode) { - id: 'folderSelector', + id: 'listFolderSelector', title: 'Select Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'listFolderId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -237,10 +237,10 @@ Return ONLY the file content - no explanations, no markdown code blocks, no extr }, // Manual Folder ID input (advanced mode) { - id: 'manualFolderId', + id: 'listManualFolderId', title: 'Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'listFolderId', placeholder: 'Enter folder ID (leave empty for root folder)', mode: 'advanced', condition: { field: 'operation', value: 'list' }, @@ -279,10 +279,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing }, // Download File Fields - File Selector (basic mode) { - id: 'fileSelector', + id: 'downloadFileSelector', title: 'Select File', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'downloadFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -292,13 +292,14 @@ Return ONLY the query string - no explanations, no quotes around the whole thing mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'download' }, + required: true, }, // Manual File ID input (advanced mode) { - id: 'manualFileId', + id: 'downloadManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'downloadFileId', placeholder: 'Enter file ID', mode: 'advanced', condition: { field: 'operation', value: 'download' }, @@ -339,10 +340,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing }, // Get File Info Fields { - id: 'fileSelector', + id: 'getFileSelector', title: 'Select File', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'getFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -352,12 +353,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'get_file' }, + required: true, }, { - id: 'manualFileId', + id: 'getManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'getFileId', placeholder: 'Enter file ID', mode: 'advanced', condition: { field: 'operation', value: 'get_file' }, @@ -365,10 +367,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing }, // Copy File Fields { - id: 'fileSelector', + id: 'copyFileSelector', title: 'Select File to Copy', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'copyFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -378,12 +380,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'copy' }, + required: true, }, { - id: 'manualFileId', + id: 'copyManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'copyFileId', placeholder: 'Enter file ID to copy', mode: 'advanced', condition: { field: 'operation', value: 'copy' }, @@ -397,10 +400,10 @@ Return ONLY the query string - no explanations, no quotes around the whole thing condition: { field: 'operation', value: 'copy' }, }, { - id: 'folderSelector', + id: 'copyDestFolderSelector', title: 'Destination Folder', type: 'file-selector', - canonicalParamId: 'destinationFolderId', + canonicalParamId: 'copyDestFolderId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -413,20 +416,20 @@ Return ONLY the query string - no explanations, no quotes around the whole thing condition: { field: 'operation', value: 'copy' }, }, { - id: 'manualDestinationFolderId', + id: 'copyManualDestFolderId', title: 'Destination Folder ID', type: 'short-input', - canonicalParamId: 'destinationFolderId', + canonicalParamId: 'copyDestFolderId', placeholder: 'Enter destination folder ID (optional)', mode: 'advanced', condition: { field: 'operation', value: 'copy' }, }, // Update File Fields { - id: 'fileSelector', + id: 'updateFileSelector', title: 'Select File to Update', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'updateFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -436,12 +439,13 @@ Return ONLY the query string - no explanations, no quotes around the whole thing mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'update' }, + required: true, }, { - id: 'manualFileId', + id: 'updateManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'updateFileId', placeholder: 'Enter file ID to update', mode: 'advanced', condition: { field: 'operation', value: 'update' }, @@ -500,10 +504,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, }, // Trash File Fields { - id: 'fileSelector', + id: 'trashFileSelector', title: 'Select File to Trash', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'trashFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -513,12 +517,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'trash' }, + required: true, }, { - id: 'manualFileId', + id: 'trashManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'trashFileId', placeholder: 'Enter file ID to trash', mode: 'advanced', condition: { field: 'operation', value: 'trash' }, @@ -526,10 +531,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, }, // Delete File Fields { - id: 'fileSelector', + id: 'deleteFileSelector', title: 'Select File to Delete', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'deleteFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -539,12 +544,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'delete' }, + required: true, }, { - id: 'manualFileId', + id: 'deleteManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'deleteFileId', placeholder: 'Enter file ID to permanently delete', mode: 'advanced', condition: { field: 'operation', value: 'delete' }, @@ -552,10 +558,10 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, }, // Share File Fields { - id: 'fileSelector', + id: 'shareFileSelector', title: 'Select File to Share', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'shareFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -565,12 +571,13 @@ Return ONLY the description text - no explanations, no quotes, no extra text.`, mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'share' }, + required: true, }, { - id: 'manualFileId', + id: 'shareManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'shareFileId', placeholder: 'Enter file ID to share', mode: 'advanced', condition: { field: 'operation', value: 'share' }, @@ -665,10 +672,10 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr }, // Unshare (Remove Permission) Fields { - id: 'fileSelector', + id: 'unshareFileSelector', title: 'Select File', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'unshareFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -678,12 +685,13 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'unshare' }, + required: true, }, { - id: 'manualFileId', + id: 'unshareManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'unshareFileId', placeholder: 'Enter file ID', mode: 'advanced', condition: { field: 'operation', value: 'unshare' }, @@ -699,10 +707,10 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr }, // List Permissions Fields { - id: 'fileSelector', + id: 'listPermissionsFileSelector', title: 'Select File', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'listPermissionsFileId', serviceId: 'google-drive', requiredScopes: [ 'https://www.googleapis.com/auth/drive.file', @@ -712,12 +720,13 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'list_permissions' }, + required: true, }, { - id: 'manualFileId', + id: 'listPermissionsManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'listPermissionsFileId', placeholder: 'Enter file ID', mode: 'advanced', condition: { field: 'operation', value: 'list_permissions' }, @@ -778,13 +787,23 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr params: (params) => { const { credential, - folderSelector, - manualFolderId, - manualDestinationFolderId, - fileSelector, - manualFileId, + // Folder canonical params (per-operation) + uploadFolderId, + createFolderParentId, + listFolderId, + copyDestFolderId, + // File canonical params (per-operation) + downloadFileId, + getFileId, + copyFileId, + updateFileId, + trashFileId, + deleteFileId, + shareFileId, + unshareFileId, + listPermissionsFileId, + // File upload file, - fileUpload, mimeType, shareType, starred, @@ -793,19 +812,58 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr } = params // Normalize file input - handles both basic (file-upload) and advanced (short-input) modes - const normalizedFile = normalizeFileInput(file ?? fileUpload, { single: true }) + const normalizedFile = normalizeFileInput(file, { single: true }) - // Use folderSelector if provided, otherwise use manualFolderId - const effectiveFolderId = (folderSelector || manualFolderId || '').trim() + // Resolve folderId based on operation + let effectiveFolderId: string | undefined + switch (params.operation) { + case 'create_file': + case 'upload': + effectiveFolderId = uploadFolderId?.trim() || undefined + break + case 'create_folder': + effectiveFolderId = createFolderParentId?.trim() || undefined + break + case 'list': + effectiveFolderId = listFolderId?.trim() || undefined + break + } - // Use fileSelector if provided, otherwise use manualFileId - const effectiveFileId = (fileSelector || manualFileId || '').trim() + // Resolve fileId based on operation + let effectiveFileId: string | undefined + switch (params.operation) { + case 'download': + effectiveFileId = downloadFileId?.trim() || undefined + break + case 'get_file': + effectiveFileId = getFileId?.trim() || undefined + break + case 'copy': + effectiveFileId = copyFileId?.trim() || undefined + break + case 'update': + effectiveFileId = updateFileId?.trim() || undefined + break + case 'trash': + effectiveFileId = trashFileId?.trim() || undefined + break + case 'delete': + effectiveFileId = deleteFileId?.trim() || undefined + break + case 'share': + effectiveFileId = shareFileId?.trim() || undefined + break + case 'unshare': + effectiveFileId = unshareFileId?.trim() || undefined + break + case 'list_permissions': + effectiveFileId = listPermissionsFileId?.trim() || undefined + break + } - // Use folderSelector for destination or manualDestinationFolderId for copy operation + // Resolve destinationFolderId for copy operation const effectiveDestinationFolderId = - params.operation === 'copy' - ? (folderSelector || manualDestinationFolderId || '').trim() - : undefined + params.operation === 'copy' ? copyDestFolderId?.trim() || undefined : undefined // Convert starred dropdown to boolean const starredValue = starred === 'true' ? true : starred === 'false' ? false : undefined @@ -816,9 +874,9 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr return { credential, - folderId: effectiveFolderId || undefined, - fileId: effectiveFileId || undefined, - destinationFolderId: effectiveDestinationFolderId || undefined, + folderId: effectiveFolderId, + fileId: effectiveFileId, + destinationFolderId: effectiveDestinationFolderId, file: normalizedFile, pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined, mimeType: mimeType, @@ -834,13 +892,21 @@ Return ONLY the message text - no subject line, no greetings/signatures, no extr inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Drive access token' }, - // File selection inputs - fileSelector: { type: 'string', description: 'Selected file' }, - manualFileId: { type: 'string', description: 'Manual file identifier' }, - // Folder selection inputs - folderSelector: { type: 'string', description: 'Selected folder' }, - manualFolderId: { type: 'string', description: 'Manual folder identifier' }, - manualDestinationFolderId: { type: 'string', description: 'Destination folder for copy' }, + // Folder canonical params (per-operation) + uploadFolderId: { type: 'string', description: 'Parent folder for upload/create' }, + createFolderParentId: { type: 'string', description: 'Parent folder for create folder' }, + listFolderId: { type: 'string', description: 'Folder to list files from' }, + copyDestFolderId: { type: 'string', description: 'Destination folder for copy' }, + // File canonical params (per-operation) + downloadFileId: { type: 'string', description: 'File to download' }, + getFileId: { type: 'string', description: 'File to get info for' }, + copyFileId: { type: 'string', description: 'File to copy' }, + updateFileId: { type: 'string', description: 'File to update' }, + trashFileId: { type: 'string', description: 'File to trash' }, + deleteFileId: { type: 'string', description: 'File to delete' }, + shareFileId: { type: 'string', description: 'File to share' }, + unshareFileId: { type: 'string', description: 'File to unshare' }, + listPermissionsFileId: { type: 'string', description: 'File to list permissions for' }, // Upload and Create inputs fileName: { type: 'string', description: 'File or folder name' }, file: { type: 'json', description: 'File to upload (UserFile object)' }, diff --git a/apps/sim/blocks/blocks/google_forms.ts b/apps/sim/blocks/blocks/google_forms.ts index 0dee68977..435259c57 100644 --- a/apps/sim/blocks/blocks/google_forms.ts +++ b/apps/sim/blocks/blocks/google_forms.ts @@ -47,10 +47,11 @@ export const GoogleFormsBlock: BlockConfig = { }, // Form selector (basic mode) { - id: 'formId', + id: 'formSelector', title: 'Select Form', type: 'file-selector', canonicalParamId: 'formId', + required: true, serviceId: 'google-forms', requiredScopes: [], mimeType: 'application/vnd.google-apps.form', @@ -234,8 +235,7 @@ Example for "Add a required multiple choice question about favorite color": const { credential, operation, - formId, - manualFormId, + formId, // Canonical param from formSelector (basic) or manualFormId (advanced) responseId, pageSize, title, @@ -252,11 +252,10 @@ Example for "Add a required multiple choice question about favorite color": } = params const baseParams = { ...rest, credential } - const effectiveFormId = (formId || manualFormId || '').toString().trim() || undefined + const effectiveFormId = formId ? String(formId).trim() : undefined switch (operation) { case 'get_responses': - if (!effectiveFormId) throw new Error('Form ID is required.') return { ...baseParams, formId: effectiveFormId, @@ -265,10 +264,8 @@ Example for "Add a required multiple choice question about favorite color": } case 'get_form': case 'list_watches': - if (!effectiveFormId) throw new Error('Form ID is required.') return { ...baseParams, formId: effectiveFormId } case 'create_form': - if (!title) throw new Error('Form title is required.') return { ...baseParams, title: String(title).trim(), @@ -276,8 +273,6 @@ Example for "Add a required multiple choice question about favorite color": unpublished: unpublished ?? false, } case 'batch_update': - if (!effectiveFormId) throw new Error('Form ID is required.') - if (!requests) throw new Error('Update requests are required.') return { ...baseParams, formId: effectiveFormId, @@ -285,7 +280,6 @@ Example for "Add a required multiple choice question about favorite color": includeFormInResponse: includeFormInResponse ?? false, } case 'set_publish_settings': - if (!effectiveFormId) throw new Error('Form ID is required.') return { ...baseParams, formId: effectiveFormId, @@ -293,9 +287,6 @@ Example for "Add a required multiple choice question about favorite color": isAcceptingResponses: isAcceptingResponses, } case 'create_watch': - if (!effectiveFormId) throw new Error('Form ID is required.') - if (!eventType) throw new Error('Event type is required.') - if (!topicName) throw new Error('Pub/Sub topic is required.') return { ...baseParams, formId: effectiveFormId, @@ -305,8 +296,6 @@ Example for "Add a required multiple choice question about favorite color": } case 'delete_watch': case 'renew_watch': - if (!effectiveFormId) throw new Error('Form ID is required.') - if (!watchId) throw new Error('Watch ID is required.') return { ...baseParams, formId: effectiveFormId, @@ -321,8 +310,7 @@ Example for "Add a required multiple choice question about favorite color": inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google OAuth credential' }, - formId: { type: 'string', description: 'Google Form ID (from selector)' }, - manualFormId: { type: 'string', description: 'Google Form ID (manual entry)' }, + formId: { type: 'string', description: 'Google Form ID' }, responseId: { type: 'string', description: 'Specific response ID' }, pageSize: { type: 'string', description: 'Max responses to retrieve' }, title: { type: 'string', description: 'Form title for creation' }, diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts index a849b718c..3294f0036 100644 --- a/apps/sim/blocks/blocks/google_sheets.ts +++ b/apps/sim/blocks/blocks/google_sheets.ts @@ -246,11 +246,11 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, } }, params: (params) => { - const { credential, values, spreadsheetId, manualSpreadsheetId, ...rest } = params + const { credential, values, spreadsheetId, ...rest } = params const parsedValues = values ? JSON.parse(values as string) : undefined - const effectiveSpreadsheetId = (spreadsheetId || manualSpreadsheetId || '').trim() + const effectiveSpreadsheetId = spreadsheetId ? String(spreadsheetId).trim() : '' if (!effectiveSpreadsheetId) { throw new Error('Spreadsheet ID is required.') @@ -268,8 +268,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Sheets access token' }, - spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' }, - manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' }, + spreadsheetId: { type: 'string', description: 'Spreadsheet identifier (canonical param)' }, range: { type: 'string', description: 'Cell range' }, values: { type: 'string', description: 'Cell values data' }, valueInputOption: { type: 'string', description: 'Value input option' }, @@ -719,9 +718,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, credential, values, spreadsheetId, - manualSpreadsheetId, sheetName, - manualSheetName, cellRange, title, sheetTitles, @@ -746,9 +743,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, } } - const effectiveSpreadsheetId = ( - (spreadsheetId || manualSpreadsheetId || '') as string - ).trim() + const effectiveSpreadsheetId = spreadsheetId ? String(spreadsheetId).trim() : '' if (!effectiveSpreadsheetId) { throw new Error('Spreadsheet ID is required.') @@ -804,7 +799,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, } // Handle read/write/update/append/clear operations (require sheet name) - const effectiveSheetName = ((sheetName || manualSheetName || '') as string).trim() + const effectiveSheetName = sheetName ? String(sheetName).trim() : '' if (!effectiveSheetName) { throw new Error('Sheet name is required. Please select or enter a sheet name.') @@ -826,10 +821,8 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Sheets access token' }, - spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' }, - manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' }, - sheetName: { type: 'string', description: 'Name of the sheet/tab' }, - manualSheetName: { type: 'string', description: 'Manual sheet name entry' }, + spreadsheetId: { type: 'string', description: 'Spreadsheet identifier (canonical param)' }, + sheetName: { type: 'string', description: 'Name of the sheet/tab (canonical param)' }, cellRange: { type: 'string', description: 'Cell range (e.g., A1:D10)' }, values: { type: 'string', description: 'Cell values data' }, valueInputOption: { type: 'string', description: 'Value input option' }, diff --git a/apps/sim/blocks/blocks/google_slides.ts b/apps/sim/blocks/blocks/google_slides.ts index 784fc73fc..baa890a2a 100644 --- a/apps/sim/blocks/blocks/google_slides.ts +++ b/apps/sim/blocks/blocks/google_slides.ts @@ -664,8 +664,6 @@ Return ONLY the text content - no explanations, no markdown formatting markers, const { credential, presentationId, - manualPresentationId, - folderSelector, folderId, slideIndex, createContent, @@ -675,8 +673,8 @@ Return ONLY the text content - no explanations, no markdown formatting markers, ...rest } = params - const effectivePresentationId = (presentationId || manualPresentationId || '').trim() - const effectiveFolderId = (folderSelector || folderId || '').trim() + const effectivePresentationId = presentationId ? String(presentationId).trim() : '' + const effectiveFolderId = folderId ? String(folderId).trim() : '' const result: Record = { ...rest, @@ -802,15 +800,13 @@ Return ONLY the text content - no explanations, no markdown formatting markers, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Google Slides access token' }, - presentationId: { type: 'string', description: 'Presentation identifier' }, - manualPresentationId: { type: 'string', description: 'Manual presentation identifier' }, + presentationId: { type: 'string', description: 'Presentation identifier (canonical param)' }, // Write operation slideIndex: { type: 'number', description: 'Slide index to write to' }, content: { type: 'string', description: 'Slide content' }, // Create operation title: { type: 'string', description: 'Presentation title' }, - folderSelector: { type: 'string', description: 'Selected folder' }, - folderId: { type: 'string', description: 'Folder identifier' }, + folderId: { type: 'string', description: 'Parent folder identifier (canonical param)' }, createContent: { type: 'string', description: 'Initial slide content' }, // Replace all text operation findText: { type: 'string', description: 'Text to find' }, @@ -826,8 +822,6 @@ Return ONLY the text content - no explanations, no markdown formatting markers, placeholderIdMappings: { type: 'string', description: 'JSON array of placeholder ID mappings' }, // Add image operation pageObjectId: { type: 'string', description: 'Slide object ID for image' }, - imageFile: { type: 'json', description: 'Uploaded image (UserFile)' }, - imageUrl: { type: 'string', description: 'Image URL or reference' }, imageSource: { type: 'json', description: 'Image source (file or URL)' }, imageWidth: { type: 'number', description: 'Image width in points' }, imageHeight: { type: 'number', description: 'Image height in points' }, @@ -936,11 +930,12 @@ const googleSlidesV2SubBlocks = (GoogleSlidesBlock.subBlocks || []).flatMap((sub }) const googleSlidesV2Inputs = GoogleSlidesBlock.inputs - ? Object.fromEntries( - Object.entries(GoogleSlidesBlock.inputs).filter( - ([key]) => key !== 'imageUrl' && key !== 'imageSource' - ) - ) + ? { + ...Object.fromEntries( + Object.entries(GoogleSlidesBlock.inputs).filter(([key]) => key !== 'imageSource') + ), + imageFile: { type: 'json', description: 'Image source (file or URL)' }, + } : {} export const GoogleSlidesV2Block: BlockConfig = { @@ -961,8 +956,7 @@ export const GoogleSlidesV2Block: BlockConfig = { } if (params.operation === 'add_image') { - const imageInput = params.imageFile || params.imageFileReference || params.imageSource - const fileObject = normalizeFileInput(imageInput, { single: true }) + const fileObject = normalizeFileInput(params.imageFile, { single: true }) if (!fileObject) { throw new Error('Image file is required.') } @@ -974,8 +968,6 @@ export const GoogleSlidesV2Block: BlockConfig = { return baseParams({ ...params, imageUrl, - imageFileReference: undefined, - imageSource: undefined, }) } @@ -983,8 +975,5 @@ export const GoogleSlidesV2Block: BlockConfig = { }, }, }, - inputs: { - ...googleSlidesV2Inputs, - imageFileReference: { type: 'json', description: 'Image file reference' }, - }, + inputs: googleSlidesV2Inputs, } diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts index 3d67b3902..263e5e363 100644 --- a/apps/sim/blocks/blocks/jira.ts +++ b/apps/sim/blocks/blocks/jira.ts @@ -106,6 +106,7 @@ export const JiraBlock: BlockConfig = { placeholder: 'Select Jira project', dependsOn: ['credential', 'domain'], mode: 'basic', + required: { field: 'operation', value: ['write', 'update', 'read-bulk'] }, }, // Manual project ID input (advanced mode) { @@ -116,6 +117,7 @@ export const JiraBlock: BlockConfig = { placeholder: 'Enter Jira project ID', dependsOn: ['credential', 'domain'], mode: 'advanced', + required: { field: 'operation', value: ['write', 'update', 'read-bulk'] }, }, // Issue selector (basic mode) { @@ -148,6 +150,28 @@ export const JiraBlock: BlockConfig = { 'remove_watcher', ], }, + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'assign', + 'transition', + 'add_comment', + 'get_comments', + 'update_comment', + 'delete_comment', + 'get_attachments', + 'add_attachment', + 'add_worklog', + 'get_worklogs', + 'update_worklog', + 'delete_worklog', + 'add_watcher', + 'remove_watcher', + ], + }, mode: 'basic', }, // Manual issue key input (advanced mode) @@ -180,6 +204,28 @@ export const JiraBlock: BlockConfig = { 'remove_watcher', ], }, + required: { + field: 'operation', + value: [ + 'read', + 'update', + 'delete', + 'assign', + 'transition', + 'add_comment', + 'get_comments', + 'update_comment', + 'delete_comment', + 'get_attachments', + 'add_attachment', + 'add_worklog', + 'get_worklogs', + 'update_worklog', + 'delete_worklog', + 'add_watcher', + 'remove_watcher', + ], + }, mode: 'advanced', }, { @@ -615,8 +661,9 @@ Return ONLY the comment text - no explanations.`, ], config: { tool: (params) => { - const effectiveProjectId = (params.projectId || params.manualProjectId || '').trim() - const effectiveIssueKey = (params.issueKey || params.manualIssueKey || '').trim() + // Use canonical param IDs (raw subBlock IDs are deleted after serialization) + const effectiveProjectId = params.projectId ? String(params.projectId).trim() : '' + const effectiveIssueKey = params.issueKey ? String(params.issueKey).trim() : '' switch (params.operation) { case 'read': @@ -676,11 +723,11 @@ Return ONLY the comment text - no explanations.`, } }, params: (params) => { - const { credential, projectId, manualProjectId, issueKey, manualIssueKey, ...rest } = params + const { credential, projectId, issueKey, ...rest } = params - // Use the selected IDs or the manually entered ones - const effectiveProjectId = (projectId || manualProjectId || '').trim() - const effectiveIssueKey = (issueKey || manualIssueKey || '').trim() + // Use canonical param IDs (raw subBlock IDs are deleted after serialization) + const effectiveProjectId = projectId ? String(projectId).trim() : '' + const effectiveIssueKey = issueKey ? String(issueKey).trim() : '' const baseParams = { credential, @@ -689,11 +736,6 @@ Return ONLY the comment text - no explanations.`, switch (params.operation) { case 'write': { - if (!effectiveProjectId) { - throw new Error( - 'Project ID is required. Please select a project or enter a project ID manually.' - ) - } // Parse comma-separated strings into arrays const parseCommaSeparated = (value: string | undefined): string[] | undefined => { if (!value || value.trim() === '') return undefined @@ -726,16 +768,6 @@ Return ONLY the comment text - no explanations.`, } } case 'update': { - if (!effectiveProjectId) { - throw new Error( - 'Project ID is required. Please select a project or enter a project ID manually.' - ) - } - if (!effectiveIssueKey) { - throw new Error( - 'Issue Key is required. Please select an issue or enter an issue key manually.' - ) - } const updateParams = { projectId: effectiveProjectId, issueKey: effectiveIssueKey, @@ -748,40 +780,20 @@ Return ONLY the comment text - no explanations.`, } } case 'read': { - // Check for project ID from either source - const projectForRead = (params.projectId || params.manualProjectId || '').trim() - const issueForRead = (params.issueKey || params.manualIssueKey || '').trim() - - if (!issueForRead) { - throw new Error( - 'Select a project to read issues, or provide an issue key to read a single issue.' - ) - } return { ...baseParams, - issueKey: issueForRead, + issueKey: effectiveIssueKey, // Include projectId if available for context - ...(projectForRead && { projectId: projectForRead }), + ...(effectiveProjectId && { projectId: effectiveProjectId }), } } case 'read-bulk': { - // Check both projectId and manualProjectId directly from params - const finalProjectId = params.projectId || params.manualProjectId || '' - - if (!finalProjectId) { - throw new Error( - 'Project ID is required. Please select a project or enter a project ID manually.' - ) - } return { ...baseParams, - projectId: finalProjectId.trim(), + projectId: effectiveProjectId.trim(), } } case 'delete': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to delete an issue.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -789,9 +801,6 @@ Return ONLY the comment text - no explanations.`, } } case 'assign': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to assign an issue.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -799,9 +808,6 @@ Return ONLY the comment text - no explanations.`, } } case 'transition': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to transition an issue.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -817,9 +823,6 @@ Return ONLY the comment text - no explanations.`, } } case 'add_comment': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to add a comment.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -827,9 +830,6 @@ Return ONLY the comment text - no explanations.`, } } case 'get_comments': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to get comments.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -837,9 +837,6 @@ Return ONLY the comment text - no explanations.`, } } case 'update_comment': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to update a comment.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -848,9 +845,6 @@ Return ONLY the comment text - no explanations.`, } } case 'delete_comment': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to delete a comment.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -858,19 +852,13 @@ Return ONLY the comment text - no explanations.`, } } case 'get_attachments': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to get attachments.') - } return { ...baseParams, issueKey: effectiveIssueKey, } } case 'add_attachment': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to add attachments.') - } - const normalizedFiles = normalizeFileInput(params.attachmentFiles || params.files) + const normalizedFiles = normalizeFileInput(params.files) if (!normalizedFiles || normalizedFiles.length === 0) { throw new Error('At least one attachment file is required.') } @@ -887,9 +875,6 @@ Return ONLY the comment text - no explanations.`, } } case 'add_worklog': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to add a worklog.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -901,9 +886,6 @@ Return ONLY the comment text - no explanations.`, } } case 'get_worklogs': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to get worklogs.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -911,9 +893,6 @@ Return ONLY the comment text - no explanations.`, } } case 'update_worklog': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to update a worklog.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -926,9 +905,6 @@ Return ONLY the comment text - no explanations.`, } } case 'delete_worklog': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to delete a worklog.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -951,9 +927,6 @@ Return ONLY the comment text - no explanations.`, } } case 'add_watcher': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to add a watcher.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -961,9 +934,6 @@ Return ONLY the comment text - no explanations.`, } } case 'remove_watcher': { - if (!effectiveIssueKey) { - throw new Error('Issue Key is required to remove a watcher.') - } return { ...baseParams, issueKey: effectiveIssueKey, @@ -990,10 +960,8 @@ Return ONLY the comment text - no explanations.`, operation: { type: 'string', description: 'Operation to perform' }, domain: { type: 'string', description: 'Jira domain' }, credential: { type: 'string', description: 'Jira access token' }, - issueKey: { type: 'string', description: 'Issue key identifier' }, - projectId: { type: 'string', description: 'Project identifier' }, - manualProjectId: { type: 'string', description: 'Manual project identifier' }, - manualIssueKey: { type: 'string', description: 'Manual issue key' }, + issueKey: { type: 'string', description: 'Issue key identifier (canonical param)' }, + projectId: { type: 'string', description: 'Project identifier (canonical param)' }, // Update/Write operation inputs summary: { type: 'string', description: 'Issue summary' }, description: { type: 'string', description: 'Issue description' }, @@ -1024,8 +992,7 @@ Return ONLY the comment text - no explanations.`, commentBody: { type: 'string', description: 'Text content for comment operations' }, commentId: { type: 'string', description: 'Comment ID for update/delete operations' }, // Attachment operation inputs - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - files: { type: 'array', description: 'Files to attach (UserFile array)' }, + files: { type: 'array', description: 'Files to attach (canonical param)' }, attachmentId: { type: 'string', description: 'Attachment ID for delete operation' }, // Worklog operation inputs timeSpentSeconds: { diff --git a/apps/sim/blocks/blocks/linear.ts b/apps/sim/blocks/blocks/linear.ts index 2b8e43587..4774f7fe1 100644 --- a/apps/sim/blocks/blocks/linear.ts +++ b/apps/sim/blocks/blocks/linear.ts @@ -1476,9 +1476,9 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n return params.operation || 'linear_read_issues' }, params: (params) => { - // Handle both selector and manual inputs - const effectiveTeamId = (params.teamId || params.manualTeamId || '').trim() - const effectiveProjectId = (params.projectId || params.manualProjectId || '').trim() + // Use canonical param IDs (raw subBlock IDs are deleted after serialization) + const effectiveTeamId = params.teamId ? String(params.teamId).trim() : '' + const effectiveProjectId = params.projectId ? String(params.projectId).trim() : '' // Base params that most operations need const baseParams: Record = { @@ -1774,16 +1774,11 @@ 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.') } - // Normalize file inputs - handles JSON stringified values from advanced mode - const attachmentFile = - normalizeFileInput(params.attachmentFileUpload, { - single: true, - errorMessage: 'Attachment file must be a single file.', - }) || - normalizeFileInput(params.file, { - single: true, - errorMessage: 'Attachment file must be a single file.', - }) + // Normalize file input - use canonical param 'file' (raw subBlock IDs are deleted after serialization) + const attachmentFile = normalizeFileInput(params.file, { + single: true, + errorMessage: 'Attachment file must be a single file.', + }) const attachmentUrl = params.url?.trim() || (attachmentFile ? (attachmentFile as { url?: string }).url : undefined) @@ -2261,10 +2256,8 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Linear access token' }, - teamId: { type: 'string', description: 'Linear team identifier' }, - projectId: { type: 'string', description: 'Linear project identifier' }, - manualTeamId: { type: 'string', description: 'Manual team identifier' }, - manualProjectId: { type: 'string', description: 'Manual project identifier' }, + teamId: { type: 'string', description: 'Linear team identifier (canonical param)' }, + projectId: { type: 'string', description: 'Linear project identifier (canonical param)' }, issueId: { type: 'string', description: 'Issue identifier' }, title: { type: 'string', description: 'Title' }, description: { type: 'string', description: 'Description' }, @@ -2294,8 +2287,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n endDate: { type: 'string', description: 'End date' }, targetDate: { type: 'string', description: 'Target date' }, url: { type: 'string', description: 'URL' }, - attachmentFileUpload: { type: 'json', description: 'File to attach (UI upload)' }, - file: { type: 'json', description: 'File to attach (UserFile)' }, + file: { type: 'json', description: 'File to attach (canonical param)' }, attachmentTitle: { type: 'string', description: 'Attachment title' }, attachmentId: { type: 'string', description: 'Attachment identifier' }, relationType: { type: 'string', description: 'Relation type' }, diff --git a/apps/sim/blocks/blocks/microsoft_excel.ts b/apps/sim/blocks/blocks/microsoft_excel.ts index 3438c5bdc..990912424 100644 --- a/apps/sim/blocks/blocks/microsoft_excel.ts +++ b/apps/sim/blocks/blocks/microsoft_excel.ts @@ -241,17 +241,10 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, } }, params: (params) => { - const { - credential, - values, - spreadsheetId, - manualSpreadsheetId, - tableName, - worksheetName, - ...rest - } = params + const { credential, values, spreadsheetId, tableName, worksheetName, ...rest } = params - const effectiveSpreadsheetId = (spreadsheetId || manualSpreadsheetId || '').trim() + // Use canonical param ID (raw subBlock IDs are deleted after serialization) + const effectiveSpreadsheetId = spreadsheetId ? String(spreadsheetId).trim() : '' let parsedValues try { @@ -300,8 +293,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Microsoft Excel access token' }, - spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' }, - manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' }, + spreadsheetId: { type: 'string', description: 'Spreadsheet identifier (canonical param)' }, range: { type: 'string', description: 'Cell range' }, tableName: { type: 'string', description: 'Table name' }, worksheetName: { type: 'string', description: 'Worksheet name' }, @@ -505,21 +497,13 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, fallbackToolId: 'microsoft_excel_read_v2', }), params: (params) => { - const { - credential, - values, - spreadsheetId, - manualSpreadsheetId, - sheetName, - manualSheetName, - cellRange, - ...rest - } = params + const { credential, values, spreadsheetId, sheetName, cellRange, ...rest } = params const parsedValues = values ? JSON.parse(values as string) : undefined - const effectiveSpreadsheetId = (spreadsheetId || manualSpreadsheetId || '').trim() - const effectiveSheetName = ((sheetName || manualSheetName || '') as string).trim() + // Use canonical param IDs (raw subBlock IDs are deleted after serialization) + const effectiveSpreadsheetId = spreadsheetId ? String(spreadsheetId).trim() : '' + const effectiveSheetName = sheetName ? String(sheetName).trim() : '' if (!effectiveSpreadsheetId) { throw new Error('Spreadsheet ID is required.') @@ -543,10 +527,8 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Microsoft Excel access token' }, - spreadsheetId: { type: 'string', description: 'Spreadsheet identifier' }, - manualSpreadsheetId: { type: 'string', description: 'Manual spreadsheet identifier' }, - sheetName: { type: 'string', description: 'Name of the sheet/tab' }, - manualSheetName: { type: 'string', description: 'Manual sheet name entry' }, + spreadsheetId: { type: 'string', description: 'Spreadsheet identifier (canonical param)' }, + sheetName: { type: 'string', description: 'Name of the sheet/tab (canonical param)' }, cellRange: { type: 'string', description: 'Cell range (e.g., A1:D10)' }, values: { type: 'string', description: 'Cell values data' }, valueInputOption: { type: 'string', description: 'Value input option' }, diff --git a/apps/sim/blocks/blocks/microsoft_planner.ts b/apps/sim/blocks/blocks/microsoft_planner.ts index 035832def..b49f8c284 100644 --- a/apps/sim/blocks/blocks/microsoft_planner.ts +++ b/apps/sim/blocks/blocks/microsoft_planner.ts @@ -84,12 +84,16 @@ export const MicrosoftPlannerBlock: BlockConfig = { field: 'operation', value: ['create_task', 'read_task', 'read_plan', 'list_buckets', 'create_bucket'], }, + required: { + field: 'operation', + value: ['read_plan', 'list_buckets', 'create_bucket', 'create_task'], + }, dependsOn: ['credential'], }, - // Task ID selector - for read_task + // Task ID selector - for read_task (basic mode) { - id: 'taskId', + id: 'taskSelector', title: 'Task ID', type: 'file-selector', placeholder: 'Select a task', @@ -97,24 +101,24 @@ export const MicrosoftPlannerBlock: BlockConfig = { condition: { field: 'operation', value: ['read_task'] }, dependsOn: ['credential', 'planId'], mode: 'basic', - canonicalParamId: 'taskId', + canonicalParamId: 'readTaskId', }, - // Manual Task ID - for read_task advanced mode + // Manual Task ID - for read_task (advanced mode) { - id: 'manualTaskId', + id: 'manualReadTaskId', title: 'Manual Task ID', type: 'short-input', placeholder: 'Enter the task ID', condition: { field: 'operation', value: ['read_task'] }, dependsOn: ['credential', 'planId'], mode: 'advanced', - canonicalParamId: 'taskId', + canonicalParamId: 'readTaskId', }, - // Task ID for update/delete operations + // Task ID for update/delete operations (no basic/advanced split, just one input) { - id: 'taskIdForUpdate', + id: 'updateTaskId', title: 'Task ID', type: 'short-input', placeholder: 'Enter the task ID', @@ -122,8 +126,8 @@ export const MicrosoftPlannerBlock: BlockConfig = { field: 'operation', value: ['update_task', 'delete_task', 'get_task_details', 'update_task_details'], }, + required: true, dependsOn: ['credential'], - canonicalParamId: 'taskId', }, // Bucket ID for bucket operations @@ -133,6 +137,7 @@ export const MicrosoftPlannerBlock: BlockConfig = { type: 'short-input', placeholder: 'Enter the bucket ID', condition: { field: 'operation', value: ['read_bucket', 'update_bucket', 'delete_bucket'] }, + required: true, dependsOn: ['credential'], }, @@ -163,6 +168,7 @@ export const MicrosoftPlannerBlock: BlockConfig = { type: 'short-input', placeholder: 'Enter the task title', condition: { field: 'operation', value: ['create_task', 'update_task'] }, + required: { field: 'operation', value: 'create_task' }, }, // Name for bucket operations @@ -172,6 +178,7 @@ export const MicrosoftPlannerBlock: BlockConfig = { type: 'short-input', placeholder: 'Enter the bucket name', condition: { field: 'operation', value: ['create_bucket', 'update_bucket'] }, + required: { field: 'operation', value: 'create_bucket' }, }, // Description for task details @@ -347,9 +354,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, operation, groupId, planId, - taskId, - manualTaskId, - taskIdForUpdate, + readTaskId, // Canonical param from taskSelector (basic) or manualReadTaskId (advanced) for read_task + updateTaskId, // Task ID for update/delete operations bucketId, bucketIdForRead, title, @@ -372,8 +378,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, credential, } - // Handle different task ID fields - const effectiveTaskId = (taskIdForUpdate || taskId || manualTaskId || '').trim() + // Handle different task ID fields based on operation + const effectiveReadTaskId = readTaskId ? String(readTaskId).trim() : '' + const effectiveUpdateTaskId = updateTaskId ? String(updateTaskId).trim() : '' const effectiveBucketId = (bucketIdForRead || bucketId || '').trim() // List Plans @@ -383,31 +390,22 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Read Plan if (operation === 'read_plan') { - if (!planId?.trim()) { - throw new Error('Plan ID is required to read a plan.') - } return { ...baseParams, - planId: planId.trim(), + planId: planId?.trim(), } } // List Buckets if (operation === 'list_buckets') { - if (!planId?.trim()) { - throw new Error('Plan ID is required to list buckets.') - } return { ...baseParams, - planId: planId.trim(), + planId: planId?.trim(), } } // Read Bucket if (operation === 'read_bucket') { - if (!effectiveBucketId) { - throw new Error('Bucket ID is required to read a bucket.') - } return { ...baseParams, bucketId: effectiveBucketId, @@ -416,31 +414,19 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Create Bucket if (operation === 'create_bucket') { - if (!planId?.trim()) { - throw new Error('Plan ID is required to create a bucket.') - } - if (!name?.trim()) { - throw new Error('Bucket name is required to create a bucket.') - } return { ...baseParams, - planId: planId.trim(), - name: name.trim(), + planId: planId?.trim(), + name: name?.trim(), } } // Update Bucket if (operation === 'update_bucket') { - if (!effectiveBucketId) { - throw new Error('Bucket ID is required to update a bucket.') - } - if (!etag?.trim()) { - throw new Error('ETag is required to update a bucket.') - } const updateBucketParams: MicrosoftPlannerBlockParams = { ...baseParams, bucketId: effectiveBucketId, - etag: etag.trim(), + etag: etag?.trim(), } if (name?.trim()) { updateBucketParams.name = name.trim() @@ -450,26 +436,19 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Delete Bucket if (operation === 'delete_bucket') { - if (!effectiveBucketId) { - throw new Error('Bucket ID is required to delete a bucket.') - } - if (!etag?.trim()) { - throw new Error('ETag is required to delete a bucket.') - } return { ...baseParams, bucketId: effectiveBucketId, - etag: etag.trim(), + etag: etag?.trim(), } } // Read Task if (operation === 'read_task') { const readParams: MicrosoftPlannerBlockParams = { ...baseParams } - const readTaskId = (taskId || manualTaskId || '').trim() - if (readTaskId) { - readParams.taskId = readTaskId + if (effectiveReadTaskId) { + readParams.taskId = effectiveReadTaskId } else if (planId?.trim()) { readParams.planId = planId.trim() } @@ -479,17 +458,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Create Task if (operation === 'create_task') { - if (!planId?.trim()) { - throw new Error('Plan ID is required to create a task.') - } - if (!title?.trim()) { - throw new Error('Task title is required to create a task.') - } - const createParams: MicrosoftPlannerBlockParams = { ...baseParams, - planId: planId.trim(), - title: title.trim(), + planId: planId?.trim(), + title: title?.trim(), } if (description?.trim()) { @@ -510,17 +482,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Update Task if (operation === 'update_task') { - if (!effectiveTaskId) { - throw new Error('Task ID is required to update a task.') - } - if (!etag?.trim()) { - throw new Error('ETag is required to update a task.') - } - const updateParams: MicrosoftPlannerBlockParams = { ...baseParams, - taskId: effectiveTaskId, - etag: etag.trim(), + taskId: effectiveUpdateTaskId, + etag: etag?.trim(), } if (title?.trim()) { @@ -550,43 +515,27 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Delete Task if (operation === 'delete_task') { - if (!effectiveTaskId) { - throw new Error('Task ID is required to delete a task.') - } - if (!etag?.trim()) { - throw new Error('ETag is required to delete a task.') - } return { ...baseParams, - taskId: effectiveTaskId, - etag: etag.trim(), + taskId: effectiveUpdateTaskId, + etag: etag?.trim(), } } // Get Task Details if (operation === 'get_task_details') { - if (!effectiveTaskId) { - throw new Error('Task ID is required to get task details.') - } return { ...baseParams, - taskId: effectiveTaskId, + taskId: effectiveUpdateTaskId, } } // Update Task Details if (operation === 'update_task_details') { - if (!effectiveTaskId) { - throw new Error('Task ID is required to update task details.') - } - if (!etag?.trim()) { - throw new Error('ETag is required to update task details.') - } - const updateDetailsParams: MicrosoftPlannerBlockParams = { ...baseParams, - taskId: effectiveTaskId, - etag: etag.trim(), + taskId: effectiveUpdateTaskId, + etag: etag?.trim(), } if (description?.trim()) { @@ -614,9 +563,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, credential: { type: 'string', description: 'Microsoft account credential' }, groupId: { type: 'string', description: 'Microsoft 365 group ID' }, planId: { type: 'string', description: 'Plan ID' }, - taskId: { type: 'string', description: 'Task ID' }, - manualTaskId: { type: 'string', description: 'Manual Task ID' }, - taskIdForUpdate: { type: 'string', description: 'Task ID for update operations' }, + readTaskId: { type: 'string', description: 'Task ID for read operation' }, + updateTaskId: { type: 'string', description: 'Task ID for update/delete operations' }, bucketId: { type: 'string', description: 'Bucket ID' }, bucketIdForRead: { type: 'string', description: 'Bucket ID for read operations' }, title: { type: 'string', description: 'Task title' }, diff --git a/apps/sim/blocks/blocks/microsoft_teams.ts b/apps/sim/blocks/blocks/microsoft_teams.ts index 44324e426..9cf21df99 100644 --- a/apps/sim/blocks/blocks/microsoft_teams.ts +++ b/apps/sim/blocks/blocks/microsoft_teams.ts @@ -71,7 +71,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { required: true, }, { - id: 'teamId', + id: 'teamSelector', title: 'Select Team', type: 'file-selector', canonicalParamId: 'teamId', @@ -92,6 +92,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { 'list_channel_members', ], }, + required: true, }, { id: 'manualTeamId', @@ -112,9 +113,10 @@ export const MicrosoftTeamsBlock: BlockConfig = { 'list_channel_members', ], }, + required: true, }, { - id: 'chatId', + id: 'chatSelector', title: 'Select Chat', type: 'file-selector', canonicalParamId: 'chatId', @@ -127,6 +129,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { field: 'operation', value: ['read_chat', 'write_chat', 'update_chat_message', 'delete_chat_message'], }, + required: true, }, { id: 'manualChatId', @@ -139,16 +142,17 @@ export const MicrosoftTeamsBlock: BlockConfig = { field: 'operation', value: ['read_chat', 'write_chat', 'update_chat_message', 'delete_chat_message'], }, + required: true, }, { - id: 'channelId', + id: 'channelSelector', title: 'Select Channel', type: 'file-selector', canonicalParamId: 'channelId', serviceId: 'microsoft-teams', requiredScopes: [], placeholder: 'Select a channel', - dependsOn: ['credential', 'teamId'], + dependsOn: ['credential', 'teamSelector'], mode: 'basic', condition: { field: 'operation', @@ -161,6 +165,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { 'list_channel_members', ], }, + required: true, }, { id: 'manualChannelId', @@ -180,6 +185,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { 'list_channel_members', ], }, + required: true, }, { id: 'messageId', @@ -249,7 +255,7 @@ export const MicrosoftTeamsBlock: BlockConfig = { }, // Variable reference (advanced mode) { - id: 'files', + id: 'fileReferences', title: 'File Attachments', type: 'short-input', canonicalParamId: 'files', @@ -317,23 +323,19 @@ export const MicrosoftTeamsBlock: BlockConfig = { const { credential, operation, - teamId, - manualTeamId, - chatId, - manualChatId, - channelId, - manualChannelId, - attachmentFiles, - files, + teamId, // Canonical param from teamSelector (basic) or manualTeamId (advanced) + chatId, // Canonical param from chatSelector (basic) or manualChatId (advanced) + channelId, // Canonical param from channelSelector (basic) or manualChannelId (advanced) + files, // Canonical param from attachmentFiles (basic) or fileReferences (advanced) messageId, reactionType, includeAttachments, ...rest } = params - const effectiveTeamId = (teamId || manualTeamId || '').trim() - const effectiveChatId = (chatId || manualChatId || '').trim() - const effectiveChannelId = (channelId || manualChannelId || '').trim() + const effectiveTeamId = teamId ? String(teamId).trim() : '' + const effectiveChatId = chatId ? String(chatId).trim() : '' + const effectiveChannelId = channelId ? String(channelId).trim() : '' const baseParams: Record = { ...rest, @@ -344,9 +346,9 @@ export const MicrosoftTeamsBlock: BlockConfig = { baseParams.includeAttachments = true } - // Add files if provided + // Add files if provided (canonical param from attachmentFiles or fileReferences) if (operation === 'write_chat' || operation === 'write_channel') { - const normalizedFiles = normalizeFileInput(attachmentFiles || files) + const normalizedFiles = normalizeFileInput(files) if (normalizedFiles) { baseParams.files = normalizedFiles } @@ -369,9 +371,6 @@ export const MicrosoftTeamsBlock: BlockConfig = { operation === 'update_chat_message' || operation === 'delete_chat_message' ) { - if (!effectiveChatId) { - throw new Error('Chat ID is required. Please select a chat or enter a chat ID.') - } return { ...baseParams, chatId: effectiveChatId } } @@ -383,31 +382,16 @@ export const MicrosoftTeamsBlock: BlockConfig = { operation === 'delete_channel_message' || operation === 'reply_to_message' ) { - if (!effectiveTeamId) { - throw new Error('Team ID is required for channel operations.') - } - if (!effectiveChannelId) { - throw new Error('Channel ID is required for channel operations.') - } return { ...baseParams, teamId: effectiveTeamId, channelId: effectiveChannelId } } // Team member operations if (operation === 'list_team_members') { - if (!effectiveTeamId) { - throw new Error('Team ID is required for team member operations.') - } return { ...baseParams, teamId: effectiveTeamId } } // Channel member operations if (operation === 'list_channel_members') { - if (!effectiveTeamId) { - throw new Error('Team ID is required for channel member operations.') - } - if (!effectiveChannelId) { - throw new Error('Channel ID is required for channel member operations.') - } return { ...baseParams, teamId: effectiveTeamId, channelId: effectiveChannelId } } @@ -440,12 +424,11 @@ export const MicrosoftTeamsBlock: BlockConfig = { type: 'string', description: 'Message identifier for update/delete/reply/reaction operations', }, - chatId: { type: 'string', description: 'Chat identifier' }, - manualChatId: { type: 'string', description: 'Manual chat identifier' }, - channelId: { type: 'string', description: 'Channel identifier' }, - manualChannelId: { type: 'string', description: 'Manual channel identifier' }, + // Canonical params (used by params function) teamId: { type: 'string', description: 'Team identifier' }, - manualTeamId: { type: 'string', description: 'Manual team identifier' }, + chatId: { type: 'string', description: 'Chat identifier' }, + channelId: { type: 'string', description: 'Channel identifier' }, + files: { type: 'array', description: 'Files to attach' }, content: { type: 'string', description: 'Message content. Mention users with userName', @@ -455,8 +438,6 @@ export const MicrosoftTeamsBlock: BlockConfig = { type: 'boolean', description: 'Download and include message attachments', }, - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - files: { type: 'array', description: 'Files to attach (UserFile array)' }, }, outputs: { content: { type: 'string', description: 'Formatted message content from chat/channel' }, diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts index b78630ed9..dca0faf33 100644 --- a/apps/sim/blocks/blocks/mistral_parse.ts +++ b/apps/sim/blocks/blocks/mistral_parse.ts @@ -215,8 +215,8 @@ export const MistralParseV2Block: BlockConfig = { resultType: params.resultType || 'markdown', } - // Original V2 pattern: fileUpload (basic) or filePath (advanced) or document (wired) - const documentInput = params.fileUpload || params.filePath || params.document + // Use canonical document param directly + const documentInput = params.document if (!documentInput) { throw new Error('PDF document is required') } @@ -261,8 +261,6 @@ export const MistralParseV2Block: BlockConfig = { }, inputs: { document: { type: 'json', description: 'Document input (file upload or URL reference)' }, - filePath: { type: 'string', description: 'PDF document URL (advanced mode)' }, - fileUpload: { type: 'json', description: 'Uploaded PDF file (basic mode)' }, apiKey: { type: 'string', description: 'Mistral API key' }, resultType: { type: 'string', description: 'Output format type' }, pages: { type: 'string', description: 'Page selection' }, @@ -345,11 +343,8 @@ export const MistralParseV3Block: BlockConfig = { resultType: params.resultType || 'markdown', } - // V3 pattern: normalize file inputs from basic/advanced modes - const documentInput = normalizeFileInput( - params.fileUpload || params.fileReference || params.document, - { single: true } - ) + // V3 pattern: use canonical document param directly + const documentInput = normalizeFileInput(params.document, { single: true }) if (!documentInput) { throw new Error('PDF document is required') } @@ -389,8 +384,6 @@ export const MistralParseV3Block: BlockConfig = { }, inputs: { document: { type: 'json', description: 'Document input (file upload or file reference)' }, - fileReference: { type: 'json', description: 'File reference (advanced mode)' }, - fileUpload: { type: 'json', description: 'Uploaded PDF file (basic mode)' }, apiKey: { type: 'string', description: 'Mistral API key' }, resultType: { type: 'string', description: 'Output format type' }, pages: { type: 'string', description: 'Page selection' }, diff --git a/apps/sim/blocks/blocks/onedrive.ts b/apps/sim/blocks/blocks/onedrive.ts index e2e3545fb..47a15d0cc 100644 --- a/apps/sim/blocks/blocks/onedrive.ts +++ b/apps/sim/blocks/blocks/onedrive.ts @@ -140,10 +140,10 @@ export const OneDriveBlock: BlockConfig = { }, { - id: 'folderSelector', + id: 'uploadFolderSelector', title: 'Select Parent Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'uploadFolderId', serviceId: 'onedrive', requiredScopes: [ 'openid', @@ -160,10 +160,10 @@ export const OneDriveBlock: BlockConfig = { condition: { field: 'operation', value: ['create_file', 'upload'] }, }, { - id: 'manualFolderId', + id: 'uploadManualFolderId', title: 'Parent Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'uploadFolderId', placeholder: 'Enter parent folder ID (leave empty for root folder)', dependsOn: ['credential'], mode: 'advanced', @@ -177,10 +177,10 @@ export const OneDriveBlock: BlockConfig = { condition: { field: 'operation', value: 'create_folder' }, }, { - id: 'folderSelector', + id: 'createFolderParentSelector', title: 'Select Parent Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'createFolderParentId', serviceId: 'onedrive', requiredScopes: [ 'openid', @@ -198,10 +198,10 @@ export const OneDriveBlock: BlockConfig = { }, // Manual Folder ID input (advanced mode) { - id: 'manualFolderId', + id: 'createFolderManualParentId', title: 'Parent Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'createFolderParentId', placeholder: 'Enter parent folder ID (leave empty for root folder)', dependsOn: ['credential'], mode: 'advanced', @@ -209,10 +209,10 @@ export const OneDriveBlock: BlockConfig = { }, // List Fields - Folder Selector (basic mode) { - id: 'folderSelector', + id: 'listFolderSelector', title: 'Select Folder', type: 'file-selector', - canonicalParamId: 'folderId', + canonicalParamId: 'listFolderId', serviceId: 'onedrive', requiredScopes: [ 'openid', @@ -230,10 +230,10 @@ export const OneDriveBlock: BlockConfig = { }, // Manual Folder ID input (advanced mode) { - id: 'manualFolderId', + id: 'listManualFolderId', title: 'Folder ID', type: 'short-input', - canonicalParamId: 'folderId', + canonicalParamId: 'listFolderId', placeholder: 'Enter folder ID (leave empty for root folder)', dependsOn: ['credential'], mode: 'advanced', @@ -255,10 +255,10 @@ export const OneDriveBlock: BlockConfig = { }, // Download File Fields - File Selector (basic mode) { - id: 'fileSelector', + id: 'downloadFileSelector', title: 'Select File', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'downloadFileId', serviceId: 'onedrive', requiredScopes: [ 'openid', @@ -273,13 +273,14 @@ export const OneDriveBlock: BlockConfig = { mode: 'basic', dependsOn: ['credential'], condition: { field: 'operation', value: 'download' }, + required: true, }, // Manual File ID input (advanced mode) { - id: 'manualFileId', + id: 'downloadManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'downloadFileId', placeholder: 'Enter file ID', mode: 'advanced', condition: { field: 'operation', value: 'download' }, @@ -294,10 +295,10 @@ export const OneDriveBlock: BlockConfig = { }, // Delete File Fields - File Selector (basic mode) { - id: 'fileSelector', + id: 'deleteFileSelector', title: 'Select File to Delete', type: 'file-selector', - canonicalParamId: 'fileId', + canonicalParamId: 'deleteFileId', serviceId: 'onedrive', requiredScopes: [ 'openid', @@ -316,10 +317,10 @@ export const OneDriveBlock: BlockConfig = { }, // Manual File ID input (advanced mode) { - id: 'manualFileId', + id: 'deleteManualFileId', title: 'File ID', type: 'short-input', - canonicalParamId: 'fileId', + canonicalParamId: 'deleteFileId', placeholder: 'Enter file or folder ID to delete', mode: 'advanced', condition: { field: 'operation', value: 'delete' }, @@ -355,13 +356,17 @@ export const OneDriveBlock: BlockConfig = { params: (params) => { const { credential, - folderId, - fileId, + // Folder canonical params (per-operation) + uploadFolderId, + createFolderParentId, + listFolderId, + // File canonical params (per-operation) + downloadFileId, + deleteFileId, mimeType, values, downloadFileName, file, - fileReference, ...rest } = params @@ -370,16 +375,42 @@ export const OneDriveBlock: BlockConfig = { normalizedValues = normalizeExcelValuesForToolParams(values) } - // Normalize file input from both basic (file-upload) and advanced (short-input) modes - const normalizedFile = normalizeFileInput(file || fileReference, { single: true }) + // Normalize file input from the canonical param + const normalizedFile = normalizeFileInput(file, { single: true }) + + // Resolve folderId based on operation + let resolvedFolderId: string | undefined + switch (params.operation) { + case 'create_file': + case 'upload': + resolvedFolderId = uploadFolderId?.trim() || undefined + break + case 'create_folder': + resolvedFolderId = createFolderParentId?.trim() || undefined + break + case 'list': + resolvedFolderId = listFolderId?.trim() || undefined + break + } + + // Resolve fileId based on operation + let resolvedFileId: string | undefined + switch (params.operation) { + case 'download': + resolvedFileId = downloadFileId?.trim() || undefined + break + case 'delete': + resolvedFileId = deleteFileId?.trim() || undefined + break + } return { credential, ...rest, values: normalizedValues, file: normalizedFile, - folderId: folderId || undefined, - fileId: fileId || undefined, + folderId: resolvedFolderId, + fileId: resolvedFileId, pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined, mimeType: mimeType, ...(downloadFileName && { fileName: downloadFileName }), @@ -390,16 +421,22 @@ export const OneDriveBlock: BlockConfig = { inputs: { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Microsoft account credential' }, - // Upload and Create Folder operation inputs + // Upload and Create operation inputs fileName: { type: 'string', description: 'File name' }, file: { type: 'json', description: 'File to upload (UserFile object)' }, - fileReference: { type: 'json', description: 'File reference from previous block' }, content: { type: 'string', description: 'Text content to upload' }, mimeType: { type: 'string', description: 'MIME type of file to create' }, values: { type: 'json', description: 'Cell values for new Excel as JSON' }, - fileId: { type: 'string', description: 'File ID to download' }, + // Folder canonical params (per-operation) + uploadFolderId: { type: 'string', description: 'Parent folder for upload/create file' }, + createFolderParentId: { type: 'string', description: 'Parent folder for create folder' }, + listFolderId: { type: 'string', description: 'Folder to list files from' }, + // File canonical params (per-operation) + downloadFileId: { type: 'string', description: 'File to download' }, + deleteFileId: { type: 'string', description: 'File to delete' }, downloadFileName: { type: 'string', description: 'File name override for download' }, - folderId: { type: 'string', description: 'Folder ID' }, + folderName: { type: 'string', description: 'Folder name for create_folder' }, + // List operation inputs query: { type: 'string', description: 'Search query' }, pageSize: { type: 'number', description: 'Results per page' }, }, diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts index b626c20a4..a22d4195c 100644 --- a/apps/sim/blocks/blocks/outlook.ts +++ b/apps/sim/blocks/blocks/outlook.ts @@ -122,7 +122,7 @@ export const OutlookBlock: BlockConfig = { }, // Variable reference (advanced mode) { - id: 'attachments', + id: 'attachmentReference', title: 'Attachments', type: 'short-input', canonicalParamId: 'attachments', @@ -171,7 +171,7 @@ export const OutlookBlock: BlockConfig = { }, // Read Email Fields - Add folder selector (basic mode) { - id: 'folder', + id: 'folderSelector', title: 'Folder', type: 'folder-selector', canonicalParamId: 'folder', @@ -328,24 +328,20 @@ export const OutlookBlock: BlockConfig = { const { credential, folder, - manualFolder, - destinationFolder, - manualDestinationFolder, + destinationId, + copyDestinationId, + attachments, moveMessageId, actionMessageId, copyMessageId, - copyDestinationFolder, - manualCopyDestinationFolder, - attachmentFiles, - attachments, ...rest } = params - // Handle both selector and manual folder input - const effectiveFolder = (folder || manualFolder || '').trim() + // folder is already the canonical param - use it directly + const effectiveFolder = folder ? String(folder).trim() : '' - // Normalize file attachments from either basic (file-upload) or advanced (short-input) mode - const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + // Normalize file attachments from the canonical attachments param + const normalizedAttachments = normalizeFileInput(attachments) if (normalizedAttachments) { rest.attachments = normalizedAttachments } @@ -359,8 +355,10 @@ export const OutlookBlock: BlockConfig = { if (moveMessageId) { rest.messageId = moveMessageId } - if (!rest.destinationId) { - rest.destinationId = (destinationFolder || manualDestinationFolder || '').trim() + // destinationId is already the canonical param + const effectiveDestinationId = destinationId ? String(destinationId).trim() : '' + if (effectiveDestinationId) { + rest.destinationId = effectiveDestinationId } } @@ -376,12 +374,12 @@ export const OutlookBlock: BlockConfig = { if (copyMessageId) { rest.messageId = copyMessageId } - // Handle copyDestinationId (from UI canonical param) or destinationId (from trigger) - if (rest.copyDestinationId) { - rest.destinationId = rest.copyDestinationId - rest.copyDestinationId = undefined - } else if (!rest.destinationId) { - rest.destinationId = (copyDestinationFolder || manualCopyDestinationFolder || '').trim() + // copyDestinationId is the canonical param - map it to destinationId for the tool + const effectiveCopyDestinationId = copyDestinationId + ? String(copyDestinationId).trim() + : '' + if (effectiveCopyDestinationId) { + rest.destinationId = effectiveCopyDestinationId } } @@ -400,30 +398,24 @@ export const OutlookBlock: BlockConfig = { subject: { type: 'string', description: 'Email subject' }, body: { type: 'string', description: 'Email content' }, contentType: { type: 'string', description: 'Content type (Text or HTML)' }, - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - attachments: { type: 'array', description: 'Files to attach (UserFile array)' }, + attachments: { type: 'array', description: 'Files to attach (canonical param)' }, // Forward operation inputs messageId: { type: 'string', description: 'Message ID to forward' }, comment: { type: 'string', description: 'Optional comment for forwarding' }, // Read operation inputs - folder: { type: 'string', description: 'Email folder' }, - manualFolder: { type: 'string', description: 'Manual folder name' }, + folder: { type: 'string', description: 'Email folder (canonical param)' }, maxResults: { type: 'number', description: 'Maximum emails' }, includeAttachments: { type: 'boolean', description: 'Include email attachments' }, // Move operation inputs moveMessageId: { type: 'string', description: 'Message ID to move' }, - destinationFolder: { type: 'string', description: 'Destination folder ID' }, - manualDestinationFolder: { type: 'string', description: 'Manual destination folder ID' }, - destinationId: { type: 'string', description: 'Destination folder ID for move' }, + destinationId: { type: 'string', description: 'Destination folder ID (canonical param)' }, // Action operation inputs actionMessageId: { type: 'string', description: 'Message ID for actions' }, copyMessageId: { type: 'string', description: 'Message ID to copy' }, - copyDestinationFolder: { type: 'string', description: 'Copy destination folder ID' }, - manualCopyDestinationFolder: { + copyDestinationId: { type: 'string', - description: 'Manual copy destination folder ID', + description: 'Destination folder ID for copy (canonical param)', }, - copyDestinationId: { type: 'string', description: 'Destination folder ID for copy' }, }, outputs: { // Common outputs diff --git a/apps/sim/blocks/blocks/pulse.ts b/apps/sim/blocks/blocks/pulse.ts index c61f11070..fda29aa16 100644 --- a/apps/sim/blocks/blocks/pulse.ts +++ b/apps/sim/blocks/blocks/pulse.ts @@ -25,6 +25,7 @@ export const PulseBlock: BlockConfig = { placeholder: 'Upload a document', mode: 'basic', maxSize: 50, + required: true, }, { id: 'filePath', @@ -33,6 +34,7 @@ export const PulseBlock: BlockConfig = { canonicalParamId: 'document', placeholder: 'Document URL', mode: 'advanced', + required: true, }, { id: 'pages', @@ -66,18 +68,12 @@ export const PulseBlock: BlockConfig = { config: { tool: () => 'pulse_parser', params: (params) => { - if (!params || !params.apiKey || params.apiKey.trim() === '') { - throw new Error('Pulse API key is required') - } - const parameters: Record = { apiKey: params.apiKey.trim(), } - const documentInput = params.fileUpload || params.filePath || params.document - if (!documentInput) { - throw new Error('Document is required') - } + // document is the canonical param from fileUpload (basic) or filePath (advanced) + const documentInput = params.document if (typeof documentInput === 'object') { parameters.file = documentInput } else if (typeof documentInput === 'string') { @@ -104,9 +100,10 @@ export const PulseBlock: BlockConfig = { }, }, inputs: { - document: { type: 'json', description: 'Document input (file upload or URL reference)' }, - filePath: { type: 'string', description: 'Document URL (advanced mode)' }, - fileUpload: { type: 'json', description: 'Uploaded document file (basic mode)' }, + document: { + type: 'json', + description: 'Document input (canonical param for file upload or URL)', + }, apiKey: { type: 'string', description: 'Pulse API key' }, pages: { type: 'string', description: 'Page range selection' }, chunking: { @@ -129,14 +126,8 @@ export const PulseBlock: BlockConfig = { }, } +// PulseV2Block uses the same canonical param 'document' for both basic and advanced modes const pulseV2Inputs = PulseBlock.inputs - ? { - ...Object.fromEntries( - Object.entries(PulseBlock.inputs).filter(([key]) => key !== 'filePath') - ), - fileReference: { type: 'json', description: 'File reference (advanced mode)' }, - } - : {} const pulseV2SubBlocks = (PulseBlock.subBlocks || []).flatMap((subBlock) => { if (subBlock.id === 'filePath') { return [] // Remove the old filePath subblock @@ -152,6 +143,7 @@ const pulseV2SubBlocks = (PulseBlock.subBlocks || []).flatMap((subBlock) => { canonicalParamId: 'document', placeholder: 'File reference', mode: 'advanced' as const, + required: true, }, ] } @@ -175,18 +167,12 @@ export const PulseV2Block: BlockConfig = { fallbackToolId: 'pulse_parser_v2', }), params: (params) => { - if (!params || !params.apiKey || params.apiKey.trim() === '') { - throw new Error('Pulse API key is required') - } - const parameters: Record = { apiKey: params.apiKey.trim(), } - const normalizedFile = normalizeFileInput( - params.fileUpload || params.fileReference || params.document, - { single: true } - ) + // document is the canonical param from fileUpload (basic) or fileReference (advanced) + const normalizedFile = normalizeFileInput(params.document, { single: true }) if (!normalizedFile) { throw new Error('Document file is required') } diff --git a/apps/sim/blocks/blocks/reducto.ts b/apps/sim/blocks/blocks/reducto.ts index fb9d39370..d0c6ed7ce 100644 --- a/apps/sim/blocks/blocks/reducto.ts +++ b/apps/sim/blocks/blocks/reducto.ts @@ -24,6 +24,7 @@ export const ReductoBlock: BlockConfig = { placeholder: 'Upload a PDF document', mode: 'basic', maxSize: 50, + required: true, }, { id: 'filePath', @@ -32,6 +33,7 @@ export const ReductoBlock: BlockConfig = { canonicalParamId: 'document', placeholder: 'Document URL', mode: 'advanced', + required: true, }, { id: 'pages', @@ -62,18 +64,12 @@ export const ReductoBlock: BlockConfig = { config: { tool: () => 'reducto_parser', params: (params) => { - if (!params || !params.apiKey || params.apiKey.trim() === '') { - throw new Error('Reducto API key is required') - } - const parameters: Record = { apiKey: params.apiKey.trim(), } - const documentInput = params.fileUpload || params.filePath || params.document - if (!documentInput) { - throw new Error('PDF document is required') - } + // document is the canonical param from fileUpload (basic) or filePath (advanced) + const documentInput = params.document if (typeof documentInput === 'object') { parameters.file = documentInput @@ -118,9 +114,10 @@ export const ReductoBlock: BlockConfig = { }, }, inputs: { - document: { type: 'json', description: 'Document input (file upload or URL reference)' }, - filePath: { type: 'string', description: 'PDF document URL (advanced mode)' }, - fileUpload: { type: 'json', description: 'Uploaded PDF file (basic mode)' }, + document: { + type: 'json', + description: 'Document input (canonical param for file upload or URL)', + }, apiKey: { type: 'string', description: 'Reducto API key' }, pages: { type: 'string', description: 'Page selection' }, tableOutputFormat: { type: 'string', description: 'Table output format' }, @@ -135,14 +132,8 @@ export const ReductoBlock: BlockConfig = { }, } +// ReductoV2Block uses the same canonical param 'document' for both basic and advanced modes const reductoV2Inputs = ReductoBlock.inputs - ? { - ...Object.fromEntries( - Object.entries(ReductoBlock.inputs).filter(([key]) => key !== 'filePath') - ), - fileReference: { type: 'json', description: 'File reference (advanced mode)' }, - } - : {} const reductoV2SubBlocks = (ReductoBlock.subBlocks || []).flatMap((subBlock) => { if (subBlock.id === 'filePath') { return [] @@ -157,6 +148,7 @@ const reductoV2SubBlocks = (ReductoBlock.subBlocks || []).flatMap((subBlock) => canonicalParamId: 'document', placeholder: 'File reference', mode: 'advanced' as const, + required: true, }, ] } @@ -179,18 +171,12 @@ export const ReductoV2Block: BlockConfig = { fallbackToolId: 'reducto_parser_v2', }), params: (params) => { - if (!params || !params.apiKey || params.apiKey.trim() === '') { - throw new Error('Reducto API key is required') - } - const parameters: Record = { apiKey: params.apiKey.trim(), } - const documentInput = normalizeFileInput( - params.fileUpload || params.fileReference || params.document, - { single: true } - ) + // document is the canonical param from fileUpload (basic) or fileReference (advanced) + const documentInput = normalizeFileInput(params.document, { single: true }) if (!documentInput) { throw new Error('PDF document file is required') } diff --git a/apps/sim/blocks/blocks/s3.ts b/apps/sim/blocks/blocks/s3.ts index 9c8c537a1..10491a078 100644 --- a/apps/sim/blocks/blocks/s3.ts +++ b/apps/sim/blocks/blocks/s3.ts @@ -87,7 +87,7 @@ export const S3Block: BlockConfig = { multiple: false, }, { - id: 'file', + id: 'fileReference', title: 'File Reference', type: 'short-input', canonicalParamId: 'file', @@ -216,7 +216,6 @@ export const S3Block: BlockConfig = { placeholder: 'Select ACL for copied object (default: private)', condition: { field: 'operation', value: 'copy_object' }, mode: 'advanced', - canonicalParamId: 'acl', }, ], tools: { @@ -271,9 +270,9 @@ export const S3Block: BlockConfig = { if (!params.objectKey) { throw new Error('Object Key is required for upload') } - // Use file from uploadFile if in basic mode, otherwise use file reference + // file is the canonical param from uploadFile (basic) or fileReference (advanced) // normalizeFileInput handles JSON stringified values from advanced mode - const fileParam = normalizeFileInput(params.uploadFile || params.file, { single: true }) + const fileParam = normalizeFileInput(params.file, { single: true }) return { accessKeyId: params.accessKeyId, @@ -396,8 +395,7 @@ export const S3Block: BlockConfig = { bucketName: { type: 'string', description: 'S3 bucket name' }, // Upload inputs objectKey: { type: 'string', description: 'Object key/path in S3' }, - uploadFile: { type: 'json', description: 'File to upload (UI)' }, - file: { type: 'json', description: 'File to upload (reference)' }, + file: { type: 'json', description: 'File to upload (canonical param)' }, content: { type: 'string', description: 'Text content to upload' }, contentType: { type: 'string', description: 'Content-Type header' }, acl: { type: 'string', description: 'Access control list' }, diff --git a/apps/sim/blocks/blocks/sendgrid.ts b/apps/sim/blocks/blocks/sendgrid.ts index c55513026..016d11f98 100644 --- a/apps/sim/blocks/blocks/sendgrid.ts +++ b/apps/sim/blocks/blocks/sendgrid.ts @@ -562,13 +562,12 @@ Return ONLY the HTML content.`, templateGenerations, listPageSize, templatePageSize, - attachmentFiles, attachments, ...rest } = params // Normalize attachments for send_mail operation - const normalizedAttachments = normalizeFileInput(attachmentFiles || attachments) + const normalizedAttachments = normalizeFileInput(attachments) // Map renamed fields back to tool parameter names return { @@ -606,8 +605,7 @@ Return ONLY the HTML content.`, replyToName: { type: 'string', description: 'Reply-to name' }, mailTemplateId: { type: 'string', description: 'Template ID for sending mail' }, dynamicTemplateData: { type: 'json', description: 'Dynamic template data' }, - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - attachments: { type: 'array', description: 'Files to attach (UserFile array)' }, + attachments: { type: 'array', description: 'Files to attach (canonical param)' }, // Contact inputs email: { type: 'string', description: 'Contact email' }, firstName: { type: 'string', description: 'Contact first name' }, diff --git a/apps/sim/blocks/blocks/sftp.ts b/apps/sim/blocks/blocks/sftp.ts index c7afdb534..0a868644d 100644 --- a/apps/sim/blocks/blocks/sftp.ts +++ b/apps/sim/blocks/blocks/sftp.ts @@ -223,7 +223,8 @@ export const SftpBlock: BlockConfig = { return { ...connectionConfig, remotePath: params.remotePath, - files: normalizeFileInput(params.uploadFiles || params.files), + // files is the canonical param from uploadFiles (basic) or files (advanced) + files: normalizeFileInput(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 e1a6aac2a..f10c1d5d9 100644 --- a/apps/sim/blocks/blocks/sharepoint.ts +++ b/apps/sim/blocks/blocks/sharepoint.ts @@ -252,7 +252,19 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`, placeholder: 'Enter site ID (leave empty for root site)', dependsOn: ['credential'], mode: 'advanced', - condition: { field: 'operation', value: 'create_page' }, + condition: { + field: 'operation', + value: [ + 'create_page', + 'read_page', + 'list_sites', + 'create_list', + 'read_list', + 'update_list', + 'add_list_items', + 'upload_file', + ], + }, }, { @@ -391,18 +403,17 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, } }, params: (params) => { - const { credential, siteSelector, manualSiteId, mimeType, ...rest } = params + const { credential, siteId, mimeType, ...rest } = params - const effectiveSiteId = (siteSelector || manualSiteId || '').trim() + // siteId is the canonical param from siteSelector (basic) or manualSiteId (advanced) + const effectiveSiteId = siteId ? String(siteId).trim() : '' const { - itemId: providedItemId, - listItemId, - listItemFields, + itemId, // canonical param from listItemId + listItemFields, // canonical param includeColumns, includeItems, - uploadFiles, - files, + files, // canonical param from uploadFiles (basic) or files (advanced) columnDefinitions, ...others } = rest as any @@ -421,11 +432,9 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, parsedItemFields = undefined } - const rawItemId = providedItemId ?? listItemId + // itemId is the canonical param from listItemId const sanitizedItemId = - rawItemId === undefined || rawItemId === null - ? undefined - : String(rawItemId).trim() || undefined + itemId === undefined || itemId === null ? undefined : String(itemId).trim() || undefined const coerceBoolean = (value: any) => { if (typeof value === 'boolean') return value @@ -449,8 +458,8 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, } catch {} } - // Handle file upload files parameter - const normalizedFiles = normalizeFileInput(uploadFiles || files) + // Handle file upload files parameter using canonical param + const normalizedFiles = normalizeFileInput(files) const baseParams: Record = { credential, siteId: effectiveSiteId || undefined, @@ -486,8 +495,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, }, pageTitle: { type: 'string', description: 'Page title' }, pageId: { type: 'string', description: 'Page ID' }, - siteSelector: { type: 'string', description: 'Site selector' }, - manualSiteId: { type: 'string', description: 'Manual site ID' }, + siteId: { type: 'string', description: 'Site ID' }, pageSize: { type: 'number', description: 'Results per page' }, listDisplayName: { type: 'string', description: 'List display name' }, listDescription: { type: 'string', description: 'List description' }, @@ -496,13 +504,12 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`, listTitle: { type: 'string', description: 'List title' }, includeColumns: { type: 'boolean', description: 'Include columns in response' }, includeItems: { type: 'boolean', description: 'Include items in response' }, - listItemId: { type: 'string', description: 'List item ID' }, - listItemFields: { type: 'string', description: 'List item fields' }, - driveId: { type: 'string', description: 'Document library (drive) ID' }, + itemId: { type: 'string', description: 'List item ID (canonical param)' }, + listItemFields: { type: 'string', description: 'List item fields (canonical param)' }, + driveId: { type: 'string', description: 'Document library (drive) ID (canonical param)' }, folderPath: { type: 'string', description: 'Folder path for file upload' }, fileName: { type: 'string', description: 'File name override' }, - uploadFiles: { type: 'json', description: 'Files to upload (UI upload)' }, - files: { type: 'array', description: 'Files to upload (UserFile array)' }, + files: { type: 'array', description: 'Files to upload (canonical param)' }, }, outputs: { sites: { diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 68e0a7a27..38f22ca78 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -92,6 +92,7 @@ export const SlackBlock: BlockConfig = { field: 'authMethod', value: 'oauth', }, + required: true, }, { id: 'botToken', @@ -104,6 +105,7 @@ export const SlackBlock: BlockConfig = { field: 'authMethod', value: 'bot_token', }, + required: true, }, { id: 'channel', @@ -124,6 +126,7 @@ export const SlackBlock: BlockConfig = { not: true, }, }, + required: true, }, { id: 'manualChannel', @@ -142,6 +145,7 @@ export const SlackBlock: BlockConfig = { not: true, }, }, + required: true, }, { id: 'dmUserId', @@ -156,6 +160,7 @@ export const SlackBlock: BlockConfig = { field: 'destinationType', value: 'dm', }, + required: true, }, { id: 'manualDmUserId', @@ -168,6 +173,7 @@ export const SlackBlock: BlockConfig = { field: 'destinationType', value: 'dm', }, + required: true, }, { id: 'text', @@ -547,15 +553,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, operation, destinationType, channel, - manualChannel, dmUserId, - manualDmUserId, text, title, content, limit, oldest, - attachmentFiles, files, threadTs, updateTimestamp, @@ -576,20 +579,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } = params const isDM = destinationType === 'dm' - const effectiveChannel = (channel || manualChannel || '').trim() - const effectiveUserId = (dmUserId || manualDmUserId || '').trim() + const effectiveChannel = channel ? String(channel).trim() : '' + const effectiveUserId = dmUserId ? String(dmUserId).trim() : '' - const noChannelOperations = ['list_channels', 'list_users', 'get_user'] const dmSupportedOperations = ['send', 'read'] - if (isDM && dmSupportedOperations.includes(operation)) { - if (!effectiveUserId) { - throw new Error('User is required for DM operations.') - } - } else if (!effectiveChannel && !noChannelOperations.includes(operation)) { - throw new Error('Channel is required.') - } - const baseParams: Record = {} if (isDM && dmSupportedOperations.includes(operation)) { @@ -600,28 +594,20 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, // Handle authentication based on method if (authMethod === 'bot_token') { - if (!botToken) { - throw new Error('Bot token is required when using bot token authentication') - } baseParams.accessToken = botToken } else { // Default to OAuth - if (!credential) { - throw new Error('Slack account credential is required when using Sim Bot') - } baseParams.credential = credential } switch (operation) { case 'send': { - if (!text || text.trim() === '') { - throw new Error('Message text is required for send operation') - } baseParams.text = text if (threadTs) { baseParams.thread_ts = threadTs } - const normalizedFiles = normalizeFileInput(attachmentFiles || files) + // files is the canonical param from attachmentFiles (basic) or files (advanced) + const normalizedFiles = normalizeFileInput(files) if (normalizedFiles) { baseParams.files = normalizedFiles } @@ -629,9 +615,6 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } case 'canvas': - if (!title || !content) { - throw new Error('Title and content are required for canvas operation') - } baseParams.title = title baseParams.content = content break @@ -649,16 +632,10 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } case 'get_message': - if (!getMessageTimestamp) { - throw new Error('Message timestamp is required for get message operation') - } baseParams.timestamp = getMessageTimestamp break case 'get_thread': { - if (!getThreadTimestamp) { - throw new Error('Thread timestamp is required for get thread operation') - } baseParams.threadTs = getThreadTimestamp if (threadLimit) { const parsedLimit = Number.parseInt(threadLimit, 10) @@ -688,18 +665,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } case 'get_user': - if (!userId) { - throw new Error('User ID is required for get user operation') - } baseParams.userId = userId break case 'download': { const fileId = (rest as any).fileId const downloadFileName = (rest as any).downloadFileName - if (!fileId) { - throw new Error('File ID is required for download operation') - } baseParams.fileId = fileId if (downloadFileName) { baseParams.fileName = downloadFileName @@ -708,24 +679,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, } case 'update': - if (!updateTimestamp || !updateText) { - throw new Error('Timestamp and text are required for update operation') - } baseParams.timestamp = updateTimestamp baseParams.text = updateText break case 'delete': - if (!deleteTimestamp) { - throw new Error('Timestamp is required for delete operation') - } baseParams.timestamp = deleteTimestamp break case 'react': - if (!reactionTimestamp || !emojiName) { - throw new Error('Timestamp and emoji name are required for reaction operation') - } baseParams.timestamp = reactionTimestamp baseParams.name = emojiName break @@ -741,19 +703,16 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, destinationType: { type: 'string', description: 'Destination type (channel or dm)' }, credential: { type: 'string', description: 'Slack access token' }, botToken: { type: 'string', description: 'Bot token' }, - channel: { type: 'string', description: 'Channel identifier' }, - manualChannel: { type: 'string', description: 'Manual channel identifier' }, - dmUserId: { type: 'string', description: 'User ID for DM recipient (selector)' }, - manualDmUserId: { type: 'string', description: 'User ID for DM recipient (manual input)' }, + channel: { type: 'string', description: 'Channel identifier (canonical param)' }, + dmUserId: { type: 'string', description: 'User ID for DM recipient (canonical param)' }, text: { type: 'string', description: 'Message text' }, - attachmentFiles: { type: 'json', description: 'Files to attach (UI upload)' }, - files: { type: 'array', description: 'Files to attach (UserFile array)' }, + files: { type: 'array', description: 'Files to attach (canonical param)' }, title: { type: 'string', description: 'Canvas title' }, content: { type: 'string', description: 'Canvas content' }, limit: { type: 'string', description: 'Message limit' }, oldest: { type: 'string', description: 'Oldest timestamp' }, fileId: { type: 'string', description: 'File ID to download' }, - downloadFileName: { type: 'string', description: 'File name override for download' }, + fileName: { type: 'string', description: 'File name override for download (canonical param)' }, // Update/Delete/React operation inputs updateTimestamp: { type: 'string', description: 'Message timestamp for update' }, updateText: { type: 'string', description: 'New text for update' }, diff --git a/apps/sim/blocks/blocks/smtp.ts b/apps/sim/blocks/blocks/smtp.ts index 640cdd680..6537beb5c 100644 --- a/apps/sim/blocks/blocks/smtp.ts +++ b/apps/sim/blocks/blocks/smtp.ts @@ -177,7 +177,7 @@ export const SmtpBlock: BlockConfig = { cc: params.cc, bcc: params.bcc, replyTo: params.replyTo, - attachments: normalizeFileInput(params.attachmentFiles || params.attachments), + attachments: normalizeFileInput(params.attachments), }), }, }, diff --git a/apps/sim/blocks/blocks/spotify.ts b/apps/sim/blocks/blocks/spotify.ts index c152b3a56..4b450bac3 100644 --- a/apps/sim/blocks/blocks/spotify.ts +++ b/apps/sim/blocks/blocks/spotify.ts @@ -824,8 +824,6 @@ export const SpotifyBlock: BlockConfig = { description: { type: 'string', description: 'Playlist description' }, public: { type: 'boolean', description: 'Whether playlist is public' }, coverImage: { type: 'json', description: 'Cover image (UserFile)' }, - coverImageFile: { type: 'json', description: 'Cover image upload (basic mode)' }, - coverImageRef: { type: 'json', description: 'Cover image reference (advanced mode)' }, range_start: { type: 'number', description: 'Start index for reorder' }, insert_before: { type: 'number', description: 'Insert before index' }, range_length: { type: 'number', description: 'Number of items to move' }, diff --git a/apps/sim/blocks/blocks/stt.ts b/apps/sim/blocks/blocks/stt.ts index 66adef7a9..92b10e2b7 100644 --- a/apps/sim/blocks/blocks/stt.ts +++ b/apps/sim/blocks/blocks/stt.ts @@ -259,8 +259,8 @@ export const SttBlock: BlockConfig = { } }, params: (params) => { - // Normalize file input from basic (file-upload) or advanced (short-input) mode - const audioFile = normalizeFileInput(params.audioFile || params.audioFileReference, { + // Normalize file input - audioFile is the canonical param for both basic and advanced modes + const audioFile = normalizeFileInput(params.audioFile, { single: true, }) @@ -269,7 +269,6 @@ export const SttBlock: BlockConfig = { apiKey: params.apiKey, model: params.model, audioFile, - audioFileReference: undefined, audioUrl: params.audioUrl, language: params.language, timestamps: params.timestamps, @@ -296,7 +295,6 @@ export const SttBlock: BlockConfig = { 'Provider-specific model (e.g., scribe_v1 for ElevenLabs, nova-3 for Deepgram, best for AssemblyAI, gemini-2.0-flash-exp for Gemini)', }, audioFile: { type: 'json', description: 'Audio/video file (UserFile)' }, - audioFileReference: { type: 'json', description: 'Audio/video file reference' }, audioUrl: { type: 'string', description: 'Audio/video URL' }, language: { type: 'string', description: 'Language code or auto' }, timestamps: { type: 'string', description: 'Timestamp granularity (none, sentence, word)' }, @@ -393,8 +391,8 @@ export const SttV2Block: BlockConfig = { fallbackToolId: 'stt_whisper_v2', }), params: (params) => { - // Normalize file input from basic (file-upload) or advanced (short-input) mode - const audioFile = normalizeFileInput(params.audioFile || params.audioFileReference, { + // Normalize file input - audioFile is the canonical param for both basic and advanced modes + const audioFile = normalizeFileInput(params.audioFile, { single: true, }) @@ -403,7 +401,6 @@ export const SttV2Block: BlockConfig = { apiKey: params.apiKey, model: params.model, audioFile, - audioFileReference: undefined, language: params.language, timestamps: params.timestamps, diarization: params.diarization, diff --git a/apps/sim/blocks/blocks/supabase.ts b/apps/sim/blocks/blocks/supabase.ts index 78256c5be..602111ffa 100644 --- a/apps/sim/blocks/blocks/supabase.ts +++ b/apps/sim/blocks/blocks/supabase.ts @@ -974,15 +974,13 @@ 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, { + // fileData is the canonical param for both basic (file) and advanced (fileContent) modes + const normalizedFileData = normalizeFileInput(fileData, { single: true, }) @@ -1156,7 +1154,7 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e // Storage operation inputs bucket: { type: 'string', description: 'Storage bucket name' }, path: { type: 'string', description: 'File or folder path in storage' }, - fileContent: { type: 'string', description: 'File content (base64 for binary)' }, + fileData: { type: 'json', description: 'File data (UserFile)' }, contentType: { type: 'string', description: 'MIME type of the file' }, fileName: { type: 'string', description: 'File name for upload or download override' }, upsert: { type: 'boolean', description: 'Whether to overwrite existing file' }, diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts index 2be6eb546..ce4076d38 100644 --- a/apps/sim/blocks/blocks/telegram.ts +++ b/apps/sim/blocks/blocks/telegram.ts @@ -269,7 +269,8 @@ export const TelegramBlock: BlockConfig = { messageId: params.messageId, } case 'telegram_send_photo': { - const photoSource = normalizeFileInput(params.photoFile || params.photo, { + // photo is the canonical param for both basic (photoFile) and advanced modes + const photoSource = normalizeFileInput(params.photo, { single: true, }) if (!photoSource) { @@ -282,7 +283,8 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_video': { - const videoSource = normalizeFileInput(params.videoFile || params.video, { + // video is the canonical param for both basic (videoFile) and advanced modes + const videoSource = normalizeFileInput(params.video, { single: true, }) if (!videoSource) { @@ -295,7 +297,8 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_audio': { - const audioSource = normalizeFileInput(params.audioFile || params.audio, { + // audio is the canonical param for both basic (audioFile) and advanced modes + const audioSource = normalizeFileInput(params.audio, { single: true, }) if (!audioSource) { @@ -308,7 +311,8 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_animation': { - const animationSource = normalizeFileInput(params.animationFile || params.animation, { + // animation is the canonical param for both basic (animationFile) and advanced modes + const animationSource = normalizeFileInput(params.animation, { single: true, }) if (!animationSource) { @@ -321,9 +325,10 @@ export const TelegramBlock: BlockConfig = { } } case 'telegram_send_document': { + // files is the canonical param for both basic (attachmentFiles) and advanced modes return { ...commonParams, - files: normalizeFileInput(params.attachmentFiles || params.files), + files: normalizeFileInput(params.files), caption: params.caption, } } @@ -341,18 +346,10 @@ export const TelegramBlock: BlockConfig = { botToken: { type: 'string', description: 'Telegram bot token' }, chatId: { type: 'string', description: 'Chat identifier' }, text: { type: 'string', description: 'Message text' }, - photoFile: { type: 'json', description: 'Uploaded photo (UserFile)' }, - photo: { type: 'json', description: 'Photo reference or URL/file_id' }, - videoFile: { type: 'json', description: 'Uploaded video (UserFile)' }, - video: { type: 'json', description: 'Video reference or URL/file_id' }, - audioFile: { type: 'json', description: 'Uploaded audio (UserFile)' }, - audio: { type: 'json', description: 'Audio reference or URL/file_id' }, - animationFile: { type: 'json', description: 'Uploaded animation (UserFile)' }, - animation: { type: 'json', description: 'Animation reference or URL/file_id' }, - attachmentFiles: { - type: 'json', - description: 'Files to attach (UI upload)', - }, + photo: { type: 'json', description: 'Photo (UserFile or URL/file_id)' }, + video: { type: 'json', description: 'Video (UserFile or URL/file_id)' }, + audio: { type: 'json', description: 'Audio (UserFile or URL/file_id)' }, + animation: { type: 'json', description: 'Animation (UserFile or URL/file_id)' }, files: { type: 'array', description: 'Files to attach (UserFile array)' }, caption: { type: 'string', description: 'Caption for media' }, messageId: { type: 'string', description: 'Message ID to delete' }, diff --git a/apps/sim/blocks/blocks/textract.ts b/apps/sim/blocks/blocks/textract.ts index 10f5a1113..a2eea3050 100644 --- a/apps/sim/blocks/blocks/textract.ts +++ b/apps/sim/blocks/blocks/textract.ts @@ -137,7 +137,8 @@ export const TextractBlock: BlockConfig = { } parameters.s3Uri = params.s3Uri.trim() } else { - const documentInput = params.fileUpload || params.filePath || params.document + // document is the canonical param for both basic (fileUpload) and advanced (filePath) modes + const documentInput = params.document if (!documentInput) { throw new Error('Document is required') } @@ -165,8 +166,6 @@ export const TextractBlock: BlockConfig = { inputs: { processingMode: { type: 'string', description: 'Document type: single-page or multi-page' }, document: { type: 'json', description: 'Document input (file upload or URL reference)' }, - filePath: { type: 'string', description: 'Document URL (advanced mode)' }, - fileUpload: { type: 'json', description: 'Uploaded document file (basic mode)' }, s3Uri: { type: 'string', description: 'S3 URI for multi-page processing (s3://bucket/key)' }, extractTables: { type: 'boolean', description: 'Extract tables from document' }, extractForms: { type: 'boolean', description: 'Extract form key-value pairs' }, @@ -192,14 +191,7 @@ export const TextractBlock: BlockConfig = { }, } -const textractV2Inputs = TextractBlock.inputs - ? { - ...Object.fromEntries( - Object.entries(TextractBlock.inputs).filter(([key]) => key !== 'filePath') - ), - fileReference: { type: 'json', description: 'File reference (advanced mode)' }, - } - : {} +const textractV2Inputs = TextractBlock.inputs ? { ...TextractBlock.inputs } : {} const textractV2SubBlocks = (TextractBlock.subBlocks || []).flatMap((subBlock) => { if (subBlock.id === 'filePath') { return [] // Remove the old filePath subblock @@ -265,10 +257,8 @@ export const TextractV2Block: BlockConfig = { } parameters.s3Uri = params.s3Uri.trim() } else { - const file = normalizeFileInput( - params.fileUpload || params.fileReference || params.document, - { single: true } - ) + // document is the canonical param for both basic (fileUpload) and advanced (fileReference) modes + const file = normalizeFileInput(params.document, { single: true }) if (!file) { throw new Error('Document file is required') } diff --git a/apps/sim/blocks/blocks/video_generator.ts b/apps/sim/blocks/blocks/video_generator.ts index ae31eb951..55c5a2472 100644 --- a/apps/sim/blocks/blocks/video_generator.ts +++ b/apps/sim/blocks/blocks/video_generator.ts @@ -691,6 +691,7 @@ export const VideoGeneratorV2Block: BlockConfig = { condition: { field: 'provider', value: 'runway' }, placeholder: 'Reference image from previous blocks', mode: 'advanced', + required: true, }, { id: 'cameraControl', @@ -734,29 +735,25 @@ export const VideoGeneratorV2Block: BlockConfig = { return 'video_runway' } }, - params: (params) => { - const visualRef = - params.visualReferenceUpload || params.visualReferenceInput || params.visualReference - return { - provider: params.provider, - apiKey: params.apiKey, - model: params.model, - endpoint: params.endpoint, - prompt: params.prompt, - duration: params.duration ? Number(params.duration) : undefined, - aspectRatio: params.aspectRatio, - resolution: params.resolution, - visualReference: normalizeFileInput(visualRef, { single: true }), - consistencyMode: params.consistencyMode, - stylePreset: params.stylePreset, - promptOptimizer: params.promptOptimizer, - cameraControl: params.cameraControl - ? typeof params.cameraControl === 'string' - ? JSON.parse(params.cameraControl) - : params.cameraControl - : undefined, - } - }, + params: (params) => ({ + provider: params.provider, + apiKey: params.apiKey, + model: params.model, + endpoint: params.endpoint, + prompt: params.prompt, + duration: params.duration ? Number(params.duration) : undefined, + aspectRatio: params.aspectRatio, + resolution: params.resolution, + visualReference: normalizeFileInput(params.visualReference, { single: true }), + consistencyMode: params.consistencyMode, + stylePreset: params.stylePreset, + promptOptimizer: params.promptOptimizer, + cameraControl: params.cameraControl + ? typeof params.cameraControl === 'string' + ? JSON.parse(params.cameraControl) + : params.cameraControl + : undefined, + }), }, }, inputs: { @@ -784,11 +781,6 @@ export const VideoGeneratorV2Block: BlockConfig = { description: 'Video resolution - not available for MiniMax (fixed per endpoint)', }, visualReference: { type: 'json', description: 'Reference image for Runway (UserFile)' }, - visualReferenceUpload: { type: 'json', description: 'Uploaded reference image (basic mode)' }, - visualReferenceInput: { - type: 'json', - description: 'Reference image from previous blocks (advanced mode)', - }, consistencyMode: { type: 'string', description: 'Consistency mode for Runway (character, object, style, location)', diff --git a/apps/sim/blocks/blocks/vision.ts b/apps/sim/blocks/blocks/vision.ts index a367b0c58..3b791966f 100644 --- a/apps/sim/blocks/blocks/vision.ts +++ b/apps/sim/blocks/blocks/vision.ts @@ -91,7 +91,6 @@ export const VisionBlock: BlockConfig = { apiKey: { type: 'string', description: 'Provider API key' }, imageUrl: { type: 'string', description: 'Image URL' }, imageFile: { type: 'json', description: 'Image file (UserFile)' }, - imageFileReference: { type: 'json', description: 'Image file reference' }, model: { type: 'string', description: 'Vision model' }, prompt: { type: 'string', description: 'Analysis prompt' }, }, @@ -117,15 +116,13 @@ export const VisionV2Block: BlockConfig = { fallbackToolId: 'vision_tool_v2', }), params: (params) => { - // normalizeFileInput handles JSON stringified values from advanced mode - // Vision expects a single file - const imageFile = normalizeFileInput(params.imageFile || params.imageFileReference, { + // imageFile is the canonical param for both basic and advanced modes + const imageFile = normalizeFileInput(params.imageFile, { single: true, }) return { ...params, imageFile, - imageFileReference: undefined, } }, }, @@ -177,7 +174,6 @@ export const VisionV2Block: BlockConfig = { inputs: { apiKey: { type: 'string', description: 'Provider API key' }, imageFile: { type: 'json', description: 'Image file (UserFile)' }, - imageFileReference: { type: 'json', description: 'Image file reference' }, model: { type: 'string', description: 'Vision model' }, prompt: { type: 'string', description: 'Analysis prompt' }, }, diff --git a/apps/sim/blocks/blocks/wealthbox.ts b/apps/sim/blocks/blocks/wealthbox.ts index 7a1eacd3d..f14921377 100644 --- a/apps/sim/blocks/blocks/wealthbox.ts +++ b/apps/sim/blocks/blocks/wealthbox.ts @@ -169,9 +169,10 @@ Return ONLY the date/time string - no explanations, no quotes, no extra text.`, } }, params: (params) => { - const { credential, operation, contactId, manualContactId, taskId, ...rest } = params + const { credential, operation, contactId, taskId, ...rest } = params - const effectiveContactId = (contactId || manualContactId || '').trim() + // contactId is the canonical param for both basic (file-selector) and advanced (manualContactId) modes + const effectiveContactId = contactId ? String(contactId).trim() : '' const baseParams = { ...rest, @@ -222,7 +223,6 @@ Return ONLY the date/time string - no explanations, no quotes, no extra text.`, credential: { type: 'string', description: 'Wealthbox access token' }, noteId: { type: 'string', description: 'Note identifier' }, contactId: { type: 'string', description: 'Contact identifier' }, - manualContactId: { type: 'string', description: 'Manual contact identifier' }, taskId: { type: 'string', description: 'Task identifier' }, content: { type: 'string', description: 'Content text' }, firstName: { type: 'string', description: 'First name' }, diff --git a/apps/sim/blocks/blocks/webflow.ts b/apps/sim/blocks/blocks/webflow.ts index cfc396257..c785413bf 100644 --- a/apps/sim/blocks/blocks/webflow.ts +++ b/apps/sim/blocks/blocks/webflow.ts @@ -40,7 +40,7 @@ export const WebflowBlock: BlockConfig = { required: true, }, { - id: 'siteId', + id: 'siteSelector', title: 'Site', type: 'project-selector', canonicalParamId: 'siteId', @@ -60,13 +60,13 @@ export const WebflowBlock: BlockConfig = { required: true, }, { - id: 'collectionId', + id: 'collectionSelector', title: 'Collection', type: 'file-selector', canonicalParamId: 'collectionId', serviceId: 'webflow', placeholder: 'Select collection', - dependsOn: ['credential', 'siteId'], + dependsOn: ['credential', 'siteSelector'], mode: 'basic', required: true, }, @@ -80,13 +80,13 @@ export const WebflowBlock: BlockConfig = { required: true, }, { - id: 'itemId', + id: 'itemSelector', title: 'Item', type: 'file-selector', canonicalParamId: 'itemId', serviceId: 'webflow', placeholder: 'Select item', - dependsOn: ['credential', 'collectionId'], + dependsOn: ['credential', 'collectionSelector'], mode: 'basic', condition: { field: 'operation', value: ['get', 'update', 'delete'] }, required: true, @@ -158,12 +158,9 @@ export const WebflowBlock: BlockConfig = { const { credential, fieldData, - siteId, - manualSiteId, - collectionId, - manualCollectionId, - itemId, - manualItemId, + siteId, // Canonical param from siteSelector (basic) or manualSiteId (advanced) + collectionId, // Canonical param from collectionSelector (basic) or manualCollectionId (advanced) + itemId, // Canonical param from itemSelector (basic) or manualItemId (advanced) ...rest } = params let parsedFieldData: any | undefined @@ -176,21 +173,9 @@ export const WebflowBlock: BlockConfig = { throw new Error(`Invalid JSON input for ${params.operation} operation: ${error.message}`) } - const effectiveSiteId = ((siteId as string) || (manualSiteId as string) || '').trim() - const effectiveCollectionId = ( - (collectionId as string) || - (manualCollectionId as string) || - '' - ).trim() - const effectiveItemId = ((itemId as string) || (manualItemId as string) || '').trim() - - if (!effectiveSiteId) { - throw new Error('Site ID is required') - } - - if (!effectiveCollectionId) { - throw new Error('Collection ID is required') - } + const effectiveSiteId = siteId ? String(siteId).trim() : '' + const effectiveCollectionId = collectionId ? String(collectionId).trim() : '' + const effectiveItemId = itemId ? String(itemId).trim() : '' const baseParams = { credential, @@ -202,9 +187,6 @@ export const WebflowBlock: BlockConfig = { switch (params.operation) { case 'create': case 'update': - if (params.operation === 'update' && !effectiveItemId) { - throw new Error('Item ID is required for update operation') - } return { ...baseParams, itemId: effectiveItemId || undefined, @@ -212,9 +194,6 @@ export const WebflowBlock: BlockConfig = { } case 'get': case 'delete': - if (!effectiveItemId) { - throw new Error(`Item ID is required for ${params.operation} operation`) - } return { ...baseParams, itemId: effectiveItemId } default: return baseParams @@ -226,11 +205,8 @@ export const WebflowBlock: BlockConfig = { operation: { type: 'string', description: 'Operation to perform' }, credential: { type: 'string', description: 'Webflow OAuth access token' }, siteId: { type: 'string', description: 'Webflow site identifier' }, - manualSiteId: { type: 'string', description: 'Manual site identifier' }, collectionId: { type: 'string', description: 'Webflow collection identifier' }, - manualCollectionId: { type: 'string', description: 'Manual collection identifier' }, itemId: { type: 'string', description: 'Item identifier' }, - manualItemId: { type: 'string', description: 'Manual item identifier' }, offset: { type: 'number', description: 'Pagination offset' }, limit: { type: 'number', description: 'Maximum items to return' }, fieldData: { type: 'json', description: 'Item field data' }, diff --git a/apps/sim/blocks/blocks/wordpress.ts b/apps/sim/blocks/blocks/wordpress.ts index e0b206ce5..207c19740 100644 --- a/apps/sim/blocks/blocks/wordpress.ts +++ b/apps/sim/blocks/blocks/wordpress.ts @@ -768,9 +768,10 @@ export const WordPressBlock: BlockConfig = { parent: params.parent ? Number(params.parent) : undefined, } case 'wordpress_upload_media': + // file is the canonical param for both basic (fileUpload) and advanced modes return { ...baseParams, - file: normalizeFileInput(params.fileUpload || params.file, { single: true }), + file: normalizeFileInput(params.file, { single: true }), filename: params.filename, title: params.mediaTitle, caption: params.caption, @@ -905,8 +906,7 @@ export const WordPressBlock: BlockConfig = { parent: { type: 'number', description: 'Parent page ID' }, menuOrder: { type: 'number', description: 'Menu order' }, // Media inputs - fileUpload: { type: 'json', description: 'File to upload (UserFile object)' }, - file: { type: 'json', description: 'File reference from previous block' }, + file: { type: 'json', description: 'File to upload (UserFile)' }, filename: { type: 'string', description: 'Optional filename override' }, mediaTitle: { type: 'string', description: 'Media title' }, caption: { type: 'string', description: 'Media caption' }, diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts index e22c654c4..622667d9f 100644 --- a/apps/sim/serializer/index.ts +++ b/apps/sim/serializer/index.ts @@ -520,7 +520,9 @@ export class Serializer { } // Check if value is missing - const fieldValue = params[subBlockConfig.id] + // For canonical subBlocks, look up the canonical param value (original IDs were deleted) + const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlockConfig.id] + const fieldValue = canonicalId ? params[canonicalId] : params[subBlockConfig.id] if (fieldValue === undefined || fieldValue === null || fieldValue === '') { missingFields.push(subBlockConfig.title || subBlockConfig.id) } diff --git a/apps/sim/triggers/googleforms/webhook.ts b/apps/sim/triggers/googleforms/webhook.ts index 12106c74f..0f74fb1a9 100644 --- a/apps/sim/triggers/googleforms/webhook.ts +++ b/apps/sim/triggers/googleforms/webhook.ts @@ -42,7 +42,7 @@ export const googleFormsWebhookTrigger: TriggerConfig = { mode: 'trigger', }, { - id: 'formId', + id: 'triggerFormId', title: 'Form ID', type: 'short-input', placeholder: '1FAIpQLSd... (Google Form ID)', diff --git a/apps/sim/triggers/microsoftteams/chat_webhook.ts b/apps/sim/triggers/microsoftteams/chat_webhook.ts index dcd155a57..9ef0b4390 100644 --- a/apps/sim/triggers/microsoftteams/chat_webhook.ts +++ b/apps/sim/triggers/microsoftteams/chat_webhook.ts @@ -47,7 +47,7 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = { }, }, { - id: 'chatId', + id: 'triggerChatId', title: 'Chat ID', type: 'short-input', placeholder: 'Enter chat ID', diff --git a/apps/sim/triggers/webflow/collection_item_changed.ts b/apps/sim/triggers/webflow/collection_item_changed.ts index 3b6c580bd..e0c43fd36 100644 --- a/apps/sim/triggers/webflow/collection_item_changed.ts +++ b/apps/sim/triggers/webflow/collection_item_changed.ts @@ -30,7 +30,7 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { }, }, { - id: 'siteId', + id: 'triggerSiteId', title: 'Site', type: 'dropdown', placeholder: 'Select a site', @@ -96,7 +96,7 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { dependsOn: ['triggerCredentials'], }, { - id: 'collectionId', + id: 'triggerCollectionId', title: 'Collection', type: 'dropdown', placeholder: 'Select a collection (optional)', @@ -112,7 +112,9 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) { return [] } @@ -142,7 +144,9 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) return null try { const response = await fetch('/api/tools/webflow/collections', { @@ -161,7 +165,7 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = { return null } }, - dependsOn: ['triggerCredentials', 'siteId'], + dependsOn: ['triggerCredentials', 'triggerSiteId'], }, { id: 'triggerSave', diff --git a/apps/sim/triggers/webflow/collection_item_created.ts b/apps/sim/triggers/webflow/collection_item_created.ts index 2ba0a11b6..3c2c6cee4 100644 --- a/apps/sim/triggers/webflow/collection_item_created.ts +++ b/apps/sim/triggers/webflow/collection_item_created.ts @@ -44,7 +44,7 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { }, }, { - id: 'siteId', + id: 'triggerSiteId', title: 'Site', type: 'dropdown', placeholder: 'Select a site', @@ -110,7 +110,7 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { dependsOn: ['triggerCredentials'], }, { - id: 'collectionId', + id: 'triggerCollectionId', title: 'Collection', type: 'dropdown', placeholder: 'Select a collection (optional)', @@ -126,7 +126,9 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) { return [] } @@ -156,7 +158,9 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) return null try { const response = await fetch('/api/tools/webflow/collections', { @@ -175,7 +179,7 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = { return null } }, - dependsOn: ['triggerCredentials', 'siteId'], + dependsOn: ['triggerCredentials', 'triggerSiteId'], }, { id: 'triggerSave', diff --git a/apps/sim/triggers/webflow/collection_item_deleted.ts b/apps/sim/triggers/webflow/collection_item_deleted.ts index c7568568e..80011af97 100644 --- a/apps/sim/triggers/webflow/collection_item_deleted.ts +++ b/apps/sim/triggers/webflow/collection_item_deleted.ts @@ -30,7 +30,7 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { }, }, { - id: 'siteId', + id: 'triggerSiteId', title: 'Site', type: 'dropdown', placeholder: 'Select a site', @@ -96,7 +96,7 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { dependsOn: ['triggerCredentials'], }, { - id: 'collectionId', + id: 'triggerCollectionId', title: 'Collection', type: 'dropdown', placeholder: 'Select a collection (optional)', @@ -112,7 +112,9 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) { return [] } @@ -142,7 +144,9 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const siteId = useSubBlockStore.getState().getValue(blockId, 'siteId') as string | null + const siteId = useSubBlockStore.getState().getValue(blockId, 'triggerSiteId') as + | string + | null if (!credentialId || !siteId) return null try { const response = await fetch('/api/tools/webflow/collections', { @@ -161,7 +165,7 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = { return null } }, - dependsOn: ['triggerCredentials', 'siteId'], + dependsOn: ['triggerCredentials', 'triggerSiteId'], }, { id: 'triggerSave', diff --git a/apps/sim/triggers/webflow/form_submission.ts b/apps/sim/triggers/webflow/form_submission.ts index 59efc5e00..2d698daa0 100644 --- a/apps/sim/triggers/webflow/form_submission.ts +++ b/apps/sim/triggers/webflow/form_submission.ts @@ -30,7 +30,7 @@ export const webflowFormSubmissionTrigger: TriggerConfig = { }, }, { - id: 'siteId', + id: 'triggerSiteId', title: 'Site', type: 'dropdown', placeholder: 'Select a site',