mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-05 12:14:59 -05:00
fix(inputs): canonical params + manual validations + params resolution cleanups (#3141)
* fix(onedrive): canonical param required validation * fix onedrive * cleanup canonical tool param resolution code * fix type * fix jira type checks * remove manual validations
This commit is contained in:
committed by
GitHub
parent
552dc56fc3
commit
ea3bab1f76
@@ -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 (
|
||||
<div className='relative w-full'>
|
||||
<Combobox
|
||||
options={options}
|
||||
value={localInputValue}
|
||||
selectedValue={selectedValue}
|
||||
onChange={(newValue) => {
|
||||
// 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]',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10 h-6 w-6 p-0'
|
||||
onClick={onClear}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? (
|
||||
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
|
||||
) : (
|
||||
<X className='h-4 w-4 opacity-50 hover:opacity-100' />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 && <div className='mb-2 text-red-600 text-sm'>{uploadError}</div>}
|
||||
|
||||
{/* File list with consistent spacing */}
|
||||
{(hasFiles || isUploading) && (
|
||||
{/* File list with consistent spacing - only show for multiple mode or when uploading */}
|
||||
{((hasFiles && multiple) || isUploading) && (
|
||||
<div className={cn('space-y-2', multiple && 'mb-2')}>
|
||||
{/* 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 && (
|
||||
<SingleFileSelector
|
||||
file={filesArray[0]}
|
||||
options={singleFileOptions}
|
||||
selectedValue={selectedFileId}
|
||||
inputValue={inputValue}
|
||||
onInputChange={handleComboboxChange}
|
||||
onClear={(e) => 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 && (
|
||||
<Combobox
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Combobox as EditableCombobox } from '@/components/emcn/components'
|
||||
import { X } from 'lucide-react'
|
||||
import { Button, Combobox as EditableCombobox } from '@/components/emcn/components'
|
||||
import { SubBlockInputController } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller'
|
||||
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
@@ -108,6 +109,20 @@ export function SelectorCombobox({
|
||||
[setStoreValue, onOptionChange, readOnly, disabled]
|
||||
)
|
||||
|
||||
const handleClear = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (readOnly || disabled) return
|
||||
setStoreValue(null)
|
||||
setInputValue('')
|
||||
onOptionChange?.('')
|
||||
},
|
||||
[setStoreValue, onOptionChange, readOnly, disabled]
|
||||
)
|
||||
|
||||
const showClearButton = Boolean(activeValue) && !disabled && !readOnly
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
<SubBlockInputController
|
||||
@@ -119,36 +134,49 @@ export function SelectorCombobox({
|
||||
isPreview={isPreview}
|
||||
>
|
||||
{({ ref, onDrop, onDragOver }) => (
|
||||
<EditableCombobox
|
||||
options={comboboxOptions}
|
||||
value={allowSearch ? inputValue : selectedLabel}
|
||||
selectedValue={activeValue ?? ''}
|
||||
onChange={(newValue) => {
|
||||
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<HTMLInputElement>}
|
||||
inputProps={{
|
||||
onDrop: onDrop as (e: React.DragEvent<HTMLInputElement>) => void,
|
||||
onDragOver: onDragOver as (e: React.DragEvent<HTMLInputElement>) => void,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
error={error instanceof Error ? error.message : null}
|
||||
/>
|
||||
<div className='relative w-full'>
|
||||
<EditableCombobox
|
||||
options={comboboxOptions}
|
||||
value={allowSearch ? inputValue : selectedLabel}
|
||||
selectedValue={activeValue ?? ''}
|
||||
onChange={(newValue) => {
|
||||
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<HTMLInputElement>}
|
||||
inputProps={{
|
||||
onDrop: onDrop as (e: React.DragEvent<HTMLInputElement>) => void,
|
||||
onDragOver: onDragOver as (e: React.DragEvent<HTMLInputElement>) => void,
|
||||
className: showClearButton ? 'pr-[60px]' : undefined,
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
error={error instanceof Error ? error.message : null}
|
||||
/>
|
||||
{showClearButton && (
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
className='-translate-y-1/2 absolute top-1/2 right-[28px] z-10 h-6 w-6 p-0'
|
||||
onClick={handleClear}
|
||||
>
|
||||
<X className='h-4 w-4 opacity-50 hover:opacity-100' />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</SubBlockInputController>
|
||||
</div>
|
||||
|
||||
@@ -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<string, number>()
|
||||
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<string, Set<string>>()
|
||||
|
||||
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<string, string[]>()
|
||||
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<string, string[]>()
|
||||
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<string, typeof block.subBlocks>()
|
||||
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')}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -216,8 +216,8 @@ export const A2ABlock: BlockConfig<A2AResponse> = {
|
||||
config: {
|
||||
tool: (params) => params.operation as string,
|
||||
params: (params) => {
|
||||
const { fileUpload, fileReference, ...rest } = params
|
||||
const normalizedFiles = normalizeFileInput(fileUpload || fileReference || params.files)
|
||||
const { files, ...rest } = params
|
||||
const normalizedFiles = normalizeFileInput(files)
|
||||
return {
|
||||
...rest,
|
||||
...(normalizedFiles && { files: normalizedFiles }),
|
||||
@@ -252,15 +252,7 @@ export const A2ABlock: BlockConfig<A2AResponse> = {
|
||||
},
|
||||
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',
|
||||
|
||||
@@ -94,6 +94,19 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
const {
|
||||
credential,
|
||||
pageId,
|
||||
manualPageId,
|
||||
operation,
|
||||
attachmentFile,
|
||||
attachmentFileName,
|
||||
@@ -272,28 +296,7 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
|
||||
...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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
],
|
||||
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<ConfluenceResponse> = {
|
||||
],
|
||||
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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
const {
|
||||
credential,
|
||||
pageId,
|
||||
manualPageId,
|
||||
operation,
|
||||
attachmentFileUpload,
|
||||
attachmentFileReference,
|
||||
attachmentFile,
|
||||
attachmentFileName,
|
||||
attachmentComment,
|
||||
@@ -875,50 +916,8 @@ export const ConfluenceV2Block: BlockConfig<ConfluenceResponse> = {
|
||||
...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<ConfluenceResponse> = {
|
||||
}
|
||||
|
||||
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<ConfluenceResponse> = {
|
||||
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<ConfluenceResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -584,7 +584,7 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
|
||||
...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<DiscordResponse> = {
|
||||
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.)' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -194,7 +194,8 @@ export const FileV2Block: BlockConfig<FileParserOutput> = {
|
||||
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<FileParserOutput> = {
|
||||
},
|
||||
},
|
||||
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<FileParserV3Output> = {
|
||||
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<FileParserV3Output> = {
|
||||
},
|
||||
},
|
||||
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: {
|
||||
|
||||
@@ -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<FirefliesResponse> = {
|
||||
}
|
||||
|
||||
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<FirefliesResponse> = {
|
||||
...params,
|
||||
audioUrl,
|
||||
audioFile: undefined,
|
||||
audioFileReference: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -643,8 +638,5 @@ export const FirefliesV2Block: BlockConfig<FirefliesResponse> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
...firefliesV2Inputs,
|
||||
audioFileReference: { type: 'json', description: 'Audio/video file reference' },
|
||||
},
|
||||
inputs: firefliesV2Inputs,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, any> = {
|
||||
...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' },
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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<string, any> = {
|
||||
...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<GoogleSlidesResponse> = {
|
||||
@@ -961,8 +956,7 @@ export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
|
||||
}
|
||||
|
||||
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<GoogleSlidesResponse> = {
|
||||
return baseParams({
|
||||
...params,
|
||||
imageUrl,
|
||||
imageFileReference: undefined,
|
||||
imageSource: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -983,8 +975,5 @@ export const GoogleSlidesV2Block: BlockConfig<GoogleSlidesResponse> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
...googleSlidesV2Inputs,
|
||||
imageFileReference: { type: 'json', description: 'Image file reference' },
|
||||
},
|
||||
inputs: googleSlidesV2Inputs,
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
||||
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<JiraResponse> = {
|
||||
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<JiraResponse> = {
|
||||
'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<JiraResponse> = {
|
||||
'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: {
|
||||
|
||||
@@ -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<string, any> = {
|
||||
@@ -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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -84,12 +84,16 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
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<MicrosoftPlannerResponse> = {
|
||||
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<MicrosoftPlannerResponse> = {
|
||||
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<MicrosoftPlannerResponse> = {
|
||||
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<MicrosoftPlannerResponse> = {
|
||||
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<MicrosoftPlannerResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -71,7 +71,7 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'teamId',
|
||||
id: 'teamSelector',
|
||||
title: 'Select Team',
|
||||
type: 'file-selector',
|
||||
canonicalParamId: 'teamId',
|
||||
@@ -92,6 +92,7 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
'list_channel_members',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'manualTeamId',
|
||||
@@ -112,9 +113,10 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
'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<MicrosoftTeamsResponse> = {
|
||||
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<MicrosoftTeamsResponse> = {
|
||||
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<MicrosoftTeamsResponse> = {
|
||||
'list_channel_members',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'manualChannelId',
|
||||
@@ -180,6 +185,7 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
'list_channel_members',
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'messageId',
|
||||
@@ -249,7 +255,7 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
},
|
||||
// Variable reference (advanced mode)
|
||||
{
|
||||
id: 'files',
|
||||
id: 'fileReferences',
|
||||
title: 'File Attachments',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'files',
|
||||
@@ -317,23 +323,19 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
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<string, any> = {
|
||||
...rest,
|
||||
@@ -344,9 +346,9 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
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<MicrosoftTeamsResponse> = {
|
||||
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<MicrosoftTeamsResponse> = {
|
||||
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<MicrosoftTeamsResponse> = {
|
||||
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 <at>userName</at>',
|
||||
@@ -455,8 +438,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -215,8 +215,8 @@ export const MistralParseV2Block: BlockConfig<MistralParserOutput> = {
|
||||
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<MistralParserOutput> = {
|
||||
},
|
||||
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<MistralParserOutput> = {
|
||||
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<MistralParserOutput> = {
|
||||
},
|
||||
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' },
|
||||
|
||||
@@ -140,10 +140,10 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
|
||||
},
|
||||
|
||||
{
|
||||
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<OneDriveResponse> = {
|
||||
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<OneDriveResponse> = {
|
||||
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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
},
|
||||
// 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<OneDriveResponse> = {
|
||||
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<OneDriveResponse> = {
|
||||
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<OneDriveResponse> = {
|
||||
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' },
|
||||
},
|
||||
|
||||
@@ -122,7 +122,7 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
|
||||
},
|
||||
// Variable reference (advanced mode)
|
||||
{
|
||||
id: 'attachments',
|
||||
id: 'attachmentReference',
|
||||
title: 'Attachments',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'attachments',
|
||||
@@ -171,7 +171,7 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
|
||||
},
|
||||
// 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<OutlookResponse> = {
|
||||
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<OutlookResponse> = {
|
||||
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<OutlookResponse> = {
|
||||
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<OutlookResponse> = {
|
||||
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
|
||||
|
||||
@@ -25,6 +25,7 @@ export const PulseBlock: BlockConfig<PulseParserOutput> = {
|
||||
placeholder: 'Upload a document',
|
||||
mode: 'basic',
|
||||
maxSize: 50,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'filePath',
|
||||
@@ -33,6 +34,7 @@ export const PulseBlock: BlockConfig<PulseParserOutput> = {
|
||||
canonicalParamId: 'document',
|
||||
placeholder: 'Document URL',
|
||||
mode: 'advanced',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'pages',
|
||||
@@ -66,18 +68,12 @@ export const PulseBlock: BlockConfig<PulseParserOutput> = {
|
||||
config: {
|
||||
tool: () => 'pulse_parser',
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Pulse API key is required')
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
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<PulseParserOutput> = {
|
||||
},
|
||||
},
|
||||
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<PulseParserOutput> = {
|
||||
},
|
||||
}
|
||||
|
||||
// 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<PulseParserOutput> = {
|
||||
fallbackToolId: 'pulse_parser_v2',
|
||||
}),
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Pulse API key is required')
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export const ReductoBlock: BlockConfig<ReductoParserOutput> = {
|
||||
placeholder: 'Upload a PDF document',
|
||||
mode: 'basic',
|
||||
maxSize: 50,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'filePath',
|
||||
@@ -32,6 +33,7 @@ export const ReductoBlock: BlockConfig<ReductoParserOutput> = {
|
||||
canonicalParamId: 'document',
|
||||
placeholder: 'Document URL',
|
||||
mode: 'advanced',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'pages',
|
||||
@@ -62,18 +64,12 @@ export const ReductoBlock: BlockConfig<ReductoParserOutput> = {
|
||||
config: {
|
||||
tool: () => 'reducto_parser',
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Reducto API key is required')
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
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<ReductoParserOutput> = {
|
||||
},
|
||||
},
|
||||
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<ReductoParserOutput> = {
|
||||
},
|
||||
}
|
||||
|
||||
// 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<ReductoParserOutput> = {
|
||||
fallbackToolId: 'reducto_parser_v2',
|
||||
}),
|
||||
params: (params) => {
|
||||
if (!params || !params.apiKey || params.apiKey.trim() === '') {
|
||||
throw new Error('Reducto API key is required')
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export const S3Block: BlockConfig<S3Response> = {
|
||||
multiple: false,
|
||||
},
|
||||
{
|
||||
id: 'file',
|
||||
id: 'fileReference',
|
||||
title: 'File Reference',
|
||||
type: 'short-input',
|
||||
canonicalParamId: 'file',
|
||||
@@ -216,7 +216,6 @@ export const S3Block: BlockConfig<S3Response> = {
|
||||
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<S3Response> = {
|
||||
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<S3Response> = {
|
||||
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' },
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -223,7 +223,8 @@ export const SftpBlock: BlockConfig<SftpUploadResult> = {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -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<string, any> = {
|
||||
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: {
|
||||
|
||||
@@ -92,6 +92,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
field: 'authMethod',
|
||||
value: 'oauth',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'botToken',
|
||||
@@ -104,6 +105,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
field: 'authMethod',
|
||||
value: 'bot_token',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'channel',
|
||||
@@ -124,6 +126,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
not: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'manualChannel',
|
||||
@@ -142,6 +145,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
not: true,
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'dmUserId',
|
||||
@@ -156,6 +160,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
field: 'destinationType',
|
||||
value: 'dm',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'manualDmUserId',
|
||||
@@ -168,6 +173,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
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<string, any> = {}
|
||||
|
||||
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' },
|
||||
|
||||
@@ -177,7 +177,7 @@ export const SmtpBlock: BlockConfig<SmtpSendMailResult> = {
|
||||
cc: params.cc,
|
||||
bcc: params.bcc,
|
||||
replyTo: params.replyTo,
|
||||
attachments: normalizeFileInput(params.attachmentFiles || params.attachments),
|
||||
attachments: normalizeFileInput(params.attachments),
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -824,8 +824,6 @@ export const SpotifyBlock: BlockConfig<ToolResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -259,8 +259,8 @@ export const SttBlock: BlockConfig<SttBlockResponse> = {
|
||||
}
|
||||
},
|
||||
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<SttBlockResponse> = {
|
||||
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<SttBlockResponse> = {
|
||||
'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<SttBlockResponse> = {
|
||||
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<SttBlockResponse> = {
|
||||
apiKey: params.apiKey,
|
||||
model: params.model,
|
||||
audioFile,
|
||||
audioFileReference: undefined,
|
||||
language: params.language,
|
||||
timestamps: params.timestamps,
|
||||
diarization: params.diarization,
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -269,7 +269,8 @@ export const TelegramBlock: BlockConfig<TelegramResponse> = {
|
||||
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<TelegramResponse> = {
|
||||
}
|
||||
}
|
||||
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<TelegramResponse> = {
|
||||
}
|
||||
}
|
||||
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<TelegramResponse> = {
|
||||
}
|
||||
}
|
||||
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<TelegramResponse> = {
|
||||
}
|
||||
}
|
||||
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<TelegramResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -137,7 +137,8 @@ export const TextractBlock: BlockConfig<TextractParserOutput> = {
|
||||
}
|
||||
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<TextractParserOutput> = {
|
||||
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<TextractParserOutput> = {
|
||||
},
|
||||
}
|
||||
|
||||
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<TextractParserOutput> = {
|
||||
}
|
||||
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')
|
||||
}
|
||||
|
||||
@@ -691,6 +691,7 @@ export const VideoGeneratorV2Block: BlockConfig<VideoBlockResponse> = {
|
||||
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<VideoBlockResponse> = {
|
||||
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<VideoBlockResponse> = {
|
||||
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)',
|
||||
|
||||
@@ -91,7 +91,6 @@ export const VisionBlock: BlockConfig<VisionResponse> = {
|
||||
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<VisionResponse> = {
|
||||
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<VisionResponse> = {
|
||||
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' },
|
||||
},
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -40,7 +40,7 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'siteId',
|
||||
id: 'siteSelector',
|
||||
title: 'Site',
|
||||
type: 'project-selector',
|
||||
canonicalParamId: 'siteId',
|
||||
@@ -60,13 +60,13 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
|
||||
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<WebflowResponse> = {
|
||||
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<WebflowResponse> = {
|
||||
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<WebflowResponse> = {
|
||||
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<WebflowResponse> = {
|
||||
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<WebflowResponse> = {
|
||||
}
|
||||
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<WebflowResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -768,9 +768,10 @@ export const WordPressBlock: BlockConfig<WordPressResponse> = {
|
||||
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<WordPressResponse> = {
|
||||
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' },
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -47,7 +47,7 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'chatId',
|
||||
id: 'triggerChatId',
|
||||
title: 'Chat ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter chat ID',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -30,7 +30,7 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'siteId',
|
||||
id: 'triggerSiteId',
|
||||
title: 'Site',
|
||||
type: 'dropdown',
|
||||
placeholder: 'Select a site',
|
||||
|
||||
Reference in New Issue
Block a user