-
Example Command
+
+ {showLabel &&
Example }
+
+ setMode('sync')}
+ className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${
+ mode === 'sync'
+ ? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground'
+ : ''
+ }`}
+ >
+ Sync
+
+ setMode('async')}
+ className={`h-6 min-w-[50px] px-2 py-1 text-xs transition-none ${
+ mode === 'async'
+ ? 'border-primary bg-primary text-primary-foreground hover:border-primary hover:bg-primary hover:text-primary-foreground'
+ : ''
+ }`}
+ >
+ Async
+
+
+
+
+ {getExampleTitle()}
+
+
+
+
+ setExampleType('execute')}
+ >
+ Async Execution
+
+ setExampleType('status')}>
+ Check Job Status
+
+ setExampleType('rate-limits')}
+ >
+ Rate Limits & Usage
+
+
+
- )}
-
-
- {formatCurlCommand(command, apiKey)}
+
+
+
+
+ {getDisplayCommand()}
-
+
)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx
index f0fd1bb0db..2713e84178 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx
@@ -22,15 +22,18 @@ import { ExampleCommand } from '@/app/workspace/[workspaceId]/w/[workflowId]/com
import type { WorkflowState } from '@/stores/workflows/workflow/types'
import { DeployedWorkflowModal } from '../../../deployment-controls/components/deployed-workflow-modal'
+interface WorkflowDeploymentInfo {
+ isDeployed: boolean
+ deployedAt?: string
+ apiKey: string
+ endpoint: string
+ exampleCommand: string
+ needsRedeployment: boolean
+}
+
interface DeploymentInfoProps {
- isLoading?: boolean
- deploymentInfo: {
- deployedAt?: string
- apiKey: string
- endpoint: string
- exampleCommand: string
- needsRedeployment: boolean
- } | null
+ isLoading: boolean
+ deploymentInfo: WorkflowDeploymentInfo | null
onRedeploy: () => void
onUndeploy: () => void
isSubmitting: boolean
@@ -38,6 +41,7 @@ interface DeploymentInfoProps {
workflowId: string | null
deployedState: WorkflowState
isLoadingDeployedState: boolean
+ getInputFormatExample?: () => string
}
export function DeploymentInfo({
@@ -49,6 +53,8 @@ export function DeploymentInfo({
isUndeploying,
workflowId,
deployedState,
+ isLoadingDeployedState,
+ getInputFormatExample,
}: DeploymentInfoProps) {
const [isViewingDeployed, setIsViewingDeployed] = useState(false)
@@ -103,7 +109,12 @@ export function DeploymentInfo({
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/deploy-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/deploy-modal.tsx
index f69d8f9bc0..30238ca624 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/deploy-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/deploy-modal.tsx
@@ -583,6 +583,7 @@ export function DeployModal({
workflowId={workflowId}
deployedState={deployedState}
isLoadingDeployedState={isLoadingDeployedState}
+ getInputFormatExample={getInputFormatExample}
/>
) : (
<>
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/channel-selector-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/channel-selector-input.tsx
index 63481121f7..12754b70d7 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/channel-selector-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/channel-selector/channel-selector-input.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import type { SubBlockConfig } from '@/blocks/types'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
+import { useSubBlockValue } from '../../hooks/use-sub-block-value'
import { type SlackChannelInfo, SlackChannelSelector } from './components/slack-channel-selector'
interface ChannelSelectorInputProps {
@@ -25,7 +26,10 @@ export function ChannelSelectorInput({
isPreview = false,
previewValue,
}: ChannelSelectorInputProps) {
- const { getValue, setValue } = useSubBlockStore()
+ const { getValue } = useSubBlockStore()
+
+ // Use the proper hook to get the current value and setter (same as file-selector)
+ const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
const [selectedChannelId, setSelectedChannelId] = useState
('')
const [_channelInfo, setChannelInfo] = useState(null)
@@ -47,9 +51,9 @@ export function ChannelSelectorInput({
}
// Use preview value when in preview mode, otherwise use store value
- const value = isPreview ? previewValue : getValue(blockId, subBlock.id)
+ const value = isPreview ? previewValue : storeValue
- // Get the current value from the store or prop value if in preview mode
+ // Get the current value from the store or prop value if in preview mode (same pattern as file-selector)
useEffect(() => {
if (isPreview && previewValue !== undefined) {
const value = previewValue
@@ -64,12 +68,12 @@ export function ChannelSelectorInput({
}
}, [blockId, subBlock.id, getValue, isPreview, previewValue])
- // Handle channel selection
+ // Handle channel selection (same pattern as file-selector)
const handleChannelChange = (channelId: string, info?: SlackChannelInfo) => {
setSelectedChannelId(channelId)
setChannelInfo(info || null)
if (!isPreview) {
- setValue(blockId, subBlock.id, channelId)
+ setStoreValue(channelId)
}
onChannelSelect?.(channelId)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
index 89c196ae02..8471516528 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx
@@ -107,15 +107,15 @@ const SCOPE_DESCRIPTIONS: Record = {
'guilds.members.read': 'Read your Discord guild members',
read: 'Read access to your workspace',
write: 'Write access to your Linear workspace',
- 'channels:read': 'Read your Slack channels',
- 'groups:read': 'Read your Slack private channels',
- 'chat:write': 'Write to your invited Slack channels',
- 'chat:write.public': 'Write to your public Slack channels',
- 'users:read': 'Read your Slack users',
- 'search:read': 'Read your Slack search',
- 'files:read': 'Read your Slack files',
- 'links:read': 'Read your Slack links',
- 'links:write': 'Write to your Slack links',
+ 'channels:read': 'View public channels',
+ 'channels:history': 'Read channel messages',
+ 'groups:read': 'View private channels',
+ 'groups:history': 'Read private messages',
+ 'chat:write': 'Send messages',
+ 'chat:write.public': 'Post to public channels',
+ 'users:read': 'View workspace users',
+ 'files:write': 'Upload files',
+ 'canvases:write': 'Create canvas documents',
}
// Convert OAuth scope to user-friendly description
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-selector/document-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-selector/document-selector.tsx
index 6ef02537c0..751d6d3e01 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-selector/document-selector.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/document-selector/document-selector.tsx
@@ -1,7 +1,7 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
-import { Check, ChevronDown, FileText } from 'lucide-react'
+import { Check, ChevronDown, FileText, RefreshCw } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Command,
@@ -54,6 +54,7 @@ export function DocumentSelector({
const [error, setError] = useState(null)
const [open, setOpen] = useState(false)
const [selectedDocument, setSelectedDocument] = useState(null)
+ const [loading, setLoading] = useState(false)
// Use the proper hook to get the current value and setter
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
@@ -72,6 +73,7 @@ export function DocumentSelector({
return
}
+ setLoading(true)
setError(null)
try {
@@ -93,6 +95,8 @@ export function DocumentSelector({
if ((err as Error).name === 'AbortError') return
setError((err as Error).message)
setDocuments([])
+ } finally {
+ setLoading(false)
}
}, [knowledgeBaseId])
@@ -192,7 +196,12 @@ export function DocumentSelector({
- {error ? (
+ {loading ? (
+
+
+ Loading documents...
+
+ ) : error ? (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx
index 8be4f04c64..b23445907a 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/dropdown.tsx
@@ -1,18 +1,19 @@
-import { useEffect, useMemo, useState } from 'react'
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select'
+import { useEffect, useMemo, useRef, useState } from 'react'
+import { Check, ChevronDown } from 'lucide-react'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { cn } from '@/lib/utils'
import { ResponseBlockHandler } from '@/executor/handlers/response/response-handler'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
interface DropdownProps {
options:
- | Array
- | (() => Array)
+ | Array<
+ string | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
+ >
+ | (() => Array<
+ string | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
+ >)
defaultValue?: string
blockId: string
subBlockId: string
@@ -20,6 +21,7 @@ interface DropdownProps {
isPreview?: boolean
previewValue?: string | null
disabled?: boolean
+ placeholder?: string
}
export function Dropdown({
@@ -31,9 +33,15 @@ export function Dropdown({
isPreview = false,
previewValue,
disabled,
+ placeholder = 'Select an option...',
}: DropdownProps) {
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
const [storeInitialized, setStoreInitialized] = useState(false)
+ const [open, setOpen] = useState(false)
+ const [highlightedIndex, setHighlightedIndex] = useState(-1)
+
+ const inputRef = useRef(null)
+ const dropdownRef = useRef(null)
// For response dataMode conversion - get builderData and data sub-blocks
const [builderData] = useSubBlockValue(blockId, 'builderData')
@@ -47,11 +55,19 @@ export function Dropdown({
return typeof options === 'function' ? options() : options
}, [options])
- const getOptionValue = (option: string | { label: string; id: string }) => {
+ const getOptionValue = (
+ option:
+ | string
+ | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
+ ) => {
return typeof option === 'string' ? option : option.id
}
- const getOptionLabel = (option: string | { label: string; id: string }) => {
+ const getOptionLabel = (
+ option:
+ | string
+ | { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
+ ) => {
return typeof option === 'string' ? option : option.label
}
@@ -85,67 +101,234 @@ export function Dropdown({
}
}, [storeInitialized, value, defaultOptionValue, setStoreValue])
- // Calculate the effective value to use in the dropdown
- const effectiveValue = useMemo(() => {
- // If we have a value from the store, use that
- if (value !== null && value !== undefined) {
- return value
+ // Event handlers
+ const handleSelect = (selectedValue: string) => {
+ if (!isPreview && !disabled) {
+ // Handle conversion when switching from Builder to Editor mode in response blocks
+ if (
+ subBlockId === 'dataMode' &&
+ storeValue === 'structured' &&
+ selectedValue === 'json' &&
+ builderData &&
+ Array.isArray(builderData) &&
+ builderData.length > 0
+ ) {
+ // Convert builderData to JSON string for editor mode
+ const jsonString = ResponseBlockHandler.convertBuilderDataToJsonString(builderData)
+ setData(jsonString)
+ }
+
+ setStoreValue(selectedValue)
}
+ setOpen(false)
+ setHighlightedIndex(-1)
+ inputRef.current?.blur()
+ }
- // Only return defaultOptionValue if store is initialized
- if (storeInitialized) {
- return defaultOptionValue
+ const handleDropdownClick = (e: React.MouseEvent) => {
+ e.preventDefault()
+ e.stopPropagation()
+ if (!disabled) {
+ setOpen(!open)
+ if (!open) {
+ inputRef.current?.focus()
+ }
}
+ }
- // While store is loading, don't use any value
- return undefined
- }, [value, defaultOptionValue, storeInitialized])
+ const handleFocus = () => {
+ setOpen(true)
+ setHighlightedIndex(-1)
+ }
- // Handle the case where evaluatedOptions changes and the current selection is no longer valid
- const isValueInOptions = useMemo(() => {
- if (!effectiveValue || evaluatedOptions.length === 0) return false
- return evaluatedOptions.some((opt) => getOptionValue(opt) === effectiveValue)
- }, [effectiveValue, evaluatedOptions, getOptionValue])
+ const handleBlur = () => {
+ // Delay closing to allow dropdown selection
+ setTimeout(() => {
+ const activeElement = document.activeElement
+ if (!activeElement || !activeElement.closest('.absolute.top-full')) {
+ setOpen(false)
+ setHighlightedIndex(-1)
+ }
+ }, 150)
+ }
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ setOpen(false)
+ setHighlightedIndex(-1)
+ return
+ }
+
+ if (e.key === 'ArrowDown') {
+ e.preventDefault()
+ if (!open) {
+ setOpen(true)
+ setHighlightedIndex(0)
+ } else {
+ setHighlightedIndex((prev) => (prev < evaluatedOptions.length - 1 ? prev + 1 : 0))
+ }
+ }
+
+ if (e.key === 'ArrowUp') {
+ e.preventDefault()
+ if (open) {
+ setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : evaluatedOptions.length - 1))
+ }
+ }
+
+ if (e.key === 'Enter' && open && highlightedIndex >= 0) {
+ e.preventDefault()
+ const selectedOption = evaluatedOptions[highlightedIndex]
+ if (selectedOption) {
+ handleSelect(getOptionValue(selectedOption))
+ }
+ }
+ }
+
+ // Effects
+ useEffect(() => {
+ setHighlightedIndex((prev) => {
+ if (prev >= 0 && prev < evaluatedOptions.length) {
+ return prev
+ }
+ return -1
+ })
+ }, [evaluatedOptions])
+
+ // Scroll highlighted option into view
+ useEffect(() => {
+ if (highlightedIndex >= 0 && dropdownRef.current) {
+ const highlightedElement = dropdownRef.current.querySelector(
+ `[data-option-index="${highlightedIndex}"]`
+ )
+ if (highlightedElement) {
+ highlightedElement.scrollIntoView({
+ behavior: 'smooth',
+ block: 'nearest',
+ })
+ }
+ }
+ }, [highlightedIndex])
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ const target = event.target as Element
+ if (
+ inputRef.current &&
+ !inputRef.current.contains(target) &&
+ !target.closest('.absolute.top-full')
+ ) {
+ setOpen(false)
+ setHighlightedIndex(-1)
+ }
+ }
+
+ if (open) {
+ document.addEventListener('mousedown', handleClickOutside)
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside)
+ }
+ }
+ }, [open])
+
+ // Display value
+ const displayValue = value?.toString() ?? ''
+ const selectedOption = evaluatedOptions.find((opt) => getOptionValue(opt) === value)
+ const selectedLabel = selectedOption ? getOptionLabel(selectedOption) : displayValue
+ const SelectedIcon =
+ selectedOption && typeof selectedOption === 'object' && 'icon' in selectedOption
+ ? (selectedOption.icon as React.ComponentType<{ className?: string }>)
+ : null
+
+ // Render component
return (
- {
- // Only update store when not in preview mode and not disabled
- if (!isPreview && !disabled) {
- // Handle conversion when switching from Builder to Editor mode in response blocks
- if (
- subBlockId === 'dataMode' &&
- storeValue === 'structured' &&
- newValue === 'json' &&
- builderData &&
- Array.isArray(builderData) &&
- builderData.length > 0
- ) {
- // Convert builderData to JSON string for editor mode
- const jsonString = ResponseBlockHandler.convertBuilderDataToJsonString(builderData)
- setData(jsonString)
- }
-
- setStoreValue(newValue)
- }
- }}
- disabled={isPreview || disabled}
- >
-
-
-
-
- {evaluatedOptions.map((option) => (
-
- {getOptionLabel(option)}
-
- ))}
-
-
+
+
+
+ {/* Icon overlay */}
+ {SelectedIcon && (
+
+
+
+ )}
+ {/* Chevron button */}
+
+
+
+
+
+ {/* Dropdown */}
+ {open && (
+
+
+
+ {evaluatedOptions.length === 0 ? (
+
+ No options available.
+
+ ) : (
+ evaluatedOptions.map((option, index) => {
+ const optionValue = getOptionValue(option)
+ const optionLabel = getOptionLabel(option)
+ const OptionIcon =
+ typeof option === 'object' && 'icon' in option
+ ? (option.icon as React.ComponentType<{ className?: string }>)
+ : null
+ const isSelected = value === optionValue
+ const isHighlighted = index === highlightedIndex
+
+ return (
+
handleSelect(optionValue)}
+ onMouseDown={(e) => {
+ e.preventDefault()
+ handleSelect(optionValue)
+ }}
+ onMouseEnter={() => setHighlightedIndex(index)}
+ className={cn(
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground',
+ isHighlighted && 'bg-accent text-accent-foreground'
+ )}
+ >
+ {OptionIcon && }
+ {optionLabel}
+ {isSelected && }
+
+ )
+ })
+ )}
+
+
+
+ )}
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx
index c0f0a43c96..be4cd80dba 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/components/schedule-modal.tsx
@@ -1,5 +1,4 @@
import { useEffect, useState } from 'react'
-import { format } from 'date-fns'
import { Trash2, X } from 'lucide-react'
import { Alert, AlertDescription } from '@/components/ui/alert'
import {
@@ -13,10 +12,8 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
-import { Calendar as CalendarComponent } from '@/components/ui/calendar'
import { DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
-import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import {
Select,
SelectContent,
@@ -54,8 +51,6 @@ export function ScheduleModal({
}: ScheduleModalProps) {
// States for schedule configuration
const [scheduleType, setScheduleType] = useSubBlockValue(blockId, 'scheduleType')
- const [scheduleStartAt, setScheduleStartAt] = useSubBlockValue(blockId, 'scheduleStartAt')
- const [scheduleTime, setScheduleTime] = useSubBlockValue(blockId, 'scheduleTime')
const [minutesInterval, setMinutesInterval] = useSubBlockValue(blockId, 'minutesInterval')
const [hourlyMinute, setHourlyMinute] = useSubBlockValue(blockId, 'hourlyMinute')
const [dailyTime, setDailyTime] = useSubBlockValue(blockId, 'dailyTime')
@@ -86,8 +81,6 @@ export function ScheduleModal({
// Capture all current values when modal opens
const currentValues = {
scheduleType: scheduleType || 'daily',
- scheduleStartAt: scheduleStartAt || '',
- scheduleTime: scheduleTime || '',
minutesInterval: minutesInterval || '',
hourlyMinute: hourlyMinute || '',
dailyTime: dailyTime || '',
@@ -111,8 +104,6 @@ export function ScheduleModal({
const currentValues = {
scheduleType: scheduleType || 'daily',
- scheduleStartAt: scheduleStartAt || '',
- scheduleTime: scheduleTime || '',
minutesInterval: minutesInterval || '',
hourlyMinute: hourlyMinute || '',
dailyTime: dailyTime || '',
@@ -160,8 +151,6 @@ export function ScheduleModal({
isOpen,
scheduleId,
scheduleType,
- scheduleStartAt,
- scheduleTime,
minutesInterval,
hourlyMinute,
dailyTime,
@@ -188,8 +177,6 @@ export function ScheduleModal({
// Revert form values to initial values
if (hasChanges) {
setScheduleType(initialValues.scheduleType)
- setScheduleStartAt(initialValues.scheduleStartAt)
- setScheduleTime(initialValues.scheduleTime)
setMinutesInterval(initialValues.minutesInterval)
setHourlyMinute(initialValues.hourlyMinute)
setDailyTime(initialValues.dailyTime)
@@ -279,8 +266,6 @@ export function ScheduleModal({
// Update initial values to match current state
const updatedValues = {
scheduleType: scheduleType || 'daily',
- scheduleStartAt: scheduleStartAt || '',
- scheduleTime: scheduleTime || '',
minutesInterval: minutesInterval || '',
hourlyMinute: hourlyMinute || '',
dailyTime: dailyTime || '',
@@ -329,15 +314,6 @@ export function ScheduleModal({
setShowDeleteConfirm(true)
}
- // Helper to format a date for display
- const formatDate = (date: string) => {
- try {
- return date ? format(new Date(date), 'PPP') : 'Select date'
- } catch (_e) {
- return 'Select date'
- }
- }
-
return (
<>
@@ -359,46 +335,6 @@ export function ScheduleModal({
)}
- {/* Common date and time fields */}
-
-
-
- Start At
-
-
-
-
- {formatDate(scheduleStartAt || '')}
-
-
-
- setScheduleStartAt(date ? date.toISOString() : '')}
- initialFocus
- />
-
-
-
-
-
-
- Time
-
-
-
-
-
{/* Frequency selector */}
@@ -469,7 +405,7 @@ export function ScheduleModal({
)}
{/* Daily schedule options */}
- {scheduleType === 'daily' && (
+ {(scheduleType === 'daily' || !scheduleType) && (
Time of Day
@@ -578,29 +514,31 @@ export function ScheduleModal({
)}
- {/* Timezone configuration */}
-
-
- Timezone
-
- setTimezone(value)}>
-
-
-
-
- UTC
- US Eastern (UTC-4)
- US Central (UTC-5)
- US Mountain (UTC-6)
- US Pacific (UTC-7)
- London (UTC+1)
- Paris (UTC+2)
- Singapore (UTC+8)
- Tokyo (UTC+9)
- Sydney (UTC+10)
-
-
-
+ {/* Timezone configuration - only show for time-specific schedules */}
+ {scheduleType !== 'minutes' && scheduleType !== 'hourly' && (
+
+
+ Timezone
+
+ setTimezone(value)}>
+
+
+
+
+ UTC
+ US Eastern (UTC-4)
+ US Central (UTC-5)
+ US Mountain (UTC-6)
+ US Pacific (UTC-7)
+ London (UTC+1)
+ Paris (UTC+2)
+ Singapore (UTC+8)
+ Tokyo (UTC+9)
+ Sydney (UTC+10)
+
+
+
+ )}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx
index 65870360d8..33d9b1c192 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/schedule/schedule-config.tsx
@@ -6,7 +6,7 @@ import { Dialog } from '@/components/ui/dialog'
import { createLogger } from '@/lib/logs/console-logger'
import { parseCronToHumanReadable } from '@/lib/schedules/utils'
import { formatDateTime } from '@/lib/utils'
-import { getWorkflowWithValues } from '@/stores/workflows'
+import { getBlockWithValues, getWorkflowWithValues } from '@/stores/workflows'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -49,7 +49,6 @@ export function ScheduleConfig({
const workflowId = params.workflowId as string
// Get workflow state from store
- const setScheduleStatus = useWorkflowStore((state) => state.setScheduleStatus)
// Get the schedule type from the block state
const [scheduleType] = useSubBlockValue(blockId, 'scheduleType')
@@ -58,12 +57,25 @@ export function ScheduleConfig({
// and expose the setter so we can update it
const [_startWorkflow, setStartWorkflow] = useSubBlockValue(blockId, 'startWorkflow')
+ // Determine if this is a schedule trigger block vs starter block
+ const blockWithValues = getBlockWithValues(blockId)
+ const isScheduleTriggerBlock = blockWithValues?.type === 'schedule'
+
// Function to check if schedule exists in the database
const checkSchedule = async () => {
setIsLoading(true)
try {
// Check if there's a schedule for this workflow, passing the mode parameter
- const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, {
+ // For schedule trigger blocks, include blockId to get the specific schedule
+ const url = new URL('/api/schedules', window.location.origin)
+ url.searchParams.set('workflowId', workflowId)
+ url.searchParams.set('mode', 'schedule')
+
+ if (isScheduleTriggerBlock) {
+ url.searchParams.set('blockId', blockId)
+ }
+
+ const response = await fetch(url.toString(), {
// Add cache: 'no-store' to prevent caching of this request
cache: 'no-store',
headers: {
@@ -82,16 +94,15 @@ export function ScheduleConfig({
setCronExpression(data.schedule.cronExpression)
setTimezone(data.schedule.timezone || 'UTC')
- // Set active schedule flag to true since we found an active schedule
- setScheduleStatus(true)
+ // Note: We no longer set global schedule status from individual components
+ // The global schedule status should be managed by a higher-level component
} else {
setScheduleId(null)
setNextRunAt(null)
setLastRanAt(null)
setCronExpression(null)
- // Set active schedule flag to false since no schedule was found
- setScheduleStatus(false)
+ // Note: We no longer set global schedule status from individual components
}
}
} catch (error) {
@@ -104,9 +115,8 @@ export function ScheduleConfig({
// Check for schedule on mount and when relevant dependencies change
useEffect(() => {
- // Only check for schedules when workflowId changes or modal opens
- // Avoid checking on every scheduleType change to prevent excessive API calls
- if (workflowId && (isModalOpen || refreshCounter > 0)) {
+ // Check for schedules when workflowId changes, modal opens, or on initial mount
+ if (workflowId) {
checkSchedule()
}
@@ -160,23 +170,33 @@ export function ScheduleConfig({
setError(null)
try {
- // 1. First, update the startWorkflow value in SubBlock store to 'schedule'
- setStartWorkflow('schedule')
+ // For starter blocks, update the startWorkflow value to 'schedule'
+ // For schedule trigger blocks, skip this step as startWorkflow is not needed
+ if (!isScheduleTriggerBlock) {
+ // 1. First, update the startWorkflow value in SubBlock store to 'schedule'
+ setStartWorkflow('schedule')
+
+ // 2. Directly access and modify the SubBlock store to guarantee the value is set
+ const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
+ if (!activeWorkflowId) {
+ setError('No active workflow found')
+ return false
+ }
+
+ // Update the SubBlock store directly to ensure the value is set correctly
+ const subBlockStore = useSubBlockStore.getState()
+ subBlockStore.setValue(blockId, 'startWorkflow', 'schedule')
+
+ // Give React time to process the state update
+ await new Promise((resolve) => setTimeout(resolve, 200))
+ }
- // 2. Directly access and modify the SubBlock store to guarantee the value is set
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (!activeWorkflowId) {
setError('No active workflow found')
return false
}
- // Update the SubBlock store directly to ensure the value is set correctly
- const subBlockStore = useSubBlockStore.getState()
- subBlockStore.setValue(blockId, 'startWorkflow', 'schedule')
-
- // Give React time to process the state update
- await new Promise((resolve) => setTimeout(resolve, 200))
-
// 3. Get the fully merged current state with updated values
// This ensures we send the complete, correct workflow state to the backend
const currentWorkflowWithValues = getWorkflowWithValues(activeWorkflowId)
@@ -188,15 +208,24 @@ export function ScheduleConfig({
// 4. Make a direct API call instead of relying on sync
// This gives us more control and better error handling
logger.debug('Making direct API call to save schedule with complete state')
+
+ // Prepare the request body
+ const requestBody: any = {
+ workflowId,
+ state: currentWorkflowWithValues.state,
+ }
+
+ // For schedule trigger blocks, include the blockId
+ if (isScheduleTriggerBlock) {
+ requestBody.blockId = blockId
+ }
+
const response = await fetch('/api/schedules', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify({
- workflowId,
- state: currentWorkflowWithValues.state,
- }),
+ body: JSON.stringify(requestBody),
})
// Parse the response
@@ -230,7 +259,7 @@ export function ScheduleConfig({
}
// 6. Update the schedule status and trigger a workflow update
- setScheduleStatus(true)
+ // Note: Global schedule status is managed at a higher level
// 7. Tell the workflow store that the state has been saved
const workflowStore = useWorkflowStore.getState()
@@ -262,24 +291,28 @@ export function ScheduleConfig({
setIsDeleting(true)
try {
- // 1. First update the workflow state to disable scheduling
- setStartWorkflow('manual')
-
- // 2. Directly update the SubBlock store to ensure the value is set
- const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
- if (!activeWorkflowId) {
- setError('No active workflow found')
- return false
- }
+ // For starter blocks, update the startWorkflow value to 'manual'
+ // For schedule trigger blocks, skip this step as startWorkflow is not relevant
+ if (!isScheduleTriggerBlock) {
+ // 1. First update the workflow state to disable scheduling
+ setStartWorkflow('manual')
+
+ // 2. Directly update the SubBlock store to ensure the value is set
+ const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
+ if (!activeWorkflowId) {
+ setError('No active workflow found')
+ return false
+ }
- // Update the store directly
- const subBlockStore = useSubBlockStore.getState()
- subBlockStore.setValue(blockId, 'startWorkflow', 'manual')
+ // Update the store directly
+ const subBlockStore = useSubBlockStore.getState()
+ subBlockStore.setValue(blockId, 'startWorkflow', 'manual')
- // 3. Update the workflow store
- const workflowStore = useWorkflowStore.getState()
- workflowStore.triggerUpdate()
- workflowStore.updateLastSaved()
+ // 3. Update the workflow store
+ const workflowStore = useWorkflowStore.getState()
+ workflowStore.triggerUpdate()
+ workflowStore.updateLastSaved()
+ }
// 4. Make the DELETE API call to remove the schedule
const response = await fetch(`/api/schedules/${scheduleId}`, {
@@ -299,7 +332,7 @@ export function ScheduleConfig({
setCronExpression(null)
// 6. Update schedule status and refresh UI
- setScheduleStatus(false)
+ // Note: Global schedule status is managed at a higher level
setRefreshCounter((prev) => prev + 1)
return true
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx
index 28a866ff07..5331483e14 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/tool-input.tsx
@@ -17,7 +17,6 @@ import { cn } from '@/lib/utils'
import { getAllBlocks } from '@/blocks'
import { getProviderFromModel, supportsToolUsageControl } from '@/providers/utils'
import { useCustomToolsStore } from '@/stores/custom-tools/store'
-import { useGeneralStore } from '@/stores/settings/general/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import {
@@ -400,7 +399,6 @@ export function ToolInput({
const isWide = useWorkflowStore((state) => state.blocks[blockId]?.isWide)
const customTools = useCustomToolsStore((state) => state.getAllTools())
const subBlockStore = useSubBlockStore()
- const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
// Get the current model from the 'model' subblock
const modelValue = useSubBlockStore.getState().getValue(blockId, 'model')
@@ -507,26 +505,13 @@ export function ToolInput({
return block.tools.access[0]
}
- // Initialize tool parameters with auto-fill if enabled
+ // Initialize tool parameters - no autofill, just return empty params
const initializeToolParams = (
toolId: string,
params: ToolParameterConfig[],
instanceId?: string
): Record => {
- const initialParams: Record = {}
-
- // Only auto-fill parameters if the setting is enabled
- if (isAutoFillEnvVarsEnabled) {
- // For each parameter, check if we have a stored/resolved value
- params.forEach((param) => {
- const resolvedValue = subBlockStore.resolveToolParamValue(toolId, param.id, instanceId)
- if (resolvedValue) {
- initialParams[param.id] = resolvedValue
- }
- })
- }
-
- return initialParams
+ return {}
}
const handleSelectTool = (toolBlock: (typeof toolBlocks)[0]) => {
@@ -682,11 +667,6 @@ export function ToolInput({
const tool = selectedTools[toolIndex]
- // Store the value in the tool params store for future use
- if (paramValue.trim()) {
- subBlockStore.setToolParam(tool.toolId, paramId, paramValue)
- }
-
// Update the value in the workflow
setStoreValue(
selectedTools.map((tool, index) =>
@@ -1026,9 +1006,9 @@ export function ToolInput({
case 'channel-selector':
return (
*
)}
- {!param.required && (
+ {(!param.required || param.visibility !== 'user-only') && (
(Optional)
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx
index 825db4c761..2de92c4a9b 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/webhook.tsx
@@ -14,7 +14,6 @@ import {
import { Button } from '@/components/ui/button'
import { createLogger } from '@/lib/logs/console-logger'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
-import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
import { ToolCredentialSelector } from '../tool-input/components/tool-credential-selector'
import { WebhookModal } from './components/webhook-modal'
@@ -314,8 +313,7 @@ export function WebhookConfig({
const [isLoading, setIsLoading] = useState(false)
const [gmailCredentialId, setGmailCredentialId] = useState('')
- // Get workflow store function to update webhook status
- const setWebhookStatus = useWorkflowStore((state) => state.setWebhookStatus)
+ // No need to manage webhook status separately - it's determined by having provider + path
// Get the webhook provider from the block state
const [storeWebhookProvider, setWebhookProvider] = useSubBlockValue(blockId, 'webhookProvider')
@@ -323,6 +321,9 @@ export function WebhookConfig({
// Store the webhook path
const [storeWebhookPath, setWebhookPath] = useSubBlockValue(blockId, 'webhookPath')
+ // Don't auto-generate webhook paths - only create them when user actually configures a webhook
+ // This prevents the "Active Webhook" badge from showing on unconfigured blocks
+
// Store provider-specific configuration
const [storeProviderConfig, setProviderConfig] = useSubBlockValue(blockId, 'providerConfig')
@@ -331,16 +332,132 @@ export function WebhookConfig({
const webhookPath = propValue?.webhookPath ?? storeWebhookPath
const providerConfig = propValue?.providerConfig ?? storeProviderConfig
- // Reset provider config when provider changes
+ // Store the actual provider from the database
+ const [actualProvider, setActualProvider] = useState(null)
+
+ // Track the previous provider to detect changes
+ const [previousProvider, setPreviousProvider] = useState(null)
+
+ // Handle provider changes - clear webhook data when switching providers
+ useEffect(() => {
+ // Skip on initial load or if no provider is set
+ if (!webhookProvider || !previousProvider) {
+ setPreviousProvider(webhookProvider)
+ return
+ }
+
+ // If the provider has changed, clear all webhook-related data
+ if (webhookProvider !== previousProvider) {
+ // IMPORTANT: Store the current webhook ID BEFORE clearing it
+ const currentWebhookId = webhookId
+
+ logger.info('Webhook provider changed, clearing webhook data', {
+ from: previousProvider,
+ to: webhookProvider,
+ blockId,
+ webhookId: currentWebhookId,
+ })
+
+ // If there's an existing webhook, delete it from the database
+ const deleteExistingWebhook = async () => {
+ if (currentWebhookId && !isPreview) {
+ try {
+ logger.info('Deleting existing webhook due to provider change', {
+ webhookId: currentWebhookId,
+ oldProvider: previousProvider,
+ newProvider: webhookProvider,
+ })
+
+ const response = await fetch(`/api/webhooks/${currentWebhookId}`, {
+ method: 'DELETE',
+ })
+
+ if (!response.ok) {
+ const errorData = await response.json()
+ logger.error('Failed to delete existing webhook', {
+ webhookId: currentWebhookId,
+ error: errorData.error,
+ })
+ } else {
+ logger.info('Successfully deleted existing webhook', { webhookId: currentWebhookId })
+
+ const store = useSubBlockStore.getState()
+ const workflowValues = store.workflowValues[workflowId] || {}
+ const blockValues = { ...workflowValues[blockId] }
+
+ // Clear webhook-related fields
+ blockValues.webhookPath = undefined
+ blockValues.providerConfig = undefined
+
+ // Update the store with the cleaned block values
+ useSubBlockStore.setState({
+ workflowValues: {
+ ...workflowValues,
+ [workflowId]: {
+ ...workflowValues,
+ [blockId]: blockValues,
+ },
+ },
+ })
+
+ logger.info('Cleared webhook data from store after successful deletion', { blockId })
+ }
+ } catch (error: any) {
+ logger.error('Error deleting existing webhook', {
+ webhookId: currentWebhookId,
+ error: error.message,
+ })
+ }
+ }
+ }
+
+ // Clear webhook fields FIRST to make badge disappear immediately
+ // Then delete from database to prevent the webhook check useEffect from restoring the path
+
+ // IMPORTANT: Clear webhook connection data FIRST
+ // This prevents the webhook check useEffect from finding and restoring the webhook
+ setWebhookId(null)
+ setActualProvider(null)
+
+ // Clear provider config
+ setProviderConfig({})
+
+ // Clear component state
+ setError(null)
+ setGmailCredentialId('')
+
+ // Note: Store will be cleared AFTER successful database deletion
+ // This ensures store and database stay perfectly in sync
+
+ // Update previous provider to the new provider
+ setPreviousProvider(webhookProvider)
+
+ // Delete existing webhook AFTER clearing the path to prevent race condition
+ // The webhook check useEffect won't restore the path if we clear it first
+ // Execute deletion asynchronously but don't block the UI
+
+ ;(async () => {
+ await deleteExistingWebhook()
+ })()
+ }
+ }, [webhookProvider, previousProvider, blockId, webhookId, isPreview])
+
+ // Reset provider config when provider changes (legacy effect - keeping for safety)
useEffect(() => {
if (webhookProvider) {
// Reset the provider config when the provider changes
setProviderConfig({})
- }
- }, [webhookProvider, setProviderConfig])
- // Store the actual provider from the database
- const [actualProvider, setActualProvider] = useState(null)
+ // Clear webhook ID and actual provider when switching providers
+ // This ensures the webhook status is properly reset
+ if (webhookProvider !== actualProvider) {
+ setWebhookId(null)
+ setActualProvider(null)
+ }
+
+ // Provider config is reset - webhook status will be determined by provider + path existence
+ }
+ }, [webhookProvider, webhookId, actualProvider])
// Check if webhook exists in the database
useEffect(() => {
@@ -353,18 +470,17 @@ export function WebhookConfig({
const checkWebhook = async () => {
setIsLoading(true)
try {
- // Check if there's a webhook for this workflow
- const response = await fetch(`/api/webhooks?workflowId=${workflowId}`)
+ // Check if there's a webhook for this specific block
+ // Always include blockId - every webhook should be associated with a specific block
+ const response = await fetch(`/api/webhooks?workflowId=${workflowId}&blockId=${blockId}`)
if (response.ok) {
const data = await response.json()
if (data.webhooks && data.webhooks.length > 0) {
const webhook = data.webhooks[0].webhook
setWebhookId(webhook.id)
- // Update the provider in the block state if it's different
- if (webhook.provider && webhook.provider !== webhookProvider) {
- setWebhookProvider(webhook.provider)
- }
+ // Don't automatically update the provider - let user control it
+ // The user should be able to change providers even when a webhook exists
// Store the actual provider from the database
setActualProvider(webhook.provider)
@@ -374,14 +490,22 @@ export function WebhookConfig({
setWebhookPath(webhook.path)
}
- // Set active webhook flag to true since we found an active webhook
- setWebhookStatus(true)
+ // Webhook found - status will be determined by provider + path existence
} else {
setWebhookId(null)
setActualProvider(null)
- // Set active webhook flag to false since no webhook was found
- setWebhookStatus(false)
+ // IMPORTANT: Clear stale webhook data from store when no webhook found in database
+ // This ensures the reactive badge status updates correctly on page refresh
+ if (webhookPath) {
+ setWebhookPath('')
+ logger.info('Cleared stale webhook path on page refresh - no webhook in database', {
+ blockId,
+ clearedPath: webhookPath,
+ })
+ }
+
+ // No webhook found - reactive blockWebhookStatus will now be false
}
}
} catch (error) {
@@ -392,15 +516,7 @@ export function WebhookConfig({
}
checkWebhook()
- }, [
- webhookPath,
- webhookProvider,
- workflowId,
- setWebhookPath,
- setWebhookProvider,
- setWebhookStatus,
- isPreview,
- ])
+ }, [workflowId, blockId, isPreview]) // Removed webhookPath dependency to prevent race condition with provider changes
const handleOpenModal = () => {
if (isPreview || disabled) return
@@ -443,6 +559,7 @@ export function WebhookConfig({
},
body: JSON.stringify({
workflowId,
+ blockId,
path,
provider: webhookProvider || 'generic',
providerConfig: finalConfig,
@@ -459,13 +576,20 @@ export function WebhookConfig({
}
const data = await response.json()
- setWebhookId(data.webhook.id)
+ const savedWebhookId = data.webhook.id
+ setWebhookId(savedWebhookId)
+
+ logger.info('Webhook saved successfully', {
+ webhookId: savedWebhookId,
+ provider: webhookProvider,
+ path,
+ blockId,
+ })
// Update the actual provider after saving
setActualProvider(webhookProvider || 'generic')
- // Set active webhook flag to true after successfully saving
- setWebhookStatus(true)
+ // Webhook saved successfully - status will be determined by provider + path existence
return true
} catch (error: any) {
@@ -504,7 +628,7 @@ export function WebhookConfig({
// Remove webhook-related fields
blockValues.webhookProvider = undefined
blockValues.providerConfig = undefined
- blockValues.webhookPath = ''
+ blockValues.webhookPath = undefined
// Update the store with the cleaned block values
store.setValue(blockId, 'startWorkflow', 'manual')
@@ -522,8 +646,7 @@ export function WebhookConfig({
setWebhookId(null)
setActualProvider(null)
- // Set active webhook flag to false
- setWebhookStatus(false)
+ // Webhook deleted - status will be determined by provider + path existence
handleCloseModal()
return true
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts
index 652b420982..44fde169ac 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value.ts
@@ -3,161 +3,12 @@ import { isEqual } from 'lodash'
import { createLogger } from '@/lib/logs/console-logger'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { getProviderFromModel } from '@/providers/utils'
-import { useGeneralStore } from '@/stores/settings/general/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
const logger = createLogger('SubBlockValue')
-// Helper function to dispatch collaborative subblock updates
-const dispatchSubblockUpdate = (blockId: string, subBlockId: string, value: any) => {
- const event = new CustomEvent('update-subblock-value', {
- detail: {
- blockId,
- subBlockId,
- value,
- },
- })
- window.dispatchEvent(event)
-}
-
-/**
- * Helper to handle API key auto-fill for provider-based blocks
- * Used for agent, router, evaluator, and any other blocks that use LLM providers
- */
-function handleProviderBasedApiKey(
- blockId: string,
- subBlockId: string,
- modelValue: string | null | undefined,
- storeValue: any,
- isModelChange = false
-) {
- // Only proceed if we have a model selected
- if (!modelValue) return
-
- // Get the provider for this model
- const provider = getProviderFromModel(modelValue)
-
- // Skip if we couldn't determine a provider
- if (!provider || provider === 'ollama') return
-
- const subBlockStore = useSubBlockStore.getState()
- const isAutoFillEnabled = useGeneralStore.getState().isAutoFillEnvVarsEnabled
-
- // Try to get a saved API key for this provider (only if auto-fill is enabled)
- const savedValue = isAutoFillEnabled
- ? subBlockStore.resolveToolParamValue(provider, 'apiKey', blockId)
- : null
-
- // If we have a valid saved API key and auto-fill is enabled, use it
- if (savedValue && savedValue !== '' && isAutoFillEnabled) {
- // Only update if the current value is different to avoid unnecessary updates
- if (storeValue !== savedValue) {
- dispatchSubblockUpdate(blockId, subBlockId, savedValue)
- }
- } else if (isModelChange && (!storeValue || storeValue === '')) {
- // Only clear the field when switching models AND the field is already empty
- // Don't clear existing user-entered values on initial load
- dispatchSubblockUpdate(blockId, subBlockId, '')
- }
- // If no saved value and this is initial load, preserve existing value
-}
-
-/**
- * Helper to handle API key auto-fill for non-agent blocks
- */
-function handleStandardBlockApiKey(
- blockId: string,
- subBlockId: string,
- blockType: string | undefined,
- storeValue: any
-) {
- if (!blockType) return
-
- const subBlockStore = useSubBlockStore.getState()
-
- // Only auto-fill if the field is empty
- if (!storeValue || storeValue === '') {
- // Pass the blockId as instanceId to check if this specific instance has been cleared
- const savedValue = subBlockStore.resolveToolParamValue(blockType, 'apiKey', blockId)
-
- if (savedValue && savedValue !== '' && savedValue !== storeValue) {
- // Auto-fill the API key from the param store
- dispatchSubblockUpdate(blockId, subBlockId, savedValue)
- }
- }
- // Handle environment variable references
- else if (
- storeValue &&
- typeof storeValue === 'string' &&
- storeValue.startsWith('{{') &&
- storeValue.endsWith('}}')
- ) {
- // Pass the blockId as instanceId
- const currentValue = subBlockStore.resolveToolParamValue(blockType, 'apiKey', blockId)
-
- if (currentValue !== storeValue) {
- // If we got a replacement or null, update the field
- if (currentValue) {
- // Replacement found - update to new reference
- dispatchSubblockUpdate(blockId, subBlockId, currentValue)
- }
- }
- }
-}
-
-/**
- * Helper to store API key values
- */
-function storeApiKeyValue(
- blockId: string,
- blockType: string | undefined,
- modelValue: string | null | undefined,
- newValue: any,
- storeValue: any
-) {
- if (!blockType) return
-
- const subBlockStore = useSubBlockStore.getState()
-
- // Check if this is user explicitly clearing a field that had a value
- // We only want to mark it as cleared if it's a user action, not an automatic
- // clearing from model switching
- if (
- storeValue &&
- storeValue !== '' &&
- (newValue === null || newValue === '' || String(newValue).trim() === '')
- ) {
- // Mark this specific instance as cleared so we don't auto-fill it
- subBlockStore.markParamAsCleared(blockId, 'apiKey')
- return
- }
-
- // Only store non-empty values
- if (!newValue || String(newValue).trim() === '') return
-
- // If user enters a value, we should clear any "cleared" flag
- // to ensure auto-fill will work in the future
- if (subBlockStore.isParamCleared(blockId, 'apiKey')) {
- subBlockStore.unmarkParamAsCleared(blockId, 'apiKey')
- }
-
- // For provider-based blocks, store the API key under the provider name
- if (
- (blockType === 'agent' || blockType === 'router' || blockType === 'evaluator') &&
- modelValue
- ) {
- const provider = getProviderFromModel(modelValue)
- if (provider && provider !== 'ollama') {
- subBlockStore.setToolParam(provider, 'apiKey', String(newValue))
- }
- } else {
- // For other blocks, store under the block type
- subBlockStore.setToolParam(blockType, 'apiKey', String(newValue))
- }
-}
-
interface UseSubBlockValueOptions {
debounceMs?: number
isStreaming?: boolean // Explicit streaming state
@@ -199,9 +50,6 @@ export function useSubBlockValue(
// Keep a ref to the latest value to prevent unnecessary re-renders
const valueRef = useRef(null)
- // Previous model reference for detecting model changes
- const prevModelRef = useRef(null)
-
// Streaming refs
const lastEmittedValueRef = useRef(null)
const streamingValueRef = useRef(null)
@@ -216,9 +64,6 @@ export function useSubBlockValue(
const isApiKey =
subBlockId === 'apiKey' || (subBlockId?.toLowerCase().includes('apikey') ?? false)
- // Check if auto-fill environment variables is enabled - always call this hook unconditionally
- const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
-
// Always call this hook unconditionally - don't wrap it in a condition
const modelSubBlockValue = useSubBlockStore((state) =>
blockId ? state.getValue(blockId, 'model') : null
@@ -276,6 +121,29 @@ export function useSubBlockValue(
},
}))
+ // Handle model changes for provider-based blocks - clear API key when provider changes
+ if (
+ subBlockId === 'model' &&
+ isProviderBasedBlock &&
+ newValue &&
+ typeof newValue === 'string'
+ ) {
+ const currentApiKeyValue = useSubBlockStore.getState().getValue(blockId, 'apiKey')
+
+ // Only clear if there's currently an API key value
+ if (currentApiKeyValue && currentApiKeyValue !== '') {
+ const oldModelValue = storeValue as string
+ const oldProvider = oldModelValue ? getProviderFromModel(oldModelValue) : null
+ const newProvider = getProviderFromModel(newValue)
+
+ // Clear API key if provider changed
+ if (oldProvider !== newProvider) {
+ // Use collaborative function to clear the API key
+ collaborativeSetSubblockValue(blockId, 'apiKey', '')
+ }
+ }
+ }
+
// Ensure we're passing the actual value, not a reference that might change
const valueCopy =
newValue === null
@@ -284,11 +152,6 @@ export function useSubBlockValue(
? JSON.parse(JSON.stringify(newValue))
: newValue
- // Handle API key storage for reuse across blocks
- if (isApiKey && blockType) {
- storeApiKeyValue(blockId, blockType, modelValue, newValue, storeValue)
- }
-
// If streaming, just store the value without emitting
if (isStreaming) {
streamingValueRef.current = valueCopy
@@ -320,61 +183,6 @@ export function useSubBlockValue(
valueRef.current = storeValue !== undefined ? storeValue : initialValue
}, [])
- // When component mounts, check for existing API key in toolParamsStore
- useEffect(() => {
- // Skip autofill if the feature is disabled in settings
- if (!isAutoFillEnvVarsEnabled) return
-
- // Only process API key fields
- if (!isApiKey) return
-
- // Handle different block types
- if (isProviderBasedBlock) {
- handleProviderBasedApiKey(blockId, subBlockId, modelValue, storeValue, false)
- } else {
- // Normal handling for non-provider blocks
- handleStandardBlockApiKey(blockId, subBlockId, blockType, storeValue)
- }
- }, [
- blockId,
- subBlockId,
- blockType,
- storeValue,
- isApiKey,
- isAutoFillEnvVarsEnabled,
- modelValue,
- isProviderBasedBlock,
- ])
-
- // Monitor for model changes in provider-based blocks
- useEffect(() => {
- // Only process API key fields in model-based blocks
- if (!isApiKey || !isProviderBasedBlock) return
-
- // Check if the model has changed
- if (modelValue !== prevModelRef.current) {
- // Update the previous model reference
- prevModelRef.current = modelValue
-
- // Handle API key auto-fill for model changes
- if (modelValue) {
- handleProviderBasedApiKey(blockId, subBlockId, modelValue, storeValue, true)
- } else {
- // If no model is selected, clear the API key field
- dispatchSubblockUpdate(blockId, subBlockId, '')
- }
- }
- }, [
- blockId,
- subBlockId,
- blockType,
- isApiKey,
- modelValue,
- isAutoFillEnvVarsEnabled,
- storeValue,
- isProviderBasedBlock,
- ])
-
// Update the ref if the store value changes
// This ensures we're always working with the latest value
useEffect(() => {
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
index 2a3cd42cb5..c6d9890e6d 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx
@@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { parseCronToHumanReadable } from '@/lib/schedules/utils'
-import { cn, formatDateTime, validateName } from '@/lib/utils'
+import { cn, validateName } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
import type { BlockConfig, SubBlockConfig } from '@/blocks/types'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useExecutionStore } from '@/stores/execution/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
+import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { mergeSubblockState } from '@/stores/workflows/utils'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import { ActionBar } from './components/action-bar/action-bar'
@@ -67,7 +68,17 @@ export function WorkflowBlock({ id, data }: NodeProps) {
)
const isWide = useWorkflowStore((state) => state.blocks[id]?.isWide ?? false)
const blockHeight = useWorkflowStore((state) => state.blocks[id]?.height ?? 0)
- const hasActiveWebhook = useWorkflowStore((state) => state.hasActiveWebhook ?? false)
+ // Get per-block webhook status by checking if webhook is configured
+ const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId)
+
+ const hasWebhookProvider = useSubBlockStore(
+ (state) => state.workflowValues[activeWorkflowId || '']?.[id]?.webhookProvider
+ )
+ const hasWebhookPath = useSubBlockStore(
+ (state) => state.workflowValues[activeWorkflowId || '']?.[id]?.webhookPath
+ )
+ const blockWebhookStatus = !!(hasWebhookProvider && hasWebhookPath)
+
const blockAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false)
// Collaborative workflow actions
@@ -89,6 +100,11 @@ export function WorkflowBlock({ id, data }: NodeProps) {
const params = useParams()
const currentWorkflowId = params.workflowId as string
+ // Check if this is a starter block or trigger block
+ const isStarterBlock = type === 'starter'
+ const isTriggerBlock = config.category === 'triggers'
+ const isWebhookTriggerBlock = type === 'webhook'
+
const reactivateSchedule = async (scheduleId: string) => {
try {
const response = await fetch(`/api/schedules/${scheduleId}`, {
@@ -112,13 +128,42 @@ export function WorkflowBlock({ id, data }: NodeProps) {
}
}
+ const disableSchedule = async (scheduleId: string) => {
+ try {
+ const response = await fetch(`/api/schedules/${scheduleId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ action: 'disable' }),
+ })
+
+ if (response.ok) {
+ // Refresh schedule info to show updated status
+ if (currentWorkflowId) {
+ fetchScheduleInfo(currentWorkflowId)
+ }
+ } else {
+ console.error('Failed to disable schedule')
+ }
+ } catch (error) {
+ console.error('Error disabling schedule:', error)
+ }
+ }
+
const fetchScheduleInfo = async (workflowId: string) => {
if (!workflowId) return
try {
setIsLoadingScheduleInfo(true)
- const response = await fetch(`/api/schedules?workflowId=${workflowId}&mode=schedule`, {
+ // For schedule trigger blocks, always include the blockId parameter
+ const url = new URL('/api/schedules', window.location.origin)
+ url.searchParams.set('workflowId', workflowId)
+ url.searchParams.set('mode', 'schedule')
+ url.searchParams.set('blockId', id) // Always include blockId for schedule blocks
+
+ const response = await fetch(url.toString(), {
cache: 'no-store',
headers: {
'Cache-Control': 'no-cache',
@@ -185,48 +230,25 @@ export function WorkflowBlock({ id, data }: NodeProps) {
}
useEffect(() => {
- if (type === 'starter' && currentWorkflowId) {
+ if (type === 'schedule' && currentWorkflowId) {
fetchScheduleInfo(currentWorkflowId)
} else {
setScheduleInfo(null)
- setIsLoadingScheduleInfo(false) // Reset loading state when not a starter block
+ setIsLoadingScheduleInfo(false) // Reset loading state when not a schedule block
}
// Cleanup function to reset loading state when component unmounts or workflow changes
return () => {
setIsLoadingScheduleInfo(false)
}
- }, [type, currentWorkflowId])
+ }, [isStarterBlock, isTriggerBlock, type, currentWorkflowId, lastUpdate])
// Get webhook information for the tooltip
useEffect(() => {
- if (type === 'starter' && hasActiveWebhook) {
- const fetchWebhookInfo = async () => {
- try {
- const workflowId = useWorkflowRegistry.getState().activeWorkflowId
- if (!workflowId) return
-
- const response = await fetch(`/api/webhooks?workflowId=${workflowId}`)
- if (response.ok) {
- const data = await response.json()
- if (data.webhooks?.[0]?.webhook) {
- const webhook = data.webhooks[0].webhook
- setWebhookInfo({
- webhookPath: webhook.path || '',
- provider: webhook.provider || 'generic',
- })
- }
- }
- } catch (error) {
- console.error('Error fetching webhook info:', error)
- }
- }
-
- fetchWebhookInfo()
- } else if (!hasActiveWebhook) {
+ if (!blockWebhookStatus) {
setWebhookInfo(null)
}
- }, [type, hasActiveWebhook])
+ }, [blockWebhookStatus])
// Update node internals when handles change
useEffect(() => {
@@ -404,9 +426,8 @@ export function WorkflowBlock({ id, data }: NodeProps) {
}
}
- // Check if this is a starter block and has active schedule or webhook
- const isStarterBlock = type === 'starter'
- const showWebhookIndicator = isStarterBlock && hasActiveWebhook
+ // Check webhook indicator
+ const showWebhookIndicator = (isStarterBlock || isWebhookTriggerBlock) && blockWebhookStatus
const getProviderName = (providerId: string): string => {
const providers: Record = {
@@ -422,7 +443,8 @@ export function WorkflowBlock({ id, data }: NodeProps) {
return providers[providerId] || 'Webhook'
}
- const shouldShowScheduleBadge = isStarterBlock && !isLoadingScheduleInfo && scheduleInfo !== null
+ const shouldShowScheduleBadge =
+ type === 'schedule' && !isLoadingScheduleInfo && scheduleInfo !== null
const userPermissions = useUserPermissionsContext()
return (
@@ -447,15 +469,18 @@ export function WorkflowBlock({ id, data }: NodeProps) {
)}
-
-
- {/* Input Handle - Don't show for starter blocks */}
- {type !== 'starter' && (
+ {/* Connection Blocks - Don't show for trigger blocks or starter blocks */}
+ {config.category !== 'triggers' && type !== 'starter' && (
+
+ )}
+
+ {/* Input Handle - Don't show for trigger blocks or starter blocks */}
+ {config.category !== 'triggers' && type !== 'starter' && (
) {
reactivateSchedule(scheduleInfo.id!)
+ scheduleInfo?.id
+ ? scheduleInfo.isDisabled
+ ? () => reactivateSchedule(scheduleInfo.id!)
+ : () => disableSchedule(scheduleInfo.id!)
: undefined
}
>
@@ -570,32 +597,12 @@ export function WorkflowBlock({ id, data }: NodeProps) {
- {scheduleInfo ? (
- <>
- {scheduleInfo.scheduleTiming}
- {scheduleInfo.isDisabled && (
-
- This schedule is currently disabled due to consecutive failures. Click the
- badge to reactivate it.
-
- )}
- {scheduleInfo.nextRunAt && !scheduleInfo.isDisabled && (
-
- Next run:{' '}
- {formatDateTime(new Date(scheduleInfo.nextRunAt), scheduleInfo.timezone)}
-
- )}
- {scheduleInfo.lastRanAt && (
-
- Last run:{' '}
- {formatDateTime(new Date(scheduleInfo.lastRanAt), scheduleInfo.timezone)}
-
- )}
- >
- ) : (
-
- This workflow is running on a schedule.
+ {scheduleInfo?.isDisabled ? (
+
+ This schedule is currently disabled. Click the badge to reactivate it.
+ ) : (
+ Click the badge to disable this schedule.
)}
@@ -825,8 +832,8 @@ export function WorkflowBlock({ id, data }: NodeProps) {
isValidConnection={(connection) => connection.target !== id}
/>
- {/* Error Handle - Don't show for starter blocks */}
- {type !== 'starter' && (
+ {/* Error Handle - Don't show for trigger blocks or starter blocks */}
+ {config.category !== 'triggers' && type !== 'starter' && (
=> {
// Use the mergeSubblockState utility to get all block states
const mergedStates = mergeSubblockState(blocks)
- const currentBlockStates = Object.entries(mergedStates).reduce(
+
+ // Filter out trigger blocks for manual execution
+ const filteredStates = Object.entries(mergedStates).reduce(
+ (acc, [id, block]) => {
+ const blockConfig = getBlock(block.type)
+ const isTriggerBlock = blockConfig?.category === 'triggers'
+
+ // Skip trigger blocks during manual execution
+ if (!isTriggerBlock) {
+ acc[id] = block
+ }
+ return acc
+ },
+ {} as typeof mergedStates
+ )
+
+ const currentBlockStates = Object.entries(filteredStates).reduce(
(acc, [id, block]) => {
acc[id] = Object.entries(block.subBlocks).reduce(
(subAcc, [key, subBlock]) => {
@@ -453,8 +470,23 @@ export function useWorkflowExecution() {
{} as Record
)
- // Create serialized workflow
- const workflow = new Serializer().serializeWorkflow(mergedStates, edges, loops, parallels)
+ // Filter edges to exclude connections to/from trigger blocks
+ const triggerBlockIds = Object.keys(mergedStates).filter((id) => {
+ const blockConfig = getBlock(mergedStates[id].type)
+ return blockConfig?.category === 'triggers'
+ })
+
+ const filteredEdges = edges.filter(
+ (edge) => !triggerBlockIds.includes(edge.source) && !triggerBlockIds.includes(edge.target)
+ )
+
+ // Create serialized workflow with filtered blocks and edges
+ const workflow = new Serializer().serializeWorkflow(
+ filteredStates,
+ filteredEdges,
+ loops,
+ parallels
+ )
// Determine if this is a chat execution
const isChatExecution =
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts
index 73cec39ce1..23faa5206e 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils.ts
@@ -1,4 +1,5 @@
import { createLogger } from '@/lib/logs/console-logger'
+import { getBlock } from '@/blocks'
const logger = createLogger('WorkflowUtils')
@@ -549,14 +550,25 @@ export const analyzeWorkflowGraph = (
const outDegreeValue = (adjacencyList.get(blockId) || []).length
const block = blocks[blockId]
- if (inDegreeValue === 0 && outDegreeValue === 0 && block.type !== 'starter') {
+ const blockConfig = getBlock(block.type)
+ const isTriggerBlock = blockConfig?.category === 'triggers'
+
+ if (
+ inDegreeValue === 0 &&
+ outDegreeValue === 0 &&
+ block.type !== 'starter' &&
+ !isTriggerBlock
+ ) {
orphanedBlocks.add(blockId)
}
})
const queue: string[] = []
inDegree.forEach((degree, blockId) => {
- if (degree === 0 || blocks[blockId].type === 'starter') {
+ const blockConfig = getBlock(blocks[blockId].type)
+ const isTriggerBlock = blockConfig?.category === 'triggers'
+
+ if (degree === 0 || blocks[blockId].type === 'starter' || isTriggerBlock) {
queue.push(blockId)
blockLayers.set(blockId, 0)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
index 0b1e45a4e0..c3d84431aa 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
@@ -1080,6 +1080,16 @@ const WorkflowContent = React.memo(() => {
if (!sourceNode || !targetNode) return
+ // Prevent incoming connections to trigger blocks (webhook, schedule, etc.)
+ if (targetNode.data?.config?.category === 'triggers') {
+ return
+ }
+
+ // Prevent incoming connections to starter blocks (still keep separate for backward compatibility)
+ if (targetNode.data?.type === 'starter') {
+ return
+ }
+
// Get parent information (handle container start node case)
const sourceParentId =
sourceNode.parentId ||
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx
index 8171d157b5..420a71e103 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx
@@ -3,7 +3,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
-import { BookOpen, LibraryBig, ScrollText, Search, Shapes } from 'lucide-react'
+import { BookOpen, Building2, LibraryBig, ScrollText, Search, Shapes, Workflow } from 'lucide-react'
import { useParams, useRouter } from 'next/navigation'
import { Dialog, DialogOverlay, DialogPortal, DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
@@ -15,7 +15,10 @@ interface SearchModalProps {
open: boolean
onOpenChange: (open: boolean) => void
templates?: TemplateData[]
+ workflows?: WorkflowItem[]
+ workspaces?: WorkspaceItem[]
loading?: boolean
+ isOnWorkflowPage?: boolean
}
interface TemplateData {
@@ -33,6 +36,20 @@ interface TemplateData {
isStarred?: boolean
}
+interface WorkflowItem {
+ id: string
+ name: string
+ href: string
+ isCurrent?: boolean
+}
+
+interface WorkspaceItem {
+ id: string
+ name: string
+ href: string
+ isCurrent?: boolean
+}
+
interface BlockItem {
id: string
name: string
@@ -69,9 +86,13 @@ export function SearchModal({
open,
onOpenChange,
templates = [],
+ workflows = [],
+ workspaces = [],
loading = false,
+ isOnWorkflowPage = false,
}: SearchModalProps) {
const [searchQuery, setSearchQuery] = useState('')
+ const [selectedIndex, setSelectedIndex] = useState(0)
const params = useParams()
const router = useRouter()
const workspaceId = params.workspaceId as string
@@ -115,12 +136,17 @@ export function SearchModal({
}
}, [])
- // Get all available blocks
+ // Get all available blocks - only when on workflow page
const blocks = useMemo(() => {
+ if (!isOnWorkflowPage) return []
+
const allBlocks = getAllBlocks()
return allBlocks
.filter(
- (block) => block.type !== 'starter' && !block.hideFromToolbar && block.category === 'blocks'
+ (block) =>
+ block.type !== 'starter' &&
+ !block.hideFromToolbar &&
+ (block.category === 'blocks' || block.category === 'triggers')
)
.map(
(block): BlockItem => ({
@@ -132,10 +158,12 @@ export function SearchModal({
})
)
.sort((a, b) => a.name.localeCompare(b.name))
- }, [])
+ }, [isOnWorkflowPage])
- // Get all available tools
+ // Get all available tools - only when on workflow page
const tools = useMemo(() => {
+ if (!isOnWorkflowPage) return []
+
const allBlocks = getAllBlocks()
return allBlocks
.filter((block) => block.category === 'tools')
@@ -149,7 +177,7 @@ export function SearchModal({
})
)
.sort((a, b) => a.name.localeCompare(b.name))
- }, [])
+ }, [isOnWorkflowPage])
// Define pages
const pages = useMemo(
@@ -197,7 +225,7 @@ export function SearchModal({
name: block.name,
icon: block.icon,
href: block.docsLink,
- type: block.category === 'blocks' ? 'block' : 'tool',
+ type: block.category === 'blocks' || block.category === 'triggers' ? 'block' : 'tool',
})
}
})
@@ -230,6 +258,18 @@ export function SearchModal({
.slice(0, 8)
}, [localTemplates, searchQuery])
+ const filteredWorkflows = useMemo(() => {
+ if (!searchQuery.trim()) return workflows
+ const query = searchQuery.toLowerCase()
+ return workflows.filter((workflow) => workflow.name.toLowerCase().includes(query))
+ }, [workflows, searchQuery])
+
+ const filteredWorkspaces = useMemo(() => {
+ if (!searchQuery.trim()) return workspaces
+ const query = searchQuery.toLowerCase()
+ return workspaces.filter((workspace) => workspace.name.toLowerCase().includes(query))
+ }, [workspaces, searchQuery])
+
const filteredPages = useMemo(() => {
if (!searchQuery.trim()) return pages
const query = searchQuery.toLowerCase()
@@ -242,6 +282,42 @@ export function SearchModal({
return docs.filter((doc) => doc.name.toLowerCase().includes(query))
}, [docs, searchQuery])
+ // Create flattened list of navigatable items for keyboard navigation
+ const navigatableItems = useMemo(() => {
+ const items: Array<{
+ type: 'workspace' | 'workflow' | 'page' | 'doc'
+ data: any
+ section: string
+ }> = []
+
+ // Add workspaces
+ filteredWorkspaces.forEach((workspace) => {
+ items.push({ type: 'workspace', data: workspace, section: 'Workspaces' })
+ })
+
+ // Add workflows
+ filteredWorkflows.forEach((workflow) => {
+ items.push({ type: 'workflow', data: workflow, section: 'Workflows' })
+ })
+
+ // Add pages
+ filteredPages.forEach((page) => {
+ items.push({ type: 'page', data: page, section: 'Pages' })
+ })
+
+ // Add docs
+ filteredDocs.forEach((doc) => {
+ items.push({ type: 'doc', data: doc, section: 'Docs' })
+ })
+
+ return items
+ }, [filteredWorkspaces, filteredWorkflows, filteredPages, filteredDocs])
+
+ // Reset selected index when items change or modal opens
+ useEffect(() => {
+ setSelectedIndex(0)
+ }, [navigatableItems, open])
+
// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
@@ -292,6 +368,15 @@ export function SearchModal({
[router, onOpenChange]
)
+ // Handle workflow/workspace navigation (same as page navigation)
+ const handleNavigationClick = useCallback(
+ (href: string) => {
+ router.push(href)
+ onOpenChange(false)
+ },
+ [router, onOpenChange]
+ )
+
// Handle docs navigation
const handleDocsClick = useCallback(
(href: string) => {
@@ -312,19 +397,28 @@ export function SearchModal({
// Only handle shortcuts when modal is open
if (!open) return
- // Don't trigger if user is typing in the search input
- const activeElement = document.activeElement
- const isEditableElement =
- activeElement instanceof HTMLInputElement ||
- activeElement instanceof HTMLTextAreaElement ||
- activeElement?.hasAttribute('contenteditable')
-
- if (isEditableElement) return
-
const isMac =
typeof navigator !== 'undefined' && navigator.platform.toUpperCase().indexOf('MAC') >= 0
const isModifierPressed = isMac ? e.metaKey : e.ctrlKey
+ // Check if this is one of our specific shortcuts
+ const isOurShortcut =
+ isModifierPressed &&
+ e.shiftKey &&
+ (e.key.toLowerCase() === 'l' || e.key.toLowerCase() === 'k')
+
+ // Don't trigger other shortcuts if user is typing in the search input
+ // But allow our specific shortcuts to pass through
+ if (!isOurShortcut) {
+ const activeElement = document.activeElement
+ const isEditableElement =
+ activeElement instanceof HTMLInputElement ||
+ activeElement instanceof HTMLTextAreaElement ||
+ activeElement?.hasAttribute('contenteditable')
+
+ if (isEditableElement) return
+ }
+
if (isModifierPressed && e.shiftKey) {
// Command+Shift+L - Navigate to Logs
if (e.key.toLowerCase() === 'l') {
@@ -360,6 +454,89 @@ export function SearchModal({
[]
)
+ // Handle item selection based on type
+ const handleItemSelection = useCallback(
+ (item: (typeof navigatableItems)[0]) => {
+ switch (item.type) {
+ case 'workspace':
+ if (item.data.isCurrent) {
+ onOpenChange(false)
+ } else {
+ handleNavigationClick(item.data.href)
+ }
+ break
+ case 'workflow':
+ if (item.data.isCurrent) {
+ onOpenChange(false)
+ } else {
+ handleNavigationClick(item.data.href)
+ }
+ break
+ case 'page':
+ handlePageClick(item.data.href)
+ break
+ case 'doc':
+ handleDocsClick(item.data.href)
+ break
+ }
+ },
+ [handleNavigationClick, handlePageClick, handleDocsClick, onOpenChange]
+ )
+
+ // Handle keyboard navigation
+ useEffect(() => {
+ if (!open) return
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ switch (e.key) {
+ case 'ArrowDown':
+ e.preventDefault()
+ setSelectedIndex((prev) => Math.min(prev + 1, navigatableItems.length - 1))
+ break
+ case 'ArrowUp':
+ e.preventDefault()
+ setSelectedIndex((prev) => Math.max(prev - 1, 0))
+ break
+ case 'Enter':
+ e.preventDefault()
+ if (navigatableItems.length > 0 && selectedIndex < navigatableItems.length) {
+ const selectedItem = navigatableItems[selectedIndex]
+ handleItemSelection(selectedItem)
+ }
+ break
+ case 'Escape':
+ onOpenChange(false)
+ break
+ }
+ }
+
+ document.addEventListener('keydown', handleKeyDown)
+ return () => document.removeEventListener('keydown', handleKeyDown)
+ }, [open, selectedIndex, navigatableItems, onOpenChange, handleItemSelection])
+
+ // Helper function to check if an item is selected
+ const isItemSelected = useCallback(
+ (item: any, itemType: string) => {
+ if (navigatableItems.length === 0 || selectedIndex >= navigatableItems.length) return false
+ const selectedItem = navigatableItems[selectedIndex]
+ return selectedItem.type === itemType && selectedItem.data.id === item.id
+ },
+ [navigatableItems, selectedIndex]
+ )
+
+ // Scroll selected item into view
+ useEffect(() => {
+ if (selectedIndex >= 0 && navigatableItems.length > 0) {
+ const selectedItem = navigatableItems[selectedIndex]
+ const itemElement = document.querySelector(
+ `[data-search-item="${selectedItem.type}-${selectedItem.data.id}"]`
+ )
+ if (itemElement) {
+ itemElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
+ }
+ }
+ }, [selectedIndex, navigatableItems])
+
// Render skeleton cards for loading state
const renderSkeletonCards = () => {
return Array.from({ length: 8 }).map((_, index) => (
@@ -376,7 +553,7 @@ export function SearchModal({
className='bg-white/50 dark:bg-black/50'
style={{ backdropFilter: 'blur(4.8px)' }}
/>
-
+
Search
@@ -560,6 +737,76 @@ export function SearchModal({
)}
+ {/* Workspaces Section */}
+ {filteredWorkspaces.length > 0 && (
+
+
+ Workspaces
+
+
+ {filteredWorkspaces.map((workspace) => (
+
+ workspace.isCurrent
+ ? onOpenChange(false)
+ : handleNavigationClick(workspace.href)
+ }
+ data-search-item={`workspace-${workspace.id}`}
+ className={`flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors focus:outline-none ${
+ isItemSelected(workspace, 'workspace')
+ ? 'bg-accent text-accent-foreground'
+ : 'hover:bg-accent/60 focus:bg-accent/60'
+ }`}
+ >
+
+
+
+
+ {workspace.name}
+ {workspace.isCurrent && ' (current)'}
+
+
+ ))}
+
+
+ )}
+
+ {/* Workflows Section */}
+ {filteredWorkflows.length > 0 && (
+
+
+ Workflows
+
+
+ {filteredWorkflows.map((workflow) => (
+
+ workflow.isCurrent
+ ? onOpenChange(false)
+ : handleNavigationClick(workflow.href)
+ }
+ data-search-item={`workflow-${workflow.id}`}
+ className={`flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors focus:outline-none ${
+ isItemSelected(workflow, 'workflow')
+ ? 'bg-accent text-accent-foreground'
+ : 'hover:bg-accent/60 focus:bg-accent/60'
+ }`}
+ >
+
+
+
+
+ {workflow.name}
+ {workflow.isCurrent && ' (current)'}
+
+
+ ))}
+
+
+ )}
+
{/* Pages Section */}
{filteredPages.length > 0 && (
@@ -571,7 +818,12 @@ export function SearchModal({
handlePageClick(page.href)}
- className='flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:bg-accent/60 focus:bg-accent/60 focus:outline-none'
+ data-search-item={`page-${page.id}`}
+ className={`flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors focus:outline-none ${
+ isItemSelected(page, 'page')
+ ? 'bg-accent text-accent-foreground'
+ : 'hover:bg-accent/60 focus:bg-accent/60'
+ }`}
>
@@ -605,7 +857,12 @@ export function SearchModal({
handleDocsClick(doc.href)}
- className='flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:bg-accent/60 focus:bg-accent/60 focus:outline-none'
+ data-search-item={`doc-${doc.id}`}
+ className={`flex h-10 w-full items-center gap-3 rounded-lg px-3 py-2 transition-colors focus:outline-none ${
+ isItemSelected(doc, 'doc')
+ ? 'bg-accent text-accent-foreground'
+ : 'hover:bg-accent/60 focus:bg-accent/60'
+ }`}
>
@@ -622,11 +879,13 @@ export function SearchModal({
{/* Empty state */}
{searchQuery &&
!loading &&
+ filteredWorkflows.length === 0 &&
+ filteredWorkspaces.length === 0 &&
+ filteredPages.length === 0 &&
+ filteredDocs.length === 0 &&
filteredBlocks.length === 0 &&
filteredTools.length === 0 &&
- filteredTemplates.length === 0 &&
- filteredPages.length === 0 &&
- filteredDocs.length === 0 && (
+ filteredTemplates.length === 0 && (
No results found for "{searchQuery}"
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/folder-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/folder-item.tsx
index 5d793abbbc..0a6fe0ce61 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/folder-item.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/folder-item.tsx
@@ -14,7 +14,6 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
-import { Input } from '@/components/ui/input'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { type FolderTreeNode, useFolderStore } from '@/stores/folders/store'
@@ -299,16 +298,20 @@ export function FolderItem({
{isEditing ? (
- setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={handleInputBlur}
- className='h-6 flex-1 border-0 bg-transparent p-0 text-muted-foreground text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
+ className='flex-1 border-0 bg-transparent p-0 text-muted-foreground text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
maxLength={50}
disabled={isRenaming}
onClick={(e) => e.stopPropagation()} // Prevent folder toggle when clicking input
+ autoComplete='off'
+ autoCorrect='off'
+ autoCapitalize='off'
+ spellCheck='false'
/>
) : (
{folder.name}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/workflow-item.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/workflow-item.tsx
index 6c2f0128dc..e17a7eaffa 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/workflow-item.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/components/workflow-item.tsx
@@ -4,7 +4,6 @@ import { useEffect, useRef, useState } from 'react'
import clsx from 'clsx'
import Link from 'next/link'
import { useParams } from 'next/navigation'
-import { Input } from '@/components/ui/input'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { useFolderStore, useIsWorkflowSelected } from '@/stores/folders/store'
@@ -220,16 +219,22 @@ export function WorkflowItem({
style={{ backgroundColor: workflow.color }}
/>
{isEditing ? (
- setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={handleInputBlur}
- className='h-6 flex-1 border-0 bg-transparent p-0 text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
+ className={`flex-1 border-0 bg-transparent p-0 font-medium text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 ${
+ active && !isDragOver ? 'text-foreground' : 'text-muted-foreground'
+ }`}
maxLength={100}
disabled={isRenaming}
onClick={(e) => e.preventDefault()} // Prevent navigation when clicking input
+ autoComplete='off'
+ autoCorrect='off'
+ autoCapitalize='off'
+ spellCheck='false'
/>
) : (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/folder-tree.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/folder-tree.tsx
index b918319bf8..7acb5c01be 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/folder-tree.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/folder-tree/folder-tree.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useCallback, useEffect, useState } from 'react'
+import { useCallback, useEffect, useMemo, useState } from 'react'
import clsx from 'clsx'
import { useParams, usePathname } from 'next/navigation'
import { Skeleton } from '@/components/ui/skeleton'
@@ -354,6 +354,7 @@ export function FolderTree({
const pathname = usePathname()
const params = useParams()
const workspaceId = params.workspaceId as string
+ const workflowId = params.workflowId as string
const {
getFolderTree,
expandedFolders,
@@ -361,9 +362,33 @@ export function FolderTree({
isLoading: foldersLoading,
clearSelection,
updateFolderAPI,
+ getFolderPath,
+ setExpanded,
} = useFolderStore()
const { updateWorkflow } = useWorkflowRegistry()
+ // Memoize the active workflow's folder ID to avoid unnecessary re-runs
+ const activeWorkflowFolderId = useMemo(() => {
+ if (!workflowId || isLoading || foldersLoading) return null
+ const activeWorkflow = regularWorkflows.find((workflow) => workflow.id === workflowId)
+ return activeWorkflow?.folderId || null
+ }, [workflowId, regularWorkflows, isLoading, foldersLoading])
+
+ // Auto-expand folders when a workflow is active
+ useEffect(() => {
+ if (!activeWorkflowFolderId) return
+
+ // Get the folder path from root to the workflow's folder
+ const folderPath = getFolderPath(activeWorkflowFolderId)
+
+ // Expand all folders in the path (only if not already expanded)
+ folderPath.forEach((folder) => {
+ if (!expandedFolders.has(folder.id)) {
+ setExpanded(folder.id, true)
+ }
+ })
+ }, [activeWorkflowFolderId, getFolderPath, setExpanded])
+
// Clean up any existing folders with 3+ levels of nesting
const cleanupDeepNesting = useCallback(async () => {
const { getFolderTree, updateFolderAPI } = useFolderStore.getState()
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx
index 59207fcc05..c6600b18ed 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx
@@ -17,7 +17,6 @@ import { useGeneralStore } from '@/stores/settings/general/store'
const TOOLTIPS = {
autoConnect: 'Automatically connect nodes.',
- autoFillEnvVars: 'Automatically fill API keys.',
autoPan: 'Automatically pan to active blocks during workflow execution.',
consoleExpandedByDefault:
'Show console entries expanded by default. When disabled, entries will be collapsed by default.',
@@ -30,13 +29,13 @@ export function General() {
const error = useGeneralStore((state) => state.error)
const theme = useGeneralStore((state) => state.theme)
const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled)
- const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled)
+
const isAutoPanEnabled = useGeneralStore((state) => state.isAutoPanEnabled)
const isConsoleExpandedByDefault = useGeneralStore((state) => state.isConsoleExpandedByDefault)
// Loading states
const isAutoConnectLoading = useGeneralStore((state) => state.isAutoConnectLoading)
- const isAutoFillEnvVarsLoading = useGeneralStore((state) => state.isAutoFillEnvVarsLoading)
+
const isAutoPanLoading = useGeneralStore((state) => state.isAutoPanLoading)
const isConsoleExpandedByDefaultLoading = useGeneralStore(
(state) => state.isConsoleExpandedByDefaultLoading
@@ -45,7 +44,7 @@ export function General() {
const setTheme = useGeneralStore((state) => state.setTheme)
const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect)
- const toggleAutoFillEnvVars = useGeneralStore((state) => state.toggleAutoFillEnvVars)
+
const toggleAutoPan = useGeneralStore((state) => state.toggleAutoPan)
const toggleConsoleExpandedByDefault = useGeneralStore(
(state) => state.toggleConsoleExpandedByDefault
@@ -69,12 +68,6 @@ export function General() {
}
}
- const handleAutoFillEnvVarsChange = async (checked: boolean) => {
- if (checked !== isAutoFillEnvVarsEnabled && !isAutoFillEnvVarsLoading) {
- await toggleAutoFillEnvVars()
- }
- }
-
const handleAutoPanChange = async (checked: boolean) => {
if (checked !== isAutoPanEnabled && !isAutoPanLoading) {
await toggleAutoPan()
@@ -167,35 +160,7 @@ export function General() {
disabled={isLoading || isAutoConnectLoading}
/>
-
-
-
- Auto-fill environment variables
-
-
-
-
-
-
-
-
- {TOOLTIPS.autoFillEnvVars}
-
-
-
-
-
+
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx
index e1b7965fb0..678d3c7b4b 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/toolbar/toolbar.tsx
@@ -25,7 +25,7 @@ interface BlockItem {
export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }: ToolbarProps) {
const [searchQuery, setSearchQuery] = useState('')
- const { regularBlocks, specialBlocks, tools } = useMemo(() => {
+ const { regularBlocks, specialBlocks, tools, triggers } = useMemo(() => {
const allBlocks = getAllBlocks()
// Filter blocks based on search query
@@ -39,9 +39,10 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }:
)
})
- // Separate regular blocks (category: 'blocks') and tools (category: 'tools')
+ // Separate blocks by category: 'blocks', 'tools', and 'triggers'
const regularBlockConfigs = filteredBlocks.filter((block) => block.category === 'blocks')
const toolConfigs = filteredBlocks.filter((block) => block.category === 'tools')
+ const triggerConfigs = filteredBlocks.filter((block) => block.category === 'triggers')
// Create regular block items and sort alphabetically
const regularBlockItems: BlockItem[] = regularBlockConfigs
@@ -75,6 +76,16 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }:
// Sort special blocks alphabetically
specialBlockItems.sort((a, b) => a.name.localeCompare(b.name))
+ // Create trigger block items and sort alphabetically
+ const triggerBlockItems: BlockItem[] = triggerConfigs
+ .map((block) => ({
+ name: block.name,
+ type: block.type,
+ config: block,
+ isCustom: false,
+ }))
+ .sort((a, b) => a.name.localeCompare(b.name))
+
// Sort tools alphabetically
toolConfigs.sort((a, b) => a.name.localeCompare(b.name))
@@ -82,6 +93,7 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }:
regularBlocks: regularBlockItems,
specialBlocks: specialBlockItems,
tools: toolConfigs,
+ triggers: triggerBlockItems,
}
}, [searchQuery])
@@ -127,6 +139,15 @@ export function Toolbar({ userPermissions, isWorkspaceSelectorVisible = false }:
return null
})}
+ {/* Triggers Section */}
+ {triggers.map((trigger) => (
+
+ ))}
+
{/* Tools Section */}
{tools.map((tool) => (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx
index 9954a794ca..448b7479c5 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx
@@ -104,8 +104,9 @@ export const WorkspaceHeader = React.memo(
case 'save': {
// Exit edit mode immediately, save in background
setIsEditingName(false)
- if (activeWorkspace && editingName.trim() !== '') {
- updateWorkspaceName(activeWorkspace.id, editingName.trim()).catch((error) => {
+ const trimmedName = editingName.trim()
+ if (activeWorkspace && trimmedName !== '' && trimmedName !== activeWorkspace.name) {
+ updateWorkspaceName(activeWorkspace.id, trimmedName).catch((error) => {
logger.error('Failed to update workspace name:', error)
})
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-selector/workspace-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-selector/workspace-selector.tsx
index 4e2ef1758d..3a5548f423 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-selector/workspace-selector.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-selector/workspace-selector.tsx
@@ -46,6 +46,7 @@ interface WorkspaceSelectorProps {
onLeaveWorkspace: (workspace: Workspace) => Promise
isDeleting: boolean
isLeaving: boolean
+ isCreating: boolean
}
export function WorkspaceSelector({
@@ -59,6 +60,7 @@ export function WorkspaceSelector({
onLeaveWorkspace,
isDeleting,
isLeaving,
+ isCreating,
}: WorkspaceSelectorProps) {
const userPermissions = useUserPermissionsContext()
@@ -256,7 +258,7 @@ export function WorkspaceSelector({
onClick={userPermissions.canAdmin ? () => setShowInviteMembers(true) : undefined}
disabled={!userPermissions.canAdmin}
className={cn(
- 'h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs hover:bg-secondary hover:text-muted-foreground',
+ 'h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs transition-colors hover:bg-muted-foreground/10 hover:text-muted-foreground',
!userPermissions.canAdmin && 'cursor-not-allowed opacity-50'
)}
>
@@ -269,7 +271,11 @@ export function WorkspaceSelector({
variant='secondary'
size='sm'
onClick={onCreateWorkspace}
- className='h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs hover:bg-secondary hover:text-muted-foreground'
+ disabled={isCreating}
+ className={cn(
+ 'h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs transition-colors hover:bg-muted-foreground/10 hover:text-muted-foreground',
+ isCreating && 'cursor-not-allowed'
+ )}
>
Create
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
index 26bc521b46..5a5fc70c79 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx
@@ -86,6 +86,8 @@ export function Sidebar() {
// Add state to prevent multiple simultaneous workflow creations
const [isCreatingWorkflow, setIsCreatingWorkflow] = useState(false)
+ // Add state to prevent multiple simultaneous workspace creations
+ const [isCreatingWorkspace, setIsCreatingWorkspace] = useState(false)
// Add sidebar collapsed state
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false)
const params = useParams()
@@ -276,7 +278,13 @@ export function Sidebar() {
* Handle create workspace
*/
const handleCreateWorkspace = useCallback(async () => {
+ if (isCreatingWorkspace) {
+ logger.info('Workspace creation already in progress, ignoring request')
+ return
+ }
+
try {
+ setIsCreatingWorkspace(true)
logger.info('Creating new workspace')
const response = await fetch('/api/workspaces', {
@@ -306,8 +314,10 @@ export function Sidebar() {
await switchWorkspace(newWorkspace)
} catch (error) {
logger.error('Error creating workspace:', error)
+ } finally {
+ setIsCreatingWorkspace(false)
}
- }, [refreshWorkspaceList, switchWorkspace])
+ }, [refreshWorkspaceList, switchWorkspace, isCreatingWorkspace])
/**
* Confirm delete workspace
@@ -570,6 +580,29 @@ export function Sidebar() {
return { regularWorkflows: regular, tempWorkflows: temp }
}, [workflows, isLoading, workspaceId])
+ // Prepare workflows for search modal
+ const searchWorkflows = useMemo(() => {
+ if (isLoading) return []
+
+ const allWorkflows = [...regularWorkflows, ...tempWorkflows]
+ return allWorkflows.map((workflow) => ({
+ id: workflow.id,
+ name: workflow.name,
+ href: `/workspace/${workspaceId}/w/${workflow.id}`,
+ isCurrent: workflow.id === workflowId,
+ }))
+ }, [regularWorkflows, tempWorkflows, workspaceId, workflowId, isLoading])
+
+ // Prepare workspaces for search modal (include all workspaces)
+ const searchWorkspaces = useMemo(() => {
+ return workspaces.map((workspace) => ({
+ id: workspace.id,
+ name: workspace.name,
+ href: `/workspace/${workspace.id}/w`,
+ isCurrent: workspace.id === workspaceId,
+ }))
+ }, [workspaces, workspaceId])
+
// Create workflow handler
const handleCreateWorkflow = async (folderId?: string): Promise => {
if (isCreatingWorkflow) {
@@ -702,7 +735,6 @@ export function Sidebar() {
icon: LibraryBig,
href: `/workspace/${workspaceId}/knowledge`,
tooltip: 'Knowledge',
- shortcut: getKeyboardShortcutText('K', true, true),
active: pathname === `/workspace/${workspaceId}/knowledge`,
},
{
@@ -752,6 +784,7 @@ export function Sidebar() {
onLeaveWorkspace={handleLeaveWorkspace}
isDeleting={isDeleting}
isLeaving={isLeaving}
+ isCreating={isCreatingWorkspace}
/>
@@ -783,7 +816,7 @@ export function Sidebar() {
}`}
>
-
+
-
+
>
)
}
diff --git a/apps/sim/blocks/blocks/agent.test.ts b/apps/sim/blocks/blocks/agent.test.ts
index a0ea12866e..e29ecc5ff3 100644
--- a/apps/sim/blocks/blocks/agent.test.ts
+++ b/apps/sim/blocks/blocks/agent.test.ts
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { AgentBlock } from './agent'
+import { AgentBlock } from '@/blocks/blocks/agent'
vi.mock('@/blocks', () => ({
getAllBlocks: vi.fn(() => [
diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts
index 776f29944b..bfaaa5a0e0 100644
--- a/apps/sim/blocks/blocks/agent.ts
+++ b/apps/sim/blocks/blocks/agent.ts
@@ -1,6 +1,7 @@
import { AgentIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
+import type { BlockConfig } from '@/blocks/types'
import {
getAllModelProviders,
getBaseModelProviders,
@@ -13,7 +14,6 @@ import {
} from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
const logger = createLogger('AgentBlock')
diff --git a/apps/sim/blocks/blocks/airtable.ts b/apps/sim/blocks/blocks/airtable.ts
index 676c69d3ea..eb99382499 100644
--- a/apps/sim/blocks/blocks/airtable.ts
+++ b/apps/sim/blocks/blocks/airtable.ts
@@ -1,19 +1,6 @@
import { AirtableIcon } from '@/components/icons'
-import type {
- AirtableCreateResponse,
- AirtableGetResponse,
- AirtableListResponse,
- AirtableUpdateMultipleResponse,
- AirtableUpdateResponse,
-} from '@/tools/airtable/types'
-import type { BlockConfig } from '../types'
-
-type AirtableResponse =
- | AirtableListResponse
- | AirtableGetResponse
- | AirtableCreateResponse
- | AirtableUpdateResponse
- | AirtableUpdateMultipleResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { AirtableResponse } from '@/tools/airtable/types'
export const AirtableBlock: BlockConfig = {
type: 'airtable',
diff --git a/apps/sim/blocks/blocks/api.ts b/apps/sim/blocks/blocks/api.ts
index 98b1752817..95c7ac171c 100644
--- a/apps/sim/blocks/blocks/api.ts
+++ b/apps/sim/blocks/blocks/api.ts
@@ -1,6 +1,6 @@
import { ApiIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { RequestResponse } from '@/tools/http/types'
-import type { BlockConfig } from '../types'
export const ApiBlock: BlockConfig = {
type: 'api',
diff --git a/apps/sim/blocks/blocks/browser_use.ts b/apps/sim/blocks/blocks/browser_use.ts
index 33bc2feabd..adce37ab77 100644
--- a/apps/sim/blocks/blocks/browser_use.ts
+++ b/apps/sim/blocks/blocks/browser_use.ts
@@ -1,15 +1,6 @@
import { BrowserUseIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface BrowserUseResponse extends ToolResponse {
- output: {
- id: string
- success: boolean
- output: any
- steps: any[]
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { BrowserUseResponse } from '@/tools/browser_use/types'
export const BrowserUseBlock: BlockConfig = {
type: 'browser_use',
diff --git a/apps/sim/blocks/blocks/clay.ts b/apps/sim/blocks/blocks/clay.ts
index 092ce98aaa..9457c45ad7 100644
--- a/apps/sim/blocks/blocks/clay.ts
+++ b/apps/sim/blocks/blocks/clay.ts
@@ -1,6 +1,6 @@
import { ClayIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { ClayPopulateResponse } from '@/tools/clay/types'
-import type { BlockConfig } from '../types'
export const ClayBlock: BlockConfig = {
type: 'clay',
diff --git a/apps/sim/blocks/blocks/condition.ts b/apps/sim/blocks/blocks/condition.ts
index 91601094d0..541c7a159c 100644
--- a/apps/sim/blocks/blocks/condition.ts
+++ b/apps/sim/blocks/blocks/condition.ts
@@ -1,5 +1,5 @@
import { ConditionalIcon } from '@/components/icons'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
interface ConditionBlockOutput {
success: boolean
diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts
index c6bfd743cb..2bef786e5b 100644
--- a/apps/sim/blocks/blocks/confluence.ts
+++ b/apps/sim/blocks/blocks/confluence.ts
@@ -1,8 +1,6 @@
import { ConfluenceIcon } from '@/components/icons'
-import type { ConfluenceRetrieveResponse, ConfluenceUpdateResponse } from '@/tools/confluence/types'
-import type { BlockConfig } from '../types'
-
-type ConfluenceResponse = ConfluenceRetrieveResponse | ConfluenceUpdateResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { ConfluenceResponse } from '@/tools/confluence/types'
export const ConfluenceBlock: BlockConfig = {
type: 'confluence',
@@ -48,7 +46,7 @@ export const ConfluenceBlock: BlockConfig = {
],
placeholder: 'Select Confluence account',
},
- // Use file-selector component for page selection
+ // Page selector (basic mode)
{
id: 'pageId',
title: 'Select Page',
@@ -57,6 +55,16 @@ export const ConfluenceBlock: BlockConfig = {
provider: 'confluence',
serviceId: 'confluence',
placeholder: 'Select Confluence page',
+ mode: 'basic',
+ },
+ // Manual page ID input (advanced mode)
+ {
+ id: 'manualPageId',
+ title: 'Page ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Confluence page ID',
+ mode: 'advanced',
},
// Update page fields
{
@@ -90,10 +98,18 @@ export const ConfluenceBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, ...rest } = params
+ const { credential, pageId, manualPageId, ...rest } = params
+
+ // Use the selected page ID or the manually entered one
+ const effectivePageId = (pageId || manualPageId || '').trim()
+
+ if (!effectivePageId) {
+ throw new Error('Page ID is required. Please select a page or enter a page ID manually.')
+ }
return {
accessToken: credential,
+ pageId: effectivePageId,
...rest,
}
},
@@ -103,7 +119,8 @@ export const ConfluenceBlock: BlockConfig = {
operation: { type: 'string', required: true },
domain: { type: 'string', required: true },
credential: { type: 'string', required: true },
- pageId: { type: 'string', required: true },
+ pageId: { type: 'string', required: false },
+ manualPageId: { type: 'string', required: false },
// Update operation inputs
title: { type: 'string', required: false },
content: { type: 'string', required: false },
diff --git a/apps/sim/blocks/blocks/discord.ts b/apps/sim/blocks/blocks/discord.ts
index 9206c2a6c6..b672c960e9 100644
--- a/apps/sim/blocks/blocks/discord.ts
+++ b/apps/sim/blocks/blocks/discord.ts
@@ -1,6 +1,6 @@
import { DiscordIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { DiscordResponse } from '@/tools/discord/types'
-import type { BlockConfig } from '../types'
export const DiscordBlock: BlockConfig = {
type: 'discord',
@@ -32,6 +32,7 @@ export const DiscordBlock: BlockConfig = {
placeholder: 'Enter Discord bot token',
password: true,
},
+ // Server selector (basic mode)
{
id: 'serverId',
title: 'Server',
@@ -40,11 +41,26 @@ export const DiscordBlock: BlockConfig = {
provider: 'discord',
serviceId: 'discord',
placeholder: 'Select Discord server',
+ mode: 'basic',
condition: {
field: 'operation',
value: ['discord_send_message', 'discord_get_messages', 'discord_get_server'],
},
},
+ // Manual server ID input (advanced mode)
+ {
+ id: 'manualServerId',
+ title: 'Server ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Discord server ID',
+ mode: 'advanced',
+ condition: {
+ field: 'operation',
+ value: ['discord_send_message', 'discord_get_messages', 'discord_get_server'],
+ },
+ },
+ // Channel selector (basic mode)
{
id: 'channelId',
title: 'Channel',
@@ -53,6 +69,17 @@ export const DiscordBlock: BlockConfig = {
provider: 'discord',
serviceId: 'discord',
placeholder: 'Select Discord channel',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['discord_send_message', 'discord_get_messages'] },
+ },
+ // Manual channel ID input (advanced mode)
+ {
+ id: 'manualChannelId',
+ title: 'Channel ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Discord channel ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['discord_send_message', 'discord_get_messages'] },
},
{
@@ -108,25 +135,56 @@ export const DiscordBlock: BlockConfig = {
if (!params.botToken) throw new Error('Bot token required for this operation')
commonParams.botToken = params.botToken
+ // Handle server ID (selector or manual)
+ const effectiveServerId = (params.serverId || params.manualServerId || '').trim()
+
+ // Handle channel ID (selector or manual)
+ const effectiveChannelId = (params.channelId || params.manualChannelId || '').trim()
+
switch (params.operation) {
case 'discord_send_message':
+ if (!effectiveServerId) {
+ throw new Error(
+ 'Server ID is required. Please select a server or enter a server ID manually.'
+ )
+ }
+ if (!effectiveChannelId) {
+ throw new Error(
+ 'Channel ID is required. Please select a channel or enter a channel ID manually.'
+ )
+ }
return {
...commonParams,
- serverId: params.serverId,
- channelId: params.channelId,
+ serverId: effectiveServerId,
+ channelId: effectiveChannelId,
content: params.content,
}
case 'discord_get_messages':
+ if (!effectiveServerId) {
+ throw new Error(
+ 'Server ID is required. Please select a server or enter a server ID manually.'
+ )
+ }
+ if (!effectiveChannelId) {
+ throw new Error(
+ 'Channel ID is required. Please select a channel or enter a channel ID manually.'
+ )
+ }
return {
...commonParams,
- serverId: params.serverId,
- channelId: params.channelId,
+ serverId: effectiveServerId,
+ channelId: effectiveChannelId,
limit: params.limit ? Math.min(Math.max(1, Number(params.limit)), 100) : 10,
}
case 'discord_get_server':
+ if (!effectiveServerId) {
+ throw new Error(
+ 'Server ID is required. Please select a server or enter a server ID manually.'
+ )
+ }
return {
...commonParams,
- serverId: params.serverId,
+ serverId: effectiveServerId,
}
case 'discord_get_user':
return {
@@ -143,7 +201,9 @@ export const DiscordBlock: BlockConfig = {
operation: { type: 'string', required: true },
botToken: { type: 'string', required: true },
serverId: { type: 'string', required: false },
+ manualServerId: { type: 'string', required: false },
channelId: { type: 'string', required: false },
+ manualChannelId: { type: 'string', required: false },
content: { type: 'string', required: false },
limit: { type: 'number', required: false },
userId: { type: 'string', required: false },
diff --git a/apps/sim/blocks/blocks/elevenlabs.ts b/apps/sim/blocks/blocks/elevenlabs.ts
index 73a2a7cafc..daa386473b 100644
--- a/apps/sim/blocks/blocks/elevenlabs.ts
+++ b/apps/sim/blocks/blocks/elevenlabs.ts
@@ -1,12 +1,6 @@
import { ElevenLabsIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface ElevenLabsBlockResponse extends ToolResponse {
- output: {
- audioUrl: string
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { ElevenLabsBlockResponse } from '@/tools/elevenlabs/types'
export const ElevenLabsBlock: BlockConfig = {
type: 'elevenlabs',
diff --git a/apps/sim/blocks/blocks/evaluator.ts b/apps/sim/blocks/blocks/evaluator.ts
index 013e6f179f..d2e559f1f8 100644
--- a/apps/sim/blocks/blocks/evaluator.ts
+++ b/apps/sim/blocks/blocks/evaluator.ts
@@ -1,11 +1,11 @@
import { ChartBarIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
+import type { BlockConfig, ParamType } from '@/blocks/types'
import type { ProviderId } from '@/providers/types'
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig, ParamType } from '../types'
const logger = createLogger('EvaluatorBlock')
diff --git a/apps/sim/blocks/blocks/exa.ts b/apps/sim/blocks/blocks/exa.ts
index 754d3ea2d8..f49396f67e 100644
--- a/apps/sim/blocks/blocks/exa.ts
+++ b/apps/sim/blocks/blocks/exa.ts
@@ -1,17 +1,6 @@
import { ExaAIIcon } from '@/components/icons'
-import type {
- ExaAnswerResponse,
- ExaFindSimilarLinksResponse,
- ExaGetContentsResponse,
- ExaSearchResponse,
-} from '@/tools/exa/types'
-import type { BlockConfig } from '../types'
-
-type ExaResponse =
- | ExaSearchResponse
- | ExaGetContentsResponse
- | ExaFindSimilarLinksResponse
- | ExaAnswerResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { ExaResponse } from '@/tools/exa/types'
export const ExaBlock: BlockConfig = {
type: 'exa',
@@ -24,7 +13,6 @@ export const ExaBlock: BlockConfig = {
bgColor: '#1F40ED',
icon: ExaAIIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
@@ -35,6 +23,7 @@ export const ExaBlock: BlockConfig = {
{ label: 'Get Contents', id: 'exa_get_contents' },
{ label: 'Find Similar Links', id: 'exa_find_similar_links' },
{ label: 'Answer', id: 'exa_answer' },
+ { label: 'Research', id: 'exa_research' },
],
value: () => 'exa_search',
},
@@ -140,6 +129,22 @@ export const ExaBlock: BlockConfig = {
layout: 'full',
condition: { field: 'operation', value: 'exa_answer' },
},
+ // Research operation inputs
+ {
+ id: 'query',
+ title: 'Research Query',
+ type: 'long-input',
+ layout: 'full',
+ placeholder: 'Enter your research topic or question...',
+ condition: { field: 'operation', value: 'exa_research' },
+ },
+ {
+ id: 'includeText',
+ title: 'Include Full Text',
+ type: 'switch',
+ layout: 'full',
+ condition: { field: 'operation', value: 'exa_research' },
+ },
// API Key (common)
{
id: 'apiKey',
@@ -151,7 +156,13 @@ export const ExaBlock: BlockConfig = {
},
],
tools: {
- access: ['exa_search', 'exa_get_contents', 'exa_find_similar_links', 'exa_answer'],
+ access: [
+ 'exa_search',
+ 'exa_get_contents',
+ 'exa_find_similar_links',
+ 'exa_answer',
+ 'exa_research',
+ ],
config: {
tool: (params) => {
// Convert numResults to a number for operations that use it
@@ -168,6 +179,8 @@ export const ExaBlock: BlockConfig = {
return 'exa_find_similar_links'
case 'exa_answer':
return 'exa_answer'
+ case 'exa_research':
+ return 'exa_research'
default:
return 'exa_search'
}
@@ -197,5 +210,7 @@ export const ExaBlock: BlockConfig = {
// Answer output
answer: 'string',
citations: 'json',
+ // Research output
+ research: 'json',
},
}
diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts
index 460c6f4992..17fe150674 100644
--- a/apps/sim/blocks/blocks/file.ts
+++ b/apps/sim/blocks/blocks/file.ts
@@ -1,8 +1,8 @@
import { DocumentIcon } from '@/components/icons'
import { isProd } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
+import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '@/blocks/types'
import type { FileParserOutput } from '@/tools/file/types'
-import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types'
const logger = createLogger('FileBlock')
diff --git a/apps/sim/blocks/blocks/firecrawl.ts b/apps/sim/blocks/blocks/firecrawl.ts
index 3eb3213cf3..a730b6568a 100644
--- a/apps/sim/blocks/blocks/firecrawl.ts
+++ b/apps/sim/blocks/blocks/firecrawl.ts
@@ -1,8 +1,6 @@
import { FirecrawlIcon } from '@/components/icons'
-import type { ScrapeResponse, SearchResponse } from '@/tools/firecrawl/types'
-import type { BlockConfig } from '../types'
-
-type FirecrawlResponse = ScrapeResponse | SearchResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { FirecrawlResponse } from '@/tools/firecrawl/types'
export const FirecrawlBlock: BlockConfig = {
type: 'firecrawl',
@@ -23,6 +21,7 @@ export const FirecrawlBlock: BlockConfig = {
options: [
{ label: 'Scrape', id: 'scrape' },
{ label: 'Search', id: 'search' },
+ { label: 'Crawl', id: 'crawl' },
],
value: () => 'scrape',
},
@@ -31,10 +30,10 @@ export const FirecrawlBlock: BlockConfig = {
title: 'Website URL',
type: 'short-input',
layout: 'full',
- placeholder: 'Enter the webpage URL to scrape',
+ placeholder: 'Enter the website URL',
condition: {
field: 'operation',
- value: 'scrape',
+ value: ['scrape', 'crawl'],
},
},
{
@@ -47,6 +46,17 @@ export const FirecrawlBlock: BlockConfig = {
value: 'scrape',
},
},
+ {
+ id: 'limit',
+ title: 'Page Limit',
+ type: 'short-input',
+ layout: 'half',
+ placeholder: '100',
+ condition: {
+ field: 'operation',
+ value: 'crawl',
+ },
+ },
{
id: 'query',
title: 'Search Query',
@@ -68,7 +78,7 @@ export const FirecrawlBlock: BlockConfig = {
},
],
tools: {
- access: ['firecrawl_scrape', 'firecrawl_search'],
+ access: ['firecrawl_scrape', 'firecrawl_search', 'firecrawl_crawl'],
config: {
tool: (params) => {
switch (params.operation) {
@@ -76,16 +86,32 @@ export const FirecrawlBlock: BlockConfig = {
return 'firecrawl_scrape'
case 'search':
return 'firecrawl_search'
+ case 'crawl':
+ return 'firecrawl_crawl'
default:
return 'firecrawl_scrape'
}
},
+ params: (params) => {
+ const { operation, limit, ...rest } = params
+
+ switch (operation) {
+ case 'crawl':
+ return {
+ ...rest,
+ limit: limit ? Number.parseInt(limit) : undefined,
+ }
+ default:
+ return rest
+ }
+ },
},
},
inputs: {
apiKey: { type: 'string', required: true },
operation: { type: 'string', required: true },
url: { type: 'string', required: false },
+ limit: { type: 'string', required: false },
query: { type: 'string', required: false },
scrapeOptions: { type: 'json', required: false },
},
@@ -97,5 +123,9 @@ export const FirecrawlBlock: BlockConfig = {
// Search output
data: 'json',
warning: 'any',
+ // Crawl output
+ pages: 'json',
+ total: 'number',
+ creditsUsed: 'number',
},
}
diff --git a/apps/sim/blocks/blocks/function.ts b/apps/sim/blocks/blocks/function.ts
index f8706924aa..3277eb30e5 100644
--- a/apps/sim/blocks/blocks/function.ts
+++ b/apps/sim/blocks/blocks/function.ts
@@ -1,6 +1,6 @@
import { CodeIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { CodeExecutionOutput } from '@/tools/function/types'
-import type { BlockConfig } from '../types'
export const FunctionBlock: BlockConfig = {
type: 'function',
diff --git a/apps/sim/blocks/blocks/github.ts b/apps/sim/blocks/blocks/github.ts
index db57259689..545dbbea33 100644
--- a/apps/sim/blocks/blocks/github.ts
+++ b/apps/sim/blocks/blocks/github.ts
@@ -1,17 +1,6 @@
import { GithubIcon } from '@/components/icons'
-import type {
- CreateCommentResponse,
- LatestCommitResponse,
- PullRequestResponse,
- RepoInfoResponse,
-} from '@/tools/github/types'
-import type { BlockConfig } from '../types'
-
-type GitHubResponse =
- | PullRequestResponse
- | CreateCommentResponse
- | LatestCommitResponse
- | RepoInfoResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { GitHubResponse } from '@/tools/github/types'
export const GitHubBlock: BlockConfig = {
type: 'github',
diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts
index 18ad1d968c..68f0fb2182 100644
--- a/apps/sim/blocks/blocks/gmail.ts
+++ b/apps/sim/blocks/blocks/gmail.ts
@@ -1,6 +1,6 @@
import { GmailIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { GmailToolResponse } from '@/tools/gmail/types'
-import type { BlockConfig } from '../types'
export const GmailBlock: BlockConfig = {
type: 'gmail',
@@ -67,7 +67,7 @@ export const GmailBlock: BlockConfig = {
placeholder: 'Email content',
condition: { field: 'operation', value: ['send_gmail', 'draft_gmail'] },
},
- // Read Email Fields - Add folder selector
+ // Label/folder selector (basic mode)
{
id: 'folder',
title: 'Label',
@@ -80,6 +80,17 @@ export const GmailBlock: BlockConfig = {
'https://www.googleapis.com/auth/gmail.labels',
],
placeholder: 'Select Gmail label/folder',
+ mode: 'basic',
+ condition: { field: 'operation', value: 'read_gmail' },
+ },
+ // Manual label/folder input (advanced mode)
+ {
+ id: 'manualFolder',
+ title: 'Label/Folder',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Gmail label name (e.g., INBOX, SENT, or custom label)',
+ mode: 'advanced',
condition: { field: 'operation', value: 'read_gmail' },
},
{
@@ -141,11 +152,14 @@ export const GmailBlock: BlockConfig = {
},
params: (params) => {
// Pass the credential directly from the credential field
- const { credential, ...rest } = params
+ const { credential, folder, manualFolder, ...rest } = params
+
+ // Handle folder input (selector or manual)
+ const effectiveFolder = (folder || manualFolder || '').trim()
// Ensure folder is always provided for read_gmail operation
if (rest.operation === 'read_gmail') {
- rest.folder = rest.folder || 'INBOX'
+ rest.folder = effectiveFolder || 'INBOX'
}
return {
@@ -164,6 +178,7 @@ export const GmailBlock: BlockConfig = {
body: { type: 'string', required: false },
// Read operation inputs
folder: { type: 'string', required: false },
+ manualFolder: { type: 'string', required: false },
messageId: { type: 'string', required: false },
unreadOnly: { type: 'boolean', required: false },
// Search operation inputs
diff --git a/apps/sim/blocks/blocks/google.ts b/apps/sim/blocks/blocks/google.ts
index 4c8b3b18db..b894dac17a 100644
--- a/apps/sim/blocks/blocks/google.ts
+++ b/apps/sim/blocks/blocks/google.ts
@@ -1,24 +1,6 @@
import { GoogleIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface GoogleSearchResponse extends ToolResponse {
- output: {
- items: Array<{
- title: string
- link: string
- snippet: string
- displayLink?: string
- pagemap?: Record
- }>
- searchInformation: {
- totalResults: string
- searchTime: number
- formattedSearchTime: string
- formattedTotalResults: string
- }
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { GoogleSearchResponse } from '@/tools/google/types'
export const GoogleSearchBlock: BlockConfig = {
type: 'google_search',
diff --git a/apps/sim/blocks/blocks/google_calendar.ts b/apps/sim/blocks/blocks/google_calendar.ts
index 2b9f13679d..2620651cca 100644
--- a/apps/sim/blocks/blocks/google_calendar.ts
+++ b/apps/sim/blocks/blocks/google_calendar.ts
@@ -1,19 +1,6 @@
import { GoogleCalendarIcon } from '@/components/icons'
-import type {
- GoogleCalendarCreateResponse,
- GoogleCalendarGetResponse,
- GoogleCalendarInviteResponse,
- GoogleCalendarListResponse,
- GoogleCalendarQuickAddResponse,
-} from '@/tools/google_calendar/types'
-import type { BlockConfig } from '../types'
-
-type GoogleCalendarResponse =
- | GoogleCalendarCreateResponse
- | GoogleCalendarListResponse
- | GoogleCalendarGetResponse
- | GoogleCalendarQuickAddResponse
- | GoogleCalendarInviteResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { GoogleCalendarResponse } from '@/tools/google_calendar/types'
export const GoogleCalendarBlock: BlockConfig = {
type: 'google_calendar',
@@ -49,6 +36,7 @@ export const GoogleCalendarBlock: BlockConfig = {
requiredScopes: ['https://www.googleapis.com/auth/calendar'],
placeholder: 'Select Google Calendar account',
},
+ // Calendar selector (basic mode)
{
id: 'calendarId',
title: 'Calendar',
@@ -58,6 +46,16 @@ export const GoogleCalendarBlock: BlockConfig = {
serviceId: 'google-calendar',
requiredScopes: ['https://www.googleapis.com/auth/calendar'],
placeholder: 'Select calendar',
+ mode: 'basic',
+ },
+ // Manual calendar ID input (advanced mode)
+ {
+ id: 'manualCalendarId',
+ title: 'Calendar ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter calendar ID (e.g., primary or calendar@gmail.com)',
+ mode: 'advanced',
},
// Create Event Fields
@@ -220,9 +218,23 @@ export const GoogleCalendarBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, operation, attendees, replaceExisting, ...rest } = params
+ const {
+ credential,
+ operation,
+ attendees,
+ replaceExisting,
+ calendarId,
+ manualCalendarId,
+ ...rest
+ } = params
+
+ // Handle calendar ID (selector or manual)
+ const effectiveCalendarId = (calendarId || manualCalendarId || '').trim()
- const processedParams = { ...rest }
+ const processedParams: Record = {
+ ...rest,
+ calendarId: effectiveCalendarId || 'primary',
+ }
// Convert comma-separated attendees string to array, only if it has content
if (attendees && typeof attendees === 'string' && attendees.trim().length > 0) {
@@ -258,6 +270,7 @@ export const GoogleCalendarBlock: BlockConfig = {
operation: { type: 'string', required: true },
credential: { type: 'string', required: true },
calendarId: { type: 'string', required: false },
+ manualCalendarId: { type: 'string', required: false },
// Create operation inputs
summary: { type: 'string', required: false },
diff --git a/apps/sim/blocks/blocks/google_docs.ts b/apps/sim/blocks/blocks/google_docs.ts
index 7c012cf2bd..b78280473c 100644
--- a/apps/sim/blocks/blocks/google_docs.ts
+++ b/apps/sim/blocks/blocks/google_docs.ts
@@ -1,15 +1,6 @@
import { GoogleDocsIcon } from '@/components/icons'
-import type {
- GoogleDocsCreateResponse,
- GoogleDocsReadResponse,
- GoogleDocsWriteResponse,
-} from '@/tools/google_docs/types'
-import type { BlockConfig } from '../types'
-
-type GoogleDocsResponse =
- | GoogleDocsReadResponse
- | GoogleDocsWriteResponse
- | GoogleDocsCreateResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { GoogleDocsResponse } from '@/tools/google_docs/types'
export const GoogleDocsBlock: BlockConfig = {
type: 'google_docs',
@@ -45,7 +36,7 @@ export const GoogleDocsBlock: BlockConfig = {
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
placeholder: 'Select Google account',
},
- // Document Selector for read operation
+ // Document selector (basic mode)
{
id: 'documentId',
title: 'Select Document',
@@ -56,38 +47,18 @@ export const GoogleDocsBlock: BlockConfig = {
requiredScopes: [],
mimeType: 'application/vnd.google-apps.document',
placeholder: 'Select a document',
- condition: { field: 'operation', value: 'read' },
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read', 'write'] },
},
- // Document Selector for write operation
- {
- id: 'documentId',
- title: 'Select Document',
- type: 'file-selector',
- layout: 'full',
- provider: 'google-drive',
- serviceId: 'google-drive',
- requiredScopes: [],
- mimeType: 'application/vnd.google-apps.document',
- placeholder: 'Select a document',
- condition: { field: 'operation', value: 'write' },
- },
- // Manual Document ID for read operation
+ // Manual document ID input (advanced mode)
{
id: 'manualDocumentId',
- title: 'Or Enter Document ID Manually',
+ title: 'Document ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the document',
- condition: { field: 'operation', value: 'read' },
- },
- // Manual Document ID for write operation
- {
- id: 'manualDocumentId',
- title: 'Or Enter Document ID Manually',
- type: 'short-input',
- layout: 'full',
- placeholder: 'ID of the document',
- condition: { field: 'operation', value: 'write' },
+ placeholder: 'Enter document ID',
+ mode: 'advanced',
+ condition: { field: 'operation', value: ['read', 'write'] },
},
// Create-specific Fields
{
@@ -98,7 +69,7 @@ export const GoogleDocsBlock: BlockConfig = {
placeholder: 'Enter title for the new document',
condition: { field: 'operation', value: 'create' },
},
- // Folder Selector for create operation
+ // Folder selector (basic mode)
{
id: 'folderSelector',
title: 'Select Parent Folder',
@@ -109,15 +80,17 @@ export const GoogleDocsBlock: BlockConfig = {
requiredScopes: [],
mimeType: 'application/vnd.google-apps.folder',
placeholder: 'Select a parent folder',
+ mode: 'basic',
condition: { field: 'operation', value: 'create' },
},
- // Manual Folder ID for create operation
+ // Manual folder ID input (advanced mode)
{
id: 'folderId',
- title: 'Or Enter Parent Folder ID Manually',
+ title: 'Parent Folder ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the parent folder (leave empty for root folder)',
+ placeholder: 'Enter parent folder ID (leave empty for root folder)',
+ mode: 'advanced',
condition: { field: 'operation', value: 'create' },
},
// Content Field for write operation
diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts
index 6ca9ccabdc..55cd2ea0b4 100644
--- a/apps/sim/blocks/blocks/google_drive.ts
+++ b/apps/sim/blocks/blocks/google_drive.ts
@@ -1,15 +1,6 @@
import { GoogleDriveIcon } from '@/components/icons'
-import type {
- GoogleDriveGetContentResponse,
- GoogleDriveListResponse,
- GoogleDriveUploadResponse,
-} from '@/tools/google_drive/types'
-import type { BlockConfig } from '../types'
-
-type GoogleDriveResponse =
- | GoogleDriveUploadResponse
- | GoogleDriveGetContentResponse
- | GoogleDriveListResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { GoogleDriveResponse } from '@/tools/google_drive/types'
export const GoogleDriveBlock: BlockConfig = {
type: 'google_drive',
@@ -87,18 +78,17 @@ export const GoogleDriveBlock: BlockConfig = {
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
mimeType: 'application/vnd.google-apps.folder',
placeholder: 'Select a parent folder',
+ mode: 'basic',
condition: { field: 'operation', value: 'upload' },
},
{
- id: 'folderId',
- title: 'Or Enter Parent Folder ID Manually',
+ id: 'manualFolderId',
+ title: 'Parent Folder ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the parent folder (leave empty for root folder)',
- condition: {
- field: 'operation',
- value: 'upload',
- },
+ placeholder: 'Enter parent folder ID (leave empty for root folder)',
+ mode: 'advanced',
+ condition: { field: 'operation', value: 'upload' },
},
// Get Content Fields
// {
@@ -160,21 +150,20 @@ export const GoogleDriveBlock: BlockConfig = {
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
mimeType: 'application/vnd.google-apps.folder',
placeholder: 'Select a parent folder',
+ mode: 'basic',
condition: { field: 'operation', value: 'create_folder' },
},
- // Manual Folder ID input (shown only when no folder is selected)
+ // Manual Folder ID input (advanced mode)
{
- id: 'folderId',
- title: 'Or Enter Parent Folder ID Manually',
+ id: 'manualFolderId',
+ title: 'Parent Folder ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the parent folder (leave empty for root folder)',
- condition: {
- field: 'operation',
- value: 'create_folder',
- },
+ placeholder: 'Enter parent folder ID (leave empty for root folder)',
+ mode: 'advanced',
+ condition: { field: 'operation', value: 'create_folder' },
},
- // List Fields - Folder Selector
+ // List Fields - Folder Selector (basic mode)
{
id: 'folderSelector',
title: 'Select Folder',
@@ -185,19 +174,18 @@ export const GoogleDriveBlock: BlockConfig = {
requiredScopes: ['https://www.googleapis.com/auth/drive.file'],
mimeType: 'application/vnd.google-apps.folder',
placeholder: 'Select a folder to list files from',
+ mode: 'basic',
condition: { field: 'operation', value: 'list' },
},
- // Manual Folder ID input (shown only when no folder is selected)
+ // Manual Folder ID input (advanced mode)
{
- id: 'folderId',
- title: 'Or Enter Folder ID Manually',
+ id: 'manualFolderId',
+ title: 'Folder ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the folder to list (leave empty for root folder)',
- condition: {
- field: 'operation',
- value: 'list',
- },
+ placeholder: 'Enter folder ID (leave empty for root folder)',
+ mode: 'advanced',
+ condition: { field: 'operation', value: 'list' },
},
{
id: 'query',
@@ -234,14 +222,14 @@ export const GoogleDriveBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, folderId, folderSelector, mimeType, ...rest } = params
+ const { credential, folderSelector, manualFolderId, mimeType, ...rest } = params
- // Use folderSelector if provided, otherwise use folderId
- const effectiveFolderId = folderSelector || folderId || ''
+ // Use folderSelector if provided, otherwise use manualFolderId
+ const effectiveFolderId = (folderSelector || manualFolderId || '').trim()
return {
accessToken: credential,
- folderId: effectiveFolderId.trim(),
+ folderId: effectiveFolderId,
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
mimeType: mimeType,
...rest,
@@ -259,8 +247,8 @@ export const GoogleDriveBlock: BlockConfig = {
// Get Content operation inputs
// fileId: { type: 'string', required: false },
// List operation inputs
- folderId: { type: 'string', required: false },
folderSelector: { type: 'string', required: false },
+ manualFolderId: { type: 'string', required: false },
query: { type: 'string', required: false },
pageSize: { type: 'number', required: false },
},
diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts
index 4d8c959d23..13ee4728f9 100644
--- a/apps/sim/blocks/blocks/google_sheets.ts
+++ b/apps/sim/blocks/blocks/google_sheets.ts
@@ -1,17 +1,6 @@
import { GoogleSheetsIcon } from '@/components/icons'
-import type {
- GoogleSheetsAppendResponse,
- GoogleSheetsReadResponse,
- GoogleSheetsUpdateResponse,
- GoogleSheetsWriteResponse,
-} from '@/tools/google_sheets/types'
-import type { BlockConfig } from '../types'
-
-type GoogleSheetsResponse =
- | GoogleSheetsReadResponse
- | GoogleSheetsWriteResponse
- | GoogleSheetsUpdateResponse
- | GoogleSheetsAppendResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { GoogleSheetsResponse } from '@/tools/google_sheets/types'
export const GoogleSheetsBlock: BlockConfig = {
type: 'google_sheets',
@@ -59,15 +48,16 @@ export const GoogleSheetsBlock: BlockConfig = {
requiredScopes: [],
mimeType: 'application/vnd.google-apps.spreadsheet',
placeholder: 'Select a spreadsheet',
+ mode: 'basic',
},
- // Manual Spreadsheet ID (hidden by default)
+ // Manual Spreadsheet ID (advanced mode)
{
id: 'manualSpreadsheetId',
- title: 'Or Enter Spreadsheet ID Manually',
+ title: 'Spreadsheet ID',
type: 'short-input',
layout: 'full',
placeholder: 'ID of the spreadsheet (from URL)',
- condition: { field: 'spreadsheetId', value: '' },
+ mode: 'advanced',
},
// Range
{
diff --git a/apps/sim/blocks/blocks/huggingface.ts b/apps/sim/blocks/blocks/huggingface.ts
index fa88698e5f..73c7df5c14 100644
--- a/apps/sim/blocks/blocks/huggingface.ts
+++ b/apps/sim/blocks/blocks/huggingface.ts
@@ -1,6 +1,6 @@
import { HuggingFaceIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { HuggingFaceChatResponse } from '@/tools/huggingface/types'
-import type { BlockConfig } from '../types'
export const HuggingFaceBlock: BlockConfig = {
type: 'huggingface',
diff --git a/apps/sim/blocks/blocks/image_generator.ts b/apps/sim/blocks/blocks/image_generator.ts
index 2f6cffba4f..0a63b7eada 100644
--- a/apps/sim/blocks/blocks/image_generator.ts
+++ b/apps/sim/blocks/blocks/image_generator.ts
@@ -1,6 +1,6 @@
import { ImageIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { DalleResponse } from '@/tools/openai/types'
-import type { BlockConfig } from '../types'
export const ImageGeneratorBlock: BlockConfig = {
type: 'image_generator',
diff --git a/apps/sim/blocks/blocks/jina.ts b/apps/sim/blocks/blocks/jina.ts
index 0275dd150b..5724ebd2dd 100644
--- a/apps/sim/blocks/blocks/jina.ts
+++ b/apps/sim/blocks/blocks/jina.ts
@@ -1,6 +1,6 @@
import { JinaAIIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { ReadUrlResponse } from '@/tools/jina/types'
-import type { BlockConfig } from '../types'
export const JinaBlock: BlockConfig = {
type: 'jina',
diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts
index 3ec546d1f6..dffb0f3658 100644
--- a/apps/sim/blocks/blocks/jira.ts
+++ b/apps/sim/blocks/blocks/jira.ts
@@ -1,17 +1,6 @@
import { JiraIcon } from '@/components/icons'
-import type {
- JiraRetrieveResponse,
- JiraRetrieveResponseBulk,
- JiraUpdateResponse,
- JiraWriteResponse,
-} from '@/tools/jira/types'
-import type { BlockConfig } from '../types'
-
-type JiraResponse =
- | JiraRetrieveResponse
- | JiraUpdateResponse
- | JiraWriteResponse
- | JiraRetrieveResponseBulk
+import type { BlockConfig } from '@/blocks/types'
+import type { JiraResponse } from '@/tools/jira/types'
export const JiraBlock: BlockConfig = {
type: 'jira',
@@ -24,7 +13,6 @@ export const JiraBlock: BlockConfig = {
bgColor: '#E0E0E0',
icon: JiraIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
@@ -62,7 +50,7 @@ export const JiraBlock: BlockConfig = {
],
placeholder: 'Select Jira account',
},
- // Use file-selector component for issue selection
+ // Project selector (basic mode)
{
id: 'projectId',
title: 'Select Project',
@@ -71,7 +59,18 @@ export const JiraBlock: BlockConfig = {
provider: 'jira',
serviceId: 'jira',
placeholder: 'Select Jira project',
+ mode: 'basic',
+ },
+ // Manual project ID input (advanced mode)
+ {
+ id: 'manualProjectId',
+ title: 'Project ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Jira project ID',
+ mode: 'advanced',
},
+ // Issue selector (basic mode)
{
id: 'issueKey',
title: 'Select Issue',
@@ -81,6 +80,17 @@ export const JiraBlock: BlockConfig = {
serviceId: 'jira',
placeholder: 'Select Jira issue',
condition: { field: 'operation', value: ['read', 'update'] },
+ mode: 'basic',
+ },
+ // Manual issue key input (advanced mode)
+ {
+ id: 'manualIssueKey',
+ title: 'Issue Key',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Jira issue key',
+ condition: { field: 'operation', value: ['read', 'update'] },
+ mode: 'advanced',
},
{
id: 'summary',
@@ -117,18 +127,32 @@ export const JiraBlock: BlockConfig = {
}
},
params: (params) => {
+ const { credential, projectId, manualProjectId, issueKey, manualIssueKey, ...rest } = params
+
// Base params that are always needed
const baseParams = {
- accessToken: params.credential,
+ accessToken: credential,
domain: params.domain,
}
+ // Use the selected project ID or the manually entered one
+ const effectiveProjectId = (projectId || manualProjectId || '').trim()
+
+ // Use the selected issue key or the manually entered one
+ const effectiveIssueKey = (issueKey || manualIssueKey || '').trim()
+
// Define allowed parameters for each operation
switch (params.operation) {
case 'write': {
+ if (!effectiveProjectId) {
+ throw new Error(
+ 'Project ID is required. Please select a project or enter a project ID manually.'
+ )
+ }
+
// For write operations, only include write-specific fields
const writeParams = {
- projectId: params.projectId,
+ projectId: effectiveProjectId,
summary: params.summary || '',
description: params.description || '',
issueType: params.issueType || 'Task',
@@ -141,10 +165,21 @@ export const JiraBlock: BlockConfig = {
}
}
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.'
+ )
+ }
+
// For update operations, only include update-specific fields
const updateParams = {
- projectId: params.projectId,
- issueKey: params.issueKey,
+ projectId: effectiveProjectId,
+ issueKey: effectiveIssueKey,
summary: params.summary || '',
description: params.description || '',
}
@@ -155,17 +190,29 @@ export const JiraBlock: BlockConfig = {
}
}
case 'read': {
+ if (!effectiveIssueKey) {
+ throw new Error(
+ 'Issue Key is required. Please select an issue or enter an issue key manually.'
+ )
+ }
+
// For read operations, only include read-specific fields
return {
...baseParams,
- issueKey: params.issueKey,
+ issueKey: effectiveIssueKey,
}
}
case 'read-bulk': {
+ if (!effectiveProjectId) {
+ throw new Error(
+ 'Project ID is required. Please select a project or enter a project ID manually.'
+ )
+ }
+
// For read-bulk operations, only include read-bulk-specific fields
return {
...baseParams,
- projectId: params.projectId,
+ projectId: effectiveProjectId,
}
}
default:
@@ -178,8 +225,10 @@ export const JiraBlock: BlockConfig = {
operation: { type: 'string', required: true },
domain: { type: 'string', required: true },
credential: { type: 'string', required: true },
- issueKey: { type: 'string', required: true },
+ issueKey: { type: 'string', required: false },
projectId: { type: 'string', required: false },
+ manualProjectId: { type: 'string', required: false },
+ manualIssueKey: { type: 'string', required: false },
// Update operation inputs
summary: { type: 'string', required: true },
description: { type: 'string', required: false },
diff --git a/apps/sim/blocks/blocks/knowledge.ts b/apps/sim/blocks/blocks/knowledge.ts
index 365c85f352..e757a19b6b 100644
--- a/apps/sim/blocks/blocks/knowledge.ts
+++ b/apps/sim/blocks/blocks/knowledge.ts
@@ -1,5 +1,5 @@
import { PackageSearchIcon } from '@/components/icons'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
export const KnowledgeBlock: BlockConfig = {
type: 'knowledge',
@@ -26,6 +26,25 @@ export const KnowledgeBlock: BlockConfig = {
return 'knowledge_search'
}
},
+ params: (params) => {
+ // Validate required fields for each operation
+ if (params.operation === 'search' && !params.knowledgeBaseIds) {
+ throw new Error('Knowledge base IDs are required for search operation')
+ }
+ if (
+ (params.operation === 'upload_chunk' || params.operation === 'create_document') &&
+ !params.knowledgeBaseId
+ ) {
+ throw new Error(
+ 'Knowledge base ID is required for upload_chunk and create_document operations'
+ )
+ }
+ if (params.operation === 'upload_chunk' && !params.documentId) {
+ throw new Error('Document ID is required for upload_chunk operation')
+ }
+
+ return params
+ },
},
},
inputs: {
diff --git a/apps/sim/blocks/blocks/linear.ts b/apps/sim/blocks/blocks/linear.ts
index 8e25458114..8e3c352237 100644
--- a/apps/sim/blocks/blocks/linear.ts
+++ b/apps/sim/blocks/blocks/linear.ts
@@ -1,8 +1,6 @@
import { LinearIcon } from '@/components/icons'
-import type { LinearCreateIssueResponse, LinearReadIssuesResponse } from '@/tools/linear/types'
-import type { BlockConfig } from '../types'
-
-type LinearResponse = LinearReadIssuesResponse | LinearCreateIssueResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { LinearResponse } from '@/tools/linear/types'
export const LinearBlock: BlockConfig = {
type: 'linear',
@@ -42,6 +40,7 @@ export const LinearBlock: BlockConfig = {
provider: 'linear',
serviceId: 'linear',
placeholder: 'Select a team',
+ mode: 'basic',
},
{
id: 'projectId',
@@ -51,6 +50,25 @@ export const LinearBlock: BlockConfig = {
provider: 'linear',
serviceId: 'linear',
placeholder: 'Select a project',
+ mode: 'basic',
+ },
+ // Manual team ID input (advanced mode)
+ {
+ id: 'manualTeamId',
+ title: 'Team ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Linear team ID',
+ mode: 'advanced',
+ },
+ // Manual project ID input (advanced mode)
+ {
+ id: 'manualProjectId',
+ title: 'Project ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Linear project ID',
+ mode: 'advanced',
},
{
id: 'title',
@@ -73,19 +91,40 @@ export const LinearBlock: BlockConfig = {
tool: (params) =>
params.operation === 'write' ? 'linear_create_issue' : 'linear_read_issues',
params: (params) => {
+ // Handle team ID (selector or manual)
+ const effectiveTeamId = (params.teamId || params.manualTeamId || '').trim()
+
+ // Handle project ID (selector or manual)
+ const effectiveProjectId = (params.projectId || params.manualProjectId || '').trim()
+
+ if (!effectiveTeamId) {
+ throw new Error('Team ID is required. Please select a team or enter a team ID manually.')
+ }
+ if (!effectiveProjectId) {
+ throw new Error(
+ 'Project ID is required. Please select a project or enter a project ID manually.'
+ )
+ }
+
if (params.operation === 'write') {
+ if (!params.title?.trim()) {
+ throw new Error('Title is required for creating issues.')
+ }
+ if (!params.description?.trim()) {
+ throw new Error('Description is required for creating issues.')
+ }
return {
credential: params.credential,
- teamId: params.teamId,
- projectId: params.projectId,
+ teamId: effectiveTeamId,
+ projectId: effectiveProjectId,
title: params.title,
description: params.description,
}
}
return {
credential: params.credential,
- teamId: params.teamId,
- projectId: params.projectId,
+ teamId: effectiveTeamId,
+ projectId: effectiveProjectId,
}
},
},
@@ -93,8 +132,10 @@ export const LinearBlock: BlockConfig = {
inputs: {
operation: { type: 'string', required: true },
credential: { type: 'string', required: true },
- teamId: { type: 'string', required: true },
- projectId: { type: 'string', required: true },
+ teamId: { type: 'string', required: false },
+ projectId: { type: 'string', required: false },
+ manualTeamId: { type: 'string', required: false },
+ manualProjectId: { type: 'string', required: false },
title: { type: 'string', required: false },
description: { type: 'string', required: false },
},
diff --git a/apps/sim/blocks/blocks/linkup.ts b/apps/sim/blocks/blocks/linkup.ts
index 0cf6f94546..00307bad31 100644
--- a/apps/sim/blocks/blocks/linkup.ts
+++ b/apps/sim/blocks/blocks/linkup.ts
@@ -1,6 +1,6 @@
import { LinkupIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { LinkupSearchToolResponse } from '@/tools/linkup/types'
-import type { BlockConfig } from '../types'
export const LinkupBlock: BlockConfig = {
type: 'linkup',
diff --git a/apps/sim/blocks/blocks/mem0.ts b/apps/sim/blocks/blocks/mem0.ts
index e85bf07978..ce138413d5 100644
--- a/apps/sim/blocks/blocks/mem0.ts
+++ b/apps/sim/blocks/blocks/mem0.ts
@@ -1,6 +1,6 @@
import { Mem0Icon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { Mem0Response } from '@/tools/mem0/types'
-import type { BlockConfig } from '../types'
export const Mem0Block: BlockConfig = {
type: 'mem0',
diff --git a/apps/sim/blocks/blocks/memory.ts b/apps/sim/blocks/blocks/memory.ts
index 59c3ff9704..a6dd1c98cd 100644
--- a/apps/sim/blocks/blocks/memory.ts
+++ b/apps/sim/blocks/blocks/memory.ts
@@ -1,5 +1,5 @@
import { BrainIcon } from '@/components/icons'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
export const MemoryBlock: BlockConfig = {
type: 'memory',
diff --git a/apps/sim/blocks/blocks/microsoft_excel.ts b/apps/sim/blocks/blocks/microsoft_excel.ts
index 9cd294d366..cd37c5423f 100644
--- a/apps/sim/blocks/blocks/microsoft_excel.ts
+++ b/apps/sim/blocks/blocks/microsoft_excel.ts
@@ -1,15 +1,6 @@
import { MicrosoftExcelIcon } from '@/components/icons'
-import type {
- MicrosoftExcelReadResponse,
- MicrosoftExcelTableAddResponse,
- MicrosoftExcelWriteResponse,
-} from '@/tools/microsoft_excel/types'
-import type { BlockConfig } from '../types'
-
-type MicrosoftExcelResponse =
- | MicrosoftExcelReadResponse
- | MicrosoftExcelWriteResponse
- | MicrosoftExcelTableAddResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { MicrosoftExcelResponse } from '@/tools/microsoft_excel/types'
export const MicrosoftExcelBlock: BlockConfig = {
type: 'microsoft_excel',
@@ -53,14 +44,15 @@ export const MicrosoftExcelBlock: BlockConfig = {
requiredScopes: [],
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
placeholder: 'Select a spreadsheet',
+ mode: 'basic',
},
{
id: 'manualSpreadsheetId',
- title: 'Or Enter Spreadsheet ID Manually',
+ title: 'Spreadsheet ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of the spreadsheet (from URL)',
- condition: { field: 'spreadsheetId', value: '' },
+ placeholder: 'Enter spreadsheet ID',
+ mode: 'advanced',
},
{
id: 'range',
diff --git a/apps/sim/blocks/blocks/microsoft_teams.ts b/apps/sim/blocks/blocks/microsoft_teams.ts
index 9296e75496..b140cbe077 100644
--- a/apps/sim/blocks/blocks/microsoft_teams.ts
+++ b/apps/sim/blocks/blocks/microsoft_teams.ts
@@ -1,11 +1,6 @@
import { MicrosoftTeamsIcon } from '@/components/icons'
-import type {
- MicrosoftTeamsReadResponse,
- MicrosoftTeamsWriteResponse,
-} from '@/tools/microsoft_teams/types'
-import type { BlockConfig } from '../types'
-
-type MicrosoftTeamsResponse = MicrosoftTeamsReadResponse | MicrosoftTeamsWriteResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { MicrosoftTeamsResponse } from '@/tools/microsoft_teams/types'
export const MicrosoftTeamsBlock: BlockConfig = {
type: 'microsoft_teams',
@@ -64,6 +59,16 @@ export const MicrosoftTeamsBlock: BlockConfig = {
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a team',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read_channel', 'write_channel'] },
+ },
+ {
+ id: 'manualTeamId',
+ title: 'Team ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter team ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['read_channel', 'write_channel'] },
},
{
@@ -75,6 +80,16 @@ export const MicrosoftTeamsBlock: BlockConfig = {
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a chat',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read_chat', 'write_chat'] },
+ },
+ {
+ id: 'manualChatId',
+ title: 'Chat ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter chat ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['read_chat', 'write_chat'] },
},
{
@@ -86,6 +101,16 @@ export const MicrosoftTeamsBlock: BlockConfig = {
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a channel',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read_channel', 'write_channel'] },
+ },
+ {
+ id: 'manualChannelId',
+ title: 'Channel ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter channel ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['read_channel', 'write_channel'] },
},
// Create-specific Fields
@@ -121,7 +146,22 @@ export const MicrosoftTeamsBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, operation, ...rest } = params
+ const {
+ credential,
+ operation,
+ teamId,
+ manualTeamId,
+ chatId,
+ manualChatId,
+ channelId,
+ manualChannelId,
+ ...rest
+ } = params
+
+ // Use the selected IDs or the manually entered ones
+ const effectiveTeamId = (teamId || manualTeamId || '').trim()
+ const effectiveChatId = (chatId || manualChatId || '').trim()
+ const effectiveChannelId = (channelId || manualChannelId || '').trim()
// Build the parameters based on operation type
const baseParams = {
@@ -131,27 +171,33 @@ export const MicrosoftTeamsBlock: BlockConfig = {
// For chat operations, we need chatId
if (operation === 'read_chat' || operation === 'write_chat') {
- if (!params.chatId) {
- throw new Error('Chat ID is required for chat operations')
+ if (!effectiveChatId) {
+ throw new Error(
+ 'Chat ID is required for chat operations. Please select a chat or enter a chat ID manually.'
+ )
}
return {
...baseParams,
- chatId: params.chatId,
+ chatId: effectiveChatId,
}
}
// For channel operations, we need teamId and channelId
if (operation === 'read_channel' || operation === 'write_channel') {
- if (!params.teamId) {
- throw new Error('Team ID is required for channel operations')
+ if (!effectiveTeamId) {
+ throw new Error(
+ 'Team ID is required for channel operations. Please select a team or enter a team ID manually.'
+ )
}
- if (!params.channelId) {
- throw new Error('Channel ID is required for channel operations')
+ if (!effectiveChannelId) {
+ throw new Error(
+ 'Channel ID is required for channel operations. Please select a channel or enter a channel ID manually.'
+ )
}
return {
...baseParams,
- teamId: params.teamId,
- channelId: params.channelId,
+ teamId: effectiveTeamId,
+ channelId: effectiveChannelId,
}
}
@@ -162,11 +208,14 @@ export const MicrosoftTeamsBlock: BlockConfig = {
inputs: {
operation: { type: 'string', required: true },
credential: { type: 'string', required: true },
- messageId: { type: 'string', required: true },
- chatId: { type: 'string', required: true },
- channelId: { type: 'string', required: true },
- teamId: { type: 'string', required: true },
- content: { type: 'string', required: true },
+ messageId: { type: 'string', required: false },
+ chatId: { type: 'string', required: false },
+ manualChatId: { type: 'string', required: false },
+ channelId: { type: 'string', required: false },
+ manualChannelId: { type: 'string', required: false },
+ teamId: { type: 'string', required: false },
+ manualTeamId: { type: 'string', required: false },
+ content: { type: 'string', required: false },
},
outputs: {
content: 'string',
diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts
index f89f372e0c..1519d7aa76 100644
--- a/apps/sim/blocks/blocks/mistral_parse.ts
+++ b/apps/sim/blocks/blocks/mistral_parse.ts
@@ -1,7 +1,7 @@
import { MistralIcon } from '@/components/icons'
import { isProd } from '@/lib/environment'
+import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '@/blocks/types'
import type { MistralParserOutput } from '@/tools/mistral/types'
-import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types'
const shouldEnableFileUpload = isProd
diff --git a/apps/sim/blocks/blocks/notion.ts b/apps/sim/blocks/blocks/notion.ts
index 10447873f4..d0e50eb1b3 100644
--- a/apps/sim/blocks/blocks/notion.ts
+++ b/apps/sim/blocks/blocks/notion.ts
@@ -1,6 +1,6 @@
import { NotionIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { NotionResponse } from '@/tools/notion/types'
-import type { BlockConfig } from '../types'
export const NotionBlock: BlockConfig = {
type: 'notion',
@@ -19,9 +19,17 @@ export const NotionBlock: BlockConfig = {
type: 'dropdown',
layout: 'full',
options: [
+ // Read Operations
{ label: 'Read Page', id: 'notion_read' },
- { label: 'Append Content', id: 'notion_write' },
+ { label: 'Read Database', id: 'notion_read_database' },
+ // Create Operations
{ label: 'Create Page', id: 'notion_create_page' },
+ { label: 'Create Database', id: 'notion_create_database' },
+ // Write Operations
+ { label: 'Append Content', id: 'notion_write' },
+ // Query & Search Operations
+ { label: 'Query Database', id: 'notion_query_database' },
+ { label: 'Search Workspace', id: 'notion_search' },
],
},
{
@@ -46,6 +54,17 @@ export const NotionBlock: BlockConfig = {
value: 'notion_read',
},
},
+ {
+ id: 'databaseId',
+ title: 'Database ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Notion database ID',
+ condition: {
+ field: 'operation',
+ value: 'notion_read_database',
+ },
+ },
{
id: 'pageId',
title: 'Page ID',
@@ -58,23 +77,12 @@ export const NotionBlock: BlockConfig = {
},
},
// Create operation fields
- {
- id: 'parentType',
- title: 'Parent Type',
- type: 'dropdown',
- layout: 'full',
- options: [
- { label: 'Page', id: 'page' },
- { label: 'Database', id: 'database' },
- ],
- condition: { field: 'operation', value: 'notion_create_page' },
- },
{
id: 'parentId',
- title: 'Parent ID',
+ title: 'Parent Page ID',
type: 'short-input',
layout: 'full',
- placeholder: 'ID of parent page or database',
+ placeholder: 'ID of parent page',
condition: { field: 'operation', value: 'notion_create_page' },
},
{
@@ -83,18 +91,6 @@ export const NotionBlock: BlockConfig = {
type: 'short-input',
layout: 'full',
placeholder: 'Title for the new page',
- condition: {
- field: 'operation',
- value: 'notion_create_page',
- and: { field: 'parentType', value: 'page' },
- },
- },
- {
- id: 'properties',
- title: 'Page Properties (JSON)',
- type: 'long-input',
- layout: 'full',
- placeholder: 'Enter page properties as JSON object',
condition: {
field: 'operation',
value: 'notion_create_page',
@@ -123,28 +119,126 @@ export const NotionBlock: BlockConfig = {
value: 'notion_create_page',
},
},
+ // Query Database Fields
+ {
+ id: 'databaseId',
+ title: 'Database ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Notion database ID',
+ condition: { field: 'operation', value: 'notion_query_database' },
+ },
+ {
+ id: 'filter',
+ title: 'Filter (JSON)',
+ type: 'long-input',
+ layout: 'full',
+ placeholder: 'Enter filter conditions as JSON (optional)',
+ condition: { field: 'operation', value: 'notion_query_database' },
+ },
+ {
+ id: 'sorts',
+ title: 'Sort Criteria (JSON)',
+ type: 'long-input',
+ layout: 'full',
+ placeholder: 'Enter sort criteria as JSON array (optional)',
+ condition: { field: 'operation', value: 'notion_query_database' },
+ },
+ {
+ id: 'pageSize',
+ title: 'Page Size',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Number of results (default: 100, max: 100)',
+ condition: { field: 'operation', value: 'notion_query_database' },
+ },
+ // Search Fields
+ {
+ id: 'query',
+ title: 'Search Query',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter search terms (leave empty for all pages)',
+ condition: { field: 'operation', value: 'notion_search' },
+ },
+ {
+ id: 'filterType',
+ title: 'Filter Type',
+ type: 'dropdown',
+ layout: 'full',
+ options: [
+ { label: 'All', id: 'all' },
+ { label: 'Pages Only', id: 'page' },
+ { label: 'Databases Only', id: 'database' },
+ ],
+ condition: { field: 'operation', value: 'notion_search' },
+ },
+ // Create Database Fields
+ {
+ id: 'parentId',
+ title: 'Parent Page ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'ID of parent page where database will be created',
+ condition: { field: 'operation', value: 'notion_create_database' },
+ },
+ {
+ id: 'title',
+ title: 'Database Title',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Title for the new database',
+ condition: { field: 'operation', value: 'notion_create_database' },
+ },
+ {
+ id: 'properties',
+ title: 'Database Properties (JSON)',
+ type: 'long-input',
+ layout: 'full',
+ placeholder: 'Enter database properties as JSON object',
+ condition: { field: 'operation', value: 'notion_create_database' },
+ },
],
tools: {
- access: ['notion_read', 'notion_write', 'notion_create_page'],
+ access: [
+ 'notion_read',
+ 'notion_read_database',
+ 'notion_write',
+ 'notion_create_page',
+ 'notion_query_database',
+ 'notion_search',
+ 'notion_create_database',
+ ],
config: {
tool: (params) => {
switch (params.operation) {
case 'notion_read':
return 'notion_read'
+ case 'notion_read_database':
+ return 'notion_read_database'
case 'notion_write':
return 'notion_write'
case 'notion_create_page':
return 'notion_create_page'
+ case 'notion_query_database':
+ return 'notion_query_database'
+ case 'notion_search':
+ return 'notion_search'
+ case 'notion_create_database':
+ return 'notion_create_database'
default:
return 'notion_read'
}
},
params: (params) => {
- const { credential, operation, properties, ...rest } = params
+ const { credential, operation, properties, filter, sorts, ...rest } = params
// Parse properties from JSON string for create operations
let parsedProperties
- if (operation === 'notion_create_page' && properties) {
+ if (
+ (operation === 'notion_create_page' || operation === 'notion_create_database') &&
+ properties
+ ) {
try {
parsedProperties = JSON.parse(properties)
} catch (error) {
@@ -154,10 +248,36 @@ export const NotionBlock: BlockConfig = {
}
}
+ // Parse filter for query database operations
+ let parsedFilter
+ if (operation === 'notion_query_database' && filter) {
+ try {
+ parsedFilter = JSON.parse(filter)
+ } catch (error) {
+ throw new Error(
+ `Invalid JSON for filter: ${error instanceof Error ? error.message : String(error)}`
+ )
+ }
+ }
+
+ // Parse sorts for query database operations
+ let parsedSorts
+ if (operation === 'notion_query_database' && sorts) {
+ try {
+ parsedSorts = JSON.parse(sorts)
+ } catch (error) {
+ throw new Error(
+ `Invalid JSON for sorts: ${error instanceof Error ? error.message : String(error)}`
+ )
+ }
+ }
+
return {
...rest,
accessToken: credential,
...(parsedProperties ? { properties: parsedProperties } : {}),
+ ...(parsedFilter ? { filter: JSON.stringify(parsedFilter) } : {}),
+ ...(parsedSorts ? { sorts: JSON.stringify(parsedSorts) } : {}),
}
},
},
@@ -168,10 +288,16 @@ export const NotionBlock: BlockConfig = {
pageId: { type: 'string', required: false },
content: { type: 'string', required: false },
// Create page inputs
- parentType: { type: 'string', required: true },
- parentId: { type: 'string', required: true },
+ parentId: { type: 'string', required: false },
title: { type: 'string', required: false },
- properties: { type: 'string', required: false },
+ // Query database inputs
+ databaseId: { type: 'string', required: false },
+ filter: { type: 'string', required: false },
+ sorts: { type: 'string', required: false },
+ pageSize: { type: 'number', required: false },
+ // Search inputs
+ query: { type: 'string', required: false },
+ filterType: { type: 'string', required: false },
},
outputs: {
content: 'string',
diff --git a/apps/sim/blocks/blocks/openai.ts b/apps/sim/blocks/blocks/openai.ts
index 7a67bd9c1a..2f5cffbd81 100644
--- a/apps/sim/blocks/blocks/openai.ts
+++ b/apps/sim/blocks/blocks/openai.ts
@@ -1,5 +1,5 @@
import { OpenAIIcon } from '@/components/icons'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
export const OpenAIBlock: BlockConfig = {
type: 'openai',
diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts
index 67d0b57323..0c5a97a39a 100644
--- a/apps/sim/blocks/blocks/outlook.ts
+++ b/apps/sim/blocks/blocks/outlook.ts
@@ -1,14 +1,8 @@
import { OutlookIcon } from '@/components/icons'
-import type {
- OutlookDraftResponse,
- OutlookReadResponse,
- OutlookSendResponse,
-} from '@/tools/outlook/types'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
+import type { OutlookResponse } from '@/tools/outlook/types'
-export const OutlookBlock: BlockConfig<
- OutlookReadResponse | OutlookSendResponse | OutlookDraftResponse
-> = {
+export const OutlookBlock: BlockConfig = {
type: 'outlook',
name: 'Outlook',
description: 'Access Outlook',
@@ -19,7 +13,6 @@ export const OutlookBlock: BlockConfig<
bgColor: '#E0E0E0',
icon: OutlookIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
@@ -31,7 +24,6 @@ export const OutlookBlock: BlockConfig<
{ label: 'Read Email', id: 'read_outlook' },
],
},
- // Gmail Credentials
{
id: 'credential',
title: 'Microsoft Account',
@@ -51,7 +43,6 @@ export const OutlookBlock: BlockConfig<
],
placeholder: 'Select Microsoft account',
},
- // Send Email Fields
{
id: 'to',
title: 'To',
@@ -76,7 +67,7 @@ export const OutlookBlock: BlockConfig<
placeholder: 'Email content',
condition: { field: 'operation', value: ['send_outlook', 'draft_outlook'] },
},
- // Read Email Fields - Add folder selector
+ // Read Email Fields - Add folder selector (basic mode)
{
id: 'folder',
title: 'Folder',
@@ -86,6 +77,17 @@ export const OutlookBlock: BlockConfig<
serviceId: 'outlook',
requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'],
placeholder: 'Select Outlook folder',
+ mode: 'basic',
+ condition: { field: 'operation', value: 'read_outlook' },
+ },
+ // Manual folder input (advanced mode)
+ {
+ id: 'manualFolder',
+ title: 'Folder',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Outlook folder name (e.g., INBOX, SENT, or custom folder)',
+ mode: 'advanced',
condition: { field: 'operation', value: 'read_outlook' },
},
{
@@ -114,11 +116,14 @@ export const OutlookBlock: BlockConfig<
},
params: (params) => {
// Pass the credential directly from the credential field
- const { credential, ...rest } = params
+ const { credential, folder, manualFolder, ...rest } = params
+
+ // Handle folder input (selector or manual)
+ const effectiveFolder = (folder || manualFolder || '').trim()
// Set default folder to INBOX if not specified
- if (rest.operation === 'read_outlook' && !rest.folder) {
- rest.folder = 'INBOX'
+ if (rest.operation === 'read_outlook') {
+ rest.folder = effectiveFolder || 'INBOX'
}
return {
@@ -137,6 +142,7 @@ export const OutlookBlock: BlockConfig<
body: { type: 'string', required: false },
// Read operation inputs
folder: { type: 'string', required: false },
+ manualFolder: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
},
outputs: {
diff --git a/apps/sim/blocks/blocks/perplexity.ts b/apps/sim/blocks/blocks/perplexity.ts
index b36856a5ed..53c09a373e 100644
--- a/apps/sim/blocks/blocks/perplexity.ts
+++ b/apps/sim/blocks/blocks/perplexity.ts
@@ -1,18 +1,6 @@
import { PerplexityIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface PerplexityChatResponse extends ToolResponse {
- output: {
- content: string
- model: string
- usage: {
- prompt_tokens: number
- completion_tokens: number
- total_tokens: number
- }
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { PerplexityChatResponse } from '@/tools/perplexity/types'
export const PerplexityBlock: BlockConfig = {
type: 'perplexity',
diff --git a/apps/sim/blocks/blocks/pinecone.ts b/apps/sim/blocks/blocks/pinecone.ts
index fea14bf697..2bbca16bba 100644
--- a/apps/sim/blocks/blocks/pinecone.ts
+++ b/apps/sim/blocks/blocks/pinecone.ts
@@ -1,7 +1,6 @@
import { PineconeIcon } from '@/components/icons'
-// You'll need to create this icon
+import type { BlockConfig } from '@/blocks/types'
import type { PineconeResponse } from '@/tools/pinecone/types'
-import type { BlockConfig } from '../types'
export const PineconeBlock: BlockConfig = {
type: 'pinecone',
diff --git a/apps/sim/blocks/blocks/reddit.ts b/apps/sim/blocks/blocks/reddit.ts
index eb129f4e1b..f5e404b927 100644
--- a/apps/sim/blocks/blocks/reddit.ts
+++ b/apps/sim/blocks/blocks/reddit.ts
@@ -1,14 +1,8 @@
import { RedditIcon } from '@/components/icons'
-import type {
- RedditCommentsResponse,
- RedditHotPostsResponse,
- RedditPostsResponse,
-} from '@/tools/reddit/types'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
+import type { RedditResponse } from '@/tools/reddit/types'
-export const RedditBlock: BlockConfig<
- RedditHotPostsResponse | RedditPostsResponse | RedditCommentsResponse
-> = {
+export const RedditBlock: BlockConfig = {
type: 'reddit',
name: 'Reddit',
description: 'Access Reddit data and content',
diff --git a/apps/sim/blocks/blocks/response.ts b/apps/sim/blocks/blocks/response.ts
index 3e6ba92d8d..6dd5581382 100644
--- a/apps/sim/blocks/blocks/response.ts
+++ b/apps/sim/blocks/blocks/response.ts
@@ -1,6 +1,6 @@
import { ResponseIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { ResponseBlockOutput } from '@/tools/response/types'
-import type { BlockConfig } from '../types'
export const ResponseBlock: BlockConfig = {
type: 'response',
diff --git a/apps/sim/blocks/blocks/router.ts b/apps/sim/blocks/blocks/router.ts
index c986cb3b43..4f48f5483a 100644
--- a/apps/sim/blocks/blocks/router.ts
+++ b/apps/sim/blocks/blocks/router.ts
@@ -1,10 +1,10 @@
import { ConnectIcon } from '@/components/icons'
import { isHosted } from '@/lib/environment'
+import type { BlockConfig } from '@/blocks/types'
import type { ProviderId } from '@/providers/types'
import { getAllModelProviders, getBaseModelProviders, getHostedModels } from '@/providers/utils'
import { useOllamaStore } from '@/stores/ollama/store'
import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
interface RouterResponse extends ToolResponse {
output: {
diff --git a/apps/sim/blocks/blocks/s3.ts b/apps/sim/blocks/blocks/s3.ts
index a5d4e86bdc..99525f4282 100644
--- a/apps/sim/blocks/blocks/s3.ts
+++ b/apps/sim/blocks/blocks/s3.ts
@@ -1,6 +1,6 @@
import { S3Icon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { S3Response } from '@/tools/s3/types'
-import type { BlockConfig } from '../types'
export const S3Block: BlockConfig = {
type: 's3',
diff --git a/apps/sim/blocks/blocks/schedule.ts b/apps/sim/blocks/blocks/schedule.ts
new file mode 100644
index 0000000000..fe1af83378
--- /dev/null
+++ b/apps/sim/blocks/blocks/schedule.ts
@@ -0,0 +1,116 @@
+import { ScheduleIcon } from '@/components/icons'
+import type { BlockConfig } from '../types'
+
+export const ScheduleBlock: BlockConfig = {
+ type: 'schedule',
+ name: 'Schedule',
+ description: 'Trigger workflow execution on a schedule',
+ longDescription:
+ 'Configure automated workflow execution with flexible timing options. Set up recurring workflows that run at specific intervals or times.',
+ category: 'triggers',
+ bgColor: '#7B68EE',
+ icon: ScheduleIcon,
+
+ subBlocks: [
+ // Schedule configuration status display
+ {
+ id: 'scheduleConfig',
+ title: 'Schedule Status',
+ type: 'schedule-config',
+ layout: 'full',
+ },
+ // Hidden fields for schedule configuration (used by the modal only)
+ {
+ id: 'scheduleType',
+ title: 'Frequency',
+ type: 'dropdown',
+ layout: 'full',
+ options: [
+ { label: 'Every X Minutes', id: 'minutes' },
+ { label: 'Hourly', id: 'hourly' },
+ { label: 'Daily', id: 'daily' },
+ { label: 'Weekly', id: 'weekly' },
+ { label: 'Monthly', id: 'monthly' },
+ { label: 'Custom Cron', id: 'custom' },
+ ],
+ value: () => 'daily',
+ hidden: true,
+ },
+ {
+ id: 'minutesInterval',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'hourlyMinute',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'dailyTime',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'weeklyDay',
+ type: 'dropdown',
+ hidden: true,
+ options: [
+ { label: 'Monday', id: 'MON' },
+ { label: 'Tuesday', id: 'TUE' },
+ { label: 'Wednesday', id: 'WED' },
+ { label: 'Thursday', id: 'THU' },
+ { label: 'Friday', id: 'FRI' },
+ { label: 'Saturday', id: 'SAT' },
+ { label: 'Sunday', id: 'SUN' },
+ ],
+ value: () => 'MON',
+ },
+ {
+ id: 'weeklyDayTime',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'monthlyDay',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'monthlyTime',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'cronExpression',
+ type: 'short-input',
+ hidden: true,
+ },
+ {
+ id: 'timezone',
+ type: 'dropdown',
+ hidden: true,
+ options: [
+ { label: 'UTC', id: 'UTC' },
+ { label: 'US Eastern (UTC-4)', id: 'America/New_York' },
+ { label: 'US Central (UTC-5)', id: 'America/Chicago' },
+ { label: 'US Mountain (UTC-6)', id: 'America/Denver' },
+ { label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' },
+ { label: 'London (UTC+1)', id: 'Europe/London' },
+ { label: 'Paris (UTC+2)', id: 'Europe/Paris' },
+ { label: 'Singapore (UTC+8)', id: 'Asia/Singapore' },
+ { label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' },
+ { label: 'Sydney (UTC+10)', id: 'Australia/Sydney' },
+ ],
+ value: () => 'UTC',
+ },
+ ],
+
+ tools: {
+ access: [], // No external tools needed
+ },
+
+ inputs: {}, // No inputs - schedule triggers initiate workflows
+
+ outputs: {}, // No outputs - schedule triggers initiate workflow execution
+}
diff --git a/apps/sim/blocks/blocks/serper.ts b/apps/sim/blocks/blocks/serper.ts
index 731724cdc0..6e1b899376 100644
--- a/apps/sim/blocks/blocks/serper.ts
+++ b/apps/sim/blocks/blocks/serper.ts
@@ -1,6 +1,6 @@
import { SerperIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { SearchResponse } from '@/tools/serper/types'
-import type { BlockConfig } from '../types'
export const SerperBlock: BlockConfig = {
type: 'serper',
diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts
index c8a58182c2..70d2964c08 100644
--- a/apps/sim/blocks/blocks/slack.ts
+++ b/apps/sim/blocks/blocks/slack.ts
@@ -1,15 +1,13 @@
import { SlackIcon } from '@/components/icons'
-import type { SlackMessageResponse } from '@/tools/slack/types'
-import type { BlockConfig } from '../types'
-
-type SlackResponse = SlackMessageResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { SlackResponse } from '@/tools/slack/types'
export const SlackBlock: BlockConfig = {
type: 'slack',
name: 'Slack',
description: 'Send messages to Slack',
longDescription:
- "Comprehensive Slack integration with OAuth authentication. Send formatted messages using Slack's mrkdwn syntax or Block Kit.",
+ "Comprehensive Slack integration with OAuth authentication. Send formatted messages using Slack's mrkdwn syntax.",
docsLink: 'https://docs.simstudio.ai/tools/slack',
category: 'tools',
bgColor: '#611f69',
@@ -20,7 +18,11 @@ export const SlackBlock: BlockConfig = {
title: 'Operation',
type: 'dropdown',
layout: 'full',
- options: [{ label: 'Send Message', id: 'send' }],
+ options: [
+ { label: 'Send Message', id: 'send' },
+ { label: 'Create Canvas', id: 'canvas' },
+ { label: 'Read Messages', id: 'read' },
+ ],
value: () => 'send',
},
{
@@ -43,13 +45,14 @@ export const SlackBlock: BlockConfig = {
serviceId: 'slack',
requiredScopes: [
'channels:read',
+ 'channels:history',
'groups:read',
+ 'groups:history',
'chat:write',
'chat:write.public',
'users:read',
- 'files:read',
- 'links:read',
- 'links:write',
+ 'files:write',
+ 'canvases:write',
],
placeholder: 'Select Slack workspace',
condition: {
@@ -76,6 +79,16 @@ export const SlackBlock: BlockConfig = {
layout: 'full',
provider: 'slack',
placeholder: 'Select Slack channel',
+ mode: 'basic',
+ },
+ // Manual channel ID input (advanced mode)
+ {
+ id: 'manualChannel',
+ title: 'Channel ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Slack channel ID (e.g., C1234567890)',
+ mode: 'advanced',
},
{
id: 'text',
@@ -83,24 +96,99 @@ export const SlackBlock: BlockConfig = {
type: 'long-input',
layout: 'full',
placeholder: 'Enter your message (supports Slack mrkdwn)',
+ condition: {
+ field: 'operation',
+ value: 'send',
+ },
+ },
+ // Canvas specific fields
+ {
+ id: 'title',
+ title: 'Canvas Title',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter canvas title',
+ condition: {
+ field: 'operation',
+ value: 'canvas',
+ },
+ },
+ {
+ id: 'content',
+ title: 'Canvas Content',
+ type: 'long-input',
+ layout: 'full',
+ placeholder: 'Enter canvas content (markdown supported)',
+ condition: {
+ field: 'operation',
+ value: 'canvas',
+ },
+ },
+ // Message Reader specific fields
+ {
+ id: 'limit',
+ title: 'Message Limit',
+ type: 'short-input',
+ layout: 'half',
+ placeholder: '50',
+ condition: {
+ field: 'operation',
+ value: 'read',
+ },
+ },
+ {
+ id: 'oldest',
+ title: 'Oldest Timestamp',
+ type: 'short-input',
+ layout: 'half',
+ placeholder: 'ISO 8601 timestamp',
+ condition: {
+ field: 'operation',
+ value: 'read',
+ },
},
],
tools: {
- access: ['slack_message'],
+ access: ['slack_message', 'slack_canvas', 'slack_message_reader'],
config: {
tool: (params) => {
switch (params.operation) {
case 'send':
return 'slack_message'
+ case 'canvas':
+ return 'slack_canvas'
+ case 'read':
+ return 'slack_message_reader'
default:
throw new Error(`Invalid Slack operation: ${params.operation}`)
}
},
params: (params) => {
- const { credential, authMethod, botToken, operation, ...rest } = params
+ const {
+ credential,
+ authMethod,
+ botToken,
+ operation,
+ channel,
+ manualChannel,
+ title,
+ content,
+ limit,
+ oldest,
+ ...rest
+ } = params
+
+ // Handle channel input (selector or manual)
+ const effectiveChannel = (channel || manualChannel || '').trim()
- const baseParams = {
- ...rest,
+ if (!effectiveChannel) {
+ throw new Error(
+ 'Channel is required. Please select a channel or enter a channel ID manually.'
+ )
+ }
+
+ const baseParams: Record = {
+ channel: effectiveChannel,
}
// Handle authentication based on method
@@ -117,6 +205,36 @@ export const SlackBlock: BlockConfig = {
baseParams.credential = credential
}
+ // Handle operation-specific params
+ switch (operation) {
+ case 'send':
+ if (!rest.text) {
+ throw new Error('Message text is required for send operation')
+ }
+ baseParams.text = rest.text
+ break
+
+ case 'canvas':
+ if (!title || !content) {
+ throw new Error('Title and content are required for canvas operation')
+ }
+ baseParams.title = title
+ baseParams.content = content
+ break
+
+ case 'read':
+ if (limit) {
+ const parsedLimit = Number.parseInt(limit, 10)
+ baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 10
+ } else {
+ baseParams.limit = 10
+ }
+ if (oldest) {
+ baseParams.oldest = oldest
+ }
+ break
+ }
+
return baseParams
},
},
@@ -126,11 +244,19 @@ export const SlackBlock: BlockConfig = {
authMethod: { type: 'string', required: true },
credential: { type: 'string', required: false },
botToken: { type: 'string', required: false },
- channel: { type: 'string', required: true },
- text: { type: 'string', required: true },
+ channel: { type: 'string', required: false },
+ manualChannel: { type: 'string', required: false },
+ text: { type: 'string', required: false },
+ title: { type: 'string', required: false },
+ content: { type: 'string', required: false },
+ limit: { type: 'string', required: false },
+ oldest: { type: 'string', required: false },
},
outputs: {
ts: 'string',
channel: 'string',
+ canvas_id: 'string',
+ title: 'string',
+ messages: 'json',
},
}
diff --git a/apps/sim/blocks/blocks/stagehand.ts b/apps/sim/blocks/blocks/stagehand.ts
index bd66fe4e92..5ca07a9f31 100644
--- a/apps/sim/blocks/blocks/stagehand.ts
+++ b/apps/sim/blocks/blocks/stagehand.ts
@@ -1,8 +1,8 @@
import { StagehandIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-interface StagehandExtractResponse extends ToolResponse {
+export interface StagehandExtractResponse extends ToolResponse {
output: {
data: Record
}
diff --git a/apps/sim/blocks/blocks/stagehand_agent.ts b/apps/sim/blocks/blocks/stagehand_agent.ts
index bd8ddd368c..f55d3a5051 100644
--- a/apps/sim/blocks/blocks/stagehand_agent.ts
+++ b/apps/sim/blocks/blocks/stagehand_agent.ts
@@ -1,22 +1,6 @@
import { StagehandIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface StagehandAgentResponse extends ToolResponse {
- output: {
- agentResult: {
- success: boolean
- completed: boolean
- message: string
- actions: Array<{
- type: string
- params: Record
- result: Record
- }>
- }
- structuredOutput?: Record
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { StagehandAgentResponse } from '@/tools/stagehand/types'
export const StagehandAgentBlock: BlockConfig = {
type: 'stagehand_agent',
diff --git a/apps/sim/blocks/blocks/starter.ts b/apps/sim/blocks/blocks/starter.ts
index 1777366898..412b670698 100644
--- a/apps/sim/blocks/blocks/starter.ts
+++ b/apps/sim/blocks/blocks/starter.ts
@@ -1,12 +1,11 @@
import { StartIcon } from '@/components/icons'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
export const StarterBlock: BlockConfig = {
type: 'starter',
name: 'Starter',
description: 'Start workflow',
- longDescription:
- 'Initiate your workflow manually, on a schedule, or via webhook triggers. Configure flexible execution patterns with customizable timing options and webhook security.',
+ longDescription: 'Initiate your workflow manually with optional structured input for API calls.',
category: 'blocks',
bgColor: '#2FB3FF',
icon: StartIcon,
@@ -19,8 +18,7 @@ export const StarterBlock: BlockConfig = {
layout: 'full',
options: [
{ label: 'Run manually', id: 'manual' },
- { label: 'On webhook call', id: 'webhook' },
- { label: 'On schedule', id: 'schedule' },
+ { label: 'Chat', id: 'chat' },
],
value: () => 'manual',
},
@@ -33,148 +31,6 @@ export const StarterBlock: BlockConfig = {
mode: 'advanced',
condition: { field: 'startWorkflow', value: 'manual' },
},
- // Webhook configuration
- {
- id: 'webhookProvider',
- title: 'Webhook Provider',
- type: 'dropdown',
- layout: 'full',
- options: [
- { label: 'Slack', id: 'slack' },
- { label: 'Gmail', id: 'gmail' },
- { label: 'Airtable', id: 'airtable' },
- { label: 'Telegram', id: 'telegram' },
- { label: 'Generic', id: 'generic' },
- // { label: 'WhatsApp', id: 'whatsapp' },
- // { label: 'GitHub', id: 'github' },
- // { label: 'Discord', id: 'discord' },
- // { label: 'Stripe', id: 'stripe' },
- ],
- value: () => 'generic',
- condition: { field: 'startWorkflow', value: 'webhook' },
- },
- {
- id: 'webhookConfig',
- title: 'Webhook Configuration',
- type: 'webhook-config',
- layout: 'full',
- condition: { field: 'startWorkflow', value: 'webhook' },
- },
- // Schedule configuration status display
- {
- id: 'scheduleConfig',
- title: 'Schedule Status',
- type: 'schedule-config',
- layout: 'full',
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- // Hidden fields for schedule configuration (used by the modal only)
- {
- id: 'scheduleType',
- title: 'Frequency',
- type: 'dropdown',
- layout: 'full',
- options: [
- { label: 'Every X Minutes', id: 'minutes' },
- { label: 'Hourly', id: 'hourly' },
- { label: 'Daily', id: 'daily' },
- { label: 'Weekly', id: 'weekly' },
- { label: 'Monthly', id: 'monthly' },
- { label: 'Custom Cron', id: 'custom' },
- ],
- value: () => 'daily',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'scheduleStartAt',
- type: 'date-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'scheduleTime',
- type: 'time-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'minutesInterval',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'hourlyMinute',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'dailyTime',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'weeklyDay',
- type: 'dropdown',
- hidden: true,
- options: [
- { label: 'Monday', id: 'MON' },
- { label: 'Tuesday', id: 'TUE' },
- { label: 'Wednesday', id: 'WED' },
- { label: 'Thursday', id: 'THU' },
- { label: 'Friday', id: 'FRI' },
- { label: 'Saturday', id: 'SAT' },
- { label: 'Sunday', id: 'SUN' },
- ],
- value: () => 'MON',
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'weeklyDayTime',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'monthlyDay',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'monthlyTime',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'cronExpression',
- type: 'short-input',
- hidden: true,
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
- {
- id: 'timezone',
- type: 'dropdown',
- hidden: true,
- options: [
- { label: 'UTC', id: 'UTC' },
- { label: 'US Eastern (UTC-4)', id: 'America/New_York' },
- { label: 'US Central (UTC-5)', id: 'America/Chicago' },
- { label: 'US Mountain (UTC-6)', id: 'America/Denver' },
- { label: 'US Pacific (UTC-7)', id: 'America/Los_Angeles' },
- { label: 'London (UTC+1)', id: 'Europe/London' },
- { label: 'Paris (UTC+2)', id: 'Europe/Paris' },
- { label: 'Singapore (UTC+8)', id: 'Asia/Singapore' },
- { label: 'Tokyo (UTC+9)', id: 'Asia/Tokyo' },
- { label: 'Sydney (UTC+10)', id: 'Australia/Sydney' },
- ],
- value: () => 'UTC',
- condition: { field: 'startWorkflow', value: 'schedule' },
- },
],
tools: {
access: [],
diff --git a/apps/sim/blocks/blocks/supabase.ts b/apps/sim/blocks/blocks/supabase.ts
index 2baa32c62e..dd7c1a1dde 100644
--- a/apps/sim/blocks/blocks/supabase.ts
+++ b/apps/sim/blocks/blocks/supabase.ts
@@ -1,14 +1,9 @@
import { SupabaseIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
+import { createLogger } from '@/lib/logs/console-logger'
+import type { BlockConfig } from '@/blocks/types'
+import type { SupabaseResponse } from '@/tools/supabase/types'
-interface SupabaseResponse extends ToolResponse {
- output: {
- message: string
- results: any
- }
- error?: string
-}
+const logger = createLogger('SupabaseBlock')
export const SupabaseBlock: BlockConfig = {
type: 'supabase',
@@ -21,23 +16,25 @@ export const SupabaseBlock: BlockConfig = {
bgColor: '#1C1C1C',
icon: SupabaseIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [
- { label: 'Read All Rows', id: 'query' },
- { label: 'Insert Rows', id: 'insert' },
+ { label: 'Get Many Rows', id: 'query' },
+ { label: 'Get a Row', id: 'get_row' },
+ { label: 'Create a Row', id: 'insert' },
+ { label: 'Update a Row', id: 'update' },
+ { label: 'Delete a Row', id: 'delete' },
],
},
- // Common Fields
{
id: 'projectId',
title: 'Project ID',
type: 'short-input',
layout: 'full',
+ password: true,
placeholder: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
},
{
@@ -49,13 +46,13 @@ export const SupabaseBlock: BlockConfig = {
},
{
id: 'apiKey',
- title: 'Client Anon Key',
+ title: 'Service Role Secret',
type: 'short-input',
layout: 'full',
- placeholder: 'Your Supabase client anon key',
+ placeholder: 'Your Supabase service role secret key',
password: true,
},
- // Insert-specific Fields
+ // Data input for create/update operations
{
id: 'data',
title: 'Data',
@@ -64,9 +61,75 @@ export const SupabaseBlock: BlockConfig = {
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'insert' },
},
+ {
+ id: 'data',
+ title: 'Data',
+ type: 'code',
+ layout: 'full',
+ placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
+ condition: { field: 'operation', value: 'update' },
+ },
+ // Filter for get_row, update, delete operations (required)
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'get_row' },
+ },
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'update' },
+ },
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'id=eq.123',
+ condition: { field: 'operation', value: 'delete' },
+ },
+ // Optional filter for query operation
+ {
+ id: 'filter',
+ title: 'Filter (PostgREST syntax)',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'status=eq.active',
+ condition: { field: 'operation', value: 'query' },
+ },
+ // Optional order by for query operation
+ {
+ id: 'orderBy',
+ title: 'Order By',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'column_name (add DESC for descending)',
+ condition: { field: 'operation', value: 'query' },
+ },
+ // Optional limit for query operation
+ {
+ id: 'limit',
+ title: 'Limit',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: '100',
+ condition: { field: 'operation', value: 'query' },
+ },
],
tools: {
- access: ['supabase_query', 'supabase_insert'],
+ access: [
+ 'supabase_query',
+ 'supabase_insert',
+ 'supabase_get_row',
+ 'supabase_update',
+ 'supabase_delete',
+ ],
config: {
tool: (params) => {
switch (params.operation) {
@@ -74,29 +137,49 @@ export const SupabaseBlock: BlockConfig = {
return 'supabase_query'
case 'insert':
return 'supabase_insert'
+ case 'get_row':
+ return 'supabase_get_row'
+ case 'update':
+ return 'supabase_update'
+ case 'delete':
+ return 'supabase_delete'
default:
throw new Error(`Invalid Supabase operation: ${params.operation}`)
}
},
params: (params) => {
- const { data, ...rest } = params
+ const { operation, data, filter, ...rest } = params
// Parse JSON data if it's a string
let parsedData
- if (data && typeof data === 'string') {
+ if (data && typeof data === 'string' && data.trim()) {
try {
parsedData = JSON.parse(data)
} catch (_e) {
throw new Error('Invalid JSON data format')
}
- } else {
+ } else if (data && typeof data === 'object') {
parsedData = data
}
- return {
- ...rest,
- data: parsedData,
+ // Handle filter - just pass through PostgREST syntax
+ let parsedFilter
+ if (filter && typeof filter === 'string' && filter.trim()) {
+ parsedFilter = filter.trim()
}
+
+ // Build params object, only including defined values
+ const result = { ...rest }
+
+ if (parsedData !== undefined) {
+ result.data = parsedData
+ }
+
+ if (parsedFilter !== undefined && parsedFilter !== '') {
+ result.filter = parsedFilter
+ }
+
+ return result
},
},
},
@@ -105,8 +188,13 @@ export const SupabaseBlock: BlockConfig = {
projectId: { type: 'string', required: true },
table: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
- // Insert operation inputs
- data: { type: 'string', required: false },
+ // Data for insert/update operations
+ data: { type: 'json', required: false },
+ // Filter for operations
+ filter: { type: 'string', required: false },
+ // Query operation inputs
+ orderBy: { type: 'string', required: false },
+ limit: { type: 'number', required: false },
},
outputs: {
message: 'string',
diff --git a/apps/sim/blocks/blocks/tavily.ts b/apps/sim/blocks/blocks/tavily.ts
index 1192989976..0a1664fbf9 100644
--- a/apps/sim/blocks/blocks/tavily.ts
+++ b/apps/sim/blocks/blocks/tavily.ts
@@ -1,8 +1,6 @@
import { TavilyIcon } from '@/components/icons'
-import type { TavilyExtractResponse, TavilySearchResponse } from '@/tools/tavily/types'
-import type { BlockConfig } from '../types'
-
-type TavilyResponse = TavilySearchResponse | TavilyExtractResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { TavilyResponse } from '@/tools/tavily/types'
export const TavilyBlock: BlockConfig = {
type: 'tavily',
@@ -15,7 +13,6 @@ export const TavilyBlock: BlockConfig = {
bgColor: '#0066FF',
icon: TavilyIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
@@ -27,7 +24,6 @@ export const TavilyBlock: BlockConfig = {
],
value: () => 'tavily_search',
},
- // API Key (common)
{
id: 'apiKey',
title: 'API Key',
@@ -36,7 +32,6 @@ export const TavilyBlock: BlockConfig = {
placeholder: 'Enter your Tavily API key',
password: true,
},
- // Search operation inputs
{
id: 'query',
title: 'Search Query',
@@ -53,7 +48,6 @@ export const TavilyBlock: BlockConfig = {
placeholder: '5',
condition: { field: 'operation', value: 'tavily_search' },
},
- // Extract operation inputs
{
id: 'urls',
title: 'URL',
@@ -93,10 +87,8 @@ export const TavilyBlock: BlockConfig = {
inputs: {
operation: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
- // Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
- // Extract operation
urls: { type: 'string', required: false },
extract_depth: { type: 'string', required: false },
},
diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts
index 0e1180abfa..7599099a95 100644
--- a/apps/sim/blocks/blocks/telegram.ts
+++ b/apps/sim/blocks/blocks/telegram.ts
@@ -1,6 +1,6 @@
import { TelegramIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { TelegramMessageResponse } from '@/tools/telegram/types'
-import type { BlockConfig } from '../types'
export const TelegramBlock: BlockConfig = {
type: 'telegram',
diff --git a/apps/sim/blocks/blocks/thinking.ts b/apps/sim/blocks/blocks/thinking.ts
index 63231c96ff..4fdd7d6c17 100644
--- a/apps/sim/blocks/blocks/thinking.ts
+++ b/apps/sim/blocks/blocks/thinking.ts
@@ -1,12 +1,6 @@
import { BrainIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface ThinkingToolResponse extends ToolResponse {
- output: {
- acknowledgedThought: string
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { ThinkingToolResponse } from '@/tools/thinking/types'
export const ThinkingBlock: BlockConfig = {
type: 'thinking',
diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts
index 6d083a7a34..160a79165d 100644
--- a/apps/sim/blocks/blocks/translate.ts
+++ b/apps/sim/blocks/blocks/translate.ts
@@ -1,7 +1,7 @@
import { TranslateIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { ProviderId } from '@/providers/types'
import { getBaseModelProviders } from '@/providers/utils'
-import type { BlockConfig } from '../types'
const getTranslationPrompt = (
targetLanguage: string
diff --git a/apps/sim/blocks/blocks/twilio.ts b/apps/sim/blocks/blocks/twilio.ts
index 6d08dd02b1..c20bcfcca9 100644
--- a/apps/sim/blocks/blocks/twilio.ts
+++ b/apps/sim/blocks/blocks/twilio.ts
@@ -1,6 +1,6 @@
import { TwilioIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { TwilioSMSBlockOutput } from '@/tools/twilio/types'
-import type { BlockConfig } from '../types'
export const TwilioSMSBlock: BlockConfig = {
type: 'twilio_sms',
diff --git a/apps/sim/blocks/blocks/typeform.ts b/apps/sim/blocks/blocks/typeform.ts
index a870311e26..544a6c74db 100644
--- a/apps/sim/blocks/blocks/typeform.ts
+++ b/apps/sim/blocks/blocks/typeform.ts
@@ -1,78 +1,6 @@
import { TypeformIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
-
-interface TypeformResponse extends ToolResponse {
- output:
- | {
- total_items: number
- page_count: number
- items: Array<{
- landing_id: string
- token: string
- landed_at: string
- submitted_at: string
- metadata: {
- user_agent: string
- platform: string
- referer: string
- network_id: string
- browser: string
- }
- answers: Array<{
- field: {
- id: string
- type: string
- ref: string
- }
- type: string
- [key: string]: any // For different answer types (text, boolean, number, etc.)
- }>
- hidden: Record
- calculated: {
- score: number
- }
- variables: Array<{
- key: string
- type: string
- [key: string]: any // For different variable types
- }>
- }>
- }
- | {
- fileUrl: string
- contentType: string
- filename: string
- }
- | {
- fields: Array<{
- dropoffs: number
- id: string
- label: string
- ref: string
- title: string
- type: string
- views: number
- }>
- form: {
- platforms: Array<{
- average_time: number
- completion_rate: number
- platform: string
- responses_count: number
- total_visits: number
- unique_visits: number
- }>
- summary: {
- average_time: number
- completion_rate: number
- responses_count: number
- total_visits: number
- unique_visits: number
- }
- }
- }
-}
+import type { BlockConfig } from '@/blocks/types'
+import type { TypeformResponse } from '@/tools/typeform/types'
export const TypeformBlock: BlockConfig = {
type: 'typeform',
diff --git a/apps/sim/blocks/blocks/vision.ts b/apps/sim/blocks/blocks/vision.ts
index a06cf762ff..6f012449c7 100644
--- a/apps/sim/blocks/blocks/vision.ts
+++ b/apps/sim/blocks/blocks/vision.ts
@@ -1,6 +1,6 @@
import { EyeIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { VisionResponse } from '@/tools/vision/types'
-import type { BlockConfig } from '../types'
export const VisionBlock: BlockConfig = {
type: 'vision',
diff --git a/apps/sim/blocks/blocks/wealthbox.ts b/apps/sim/blocks/blocks/wealthbox.ts
index 8171635aff..2ea67c34c6 100644
--- a/apps/sim/blocks/blocks/wealthbox.ts
+++ b/apps/sim/blocks/blocks/wealthbox.ts
@@ -1,8 +1,6 @@
import { WealthboxIcon } from '@/components/icons'
-import type { WealthboxReadResponse, WealthboxWriteResponse } from '@/tools/wealthbox/types'
-import type { BlockConfig } from '../types'
-
-type WealthboxResponse = WealthboxReadResponse | WealthboxWriteResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { WealthboxResponse } from '@/tools/wealthbox/types'
export const WealthboxBlock: BlockConfig = {
type: 'wealthbox',
@@ -56,17 +54,37 @@ export const WealthboxBlock: BlockConfig = {
requiredScopes: ['login', 'data'],
layout: 'full',
placeholder: 'Enter Contact ID',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read_contact', 'write_task', 'write_note'] },
+ },
+ {
+ id: 'manualContactId',
+ title: 'Contact ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Contact ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['read_contact', 'write_task', 'write_note'] },
},
{
id: 'taskId',
title: 'Select Task',
- type: 'short-input',
+ type: 'file-selector',
provider: 'wealthbox',
serviceId: 'wealthbox',
requiredScopes: ['login', 'data'],
layout: 'full',
placeholder: 'Enter Task ID',
+ mode: 'basic',
+ condition: { field: 'operation', value: ['read_task'] },
+ },
+ {
+ id: 'manualTaskId',
+ title: 'Task ID',
+ type: 'short-input',
+ layout: 'full',
+ placeholder: 'Enter Task ID',
+ mode: 'advanced',
condition: { field: 'operation', value: ['read_task'] },
},
{
@@ -155,7 +173,14 @@ export const WealthboxBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, operation, ...rest } = params
+ const { credential, operation, contactId, manualContactId, taskId, manualTaskId, ...rest } =
+ params
+
+ // Handle contact ID input (selector or manual)
+ const effectiveContactId = (contactId || manualContactId || '').trim()
+
+ // Handle task ID input (selector or manual)
+ const effectiveTaskId = (taskId || manualTaskId || '').trim()
// Build the parameters based on operation type
const baseParams = {
@@ -168,25 +193,40 @@ export const WealthboxBlock: BlockConfig = {
return {
...baseParams,
noteId: params.noteId,
+ contactId: effectiveContactId,
}
}
// For contact operations, we need contactId
if (operation === 'read_contact') {
- if (!params.contactId) {
+ if (!effectiveContactId) {
throw new Error('Contact ID is required for contact operations')
}
return {
...baseParams,
- contactId: params.contactId,
+ contactId: effectiveContactId,
}
}
// For task operations, we need taskId
if (operation === 'read_task') {
+ if (!effectiveTaskId) {
+ throw new Error('Task ID is required for task operations')
+ }
+ return {
+ ...baseParams,
+ taskId: effectiveTaskId,
+ }
+ }
+
+ // For write_task and write_note operations, we need contactId
+ if (operation === 'write_task' || operation === 'write_note') {
+ if (!effectiveContactId) {
+ throw new Error('Contact ID is required for this operation')
+ }
return {
...baseParams,
- taskId: params.taskId,
+ contactId: effectiveContactId,
}
}
@@ -199,7 +239,9 @@ export const WealthboxBlock: BlockConfig = {
credential: { type: 'string', required: true },
noteId: { type: 'string', required: false },
contactId: { type: 'string', required: false },
+ manualContactId: { type: 'string', required: false },
taskId: { type: 'string', required: false },
+ manualTaskId: { type: 'string', required: false },
content: { type: 'string', required: false },
firstName: { type: 'string', required: false },
lastName: { type: 'string', required: false },
diff --git a/apps/sim/blocks/blocks/webhook.ts b/apps/sim/blocks/blocks/webhook.ts
new file mode 100644
index 0000000000..2f361e4f5a
--- /dev/null
+++ b/apps/sim/blocks/blocks/webhook.ts
@@ -0,0 +1,92 @@
+import {
+ AirtableIcon,
+ DiscordIcon,
+ GithubIcon,
+ GmailIcon,
+ SignalIcon,
+ SlackIcon,
+ StripeIcon,
+ TelegramIcon,
+ WebhookIcon,
+ WhatsAppIcon,
+} from '@/components/icons'
+import type { BlockConfig } from '../types'
+
+const getWebhookProviderIcon = (provider: string) => {
+ const iconMap: Record> = {
+ slack: SlackIcon,
+ gmail: GmailIcon,
+ airtable: AirtableIcon,
+ telegram: TelegramIcon,
+ generic: SignalIcon,
+ whatsapp: WhatsAppIcon,
+ github: GithubIcon,
+ discord: DiscordIcon,
+ stripe: StripeIcon,
+ }
+
+ return iconMap[provider.toLowerCase()]
+}
+
+export const WebhookBlock: BlockConfig = {
+ type: 'webhook',
+ name: 'Webhook',
+ description: 'Trigger workflow execution from external webhooks',
+ category: 'triggers',
+ icon: WebhookIcon,
+ bgColor: '#10B981', // Green color for triggers
+
+ subBlocks: [
+ {
+ id: 'webhookProvider',
+ title: 'Webhook Provider',
+ type: 'dropdown',
+ layout: 'full',
+ options: [
+ 'slack',
+ 'gmail',
+ 'airtable',
+ 'telegram',
+ 'generic',
+ 'whatsapp',
+ 'github',
+ 'discord',
+ 'stripe',
+ ].map((provider) => {
+ const providerLabels = {
+ slack: 'Slack',
+ gmail: 'Gmail',
+ airtable: 'Airtable',
+ telegram: 'Telegram',
+ generic: 'Generic',
+ whatsapp: 'WhatsApp',
+ github: 'GitHub',
+ discord: 'Discord',
+ stripe: 'Stripe',
+ }
+
+ const icon = getWebhookProviderIcon(provider)
+ return {
+ label: providerLabels[provider as keyof typeof providerLabels],
+ id: provider,
+ ...(icon && { icon }),
+ }
+ }),
+ value: () => 'generic',
+ },
+ {
+ id: 'webhookConfig',
+ title: 'Webhook Configuration',
+ type: 'webhook-config',
+ layout: 'full',
+ },
+ ],
+
+ tools: {
+ access: [], // No external tools needed
+ },
+
+ inputs: {}, // No inputs - webhook triggers are pure input sources
+
+ outputs: {}, // No outputs - webhook data is injected directly into workflow context
+}
diff --git a/apps/sim/blocks/blocks/whatsapp.ts b/apps/sim/blocks/blocks/whatsapp.ts
index 47485883c9..f9687bb4a2 100644
--- a/apps/sim/blocks/blocks/whatsapp.ts
+++ b/apps/sim/blocks/blocks/whatsapp.ts
@@ -1,16 +1,8 @@
import { WhatsAppIcon } from '@/components/icons'
-import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
+import type { BlockConfig } from '@/blocks/types'
+import type { WhatsAppResponse } from '@/tools/whatsapp/types'
-interface WhatsAppBlockOutput extends ToolResponse {
- output: {
- success: boolean
- messageId?: string
- error?: string
- }
-}
-
-export const WhatsAppBlock: BlockConfig = {
+export const WhatsAppBlock: BlockConfig = {
type: 'whatsapp',
name: 'WhatsApp',
description: 'Send WhatsApp messages',
diff --git a/apps/sim/blocks/blocks/workflow.ts b/apps/sim/blocks/blocks/workflow.ts
index fac3ae58e3..536763410a 100644
--- a/apps/sim/blocks/blocks/workflow.ts
+++ b/apps/sim/blocks/blocks/workflow.ts
@@ -1,8 +1,8 @@
import { WorkflowIcon } from '@/components/icons'
import { createLogger } from '@/lib/logs/console-logger'
+import type { BlockConfig } from '@/blocks/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import type { ToolResponse } from '@/tools/types'
-import type { BlockConfig } from '../types'
const logger = createLogger('WorkflowBlock')
diff --git a/apps/sim/blocks/blocks/x.ts b/apps/sim/blocks/blocks/x.ts
index 24d3140d05..398565a003 100644
--- a/apps/sim/blocks/blocks/x.ts
+++ b/apps/sim/blocks/blocks/x.ts
@@ -1,8 +1,6 @@
import { xIcon } from '@/components/icons'
-import type { XReadResponse, XSearchResponse, XUserResponse, XWriteResponse } from '@/tools/x/types'
-import type { BlockConfig } from '../types'
-
-type XResponse = XWriteResponse | XReadResponse | XSearchResponse | XUserResponse
+import type { BlockConfig } from '@/blocks/types'
+import type { XResponse } from '@/tools/x/types'
export const XBlock: BlockConfig = {
type: 'x',
@@ -15,7 +13,6 @@ export const XBlock: BlockConfig = {
bgColor: '#000000', // X's black color
icon: xIcon,
subBlocks: [
- // Operation selector
{
id: 'operation',
title: 'Operation',
@@ -29,7 +26,6 @@ export const XBlock: BlockConfig = {
],
value: () => 'x_write',
},
- // X OAuth Authentication
{
id: 'credential',
title: 'X Account',
@@ -40,7 +36,6 @@ export const XBlock: BlockConfig = {
requiredScopes: ['tweet.read', 'tweet.write', 'users.read'],
placeholder: 'Select X account',
},
- // Write operation inputs
{
id: 'text',
title: 'Tweet Text',
@@ -65,7 +60,6 @@ export const XBlock: BlockConfig = {
placeholder: 'Enter comma-separated media IDs',
condition: { field: 'operation', value: 'x_write' },
},
- // Read operation inputs
{
id: 'tweetId',
title: 'Tweet ID',
@@ -86,7 +80,6 @@ export const XBlock: BlockConfig = {
value: () => 'false',
condition: { field: 'operation', value: 'x_read' },
},
- // Search operation inputs
{
id: 'query',
title: 'Search Query',
@@ -131,7 +124,6 @@ export const XBlock: BlockConfig = {
placeholder: 'YYYY-MM-DDTHH:mm:ssZ',
condition: { field: 'operation', value: 'x_search' },
},
- // User operation inputs
{
id: 'username',
title: 'Username',
@@ -198,21 +190,17 @@ export const XBlock: BlockConfig = {
inputs: {
operation: { type: 'string', required: true },
credential: { type: 'string', required: true },
- // Write operation
text: { type: 'string', required: false },
replyTo: { type: 'string', required: false },
mediaIds: { type: 'string', required: false },
poll: { type: 'json', required: false },
- // Read operation
tweetId: { type: 'string', required: false },
includeReplies: { type: 'boolean', required: false },
- // Search operation
query: { type: 'string', required: false },
maxResults: { type: 'number', required: false },
startTime: { type: 'string', required: false },
endTime: { type: 'string', required: false },
sortOrder: { type: 'string', required: false },
- // User operation
username: { type: 'string', required: false },
includeRecentTweets: { type: 'boolean', required: false },
},
diff --git a/apps/sim/blocks/blocks/youtube.ts b/apps/sim/blocks/blocks/youtube.ts
index 5ff45080d4..b736892228 100644
--- a/apps/sim/blocks/blocks/youtube.ts
+++ b/apps/sim/blocks/blocks/youtube.ts
@@ -1,6 +1,6 @@
import { YouTubeIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
import type { YouTubeSearchResponse } from '@/tools/youtube/types'
-import type { BlockConfig } from '../types'
export const YouTubeBlock: BlockConfig = {
type: 'youtube',
diff --git a/apps/sim/blocks/index.ts b/apps/sim/blocks/index.ts
index d46c9ebcdc..f23585b2fd 100644
--- a/apps/sim/blocks/index.ts
+++ b/apps/sim/blocks/index.ts
@@ -5,8 +5,8 @@ import {
getBlocksByCategory,
isValidBlockType,
registry,
-} from './registry'
+} from '@/blocks/registry'
export { registry, getBlock, getBlocksByCategory, getAllBlockTypes, isValidBlockType, getAllBlocks }
-export type { BlockConfig } from './types'
+export type { BlockConfig } from '@/blocks/types'
diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts
index eebfa23cde..37c9287e01 100644
--- a/apps/sim/blocks/registry.ts
+++ b/apps/sim/blocks/registry.ts
@@ -2,68 +2,70 @@
* Blocks Registry
*
*/
-// Import all blocks directly here
-import { AgentBlock } from './blocks/agent'
-import { AirtableBlock } from './blocks/airtable'
-import { ApiBlock } from './blocks/api'
-import { BrowserUseBlock } from './blocks/browser_use'
-import { ClayBlock } from './blocks/clay'
-import { ConditionBlock } from './blocks/condition'
-import { ConfluenceBlock } from './blocks/confluence'
-import { DiscordBlock } from './blocks/discord'
-import { ElevenLabsBlock } from './blocks/elevenlabs'
-import { EvaluatorBlock } from './blocks/evaluator'
-import { ExaBlock } from './blocks/exa'
-import { FileBlock } from './blocks/file'
-import { FirecrawlBlock } from './blocks/firecrawl'
-import { FunctionBlock } from './blocks/function'
-import { GitHubBlock } from './blocks/github'
-import { GmailBlock } from './blocks/gmail'
-import { GoogleSearchBlock } from './blocks/google'
-import { GoogleCalendarBlock } from './blocks/google_calendar'
-import { GoogleDocsBlock } from './blocks/google_docs'
-import { GoogleDriveBlock } from './blocks/google_drive'
-import { GoogleSheetsBlock } from './blocks/google_sheets'
-import { HuggingFaceBlock } from './blocks/huggingface'
-import { ImageGeneratorBlock } from './blocks/image_generator'
-import { JinaBlock } from './blocks/jina'
-import { JiraBlock } from './blocks/jira'
-import { KnowledgeBlock } from './blocks/knowledge'
-import { LinearBlock } from './blocks/linear'
-import { LinkupBlock } from './blocks/linkup'
-import { Mem0Block } from './blocks/mem0'
-import { MemoryBlock } from './blocks/memory'
-import { MicrosoftExcelBlock } from './blocks/microsoft_excel'
-import { MicrosoftTeamsBlock } from './blocks/microsoft_teams'
-import { MistralParseBlock } from './blocks/mistral_parse'
-import { NotionBlock } from './blocks/notion'
-import { OpenAIBlock } from './blocks/openai'
-import { OutlookBlock } from './blocks/outlook'
-import { PerplexityBlock } from './blocks/perplexity'
-import { PineconeBlock } from './blocks/pinecone'
-import { RedditBlock } from './blocks/reddit'
-import { ResponseBlock } from './blocks/response'
-import { RouterBlock } from './blocks/router'
-import { S3Block } from './blocks/s3'
-import { SerperBlock } from './blocks/serper'
-import { SlackBlock } from './blocks/slack'
-import { StagehandBlock } from './blocks/stagehand'
-import { StagehandAgentBlock } from './blocks/stagehand_agent'
-import { StarterBlock } from './blocks/starter'
-import { SupabaseBlock } from './blocks/supabase'
-import { TavilyBlock } from './blocks/tavily'
-import { TelegramBlock } from './blocks/telegram'
-import { ThinkingBlock } from './blocks/thinking'
-import { TranslateBlock } from './blocks/translate'
-import { TwilioSMSBlock } from './blocks/twilio'
-import { TypeformBlock } from './blocks/typeform'
-import { VisionBlock } from './blocks/vision'
-import { WealthboxBlock } from './blocks/wealthbox'
-import { WhatsAppBlock } from './blocks/whatsapp'
-import { WorkflowBlock } from './blocks/workflow'
-import { XBlock } from './blocks/x'
-import { YouTubeBlock } from './blocks/youtube'
-import type { BlockConfig } from './types'
+
+import { AgentBlock } from '@/blocks/blocks/agent'
+import { AirtableBlock } from '@/blocks/blocks/airtable'
+import { ApiBlock } from '@/blocks/blocks/api'
+import { BrowserUseBlock } from '@/blocks/blocks/browser_use'
+import { ClayBlock } from '@/blocks/blocks/clay'
+import { ConditionBlock } from '@/blocks/blocks/condition'
+import { ConfluenceBlock } from '@/blocks/blocks/confluence'
+import { DiscordBlock } from '@/blocks/blocks/discord'
+import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs'
+import { EvaluatorBlock } from '@/blocks/blocks/evaluator'
+import { ExaBlock } from '@/blocks/blocks/exa'
+import { FileBlock } from '@/blocks/blocks/file'
+import { FirecrawlBlock } from '@/blocks/blocks/firecrawl'
+import { FunctionBlock } from '@/blocks/blocks/function'
+import { GitHubBlock } from '@/blocks/blocks/github'
+import { GmailBlock } from '@/blocks/blocks/gmail'
+import { GoogleSearchBlock } from '@/blocks/blocks/google'
+import { GoogleCalendarBlock } from '@/blocks/blocks/google_calendar'
+import { GoogleDocsBlock } from '@/blocks/blocks/google_docs'
+import { GoogleDriveBlock } from '@/blocks/blocks/google_drive'
+import { GoogleSheetsBlock } from '@/blocks/blocks/google_sheets'
+import { HuggingFaceBlock } from '@/blocks/blocks/huggingface'
+import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator'
+import { JinaBlock } from '@/blocks/blocks/jina'
+import { JiraBlock } from '@/blocks/blocks/jira'
+import { KnowledgeBlock } from '@/blocks/blocks/knowledge'
+import { LinearBlock } from '@/blocks/blocks/linear'
+import { LinkupBlock } from '@/blocks/blocks/linkup'
+import { Mem0Block } from '@/blocks/blocks/mem0'
+import { MemoryBlock } from '@/blocks/blocks/memory'
+import { MicrosoftExcelBlock } from '@/blocks/blocks/microsoft_excel'
+import { MicrosoftTeamsBlock } from '@/blocks/blocks/microsoft_teams'
+import { MistralParseBlock } from '@/blocks/blocks/mistral_parse'
+import { NotionBlock } from '@/blocks/blocks/notion'
+import { OpenAIBlock } from '@/blocks/blocks/openai'
+import { OutlookBlock } from '@/blocks/blocks/outlook'
+import { PerplexityBlock } from '@/blocks/blocks/perplexity'
+import { PineconeBlock } from '@/blocks/blocks/pinecone'
+import { RedditBlock } from '@/blocks/blocks/reddit'
+import { ResponseBlock } from '@/blocks/blocks/response'
+import { RouterBlock } from '@/blocks/blocks/router'
+import { S3Block } from '@/blocks/blocks/s3'
+import { ScheduleBlock } from '@/blocks/blocks/schedule'
+import { SerperBlock } from '@/blocks/blocks/serper'
+import { SlackBlock } from '@/blocks/blocks/slack'
+import { StagehandBlock } from '@/blocks/blocks/stagehand'
+import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent'
+import { StarterBlock } from '@/blocks/blocks/starter'
+import { SupabaseBlock } from '@/blocks/blocks/supabase'
+import { TavilyBlock } from '@/blocks/blocks/tavily'
+import { TelegramBlock } from '@/blocks/blocks/telegram'
+import { ThinkingBlock } from '@/blocks/blocks/thinking'
+import { TranslateBlock } from '@/blocks/blocks/translate'
+import { TwilioSMSBlock } from '@/blocks/blocks/twilio'
+import { TypeformBlock } from '@/blocks/blocks/typeform'
+import { VisionBlock } from '@/blocks/blocks/vision'
+import { WealthboxBlock } from '@/blocks/blocks/wealthbox'
+import { WebhookBlock } from '@/blocks/blocks/webhook'
+import { WhatsAppBlock } from '@/blocks/blocks/whatsapp'
+import { WorkflowBlock } from '@/blocks/blocks/workflow'
+import { XBlock } from '@/blocks/blocks/x'
+import { YouTubeBlock } from '@/blocks/blocks/youtube'
+import type { BlockConfig } from '@/blocks/types'
// Registry of all available blocks, alphabetically sorted
export const registry: Record = {
@@ -108,6 +110,7 @@ export const registry: Record = {
reddit: RedditBlock,
response: ResponseBlock,
router: RouterBlock,
+ schedule: ScheduleBlock,
s3: S3Block,
serper: SerperBlock,
stagehand: StagehandBlock,
@@ -123,16 +126,16 @@ export const registry: Record = {
typeform: TypeformBlock,
vision: VisionBlock,
wealthbox: WealthboxBlock,
+ webhook: WebhookBlock,
whatsapp: WhatsAppBlock,
workflow: WorkflowBlock,
x: XBlock,
youtube: YouTubeBlock,
}
-// Helper functions to access the registry
export const getBlock = (type: string): BlockConfig | undefined => registry[type]
-export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
+export const getBlocksByCategory = (category: 'blocks' | 'tools' | 'triggers'): BlockConfig[] =>
Object.values(registry).filter((block) => block.category === category)
export const getAllBlockTypes = (): string[] => Object.keys(registry)
diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts
index d68c2158ff..bcc7a75dd5 100644
--- a/apps/sim/blocks/types.ts
+++ b/apps/sim/blocks/types.ts
@@ -7,7 +7,7 @@ export type ParamType = 'string' | 'number' | 'boolean' | 'json'
export type PrimitiveValueType = 'string' | 'number' | 'boolean' | 'json' | 'any'
// Block classification
-export type BlockCategory = 'blocks' | 'tools'
+export type BlockCategory = 'blocks' | 'tools' | 'triggers'
// SubBlock types
export type SubBlockType =
diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx
index 8e2b475b2b..df94a90ca3 100644
--- a/apps/sim/components/icons.tsx
+++ b/apps/sim/components/icons.tsx
@@ -2808,7 +2808,7 @@ export const ResponseIcon = (props: SVGProps) => (
>
)
@@ -2988,3 +2988,41 @@ export function WealthboxIcon(props: SVGProps) {
)
}
+
+export function WebhookIcon(props: SVGProps) {
+ return (
+
+
+
+
+ )
+}
+
+export function ScheduleIcon(props: SVGProps) {
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx
index 40c9bb66a7..df2f613df8 100644
--- a/apps/sim/components/ui/tag-dropdown.tsx
+++ b/apps/sim/components/ui/tag-dropdown.tsx
@@ -184,19 +184,33 @@ export const TagDropdown: React.FC = ({
} else if (Object.keys(blockConfig.outputs).length === 0) {
// Handle blocks with no outputs (like starter) - check for custom input fields
if (sourceBlock.type === 'starter') {
- // Check for custom input format fields
- const inputFormatValue = useSubBlockStore
+ // Check what start workflow mode is selected
+ const startWorkflowValue = useSubBlockStore
.getState()
- .getValue(activeSourceBlockId, 'inputFormat')
+ .getValue(activeSourceBlockId, 'startWorkflow')
- if (inputFormatValue && Array.isArray(inputFormatValue) && inputFormatValue.length > 0) {
- // Use custom input fields if they exist
- blockTags = inputFormatValue
- .filter((field: any) => field.name && field.name.trim() !== '')
- .map((field: any) => `${normalizedBlockName}.${field.name}`)
+ if (startWorkflowValue === 'chat') {
+ // For chat mode, provide input and conversationId
+ blockTags = [`${normalizedBlockName}.input`, `${normalizedBlockName}.conversationId`]
} else {
- // Fallback to just the block name
- blockTags = [normalizedBlockName]
+ // Check for custom input format fields (for manual mode)
+ const inputFormatValue = useSubBlockStore
+ .getState()
+ .getValue(activeSourceBlockId, 'inputFormat')
+
+ if (
+ inputFormatValue &&
+ Array.isArray(inputFormatValue) &&
+ inputFormatValue.length > 0
+ ) {
+ // Use custom input fields if they exist
+ blockTags = inputFormatValue
+ .filter((field: any) => field.name && field.name.trim() !== '')
+ .map((field: any) => `${normalizedBlockName}.${field.name}`)
+ } else {
+ // Fallback to just the block name
+ blockTags = [normalizedBlockName]
+ }
}
} else {
// Other blocks with no outputs - show as just
@@ -429,19 +443,33 @@ export const TagDropdown: React.FC = ({
} else if (Object.keys(blockConfig.outputs).length === 0) {
// Handle blocks with no outputs (like starter) - check for custom input fields
if (accessibleBlock.type === 'starter') {
- // Check for custom input format fields
- const inputFormatValue = useSubBlockStore
+ // Check what start workflow mode is selected
+ const startWorkflowValue = useSubBlockStore
.getState()
- .getValue(accessibleBlockId, 'inputFormat')
+ .getValue(accessibleBlockId, 'startWorkflow')
- if (inputFormatValue && Array.isArray(inputFormatValue) && inputFormatValue.length > 0) {
- // Use custom input fields if they exist
- blockTags = inputFormatValue
- .filter((field: any) => field.name && field.name.trim() !== '')
- .map((field: any) => `${normalizedBlockName}.${field.name}`)
+ if (startWorkflowValue === 'chat') {
+ // For chat mode, provide input and conversationId
+ blockTags = [`${normalizedBlockName}.input`, `${normalizedBlockName}.conversationId`]
} else {
- // Fallback to just the block name
- blockTags = [normalizedBlockName]
+ // Check for custom input format fields (for manual mode)
+ const inputFormatValue = useSubBlockStore
+ .getState()
+ .getValue(accessibleBlockId, 'inputFormat')
+
+ if (
+ inputFormatValue &&
+ Array.isArray(inputFormatValue) &&
+ inputFormatValue.length > 0
+ ) {
+ // Use custom input fields if they exist
+ blockTags = inputFormatValue
+ .filter((field: any) => field.name && field.name.trim() !== '')
+ .map((field: any) => `${normalizedBlockName}.${field.name}`)
+ } else {
+ // Fallback to just the block name
+ blockTags = [normalizedBlockName]
+ }
}
} else {
// Other blocks with no outputs - show as just
diff --git a/apps/sim/contexts/socket-context.tsx b/apps/sim/contexts/socket-context.tsx
index 90f28477c2..f0f58dc546 100644
--- a/apps/sim/contexts/socket-context.tsx
+++ b/apps/sim/contexts/socket-context.tsx
@@ -357,7 +357,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
})
socketInstance.on('workflow-state', (state) => {
- logger.info('Received workflow state from server:', state)
+ // logger.info('Received workflow state from server:', state)
// This will be used to sync initial state when joining a workflow
})
diff --git a/apps/sim/db/migrations/0056_adorable_franklin_richards.sql b/apps/sim/db/migrations/0056_adorable_franklin_richards.sql
new file mode 100644
index 0000000000..4fcc959fe3
--- /dev/null
+++ b/apps/sim/db/migrations/0056_adorable_franklin_richards.sql
@@ -0,0 +1,11 @@
+CREATE TABLE "user_rate_limits" (
+ "user_id" text PRIMARY KEY NOT NULL,
+ "sync_api_requests" integer DEFAULT 0 NOT NULL,
+ "async_api_requests" integer DEFAULT 0 NOT NULL,
+ "window_start" timestamp DEFAULT now() NOT NULL,
+ "last_request_at" timestamp DEFAULT now() NOT NULL,
+ "is_rate_limited" boolean DEFAULT false NOT NULL,
+ "rate_limit_reset_at" timestamp
+);
+--> statement-breakpoint
+ALTER TABLE "user_rate_limits" ADD CONSTRAINT "user_rate_limits_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/apps/sim/db/migrations/0057_charming_star_brand.sql b/apps/sim/db/migrations/0057_charming_star_brand.sql
new file mode 100644
index 0000000000..4825c79305
--- /dev/null
+++ b/apps/sim/db/migrations/0057_charming_star_brand.sql
@@ -0,0 +1,6 @@
+ALTER TABLE "workflow_schedule" DROP CONSTRAINT "workflow_schedule_workflow_id_unique";--> statement-breakpoint
+ALTER TABLE "webhook" ADD COLUMN "block_id" text;--> statement-breakpoint
+ALTER TABLE "workflow_schedule" ADD COLUMN "block_id" text;--> statement-breakpoint
+ALTER TABLE "webhook" ADD CONSTRAINT "webhook_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "workflow_schedule" ADD CONSTRAINT "workflow_schedule_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "workflow_schedule_workflow_block_unique" ON "workflow_schedule" USING btree ("workflow_id","block_id");
\ No newline at end of file
diff --git a/apps/sim/db/migrations/meta/0056_snapshot.json b/apps/sim/db/migrations/meta/0056_snapshot.json
new file mode 100644
index 0000000000..fc54beb41b
--- /dev/null
+++ b/apps/sim/db/migrations/meta/0056_snapshot.json
@@ -0,0 +1,5609 @@
+{
+ "id": "3bb59215-ddd2-4c4a-82e7-4fc9e998ca08",
+ "prevId": "fef16f30-2233-465e-b40a-27a255010589",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.api_key": {
+ "name": "api_key",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_key_user_id_user_id_fk": {
+ "name": "api_key_user_id_user_id_fk",
+ "tableFrom": "api_key",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "api_key_key_unique": {
+ "name": "api_key_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat": {
+ "name": "chat",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "customizations": {
+ "name": "customizations",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'public'"
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_emails": {
+ "name": "allowed_emails",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "output_configs": {
+ "name": "output_configs",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "subdomain_idx": {
+ "name": "subdomain_idx",
+ "columns": [
+ {
+ "expression": "subdomain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_workflow_id_workflow_id_fk": {
+ "name": "chat_workflow_id_workflow_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chat_user_id_user_id_fk": {
+ "name": "chat_user_id_user_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_chats": {
+ "name": "copilot_chats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "messages": {
+ "name": "messages",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'claude-3-7-sonnet-latest'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_chats_user_id_idx": {
+ "name": "copilot_chats_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_workflow_id_idx": {
+ "name": "copilot_chats_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_user_workflow_idx": {
+ "name": "copilot_chats_user_workflow_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_created_at_idx": {
+ "name": "copilot_chats_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_updated_at_idx": {
+ "name": "copilot_chats_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_chats_user_id_user_id_fk": {
+ "name": "copilot_chats_user_id_user_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_chats_workflow_id_workflow_id_fk": {
+ "name": "copilot_chats_workflow_id_workflow_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom_tools": {
+ "name": "custom_tools",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schema": {
+ "name": "schema",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "custom_tools_user_id_user_id_fk": {
+ "name": "custom_tools_user_id_user_id_fk",
+ "tableFrom": "custom_tools",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.docs_embeddings": {
+ "name": "docs_embeddings",
+ "schema": "",
+ "columns": {
+ "chunk_id": {
+ "name": "chunk_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chunk_text": {
+ "name": "chunk_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_document": {
+ "name": "source_document",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "header_text": {
+ "name": "header_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "header_level": {
+ "name": "header_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "vector(1536)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "chunk_text_tsv": {
+ "name": "chunk_text_tsv",
+ "type": "tsvector",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")",
+ "type": "stored"
+ }
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "docs_emb_source_document_idx": {
+ "name": "docs_emb_source_document_idx",
+ "columns": [
+ {
+ "expression": "source_document",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_header_level_idx": {
+ "name": "docs_emb_header_level_idx",
+ "columns": [
+ {
+ "expression": "header_level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_source_header_idx": {
+ "name": "docs_emb_source_header_idx",
+ "columns": [
+ {
+ "expression": "source_document",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "header_level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_model_idx": {
+ "name": "docs_emb_model_idx",
+ "columns": [
+ {
+ "expression": "embedding_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_created_at_idx": {
+ "name": "docs_emb_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_embedding_vector_hnsw_idx": {
+ "name": "docs_embedding_vector_hnsw_idx",
+ "columns": [
+ {
+ "expression": "embedding",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "vector_cosine_ops"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "hnsw",
+ "with": {
+ "m": 16,
+ "ef_construction": 64
+ }
+ },
+ "docs_emb_metadata_gin_idx": {
+ "name": "docs_emb_metadata_gin_idx",
+ "columns": [
+ {
+ "expression": "metadata",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ },
+ "docs_emb_chunk_text_fts_idx": {
+ "name": "docs_emb_chunk_text_fts_idx",
+ "columns": [
+ {
+ "expression": "chunk_text_tsv",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "docs_embedding_not_null_check": {
+ "name": "docs_embedding_not_null_check",
+ "value": "\"embedding\" IS NOT NULL"
+ },
+ "docs_header_level_check": {
+ "name": "docs_header_level_check",
+ "value": "\"header_level\" >= 1 AND \"header_level\" <= 6"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.document": {
+ "name": "document",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_url": {
+ "name": "file_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_size": {
+ "name": "file_size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_count": {
+ "name": "chunk_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "character_count": {
+ "name": "character_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "processing_status": {
+ "name": "processing_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "processing_started_at": {
+ "name": "processing_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_completed_at": {
+ "name": "processing_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_error": {
+ "name": "processing_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "doc_kb_id_idx": {
+ "name": "doc_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_filename_idx": {
+ "name": "doc_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_kb_uploaded_at_idx": {
+ "name": "doc_kb_uploaded_at_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_processing_status_idx": {
+ "name": "doc_processing_status_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "processing_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag1_idx": {
+ "name": "doc_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag2_idx": {
+ "name": "doc_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag3_idx": {
+ "name": "doc_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag4_idx": {
+ "name": "doc_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag5_idx": {
+ "name": "doc_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag6_idx": {
+ "name": "doc_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag7_idx": {
+ "name": "doc_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "document_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "document_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "document",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.embedding": {
+ "name": "embedding",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_index": {
+ "name": "chunk_index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_hash": {
+ "name": "chunk_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content_length": {
+ "name": "content_length",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "vector(1536)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "start_offset": {
+ "name": "start_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_offset": {
+ "name": "end_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content_tsv": {
+ "name": "content_tsv",
+ "type": "tsvector",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "to_tsvector('english', \"embedding\".\"content\")",
+ "type": "stored"
+ }
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "emb_kb_id_idx": {
+ "name": "emb_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_id_idx": {
+ "name": "emb_doc_id_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_chunk_idx": {
+ "name": "emb_doc_chunk_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chunk_index",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_model_idx": {
+ "name": "emb_kb_model_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "embedding_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_enabled_idx": {
+ "name": "emb_kb_enabled_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_enabled_idx": {
+ "name": "emb_doc_enabled_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "embedding_vector_hnsw_idx": {
+ "name": "embedding_vector_hnsw_idx",
+ "columns": [
+ {
+ "expression": "embedding",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "vector_cosine_ops"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "hnsw",
+ "with": {
+ "m": 16,
+ "ef_construction": 64
+ }
+ },
+ "emb_tag1_idx": {
+ "name": "emb_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag2_idx": {
+ "name": "emb_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag3_idx": {
+ "name": "emb_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag4_idx": {
+ "name": "emb_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag5_idx": {
+ "name": "emb_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag6_idx": {
+ "name": "emb_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag7_idx": {
+ "name": "emb_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_content_fts_idx": {
+ "name": "emb_content_fts_idx",
+ "columns": [
+ {
+ "expression": "content_tsv",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "embedding_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "embedding_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "embedding_document_id_document_id_fk": {
+ "name": "embedding_document_id_document_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "document",
+ "columnsFrom": ["document_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "embedding_not_null_check": {
+ "name": "embedding_not_null_check",
+ "value": "\"embedding\" IS NOT NULL"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_user_id_user_id_fk": {
+ "name": "environment_user_id_user_id_fk",
+ "tableFrom": "environment",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_user_id_unique": {
+ "name": "environment_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": ["inviter_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.knowledge_base": {
+ "name": "knowledge_base",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "embedding_dimension": {
+ "name": "embedding_dimension",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1536
+ },
+ "chunking_config": {
+ "name": "chunking_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"maxSize\": 1024, \"minSize\": 100, \"overlap\": 200}'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "kb_user_id_idx": {
+ "name": "kb_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_workspace_id_idx": {
+ "name": "kb_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_user_workspace_idx": {
+ "name": "kb_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_deleted_at_idx": {
+ "name": "kb_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "knowledge_base_user_id_user_id_fk": {
+ "name": "knowledge_base_user_id_user_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "knowledge_base_workspace_id_workspace_id_fk": {
+ "name": "knowledge_base_workspace_id_workspace_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.marketplace": {
+ "name": "marketplace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_name": {
+ "name": "author_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "views": {
+ "name": "views",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "marketplace_workflow_id_workflow_id_fk": {
+ "name": "marketplace_workflow_id_workflow_id_fk",
+ "tableFrom": "marketplace",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "marketplace_author_id_user_id_fk": {
+ "name": "marketplace_author_id_user_id_fk",
+ "tableFrom": "marketplace",
+ "tableTo": "user",
+ "columnsFrom": ["author_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory": {
+ "name": "memory",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "memory_key_idx": {
+ "name": "memory_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workflow_idx": {
+ "name": "memory_workflow_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workflow_key_idx": {
+ "name": "memory_workflow_key_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_workflow_id_workflow_id_fk": {
+ "name": "memory_workflow_id_workflow_id_fk",
+ "tableFrom": "memory",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permissions": {
+ "name": "permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_type": {
+ "name": "permission_type",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permissions_user_id_idx": {
+ "name": "permissions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_entity_idx": {
+ "name": "permissions_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_type_idx": {
+ "name": "permissions_user_entity_type_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_permission_idx": {
+ "name": "permissions_user_entity_permission_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "permission_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_idx": {
+ "name": "permissions_user_entity_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_unique_constraint": {
+ "name": "permissions_unique_constraint",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permissions_user_id_user_id_fk": {
+ "name": "permissions_user_id_user_id_fk",
+ "tableFrom": "permissions",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_active_organization_id_organization_id_fk": {
+ "name": "session_active_organization_id_organization_id_fk",
+ "tableFrom": "session",
+ "tableTo": "organization",
+ "columnsFrom": ["active_organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'system'"
+ },
+ "auto_connect": {
+ "name": "auto_connect",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auto_fill_env_vars": {
+ "name": "auto_fill_env_vars",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auto_pan": {
+ "name": "auto_pan",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "console_expanded_by_default": {
+ "name": "console_expanded_by_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "telemetry_enabled": {
+ "name": "telemetry_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "telemetry_notified_user": {
+ "name": "telemetry_notified_user",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "email_preferences": {
+ "name": "email_preferences",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "general": {
+ "name": "general",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "settings_user_id_user_id_fk": {
+ "name": "settings_user_id_user_id_fk",
+ "tableFrom": "settings",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "settings_user_id_unique": {
+ "name": "settings_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.subscription": {
+ "name": "subscription",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan": {
+ "name": "plan",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_start": {
+ "name": "period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_end": {
+ "name": "period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_at_period_end": {
+ "name": "cancel_at_period_end",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seats": {
+ "name": "seats",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_start": {
+ "name": "trial_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_end": {
+ "name": "trial_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "subscription_reference_status_idx": {
+ "name": "subscription_reference_status_idx",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "check_enterprise_metadata": {
+ "name": "check_enterprise_metadata",
+ "value": "plan != 'enterprise' OR (metadata IS NOT NULL AND (metadata->>'perSeatAllowance' IS NOT NULL OR metadata->>'totalAllowance' IS NOT NULL))"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.template_stars": {
+ "name": "template_stars",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "template_id": {
+ "name": "template_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "starred_at": {
+ "name": "starred_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "template_stars_user_id_idx": {
+ "name": "template_stars_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_id_idx": {
+ "name": "template_stars_template_id_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_user_template_idx": {
+ "name": "template_stars_user_template_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_user_idx": {
+ "name": "template_stars_template_user_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_starred_at_idx": {
+ "name": "template_stars_starred_at_idx",
+ "columns": [
+ {
+ "expression": "starred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_starred_at_idx": {
+ "name": "template_stars_template_starred_at_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "starred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_user_template_unique": {
+ "name": "template_stars_user_template_unique",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "template_stars_user_id_user_id_fk": {
+ "name": "template_stars_user_id_user_id_fk",
+ "tableFrom": "template_stars",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "template_stars_template_id_templates_id_fk": {
+ "name": "template_stars_template_id_templates_id_fk",
+ "tableFrom": "template_stars",
+ "tableTo": "templates",
+ "columnsFrom": ["template_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.templates": {
+ "name": "templates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "views": {
+ "name": "views",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "stars": {
+ "name": "stars",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'#3972F6'"
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'FileText'"
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "templates_workflow_id_idx": {
+ "name": "templates_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_user_id_idx": {
+ "name": "templates_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_idx": {
+ "name": "templates_category_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_views_idx": {
+ "name": "templates_views_idx",
+ "columns": [
+ {
+ "expression": "views",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_stars_idx": {
+ "name": "templates_stars_idx",
+ "columns": [
+ {
+ "expression": "stars",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_views_idx": {
+ "name": "templates_category_views_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "views",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_stars_idx": {
+ "name": "templates_category_stars_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "stars",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_user_category_idx": {
+ "name": "templates_user_category_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_created_at_idx": {
+ "name": "templates_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_updated_at_idx": {
+ "name": "templates_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "templates_workflow_id_workflow_id_fk": {
+ "name": "templates_workflow_id_workflow_id_fk",
+ "tableFrom": "templates",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "templates_user_id_user_id_fk": {
+ "name": "templates_user_id_user_id_fk",
+ "tableFrom": "templates",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_rate_limits": {
+ "name": "user_rate_limits",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sync_api_requests": {
+ "name": "sync_api_requests",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "async_api_requests": {
+ "name": "async_api_requests",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "window_start": {
+ "name": "window_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_request_at": {
+ "name": "last_request_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_rate_limited": {
+ "name": "is_rate_limited",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "rate_limit_reset_at": {
+ "name": "rate_limit_reset_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_rate_limits_user_id_user_id_fk": {
+ "name": "user_rate_limits_user_id_user_id_fk",
+ "tableFrom": "user_rate_limits",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_stats": {
+ "name": "user_stats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_manual_executions": {
+ "name": "total_manual_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_api_calls": {
+ "name": "total_api_calls",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_webhook_triggers": {
+ "name": "total_webhook_triggers",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_scheduled_executions": {
+ "name": "total_scheduled_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_chat_executions": {
+ "name": "total_chat_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_tokens_used": {
+ "name": "total_tokens_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_usage_limit": {
+ "name": "current_usage_limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'5'"
+ },
+ "usage_limit_set_by": {
+ "name": "usage_limit_set_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "usage_limit_updated_at": {
+ "name": "usage_limit_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "current_period_cost": {
+ "name": "current_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "billing_period_start": {
+ "name": "billing_period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "billing_period_end": {
+ "name": "billing_period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_period_cost": {
+ "name": "last_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "last_active": {
+ "name": "last_active",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_stats_user_id_user_id_fk": {
+ "name": "user_stats_user_id_user_id_fk",
+ "tableFrom": "user_stats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_stats_user_id_unique": {
+ "name": "user_stats_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.waitlist": {
+ "name": "waitlist",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "waitlist_email_unique": {
+ "name": "waitlist_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webhook": {
+ "name": "webhook",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_config": {
+ "name": "provider_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "path_idx": {
+ "name": "path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "webhook_workflow_id_workflow_id_fk": {
+ "name": "webhook_workflow_id_workflow_id_fk",
+ "tableFrom": "webhook",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow": {
+ "name": "workflow",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'#3972F6'"
+ },
+ "last_synced": {
+ "name": "last_synced",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_deployed": {
+ "name": "is_deployed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "deployed_state": {
+ "name": "deployed_state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deployed_at": {
+ "name": "deployed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "collaborators": {
+ "name": "collaborators",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "run_count": {
+ "name": "run_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "is_published": {
+ "name": "is_published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "marketplace_data": {
+ "name": "marketplace_data",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_user_id_user_id_fk": {
+ "name": "workflow_user_id_user_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_workspace_id_workspace_id_fk": {
+ "name": "workflow_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_folder_id_workflow_folder_id_fk": {
+ "name": "workflow_folder_id_workflow_folder_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workflow_folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_blocks": {
+ "name": "workflow_blocks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_x": {
+ "name": "position_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_y": {
+ "name": "position_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "horizontal_handles": {
+ "name": "horizontal_handles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "is_wide": {
+ "name": "is_wide",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "advanced_mode": {
+ "name": "advanced_mode",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "sub_blocks": {
+ "name": "sub_blocks",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "extent": {
+ "name": "extent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_blocks_workflow_id_idx": {
+ "name": "workflow_blocks_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_parent_id_idx": {
+ "name": "workflow_blocks_parent_id_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_workflow_parent_idx": {
+ "name": "workflow_blocks_workflow_parent_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_workflow_type_idx": {
+ "name": "workflow_blocks_workflow_type_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_blocks_workflow_id_workflow_id_fk": {
+ "name": "workflow_blocks_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_blocks",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_edges": {
+ "name": "workflow_edges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_block_id": {
+ "name": "source_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_block_id": {
+ "name": "target_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_handle": {
+ "name": "source_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "target_handle": {
+ "name": "target_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_edges_workflow_id_idx": {
+ "name": "workflow_edges_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_source_block_idx": {
+ "name": "workflow_edges_source_block_idx",
+ "columns": [
+ {
+ "expression": "source_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_target_block_idx": {
+ "name": "workflow_edges_target_block_idx",
+ "columns": [
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_source_idx": {
+ "name": "workflow_edges_workflow_source_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_target_idx": {
+ "name": "workflow_edges_workflow_target_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_edges_workflow_id_workflow_id_fk": {
+ "name": "workflow_edges_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_source_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_source_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["source_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_target_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_target_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["target_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_blocks": {
+ "name": "workflow_execution_blocks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_name": {
+ "name": "block_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_type": {
+ "name": "block_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_stack_trace": {
+ "name": "error_stack_trace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "input_data": {
+ "name": "input_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output_data": {
+ "name": "output_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_input": {
+ "name": "cost_input",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_output": {
+ "name": "cost_output",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_total": {
+ "name": "cost_total",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_prompt": {
+ "name": "tokens_prompt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_completion": {
+ "name": "tokens_completion",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_total": {
+ "name": "tokens_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model_used": {
+ "name": "model_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "execution_blocks_execution_id_idx": {
+ "name": "execution_blocks_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_workflow_id_idx": {
+ "name": "execution_blocks_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_block_id_idx": {
+ "name": "execution_blocks_block_id_idx",
+ "columns": [
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_status_idx": {
+ "name": "execution_blocks_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_duration_idx": {
+ "name": "execution_blocks_duration_idx",
+ "columns": [
+ {
+ "expression": "duration_ms",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_cost_idx": {
+ "name": "execution_blocks_cost_idx",
+ "columns": [
+ {
+ "expression": "cost_total",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_workflow_execution_idx": {
+ "name": "execution_blocks_workflow_execution_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_execution_status_idx": {
+ "name": "execution_blocks_execution_status_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_started_at_idx": {
+ "name": "execution_blocks_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_blocks_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_blocks_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_blocks",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_logs": {
+ "name": "workflow_execution_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_snapshot_id": {
+ "name": "state_snapshot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_duration_ms": {
+ "name": "total_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_count": {
+ "name": "block_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "error_count": {
+ "name": "error_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "skipped_count": {
+ "name": "skipped_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_input_cost": {
+ "name": "total_input_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_output_cost": {
+ "name": "total_output_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_execution_logs_workflow_id_idx": {
+ "name": "workflow_execution_logs_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_execution_id_idx": {
+ "name": "workflow_execution_logs_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_trigger_idx": {
+ "name": "workflow_execution_logs_trigger_idx",
+ "columns": [
+ {
+ "expression": "trigger",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_level_idx": {
+ "name": "workflow_execution_logs_level_idx",
+ "columns": [
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_started_at_idx": {
+ "name": "workflow_execution_logs_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_cost_idx": {
+ "name": "workflow_execution_logs_cost_idx",
+ "columns": [
+ {
+ "expression": "total_cost",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_duration_idx": {
+ "name": "workflow_execution_logs_duration_idx",
+ "columns": [
+ {
+ "expression": "total_duration_ms",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_execution_id_unique": {
+ "name": "workflow_execution_logs_execution_id_unique",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_logs_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_logs_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": {
+ "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow_execution_snapshots",
+ "columnsFrom": ["state_snapshot_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_snapshots": {
+ "name": "workflow_execution_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_hash": {
+ "name": "state_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_data": {
+ "name": "state_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_snapshots_workflow_id_idx": {
+ "name": "workflow_snapshots_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_hash_idx": {
+ "name": "workflow_snapshots_hash_idx",
+ "columns": [
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_workflow_hash_idx": {
+ "name": "workflow_snapshots_workflow_hash_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_created_at_idx": {
+ "name": "workflow_snapshots_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_snapshots_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_snapshots",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_folder": {
+ "name": "workflow_folder",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'#6B7280'"
+ },
+ "is_expanded": {
+ "name": "is_expanded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_folder_user_idx": {
+ "name": "workflow_folder_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_folder_workspace_parent_idx": {
+ "name": "workflow_folder_workspace_parent_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_folder_parent_sort_idx": {
+ "name": "workflow_folder_parent_sort_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_folder_user_id_user_id_fk": {
+ "name": "workflow_folder_user_id_user_id_fk",
+ "tableFrom": "workflow_folder",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_folder_workspace_id_workspace_id_fk": {
+ "name": "workflow_folder_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow_folder",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_logs": {
+ "name": "workflow_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration": {
+ "name": "duration",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_logs_workflow_id_workflow_id_fk": {
+ "name": "workflow_logs_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_logs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_schedule": {
+ "name": "workflow_schedule",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_ran_at": {
+ "name": "last_ran_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "failed_count": {
+ "name": "failed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "last_failed_at": {
+ "name": "last_failed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_schedule_workflow_id_workflow_id_fk": {
+ "name": "workflow_schedule_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workflow_schedule_workflow_id_unique": {
+ "name": "workflow_schedule_workflow_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["workflow_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_subflows": {
+ "name": "workflow_subflows",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_subflows_workflow_id_idx": {
+ "name": "workflow_subflows_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_subflows_workflow_type_idx": {
+ "name": "workflow_subflows_workflow_type_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_subflows_workflow_id_workflow_id_fk": {
+ "name": "workflow_subflows_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_subflows",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace": {
+ "name": "workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_owner_id_user_id_fk": {
+ "name": "workspace_owner_id_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_invitation": {
+ "name": "workspace_invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'admin'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_invitation_workspace_id_workspace_id_fk": {
+ "name": "workspace_invitation_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_invitation",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_invitation_inviter_id_user_id_fk": {
+ "name": "workspace_invitation_inviter_id_user_id_fk",
+ "tableFrom": "workspace_invitation",
+ "tableTo": "user",
+ "columnsFrom": ["inviter_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_invitation_token_unique": {
+ "name": "workspace_invitation_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.permission_type": {
+ "name": "permission_type",
+ "schema": "public",
+ "values": ["admin", "write", "read"]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/apps/sim/db/migrations/meta/0057_snapshot.json b/apps/sim/db/migrations/meta/0057_snapshot.json
new file mode 100644
index 0000000000..d9a6cebabe
--- /dev/null
+++ b/apps/sim/db/migrations/meta/0057_snapshot.json
@@ -0,0 +1,5655 @@
+{
+ "id": "629121b5-cdc9-4e2f-b671-7e3b11af7af1",
+ "prevId": "3bb59215-ddd2-4c4a-82e7-4fc9e998ca08",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.api_key": {
+ "name": "api_key",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_key_user_id_user_id_fk": {
+ "name": "api_key_user_id_user_id_fk",
+ "tableFrom": "api_key",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "api_key_key_unique": {
+ "name": "api_key_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat": {
+ "name": "chat",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subdomain": {
+ "name": "subdomain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "customizations": {
+ "name": "customizations",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'public'"
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_emails": {
+ "name": "allowed_emails",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "output_configs": {
+ "name": "output_configs",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "subdomain_idx": {
+ "name": "subdomain_idx",
+ "columns": [
+ {
+ "expression": "subdomain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_workflow_id_workflow_id_fk": {
+ "name": "chat_workflow_id_workflow_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chat_user_id_user_id_fk": {
+ "name": "chat_user_id_user_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_chats": {
+ "name": "copilot_chats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "messages": {
+ "name": "messages",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'claude-3-7-sonnet-latest'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_chats_user_id_idx": {
+ "name": "copilot_chats_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_workflow_id_idx": {
+ "name": "copilot_chats_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_user_workflow_idx": {
+ "name": "copilot_chats_user_workflow_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_created_at_idx": {
+ "name": "copilot_chats_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_updated_at_idx": {
+ "name": "copilot_chats_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_chats_user_id_user_id_fk": {
+ "name": "copilot_chats_user_id_user_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_chats_workflow_id_workflow_id_fk": {
+ "name": "copilot_chats_workflow_id_workflow_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom_tools": {
+ "name": "custom_tools",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "schema": {
+ "name": "schema",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code": {
+ "name": "code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "custom_tools_user_id_user_id_fk": {
+ "name": "custom_tools_user_id_user_id_fk",
+ "tableFrom": "custom_tools",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.docs_embeddings": {
+ "name": "docs_embeddings",
+ "schema": "",
+ "columns": {
+ "chunk_id": {
+ "name": "chunk_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chunk_text": {
+ "name": "chunk_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_document": {
+ "name": "source_document",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_link": {
+ "name": "source_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "header_text": {
+ "name": "header_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "header_level": {
+ "name": "header_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "vector(1536)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "chunk_text_tsv": {
+ "name": "chunk_text_tsv",
+ "type": "tsvector",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")",
+ "type": "stored"
+ }
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "docs_emb_source_document_idx": {
+ "name": "docs_emb_source_document_idx",
+ "columns": [
+ {
+ "expression": "source_document",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_header_level_idx": {
+ "name": "docs_emb_header_level_idx",
+ "columns": [
+ {
+ "expression": "header_level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_source_header_idx": {
+ "name": "docs_emb_source_header_idx",
+ "columns": [
+ {
+ "expression": "source_document",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "header_level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_model_idx": {
+ "name": "docs_emb_model_idx",
+ "columns": [
+ {
+ "expression": "embedding_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_emb_created_at_idx": {
+ "name": "docs_emb_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "docs_embedding_vector_hnsw_idx": {
+ "name": "docs_embedding_vector_hnsw_idx",
+ "columns": [
+ {
+ "expression": "embedding",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "vector_cosine_ops"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "hnsw",
+ "with": {
+ "m": 16,
+ "ef_construction": 64
+ }
+ },
+ "docs_emb_metadata_gin_idx": {
+ "name": "docs_emb_metadata_gin_idx",
+ "columns": [
+ {
+ "expression": "metadata",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ },
+ "docs_emb_chunk_text_fts_idx": {
+ "name": "docs_emb_chunk_text_fts_idx",
+ "columns": [
+ {
+ "expression": "chunk_text_tsv",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "docs_embedding_not_null_check": {
+ "name": "docs_embedding_not_null_check",
+ "value": "\"embedding\" IS NOT NULL"
+ },
+ "docs_header_level_check": {
+ "name": "docs_header_level_check",
+ "value": "\"header_level\" >= 1 AND \"header_level\" <= 6"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.document": {
+ "name": "document",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_url": {
+ "name": "file_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_size": {
+ "name": "file_size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mime_type": {
+ "name": "mime_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_count": {
+ "name": "chunk_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "character_count": {
+ "name": "character_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "processing_status": {
+ "name": "processing_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "processing_started_at": {
+ "name": "processing_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_completed_at": {
+ "name": "processing_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_error": {
+ "name": "processing_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "doc_kb_id_idx": {
+ "name": "doc_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_filename_idx": {
+ "name": "doc_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_kb_uploaded_at_idx": {
+ "name": "doc_kb_uploaded_at_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uploaded_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_processing_status_idx": {
+ "name": "doc_processing_status_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "processing_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag1_idx": {
+ "name": "doc_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag2_idx": {
+ "name": "doc_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag3_idx": {
+ "name": "doc_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag4_idx": {
+ "name": "doc_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag5_idx": {
+ "name": "doc_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag6_idx": {
+ "name": "doc_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag7_idx": {
+ "name": "doc_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "document_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "document_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "document",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.embedding": {
+ "name": "embedding",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_index": {
+ "name": "chunk_index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_hash": {
+ "name": "chunk_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content_length": {
+ "name": "content_length",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "vector(1536)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "start_offset": {
+ "name": "start_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_offset": {
+ "name": "end_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content_tsv": {
+ "name": "content_tsv",
+ "type": "tsvector",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "to_tsvector('english', \"embedding\".\"content\")",
+ "type": "stored"
+ }
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "emb_kb_id_idx": {
+ "name": "emb_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_id_idx": {
+ "name": "emb_doc_id_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_chunk_idx": {
+ "name": "emb_doc_chunk_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chunk_index",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_model_idx": {
+ "name": "emb_kb_model_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "embedding_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_enabled_idx": {
+ "name": "emb_kb_enabled_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_enabled_idx": {
+ "name": "emb_doc_enabled_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "embedding_vector_hnsw_idx": {
+ "name": "embedding_vector_hnsw_idx",
+ "columns": [
+ {
+ "expression": "embedding",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "vector_cosine_ops"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "hnsw",
+ "with": {
+ "m": 16,
+ "ef_construction": 64
+ }
+ },
+ "emb_tag1_idx": {
+ "name": "emb_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag2_idx": {
+ "name": "emb_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag3_idx": {
+ "name": "emb_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag4_idx": {
+ "name": "emb_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag5_idx": {
+ "name": "emb_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag6_idx": {
+ "name": "emb_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag7_idx": {
+ "name": "emb_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_content_fts_idx": {
+ "name": "emb_content_fts_idx",
+ "columns": [
+ {
+ "expression": "content_tsv",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "embedding_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "embedding_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "embedding_document_id_document_id_fk": {
+ "name": "embedding_document_id_document_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "document",
+ "columnsFrom": ["document_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "embedding_not_null_check": {
+ "name": "embedding_not_null_check",
+ "value": "\"embedding\" IS NOT NULL"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_user_id_user_id_fk": {
+ "name": "environment_user_id_user_id_fk",
+ "tableFrom": "environment",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_user_id_unique": {
+ "name": "environment_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": ["inviter_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.knowledge_base": {
+ "name": "knowledge_base",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "embedding_dimension": {
+ "name": "embedding_dimension",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1536
+ },
+ "chunking_config": {
+ "name": "chunking_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"maxSize\": 1024, \"minSize\": 100, \"overlap\": 200}'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "kb_user_id_idx": {
+ "name": "kb_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_workspace_id_idx": {
+ "name": "kb_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_user_workspace_idx": {
+ "name": "kb_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_deleted_at_idx": {
+ "name": "kb_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "knowledge_base_user_id_user_id_fk": {
+ "name": "knowledge_base_user_id_user_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "knowledge_base_workspace_id_workspace_id_fk": {
+ "name": "knowledge_base_workspace_id_workspace_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.marketplace": {
+ "name": "marketplace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_name": {
+ "name": "author_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "views": {
+ "name": "views",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "marketplace_workflow_id_workflow_id_fk": {
+ "name": "marketplace_workflow_id_workflow_id_fk",
+ "tableFrom": "marketplace",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "marketplace_author_id_user_id_fk": {
+ "name": "marketplace_author_id_user_id_fk",
+ "tableFrom": "marketplace",
+ "tableTo": "user",
+ "columnsFrom": ["author_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory": {
+ "name": "memory",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "memory_key_idx": {
+ "name": "memory_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workflow_idx": {
+ "name": "memory_workflow_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workflow_key_idx": {
+ "name": "memory_workflow_key_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_workflow_id_workflow_id_fk": {
+ "name": "memory_workflow_id_workflow_id_fk",
+ "tableFrom": "memory",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permissions": {
+ "name": "permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_type": {
+ "name": "permission_type",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permissions_user_id_idx": {
+ "name": "permissions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_entity_idx": {
+ "name": "permissions_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_type_idx": {
+ "name": "permissions_user_entity_type_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_permission_idx": {
+ "name": "permissions_user_entity_permission_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "permission_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_idx": {
+ "name": "permissions_user_entity_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_unique_constraint": {
+ "name": "permissions_unique_constraint",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permissions_user_id_user_id_fk": {
+ "name": "permissions_user_id_user_id_fk",
+ "tableFrom": "permissions",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_active_organization_id_organization_id_fk": {
+ "name": "session_active_organization_id_organization_id_fk",
+ "tableFrom": "session",
+ "tableTo": "organization",
+ "columnsFrom": ["active_organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'system'"
+ },
+ "auto_connect": {
+ "name": "auto_connect",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auto_fill_env_vars": {
+ "name": "auto_fill_env_vars",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auto_pan": {
+ "name": "auto_pan",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "console_expanded_by_default": {
+ "name": "console_expanded_by_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "telemetry_enabled": {
+ "name": "telemetry_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "telemetry_notified_user": {
+ "name": "telemetry_notified_user",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "email_preferences": {
+ "name": "email_preferences",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "general": {
+ "name": "general",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "settings_user_id_user_id_fk": {
+ "name": "settings_user_id_user_id_fk",
+ "tableFrom": "settings",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "settings_user_id_unique": {
+ "name": "settings_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.subscription": {
+ "name": "subscription",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan": {
+ "name": "plan",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_start": {
+ "name": "period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_end": {
+ "name": "period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_at_period_end": {
+ "name": "cancel_at_period_end",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seats": {
+ "name": "seats",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_start": {
+ "name": "trial_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_end": {
+ "name": "trial_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "subscription_reference_status_idx": {
+ "name": "subscription_reference_status_idx",
+ "columns": [
+ {
+ "expression": "reference_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "check_enterprise_metadata": {
+ "name": "check_enterprise_metadata",
+ "value": "plan != 'enterprise' OR (metadata IS NOT NULL AND (metadata->>'perSeatAllowance' IS NOT NULL OR metadata->>'totalAllowance' IS NOT NULL))"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.template_stars": {
+ "name": "template_stars",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "template_id": {
+ "name": "template_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "starred_at": {
+ "name": "starred_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "template_stars_user_id_idx": {
+ "name": "template_stars_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_id_idx": {
+ "name": "template_stars_template_id_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_user_template_idx": {
+ "name": "template_stars_user_template_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_user_idx": {
+ "name": "template_stars_template_user_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_starred_at_idx": {
+ "name": "template_stars_starred_at_idx",
+ "columns": [
+ {
+ "expression": "starred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_template_starred_at_idx": {
+ "name": "template_stars_template_starred_at_idx",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "starred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "template_stars_user_template_unique": {
+ "name": "template_stars_user_template_unique",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "template_stars_user_id_user_id_fk": {
+ "name": "template_stars_user_id_user_id_fk",
+ "tableFrom": "template_stars",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "template_stars_template_id_templates_id_fk": {
+ "name": "template_stars_template_id_templates_id_fk",
+ "tableFrom": "template_stars",
+ "tableTo": "templates",
+ "columnsFrom": ["template_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.templates": {
+ "name": "templates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "views": {
+ "name": "views",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "stars": {
+ "name": "stars",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'#3972F6'"
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'FileText'"
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "templates_workflow_id_idx": {
+ "name": "templates_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_user_id_idx": {
+ "name": "templates_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_idx": {
+ "name": "templates_category_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_views_idx": {
+ "name": "templates_views_idx",
+ "columns": [
+ {
+ "expression": "views",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_stars_idx": {
+ "name": "templates_stars_idx",
+ "columns": [
+ {
+ "expression": "stars",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_views_idx": {
+ "name": "templates_category_views_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "views",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_category_stars_idx": {
+ "name": "templates_category_stars_idx",
+ "columns": [
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "stars",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_user_category_idx": {
+ "name": "templates_user_category_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "category",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_created_at_idx": {
+ "name": "templates_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "templates_updated_at_idx": {
+ "name": "templates_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "templates_workflow_id_workflow_id_fk": {
+ "name": "templates_workflow_id_workflow_id_fk",
+ "tableFrom": "templates",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "templates_user_id_user_id_fk": {
+ "name": "templates_user_id_user_id_fk",
+ "tableFrom": "templates",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_rate_limits": {
+ "name": "user_rate_limits",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "sync_api_requests": {
+ "name": "sync_api_requests",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "async_api_requests": {
+ "name": "async_api_requests",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "window_start": {
+ "name": "window_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_request_at": {
+ "name": "last_request_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_rate_limited": {
+ "name": "is_rate_limited",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "rate_limit_reset_at": {
+ "name": "rate_limit_reset_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_rate_limits_user_id_user_id_fk": {
+ "name": "user_rate_limits_user_id_user_id_fk",
+ "tableFrom": "user_rate_limits",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_stats": {
+ "name": "user_stats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_manual_executions": {
+ "name": "total_manual_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_api_calls": {
+ "name": "total_api_calls",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_webhook_triggers": {
+ "name": "total_webhook_triggers",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_scheduled_executions": {
+ "name": "total_scheduled_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_chat_executions": {
+ "name": "total_chat_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_tokens_used": {
+ "name": "total_tokens_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_usage_limit": {
+ "name": "current_usage_limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'5'"
+ },
+ "usage_limit_set_by": {
+ "name": "usage_limit_set_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "usage_limit_updated_at": {
+ "name": "usage_limit_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "current_period_cost": {
+ "name": "current_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "billing_period_start": {
+ "name": "billing_period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "billing_period_end": {
+ "name": "billing_period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_period_cost": {
+ "name": "last_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "last_active": {
+ "name": "last_active",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_stats_user_id_user_id_fk": {
+ "name": "user_stats_user_id_user_id_fk",
+ "tableFrom": "user_stats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_stats_user_id_unique": {
+ "name": "user_stats_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.waitlist": {
+ "name": "waitlist",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "waitlist_email_unique": {
+ "name": "waitlist_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webhook": {
+ "name": "webhook",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_config": {
+ "name": "provider_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "path_idx": {
+ "name": "path_idx",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "webhook_workflow_id_workflow_id_fk": {
+ "name": "webhook_workflow_id_workflow_id_fk",
+ "tableFrom": "webhook",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "webhook_block_id_workflow_blocks_id_fk": {
+ "name": "webhook_block_id_workflow_blocks_id_fk",
+ "tableFrom": "webhook",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow": {
+ "name": "workflow",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'#3972F6'"
+ },
+ "last_synced": {
+ "name": "last_synced",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_deployed": {
+ "name": "is_deployed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "deployed_state": {
+ "name": "deployed_state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deployed_at": {
+ "name": "deployed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "collaborators": {
+ "name": "collaborators",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "run_count": {
+ "name": "run_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "is_published": {
+ "name": "is_published",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "marketplace_data": {
+ "name": "marketplace_data",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_user_id_user_id_fk": {
+ "name": "workflow_user_id_user_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_workspace_id_workspace_id_fk": {
+ "name": "workflow_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_folder_id_workflow_folder_id_fk": {
+ "name": "workflow_folder_id_workflow_folder_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workflow_folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_blocks": {
+ "name": "workflow_blocks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_x": {
+ "name": "position_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_y": {
+ "name": "position_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "horizontal_handles": {
+ "name": "horizontal_handles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "is_wide": {
+ "name": "is_wide",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "advanced_mode": {
+ "name": "advanced_mode",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "sub_blocks": {
+ "name": "sub_blocks",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "extent": {
+ "name": "extent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_blocks_workflow_id_idx": {
+ "name": "workflow_blocks_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_parent_id_idx": {
+ "name": "workflow_blocks_parent_id_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_workflow_parent_idx": {
+ "name": "workflow_blocks_workflow_parent_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_workflow_type_idx": {
+ "name": "workflow_blocks_workflow_type_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_blocks_workflow_id_workflow_id_fk": {
+ "name": "workflow_blocks_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_blocks",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_edges": {
+ "name": "workflow_edges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_block_id": {
+ "name": "source_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_block_id": {
+ "name": "target_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_handle": {
+ "name": "source_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "target_handle": {
+ "name": "target_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_edges_workflow_id_idx": {
+ "name": "workflow_edges_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_source_block_idx": {
+ "name": "workflow_edges_source_block_idx",
+ "columns": [
+ {
+ "expression": "source_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_target_block_idx": {
+ "name": "workflow_edges_target_block_idx",
+ "columns": [
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_source_idx": {
+ "name": "workflow_edges_workflow_source_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_target_idx": {
+ "name": "workflow_edges_workflow_target_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_edges_workflow_id_workflow_id_fk": {
+ "name": "workflow_edges_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_source_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_source_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["source_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_target_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_target_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["target_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_blocks": {
+ "name": "workflow_execution_blocks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_name": {
+ "name": "block_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_type": {
+ "name": "block_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_stack_trace": {
+ "name": "error_stack_trace",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "input_data": {
+ "name": "input_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "output_data": {
+ "name": "output_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_input": {
+ "name": "cost_input",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_output": {
+ "name": "cost_output",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_total": {
+ "name": "cost_total",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_prompt": {
+ "name": "tokens_prompt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_completion": {
+ "name": "tokens_completion",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_total": {
+ "name": "tokens_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model_used": {
+ "name": "model_used",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "execution_blocks_execution_id_idx": {
+ "name": "execution_blocks_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_workflow_id_idx": {
+ "name": "execution_blocks_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_block_id_idx": {
+ "name": "execution_blocks_block_id_idx",
+ "columns": [
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_status_idx": {
+ "name": "execution_blocks_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_duration_idx": {
+ "name": "execution_blocks_duration_idx",
+ "columns": [
+ {
+ "expression": "duration_ms",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_cost_idx": {
+ "name": "execution_blocks_cost_idx",
+ "columns": [
+ {
+ "expression": "cost_total",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_workflow_execution_idx": {
+ "name": "execution_blocks_workflow_execution_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_execution_status_idx": {
+ "name": "execution_blocks_execution_status_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_blocks_started_at_idx": {
+ "name": "execution_blocks_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_blocks_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_blocks_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_blocks",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_logs": {
+ "name": "workflow_execution_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_snapshot_id": {
+ "name": "state_snapshot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_duration_ms": {
+ "name": "total_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_count": {
+ "name": "block_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "success_count": {
+ "name": "success_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "error_count": {
+ "name": "error_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "skipped_count": {
+ "name": "skipped_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_input_cost": {
+ "name": "total_input_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_output_cost": {
+ "name": "total_output_cost",
+ "type": "numeric(10, 6)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_tokens": {
+ "name": "total_tokens",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_execution_logs_workflow_id_idx": {
+ "name": "workflow_execution_logs_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_execution_id_idx": {
+ "name": "workflow_execution_logs_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_trigger_idx": {
+ "name": "workflow_execution_logs_trigger_idx",
+ "columns": [
+ {
+ "expression": "trigger",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_level_idx": {
+ "name": "workflow_execution_logs_level_idx",
+ "columns": [
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_started_at_idx": {
+ "name": "workflow_execution_logs_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_cost_idx": {
+ "name": "workflow_execution_logs_cost_idx",
+ "columns": [
+ {
+ "expression": "total_cost",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_duration_idx": {
+ "name": "workflow_execution_logs_duration_idx",
+ "columns": [
+ {
+ "expression": "total_duration_ms",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_execution_id_unique": {
+ "name": "workflow_execution_logs_execution_id_unique",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_logs_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_logs_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": {
+ "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow_execution_snapshots",
+ "columnsFrom": ["state_snapshot_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_snapshots": {
+ "name": "workflow_execution_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_hash": {
+ "name": "state_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_data": {
+ "name": "state_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_snapshots_workflow_id_idx": {
+ "name": "workflow_snapshots_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_hash_idx": {
+ "name": "workflow_snapshots_hash_idx",
+ "columns": [
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_workflow_hash_idx": {
+ "name": "workflow_snapshots_workflow_hash_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_created_at_idx": {
+ "name": "workflow_snapshots_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_snapshots_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_snapshots",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_folder": {
+ "name": "workflow_folder",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'#6B7280'"
+ },
+ "is_expanded": {
+ "name": "is_expanded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_folder_user_idx": {
+ "name": "workflow_folder_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_folder_workspace_parent_idx": {
+ "name": "workflow_folder_workspace_parent_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_folder_parent_sort_idx": {
+ "name": "workflow_folder_parent_sort_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_folder_user_id_user_id_fk": {
+ "name": "workflow_folder_user_id_user_id_fk",
+ "tableFrom": "workflow_folder",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_folder_workspace_id_workspace_id_fk": {
+ "name": "workflow_folder_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow_folder",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_logs": {
+ "name": "workflow_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration": {
+ "name": "duration",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_logs_workflow_id_workflow_id_fk": {
+ "name": "workflow_logs_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_logs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_schedule": {
+ "name": "workflow_schedule",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_ran_at": {
+ "name": "last_ran_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "failed_count": {
+ "name": "failed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "last_failed_at": {
+ "name": "last_failed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_schedule_workflow_block_unique": {
+ "name": "workflow_schedule_workflow_block_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_schedule_workflow_id_workflow_id_fk": {
+ "name": "workflow_schedule_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_schedule_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_schedule_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_subflows": {
+ "name": "workflow_subflows",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_subflows_workflow_id_idx": {
+ "name": "workflow_subflows_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_subflows_workflow_type_idx": {
+ "name": "workflow_subflows_workflow_type_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_subflows_workflow_id_workflow_id_fk": {
+ "name": "workflow_subflows_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_subflows",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace": {
+ "name": "workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_owner_id_user_id_fk": {
+ "name": "workspace_owner_id_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_invitation": {
+ "name": "workspace_invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permissions": {
+ "name": "permissions",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'admin'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_invitation_workspace_id_workspace_id_fk": {
+ "name": "workspace_invitation_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_invitation",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_invitation_inviter_id_user_id_fk": {
+ "name": "workspace_invitation_inviter_id_user_id_fk",
+ "tableFrom": "workspace_invitation",
+ "tableTo": "user",
+ "columnsFrom": ["inviter_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_invitation_token_unique": {
+ "name": "workspace_invitation_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.permission_type": {
+ "name": "permission_type",
+ "schema": "public",
+ "values": ["admin", "write", "read"]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json
index 084f03e729..d7979a916f 100644
--- a/apps/sim/db/migrations/meta/_journal.json
+++ b/apps/sim/db/migrations/meta/_journal.json
@@ -386,6 +386,20 @@
"when": 1752720748565,
"tag": "0055_amused_ender_wiggin",
"breakpoints": true
+ },
+ {
+ "idx": 56,
+ "version": "7",
+ "when": 1752789061522,
+ "tag": "0056_adorable_franklin_richards",
+ "breakpoints": true
+ },
+ {
+ "idx": 57,
+ "version": "7",
+ "when": 1752980338632,
+ "tag": "0057_charming_star_brand",
+ "breakpoints": true
}
]
}
diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts
index e7b56d726b..46e28de8cc 100644
--- a/apps/sim/db/schema.ts
+++ b/apps/sim/db/schema.ts
@@ -159,7 +159,7 @@ export const workflowBlocks = pgTable(
data: jsonb('data').default('{}'),
parentId: text('parent_id'),
- extent: text('extent'), // 'parent' or null
+ extent: text('extent'), // 'parent' or null or 'subflow'
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
@@ -393,7 +393,7 @@ export const settings = pgTable('settings', {
// General settings
theme: text('theme').notNull().default('system'),
autoConnect: boolean('auto_connect').notNull().default(true),
- autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true),
+ autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true), // DEPRECATED: autofill feature removed
autoPan: boolean('auto_pan').notNull().default(true),
consoleExpandedByDefault: boolean('console_expanded_by_default').notNull().default(true),
@@ -410,23 +410,34 @@ export const settings = pgTable('settings', {
updatedAt: timestamp('updated_at').notNull().defaultNow(),
})
-export const workflowSchedule = pgTable('workflow_schedule', {
- id: text('id').primaryKey(),
- workflowId: text('workflow_id')
- .notNull()
- .references(() => workflow.id, { onDelete: 'cascade' })
- .unique(),
- cronExpression: text('cron_expression'),
- nextRunAt: timestamp('next_run_at'),
- lastRanAt: timestamp('last_ran_at'),
- triggerType: text('trigger_type').notNull(), // "manual", "webhook", "schedule"
- timezone: text('timezone').notNull().default('UTC'),
- failedCount: integer('failed_count').notNull().default(0), // Track consecutive failures
- status: text('status').notNull().default('active'), // 'active' or 'disabled'
- lastFailedAt: timestamp('last_failed_at'), // When the schedule last failed
- createdAt: timestamp('created_at').notNull().defaultNow(),
- updatedAt: timestamp('updated_at').notNull().defaultNow(),
-})
+export const workflowSchedule = pgTable(
+ 'workflow_schedule',
+ {
+ id: text('id').primaryKey(),
+ workflowId: text('workflow_id')
+ .notNull()
+ .references(() => workflow.id, { onDelete: 'cascade' }),
+ blockId: text('block_id').references(() => workflowBlocks.id, { onDelete: 'cascade' }),
+ cronExpression: text('cron_expression'),
+ nextRunAt: timestamp('next_run_at'),
+ lastRanAt: timestamp('last_ran_at'),
+ triggerType: text('trigger_type').notNull(), // "manual", "webhook", "schedule"
+ timezone: text('timezone').notNull().default('UTC'),
+ failedCount: integer('failed_count').notNull().default(0), // Track consecutive failures
+ status: text('status').notNull().default('active'), // 'active' or 'disabled'
+ lastFailedAt: timestamp('last_failed_at'), // When the schedule last failed
+ createdAt: timestamp('created_at').notNull().defaultNow(),
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
+ },
+ (table) => {
+ return {
+ workflowBlockUnique: uniqueIndex('workflow_schedule_workflow_block_unique').on(
+ table.workflowId,
+ table.blockId
+ ),
+ }
+ }
+)
export const webhook = pgTable(
'webhook',
@@ -435,6 +446,7 @@ export const webhook = pgTable(
workflowId: text('workflow_id')
.notNull()
.references(() => workflow.id, { onDelete: 'cascade' }),
+ blockId: text('block_id').references(() => workflowBlocks.id, { onDelete: 'cascade' }), // ID of the webhook trigger block (nullable for legacy starter block webhooks)
path: text('path').notNull(),
provider: text('provider'), // e.g., "whatsapp", "github", etc.
providerConfig: json('provider_config'), // Store provider-specific configuration
@@ -546,6 +558,18 @@ export const subscription = pgTable(
})
)
+export const userRateLimits = pgTable('user_rate_limits', {
+ userId: text('user_id')
+ .primaryKey()
+ .references(() => user.id, { onDelete: 'cascade' }),
+ syncApiRequests: integer('sync_api_requests').notNull().default(0), // Sync API requests counter
+ asyncApiRequests: integer('async_api_requests').notNull().default(0), // Async API requests counter
+ windowStart: timestamp('window_start').notNull().defaultNow(),
+ lastRequestAt: timestamp('last_request_at').notNull().defaultNow(),
+ isRateLimited: boolean('is_rate_limited').notNull().default(false),
+ rateLimitResetAt: timestamp('rate_limit_reset_at'),
+})
+
export const chat = pgTable(
'chat',
{
diff --git a/apps/sim/executor/__test-utils__/mock-dependencies.ts b/apps/sim/executor/__test-utils__/mock-dependencies.ts
index b8005878b5..7266a6aadb 100644
--- a/apps/sim/executor/__test-utils__/mock-dependencies.ts
+++ b/apps/sim/executor/__test-utils__/mock-dependencies.ts
@@ -12,6 +12,11 @@ vi.mock('@/lib/logs/console-logger', () => ({
})),
}))
+// Blocks
+vi.mock('@/blocks/index', () => ({
+ getBlock: vi.fn(),
+}))
+
// Tools
vi.mock('@/tools/utils', () => ({
getTool: vi.fn(),
diff --git a/apps/sim/executor/consts.ts b/apps/sim/executor/consts.ts
index b5ebea7154..6304ee848a 100644
--- a/apps/sim/executor/consts.ts
+++ b/apps/sim/executor/consts.ts
@@ -14,6 +14,8 @@ export enum BlockType {
RESPONSE = 'response',
WORKFLOW = 'workflow',
STARTER = 'starter',
+ SCHEDULE = 'schedule',
+ WEBHOOK_TRIGGER = 'webhook_trigger',
}
/**
diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts
index 22508b7697..0703ff3c87 100644
--- a/apps/sim/executor/index.ts
+++ b/apps/sim/executor/index.ts
@@ -167,9 +167,13 @@ export class Executor {
* Executes the workflow and returns the result.
*
* @param workflowId - Unique identifier for the workflow execution
+ * @param startBlockId - Optional block ID to start execution from (for webhook or schedule triggers)
* @returns Execution result containing output, logs, and metadata, or a stream, or combined execution and stream
*/
- async execute(workflowId: string): Promise {
+ async execute(
+ workflowId: string,
+ startBlockId?: string
+ ): Promise {
const { setIsExecuting, setIsDebugging, setPendingBlocks, reset } = useExecutionStore.getState()
const startTime = new Date()
let finalOutput: NormalizedBlockOutput = {}
@@ -182,9 +186,9 @@ export class Executor {
startTime: startTime.toISOString(),
})
- this.validateWorkflow()
+ this.validateWorkflow(startBlockId)
- const context = this.createExecutionContext(workflowId, startTime)
+ const context = this.createExecutionContext(workflowId, startTime, startBlockId)
try {
setIsExecuting(true)
@@ -543,30 +547,46 @@ export class Executor {
/**
* Validates that the workflow meets requirements for execution.
- * Checks for starter block, connections, and loop configurations.
+ * Checks for starter block, webhook trigger block, or schedule trigger block, connections, and loop configurations.
*
+ * @param startBlockId - Optional specific block to start from
* @throws Error if workflow validation fails
*/
- private validateWorkflow(): void {
- const starterBlock = this.actualWorkflow.blocks.find(
- (block) => block.metadata?.id === BlockType.STARTER
- )
- if (!starterBlock || !starterBlock.enabled) {
- throw new Error('Workflow must have an enabled starter block')
- }
+ private validateWorkflow(startBlockId?: string): void {
+ let validationBlock: SerializedBlock | undefined
+
+ if (startBlockId) {
+ // If starting from a specific block (webhook trigger or schedule trigger), validate that block exists
+ const startBlock = this.actualWorkflow.blocks.find((block) => block.id === startBlockId)
+ if (!startBlock || !startBlock.enabled) {
+ throw new Error(`Start block ${startBlockId} not found or disabled`)
+ }
+ validationBlock = startBlock
+ // Trigger blocks (webhook and schedule) can have incoming connections, so no need to check that
+ } else {
+ // Default validation for starter block
+ const starterBlock = this.actualWorkflow.blocks.find(
+ (block) => block.metadata?.id === BlockType.STARTER
+ )
+ if (!starterBlock || !starterBlock.enabled) {
+ throw new Error('Workflow must have an enabled starter block')
+ }
+ validationBlock = starterBlock
- const incomingToStarter = this.actualWorkflow.connections.filter(
- (conn) => conn.target === starterBlock.id
- )
- if (incomingToStarter.length > 0) {
- throw new Error('Starter block cannot have incoming connections')
- }
+ const incomingToStarter = this.actualWorkflow.connections.filter(
+ (conn) => conn.target === starterBlock.id
+ )
+ if (incomingToStarter.length > 0) {
+ throw new Error('Starter block cannot have incoming connections')
+ }
- const outgoingFromStarter = this.actualWorkflow.connections.filter(
- (conn) => conn.source === starterBlock.id
- )
- if (outgoingFromStarter.length === 0) {
- throw new Error('Starter block must have at least one outgoing connection')
+ // Only check outgoing connections for starter blocks, not trigger blocks
+ const outgoingFromStarter = this.actualWorkflow.connections.filter(
+ (conn) => conn.source === starterBlock.id
+ )
+ if (outgoingFromStarter.length === 0) {
+ throw new Error('Starter block must have at least one outgoing connection')
+ }
}
const blockIds = new Set(this.actualWorkflow.blocks.map((block) => block.id))
@@ -603,13 +623,18 @@ export class Executor {
/**
* Creates the initial execution context with predefined states.
- * Sets up the starter block and its connections in the active execution path.
+ * Sets up the starter block, webhook trigger block, or schedule trigger block and its connections in the active execution path.
*
* @param workflowId - Unique identifier for the workflow execution
* @param startTime - Execution start time
+ * @param startBlockId - Optional specific block to start from
* @returns Initialized execution context
*/
- private createExecutionContext(workflowId: string, startTime: Date): ExecutionContext {
+ private createExecutionContext(
+ workflowId: string,
+ startTime: Date,
+ startBlockId?: string
+ ): ExecutionContext {
const context: ExecutionContext = {
workflowId,
blockStates: new Map(),
@@ -652,13 +677,22 @@ export class Executor {
}
}
- const starterBlock = this.actualWorkflow.blocks.find(
- (block) => block.metadata?.id === BlockType.STARTER
- )
- if (starterBlock) {
- // Initialize the starter block with the workflow input
+ // Determine which block to initialize as the starting point
+ let initBlock: SerializedBlock | undefined
+ if (startBlockId) {
+ // Starting from a specific block (webhook trigger or schedule trigger)
+ initBlock = this.actualWorkflow.blocks.find((block) => block.id === startBlockId)
+ } else {
+ // Default to starter block
+ initBlock = this.actualWorkflow.blocks.find(
+ (block) => block.metadata?.id === BlockType.STARTER
+ )
+ }
+
+ if (initBlock) {
+ // Initialize the starting block with the workflow input
try {
- const blockParams = starterBlock.config.params
+ const blockParams = initBlock.config.params
const inputFormat = blockParams?.inputFormat
// If input format is defined, structure the input according to the schema
@@ -718,17 +752,17 @@ export class Executor {
// Use the structured input if we processed fields, otherwise use raw input
const finalInput = hasProcessedFields ? structuredInput : rawInputData
- // Initialize the starter block with structured input (flattened)
- const starterOutput = {
+ // Initialize the starting block with structured input (flattened)
+ const blockOutput = {
input: finalInput,
conversationId: this.workflowInput?.conversationId, // Add conversationId to root
...finalInput, // Add input fields directly at top level
}
- logger.info(`[Executor] Starter output:`, JSON.stringify(starterOutput, null, 2))
+ logger.info(`[Executor] Starting block output:`, JSON.stringify(blockOutput, null, 2))
- context.blockStates.set(starterBlock.id, {
- output: starterOutput,
+ context.blockStates.set(initBlock.id, {
+ output: blockOutput,
executed: true,
executionTime: 0,
})
@@ -746,7 +780,7 @@ export class Executor {
conversationId: this.workflowInput.conversationId,
}
- context.blockStates.set(starterBlock.id, {
+ context.blockStates.set(initBlock.id, {
output: starterOutput,
executed: true,
executionTime: 0,
@@ -755,7 +789,7 @@ export class Executor {
// API workflow: spread the raw data directly (no wrapping)
const starterOutput = { ...this.workflowInput }
- context.blockStates.set(starterBlock.id, {
+ context.blockStates.set(initBlock.id, {
output: starterOutput,
executed: true,
executionTime: 0,
@@ -767,7 +801,7 @@ export class Executor {
input: this.workflowInput,
}
- context.blockStates.set(starterBlock.id, {
+ context.blockStates.set(initBlock.id, {
output: starterOutput,
executed: true,
executionTime: 0,
@@ -778,7 +812,7 @@ export class Executor {
logger.warn('Error processing starter block input format:', e)
// Error handler fallback - use appropriate structure
- let starterOutput: any
+ let blockOutput: any
if (this.workflowInput && typeof this.workflowInput === 'object') {
// Check if this is a chat workflow input (has both input and conversationId)
if (
@@ -786,40 +820,43 @@ export class Executor {
Object.hasOwn(this.workflowInput, 'conversationId')
) {
// Chat workflow: extract input and conversationId to root level
- starterOutput = {
+ blockOutput = {
input: this.workflowInput.input,
conversationId: this.workflowInput.conversationId,
}
} else {
// API workflow: spread the raw data directly (no wrapping)
- starterOutput = { ...this.workflowInput }
+ blockOutput = { ...this.workflowInput }
}
} else {
// Primitive input
- starterOutput = {
+ blockOutput = {
input: this.workflowInput,
}
}
- logger.info('[Executor] Fallback starter output:', JSON.stringify(starterOutput, null, 2))
+ logger.info(
+ '[Executor] Fallback starting block output:',
+ JSON.stringify(blockOutput, null, 2)
+ )
- context.blockStates.set(starterBlock.id, {
- output: starterOutput,
+ context.blockStates.set(initBlock.id, {
+ output: blockOutput,
executed: true,
executionTime: 0,
})
}
- // Ensure the starter block is in the active execution path
- context.activeExecutionPath.add(starterBlock.id)
- // Mark the starter block as executed
- context.executedBlocks.add(starterBlock.id)
-
- // Add all blocks connected to the starter to the active execution path
- const connectedToStarter = this.actualWorkflow.connections
- .filter((conn) => conn.source === starterBlock.id)
+ // Ensure the starting block is in the active execution path
+ context.activeExecutionPath.add(initBlock.id)
+ // Mark the starting block as executed
+ context.executedBlocks.add(initBlock.id)
+
+ // Add all blocks connected to the starting block to the active execution path
+ const connectedToStartBlock = this.actualWorkflow.connections
+ .filter((conn) => conn.source === initBlock.id)
.map((conn) => conn.target)
- connectedToStarter.forEach((blockId) => {
+ connectedToStartBlock.forEach((blockId) => {
context.activeExecutionPath.add(blockId)
})
}
diff --git a/apps/sim/executor/resolver/resolver.test.ts b/apps/sim/executor/resolver/resolver.test.ts
index 81b34e9bd9..ae63c0b61c 100644
--- a/apps/sim/executor/resolver/resolver.test.ts
+++ b/apps/sim/executor/resolver/resolver.test.ts
@@ -1,19 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { getBlock } from '@/blocks/index'
import { BlockType } from '@/executor/consts'
import { InputResolver } from '@/executor/resolver/resolver'
import type { ExecutionContext } from '@/executor/types'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
-// Mock logger
-vi.mock('@/lib/logs/console-logger', () => ({
- createLogger: vi.fn().mockReturnValue({
- debug: vi.fn(),
- info: vi.fn(),
- warn: vi.fn(),
- error: vi.fn(),
- }),
-}))
-
describe('InputResolver', () => {
let sampleWorkflow: SerializedWorkflow
let mockContext: any
@@ -1898,4 +1889,502 @@ describe('InputResolver', () => {
expect(() => loopResolver.resolveInputs(testBlock, loopContext)).not.toThrow()
})
})
+
+ describe('Conditional Input Filtering', () => {
+ const mockGetBlock = getBlock as ReturnType
+
+ afterEach(() => {
+ mockGetBlock.mockReset()
+ })
+
+ it('should filter inputs based on operation conditions for Knowledge block', () => {
+ // Mock the Knowledge block configuration
+ mockGetBlock.mockReturnValue({
+ type: 'knowledge',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Search', id: 'search' },
+ { label: 'Upload Chunk', id: 'upload_chunk' },
+ ],
+ },
+ {
+ id: 'query',
+ type: 'short-input',
+ condition: { field: 'operation', value: 'search' },
+ },
+ {
+ id: 'knowledgeBaseIds',
+ type: 'knowledge-base-selector',
+ condition: { field: 'operation', value: 'search' },
+ },
+ {
+ id: 'documentId',
+ type: 'document-selector',
+ condition: { field: 'operation', value: 'upload_chunk' },
+ },
+ {
+ id: 'content',
+ type: 'long-input',
+ condition: { field: 'operation', value: 'upload_chunk' },
+ },
+ ],
+ })
+
+ // Create a Knowledge block with upload_chunk operation
+ const knowledgeBlock: SerializedBlock = {
+ id: 'knowledge-block',
+ metadata: { id: 'knowledge', name: 'Knowledge Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'knowledge',
+ params: {
+ operation: 'upload_chunk',
+ query: '', // This should be filtered out
+ knowledgeBaseIds: 'kb-1', // This should be filtered out
+ documentId: 'doc-1', // This should be included
+ content: 'chunk content', // This should be included
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(knowledgeBlock, mockContext)
+
+ // Should only include inputs for upload_chunk operation
+ expect(result).toHaveProperty('operation', 'upload_chunk')
+ expect(result).toHaveProperty('documentId', 'doc-1')
+ expect(result).toHaveProperty('content', 'chunk content')
+
+ // Should NOT include inputs for search operation
+ expect(result).not.toHaveProperty('query')
+ expect(result).not.toHaveProperty('knowledgeBaseIds')
+ })
+
+ it('should filter inputs based on operation conditions for Knowledge block search operation', () => {
+ // Mock the Knowledge block configuration
+ mockGetBlock.mockReturnValue({
+ type: 'knowledge',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Search', id: 'search' },
+ { label: 'Upload Chunk', id: 'upload_chunk' },
+ ],
+ },
+ {
+ id: 'query',
+ type: 'short-input',
+ condition: { field: 'operation', value: 'search' },
+ },
+ {
+ id: 'knowledgeBaseIds',
+ type: 'knowledge-base-selector',
+ condition: { field: 'operation', value: 'search' },
+ },
+ {
+ id: 'documentId',
+ type: 'document-selector',
+ condition: { field: 'operation', value: 'upload_chunk' },
+ },
+ {
+ id: 'content',
+ type: 'long-input',
+ condition: { field: 'operation', value: 'upload_chunk' },
+ },
+ ],
+ })
+
+ // Create a Knowledge block with search operation
+ const knowledgeBlock: SerializedBlock = {
+ id: 'knowledge-block',
+ metadata: { id: 'knowledge', name: 'Knowledge Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'knowledge',
+ params: {
+ operation: 'search',
+ query: 'search query',
+ knowledgeBaseIds: 'kb-1',
+ documentId: 'doc-1', // This should be filtered out
+ content: 'chunk content', // This should be filtered out
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(knowledgeBlock, mockContext)
+
+ // Should only include inputs for search operation
+ expect(result).toHaveProperty('operation', 'search')
+ expect(result).toHaveProperty('query', 'search query')
+ expect(result).toHaveProperty('knowledgeBaseIds', 'kb-1')
+
+ // Should NOT include inputs for upload_chunk operation
+ expect(result).not.toHaveProperty('documentId')
+ expect(result).not.toHaveProperty('content')
+ })
+
+ it('should handle array conditions correctly', () => {
+ // Mock a block with array condition
+ mockGetBlock.mockReturnValue({
+ type: 'test-block',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Create', id: 'create' },
+ { label: 'Update', id: 'update' },
+ { label: 'Delete', id: 'delete' },
+ ],
+ },
+ {
+ id: 'data',
+ type: 'long-input',
+ condition: { field: 'operation', value: ['create', 'update'] },
+ },
+ {
+ id: 'id',
+ type: 'short-input',
+ condition: { field: 'operation', value: ['update', 'delete'] },
+ },
+ ],
+ })
+
+ const testBlock: SerializedBlock = {
+ id: 'test-block',
+ metadata: { id: 'test-block', name: 'Test Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'test-block',
+ params: {
+ operation: 'update',
+ data: 'some data',
+ id: 'item-1',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(testBlock, mockContext)
+
+ // Should include inputs for update operation (both data and id)
+ expect(result).toHaveProperty('operation', 'update')
+ expect(result).toHaveProperty('data', 'some data')
+ expect(result).toHaveProperty('id', 'item-1')
+ })
+
+ it('should include all inputs when no conditions are present', () => {
+ // Mock a block with no conditions
+ mockGetBlock.mockReturnValue({
+ type: 'simple-block',
+ subBlocks: [
+ {
+ id: 'param1',
+ type: 'short-input',
+ },
+ {
+ id: 'param2',
+ type: 'long-input',
+ },
+ ],
+ })
+
+ const simpleBlock: SerializedBlock = {
+ id: 'simple-block',
+ metadata: { id: 'simple-block', name: 'Simple Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'simple-block',
+ params: {
+ param1: 'value1',
+ param2: 'value2',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(simpleBlock, mockContext)
+
+ // Should include all inputs
+ expect(result).toHaveProperty('param1', 'value1')
+ expect(result).toHaveProperty('param2', 'value2')
+ })
+
+ it('should return all inputs when block config is not found', () => {
+ // Mock getBlock to return undefined
+ mockGetBlock.mockReturnValue(undefined)
+
+ const unknownBlock: SerializedBlock = {
+ id: 'unknown-block',
+ metadata: { id: 'unknown-type', name: 'Unknown Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'unknown-type',
+ params: {
+ param1: 'value1',
+ param2: 'value2',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(unknownBlock, mockContext)
+
+ // Should include all inputs when block config is not found
+ expect(result).toHaveProperty('param1', 'value1')
+ expect(result).toHaveProperty('param2', 'value2')
+ })
+
+ it('should handle negated conditions correctly', () => {
+ // Mock a block with negated condition
+ mockGetBlock.mockReturnValue({
+ type: 'test-block',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Create', id: 'create' },
+ { label: 'Delete', id: 'delete' },
+ ],
+ },
+ {
+ id: 'confirmationField',
+ type: 'short-input',
+ condition: { field: 'operation', value: 'create', not: true },
+ },
+ ],
+ })
+
+ const testBlock: SerializedBlock = {
+ id: 'test-block',
+ metadata: { id: 'test-block', name: 'Test Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'test-block',
+ params: {
+ operation: 'delete',
+ confirmationField: 'confirmed',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(testBlock, mockContext)
+
+ // Should include confirmationField because operation is NOT 'create'
+ expect(result).toHaveProperty('operation', 'delete')
+ expect(result).toHaveProperty('confirmationField', 'confirmed')
+ })
+
+ it('should handle compound AND conditions correctly', () => {
+ // Mock a block with compound AND condition
+ mockGetBlock.mockReturnValue({
+ type: 'test-block',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Create', id: 'create' },
+ { label: 'Update', id: 'update' },
+ ],
+ },
+ {
+ id: 'enabled',
+ type: 'switch',
+ },
+ {
+ id: 'specialField',
+ type: 'short-input',
+ condition: {
+ field: 'operation',
+ value: 'update',
+ and: { field: 'enabled', value: true },
+ },
+ },
+ ],
+ })
+
+ const testBlock: SerializedBlock = {
+ id: 'test-block',
+ metadata: { id: 'test-block', name: 'Test Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'test-block',
+ params: {
+ operation: 'update',
+ enabled: true,
+ specialField: 'special value',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(testBlock, mockContext)
+
+ // Should include specialField because operation is 'update' AND enabled is true
+ expect(result).toHaveProperty('operation', 'update')
+ expect(result).toHaveProperty('enabled', true)
+ expect(result).toHaveProperty('specialField', 'special value')
+ })
+
+ it('should always include inputs without conditions', () => {
+ // Mock a block with mixed conditions
+ mockGetBlock.mockReturnValue({
+ type: 'test-block',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ // No condition - should always be included
+ },
+ {
+ id: 'alwaysVisible',
+ type: 'short-input',
+ // No condition - should always be included
+ },
+ {
+ id: 'conditionalField',
+ type: 'short-input',
+ condition: { field: 'operation', value: 'search' },
+ },
+ ],
+ })
+
+ const testBlock: SerializedBlock = {
+ id: 'test-block',
+ metadata: { id: 'test-block', name: 'Test Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'test-block',
+ params: {
+ operation: 'upload',
+ alwaysVisible: 'always here',
+ conditionalField: 'should be filtered out',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result = resolver.resolveInputs(testBlock, mockContext)
+
+ // Should include inputs without conditions
+ expect(result).toHaveProperty('operation', 'upload')
+ expect(result).toHaveProperty('alwaysVisible', 'always here')
+
+ // Should NOT include conditional field that doesn't match
+ expect(result).not.toHaveProperty('conditionalField')
+ })
+
+ it('should handle duplicate field names with different conditions (Knowledge block case)', () => {
+ // Mock Knowledge block with duplicate content fields
+ mockGetBlock.mockReturnValue({
+ type: 'knowledge',
+ subBlocks: [
+ {
+ id: 'operation',
+ type: 'dropdown',
+ },
+ {
+ id: 'content',
+ title: 'Chunk Content',
+ type: 'long-input',
+ condition: { field: 'operation', value: 'upload_chunk' },
+ },
+ {
+ id: 'content',
+ title: 'Document Content',
+ type: 'long-input',
+ condition: { field: 'operation', value: 'create_document' },
+ },
+ ],
+ })
+
+ // Test upload_chunk operation
+ const uploadChunkBlock: SerializedBlock = {
+ id: 'knowledge-block',
+ metadata: { id: 'knowledge', name: 'Knowledge Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'knowledge',
+ params: {
+ operation: 'upload_chunk',
+ content: 'chunk content here',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result1 = resolver.resolveInputs(uploadChunkBlock, mockContext)
+ expect(result1).toHaveProperty('operation', 'upload_chunk')
+ expect(result1).toHaveProperty('content', 'chunk content here')
+
+ // Test create_document operation
+ const createDocBlock: SerializedBlock = {
+ id: 'knowledge-block',
+ metadata: { id: 'knowledge', name: 'Knowledge Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'knowledge',
+ params: {
+ operation: 'create_document',
+ content: 'document content here',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result2 = resolver.resolveInputs(createDocBlock, mockContext)
+ expect(result2).toHaveProperty('operation', 'create_document')
+ expect(result2).toHaveProperty('content', 'document content here')
+
+ // Test search operation (should NOT include content)
+ const searchBlock: SerializedBlock = {
+ id: 'knowledge-block',
+ metadata: { id: 'knowledge', name: 'Knowledge Block' },
+ position: { x: 0, y: 0 },
+ config: {
+ tool: 'knowledge',
+ params: {
+ operation: 'search',
+ content: 'should be filtered out',
+ },
+ },
+ inputs: {},
+ outputs: {},
+ enabled: true,
+ }
+
+ const result3 = resolver.resolveInputs(searchBlock, mockContext)
+ expect(result3).toHaveProperty('operation', 'search')
+ expect(result3).not.toHaveProperty('content')
+ })
+ })
})
diff --git a/apps/sim/executor/resolver/resolver.ts b/apps/sim/executor/resolver/resolver.ts
index 0e4d926148..41a848d473 100644
--- a/apps/sim/executor/resolver/resolver.ts
+++ b/apps/sim/executor/resolver/resolver.ts
@@ -1,6 +1,7 @@
import { BlockPathCalculator } from '@/lib/block-path-calculator'
import { createLogger } from '@/lib/logs/console-logger'
import { VariableManager } from '@/lib/variables/variable-manager'
+import { getBlock } from '@/blocks/index'
import type { LoopManager } from '@/executor/loops/loops'
import type { ExecutionContext } from '@/executor/types'
import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
@@ -55,6 +56,102 @@ export class InputResolver {
}
}
+ /**
+ * Evaluates if a sub-block should be active based on its condition
+ * @param condition - The condition to evaluate
+ * @param currentValues - Current values of all inputs
+ * @returns True if the sub-block should be active
+ */
+ private evaluateSubBlockCondition(
+ condition:
+ | {
+ field: string
+ value: any
+ not?: boolean
+ and?: { field: string; value: any; not?: boolean }
+ }
+ | undefined,
+ currentValues: Record
+ ): boolean {
+ if (!condition) return true
+
+ // Get the field value
+ const fieldValue = currentValues[condition.field]
+
+ // Check if the condition value is an array
+ const isValueMatch = Array.isArray(condition.value)
+ ? fieldValue != null &&
+ (condition.not
+ ? !condition.value.includes(fieldValue)
+ : condition.value.includes(fieldValue))
+ : condition.not
+ ? fieldValue !== condition.value
+ : fieldValue === condition.value
+
+ // Check both conditions if 'and' is present
+ const isAndValueMatch =
+ !condition.and ||
+ (() => {
+ const andFieldValue = currentValues[condition.and!.field]
+ return Array.isArray(condition.and!.value)
+ ? andFieldValue != null &&
+ (condition.and!.not
+ ? !condition.and!.value.includes(andFieldValue)
+ : condition.and!.value.includes(andFieldValue))
+ : condition.and!.not
+ ? andFieldValue !== condition.and!.value
+ : andFieldValue === condition.and!.value
+ })()
+
+ return isValueMatch && isAndValueMatch
+ }
+
+ /**
+ * Filters inputs based on sub-block conditions
+ * @param block - Block to filter inputs for
+ * @param inputs - All input parameters
+ * @returns Filtered input parameters that should be processed
+ */
+ private filterInputsByConditions(
+ block: SerializedBlock,
+ inputs: Record
+ ): Record {
+ const blockType = block.metadata?.id
+ if (!blockType) return inputs
+
+ const blockConfig = getBlock(blockType)
+ if (!blockConfig || !blockConfig.subBlocks) return inputs
+
+ // Filter inputs based on conditions
+ const filteredInputs: Record = {}
+ for (const [key, value] of Object.entries(inputs)) {
+ // Check if this input should be included based on subBlock conditions
+ let shouldInclude = false
+
+ // Find all subBlocks with this ID
+ const matchingSubBlocks = blockConfig.subBlocks.filter((sb) => sb.id === key)
+
+ if (matchingSubBlocks.length === 0) {
+ // No subBlock config found for this input - include it
+ shouldInclude = true
+ } else {
+ // Check if any of the matching subBlocks should be active
+ for (const subBlock of matchingSubBlocks) {
+ if (!subBlock.condition || this.evaluateSubBlockCondition(subBlock.condition, inputs)) {
+ shouldInclude = true
+ break
+ }
+ }
+ }
+
+ if (shouldInclude) {
+ filteredInputs[key] = value
+ }
+ }
+
+ return filteredInputs
+ }
+
/**
* Resolves all inputs for a block based on current context.
* Handles block references, environment variables, and JSON parsing.
@@ -64,7 +161,9 @@ export class InputResolver {
* @returns Resolved input parameters
*/
resolveInputs(block: SerializedBlock, context: ExecutionContext): Record {
- const inputs = { ...block.config.params }
+ const allInputs = { ...block.config.params }
+ // Filter inputs based on sub-block conditions to only process active fields
+ const inputs = this.filterInputsByConditions(block, allInputs)
const result: Record = {}
// Process each input parameter
for (const [key, value] of Object.entries(inputs)) {
diff --git a/apps/sim/hooks/use-collaborative-workflow.ts b/apps/sim/hooks/use-collaborative-workflow.ts
index b303109ec8..cd7eb6f374 100644
--- a/apps/sim/hooks/use-collaborative-workflow.ts
+++ b/apps/sim/hooks/use-collaborative-workflow.ts
@@ -289,7 +289,6 @@ export function useCollaborativeWorkflow() {
isDeployed: workflowData.state.isDeployed || false,
deployedAt: workflowData.state.deployedAt,
lastSaved: workflowData.state.lastSaved || Date.now(),
- hasActiveSchedule: workflowData.state.hasActiveSchedule || false,
hasActiveWebhook: workflowData.state.hasActiveWebhook || false,
deploymentStatuses: workflowData.state.deploymentStatuses || {},
})
diff --git a/apps/sim/instrumentation-edge.ts b/apps/sim/instrumentation-edge.ts
new file mode 100644
index 0000000000..4b8a3703a8
--- /dev/null
+++ b/apps/sim/instrumentation-edge.ts
@@ -0,0 +1,22 @@
+/**
+ * Sim Studio Telemetry - Edge Runtime Instrumentation
+ *
+ * This file contains Edge Runtime-compatible instrumentation logic.
+ * No Node.js APIs (like process.on, crypto, fs, etc.) are allowed here.
+ */
+
+import { createLogger } from './lib/logs/console-logger'
+
+const logger = createLogger('EdgeInstrumentation')
+
+export async function register() {
+ try {
+ // Only Web API compatible code here
+ // No Node.js APIs like process.on, crypto, fs, etc.
+
+ // Future: Add Edge Runtime compatible telemetry here
+ logger.info('Edge Runtime instrumentation initialized')
+ } catch (error) {
+ logger.error('Failed to initialize Edge Runtime instrumentation', error)
+ }
+}
diff --git a/apps/sim/instrumentation-server.ts b/apps/sim/instrumentation-node.ts
similarity index 98%
rename from apps/sim/instrumentation-server.ts
rename to apps/sim/instrumentation-node.ts
index b307a5c2d9..69c147f99a 100644
--- a/apps/sim/instrumentation-server.ts
+++ b/apps/sim/instrumentation-node.ts
@@ -105,8 +105,6 @@ async function initializeSentry() {
if (!isProd) return
try {
- const Sentry = await import('@sentry/nextjs')
-
// Skip initialization if Sentry appears to be already configured
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore accessing internal API
@@ -120,7 +118,7 @@ async function initializeSentry() {
enabled: true,
environment: env.NODE_ENV || 'development',
tracesSampleRate: 0.2,
- beforeSend(event) {
+ beforeSend(event: any) {
if (event.request && typeof event.request === 'object') {
;(event.request as any).ip = null
}
diff --git a/apps/sim/instrumentation.ts b/apps/sim/instrumentation.ts
index 4613481c17..8687311f04 100644
--- a/apps/sim/instrumentation.ts
+++ b/apps/sim/instrumentation.ts
@@ -1,9 +1,32 @@
export async function register() {
+ console.log('[Main Instrumentation] register() called, environment:', {
+ NEXT_RUNTIME: process.env.NEXT_RUNTIME,
+ NODE_ENV: process.env.NODE_ENV,
+ })
+
+ // Load Node.js-specific instrumentation
if (process.env.NEXT_RUNTIME === 'nodejs') {
- await import('./instrumentation-server')
+ console.log('[Main Instrumentation] Loading Node.js instrumentation...')
+ const nodeInstrumentation = await import('./instrumentation-node')
+ if (nodeInstrumentation.register) {
+ console.log('[Main Instrumentation] Calling Node.js register()...')
+ await nodeInstrumentation.register()
+ }
+ }
+
+ // Load Edge Runtime-specific instrumentation
+ if (process.env.NEXT_RUNTIME === 'edge') {
+ console.log('[Main Instrumentation] Loading Edge Runtime instrumentation...')
+ const edgeInstrumentation = await import('./instrumentation-edge')
+ if (edgeInstrumentation.register) {
+ console.log('[Main Instrumentation] Calling Edge Runtime register()...')
+ await edgeInstrumentation.register()
+ }
}
+ // Load client instrumentation if we're on the client
if (typeof window !== 'undefined') {
+ console.log('[Main Instrumentation] Loading client instrumentation...')
await import('./instrumentation-client')
}
}
diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts
index 7f1e682dc5..08b23d70c8 100644
--- a/apps/sim/lib/auth.ts
+++ b/apps/sim/lib/auth.ts
@@ -993,13 +993,14 @@ export const auth = betterAuth({
scopes: [
// Bot token scopes only - app acts as a bot user
'channels:read',
+ 'channels:history',
'groups:read',
+ 'groups:history',
'chat:write',
'chat:write.public',
- 'files:read',
- 'links:read',
- 'links:write',
'users:read',
+ 'files:write',
+ 'canvases:write',
],
responseType: 'code',
accessType: 'offline',
diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts
index 9c125363f9..09b97dfeaa 100644
--- a/apps/sim/lib/env.ts
+++ b/apps/sim/lib/env.ts
@@ -48,6 +48,9 @@ export const env = createEnv({
NEXT_RUNTIME: z.string().optional(),
VERCEL_ENV: z.string().optional(),
+ // Trigger.dev
+ TRIGGER_SECRET_KEY: z.string().min(1).optional(),
+
// Storage
AWS_REGION: z.string().optional(),
AWS_ACCESS_KEY_ID: z.string().optional(),
@@ -107,6 +110,8 @@ export const env = createEnv({
SOCKET_PORT: z.number().optional(),
PORT: z.number().optional(),
ALLOWED_ORIGINS: z.string().optional(),
+ // Job Queue Configuration
+ JOB_RETENTION_DAYS: z.string().optional().default('1'), // How long to keep completed jobs
},
client: {
diff --git a/apps/sim/lib/webhooks/utils.ts b/apps/sim/lib/webhooks/utils.ts
index 6343fcd203..0aee84245d 100644
--- a/apps/sim/lib/webhooks/utils.ts
+++ b/apps/sim/lib/webhooks/utils.ts
@@ -423,7 +423,8 @@ export async function executeWorkflowFromPayload(
foundWorkflow: any,
input: any,
executionId: string,
- requestId: string
+ requestId: string,
+ startBlockId?: string | null
): Promise {
// Add log at the beginning of this function for clarity
logger.info(`[${requestId}] Preparing to execute workflow`, {
@@ -668,7 +669,7 @@ export async function executeWorkflowFromPayload(
)
// This is THE critical line where the workflow actually executes
- const result = await executor.execute(foundWorkflow.id)
+ const result = await executor.execute(foundWorkflow.id, startBlockId || undefined)
// Check if we got a StreamingExecution result (with stream + execution properties)
// For webhook executions, we only care about the ExecutionResult part, not the stream
@@ -1275,9 +1276,7 @@ export async function fetchAndProcessAirtablePayloads(
}
)
- // Execute using the original requestId as the executionId
- // This is the exact point in the old code where execution happens - we're matching it exactly
- await executeWorkflowFromPayload(workflowData, input, requestId, requestId)
+ await executeWorkflowFromPayload(workflowData, input, requestId, requestId, null)
// COMPLETION LOG - This will only appear if execution succeeds
logger.info(`[${requestId}] CRITICAL_TRACE: Workflow execution completed successfully`, {
@@ -1373,8 +1372,14 @@ export async function processWebhook(
logger.info(
`[${requestId}] Executing workflow ${foundWorkflow.id} for webhook ${foundWebhook.id} (Execution: ${executionId})`
)
- // Call the refactored execution function
- await executeWorkflowFromPayload(foundWorkflow, input, executionId, requestId)
+
+ await executeWorkflowFromPayload(
+ foundWorkflow,
+ input,
+ executionId,
+ requestId,
+ foundWebhook.blockId
+ )
// Since executeWorkflowFromPayload handles logging and errors internally,
// we just need to return a standard success response for synchronous webhooks.
diff --git a/apps/sim/lib/workflows/db-helpers.test.ts b/apps/sim/lib/workflows/db-helpers.test.ts
index 66035a06b7..c82858a1b4 100644
--- a/apps/sim/lib/workflows/db-helpers.test.ts
+++ b/apps/sim/lib/workflows/db-helpers.test.ts
@@ -207,7 +207,6 @@ const mockWorkflowState: WorkflowState = {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
@@ -463,7 +462,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
@@ -643,7 +641,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
@@ -731,7 +728,6 @@ describe('Database Helpers', () => {
lastSaved: Date.now(),
isDeployed: false,
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
diff --git a/apps/sim/lib/workflows/db-helpers.ts b/apps/sim/lib/workflows/db-helpers.ts
index 89672ef22e..3545c4c1c2 100644
--- a/apps/sim/lib/workflows/db-helpers.ts
+++ b/apps/sim/lib/workflows/db-helpers.ts
@@ -211,7 +211,6 @@ export async function saveWorkflowToNormalizedTables(
isDeployed: state.isDeployed,
deployedAt: state.deployedAt,
deploymentStatuses: state.deploymentStatuses,
- hasActiveSchedule: state.hasActiveSchedule,
hasActiveWebhook: state.hasActiveWebhook,
}
@@ -264,7 +263,6 @@ export async function migrateWorkflowToNormalizedTables(
isDeployed: jsonState.isDeployed,
deployedAt: jsonState.deployedAt,
deploymentStatuses: jsonState.deploymentStatuses || {},
- hasActiveSchedule: jsonState.hasActiveSchedule,
hasActiveWebhook: jsonState.hasActiveWebhook,
}
diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts
index 0e6cc1afae..34db4f5432 100644
--- a/apps/sim/next.config.ts
+++ b/apps/sim/next.config.ts
@@ -140,7 +140,7 @@ const nextConfig: NextConfig = {
},
{
key: 'Content-Security-Policy',
- value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
+ value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://api.exa.ai https://api.firecrawl.dev https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://*.supabase.co https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
},
],
},
diff --git a/apps/sim/package.json b/apps/sim/package.json
index d35fe9f84d..085ac253de 100644
--- a/apps/sim/package.json
+++ b/apps/sim/package.json
@@ -65,6 +65,7 @@
"@radix-ui/react-tooltip": "^1.1.6",
"@react-email/components": "^0.0.34",
"@sentry/nextjs": "^9.15.0",
+ "@trigger.dev/sdk": "3.3.17",
"@types/three": "0.177.0",
"@vercel/og": "^0.6.5",
"@vercel/speed-insights": "^1.2.0",
@@ -82,8 +83,8 @@
"drizzle-orm": "^0.41.0",
"framer-motion": "^12.5.0",
"freestyle-sandboxes": "^0.0.38",
- "geist": "1.4.2",
"fuse.js": "7.1.0",
+ "geist": "1.4.2",
"groq-sdk": "^0.15.0",
"input-otp": "^1.4.2",
"ioredis": "^5.6.0",
@@ -124,6 +125,7 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
+ "@trigger.dev/build": "3.3.17",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.7",
"@types/lodash": "^4.17.16",
diff --git a/apps/sim/services/queue/RateLimiter.test.ts b/apps/sim/services/queue/RateLimiter.test.ts
new file mode 100644
index 0000000000..c2ad391e50
--- /dev/null
+++ b/apps/sim/services/queue/RateLimiter.test.ts
@@ -0,0 +1,184 @@
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { RateLimiter } from './RateLimiter'
+import { RATE_LIMITS } from './types'
+
+// Mock the database module
+vi.mock('@/db', () => ({
+ db: {
+ select: vi.fn(),
+ insert: vi.fn(),
+ update: vi.fn(),
+ delete: vi.fn(),
+ },
+}))
+
+// Mock drizzle-orm
+vi.mock('drizzle-orm', () => ({
+ eq: vi.fn((field, value) => ({ field, value })),
+ sql: vi.fn((strings, ...values) => ({ sql: strings.join('?'), values })),
+ and: vi.fn((...conditions) => ({ and: conditions })),
+}))
+
+import { db } from '@/db'
+
+describe('RateLimiter', () => {
+ const rateLimiter = new RateLimiter()
+ const testUserId = 'test-user-123'
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ describe('checkRateLimit', () => {
+ it('should allow unlimited requests for manual trigger type', async () => {
+ const result = await rateLimiter.checkRateLimit(testUserId, 'free', 'manual', false)
+
+ expect(result.allowed).toBe(true)
+ expect(result.remaining).toBe(999999)
+ expect(result.resetAt).toBeInstanceOf(Date)
+ expect(db.select).not.toHaveBeenCalled()
+ })
+
+ it('should allow first API request for sync execution', async () => {
+ // Mock select to return empty array (no existing record)
+ vi.mocked(db.select).mockReturnValue({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([]), // No existing record
+ }),
+ }),
+ } as any)
+
+ // Mock insert to return the expected structure
+ vi.mocked(db.insert).mockReturnValue({
+ values: vi.fn().mockReturnValue({
+ onConflictDoUpdate: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([
+ {
+ syncApiRequests: 1,
+ asyncApiRequests: 0,
+ windowStart: new Date(),
+ },
+ ]),
+ }),
+ }),
+ } as any)
+
+ const result = await rateLimiter.checkRateLimit(testUserId, 'free', 'api', false)
+
+ expect(result.allowed).toBe(true)
+ expect(result.remaining).toBe(RATE_LIMITS.free.syncApiExecutionsPerMinute - 1)
+ expect(result.resetAt).toBeInstanceOf(Date)
+ })
+
+ it('should allow first API request for async execution', async () => {
+ // Mock select to return empty array (no existing record)
+ vi.mocked(db.select).mockReturnValue({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([]), // No existing record
+ }),
+ }),
+ } as any)
+
+ // Mock insert to return the expected structure
+ vi.mocked(db.insert).mockReturnValue({
+ values: vi.fn().mockReturnValue({
+ onConflictDoUpdate: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([
+ {
+ syncApiRequests: 0,
+ asyncApiRequests: 1,
+ windowStart: new Date(),
+ },
+ ]),
+ }),
+ }),
+ } as any)
+
+ const result = await rateLimiter.checkRateLimit(testUserId, 'free', 'api', true)
+
+ expect(result.allowed).toBe(true)
+ expect(result.remaining).toBe(RATE_LIMITS.free.asyncApiExecutionsPerMinute - 1)
+ expect(result.resetAt).toBeInstanceOf(Date)
+ })
+
+ it('should work for all trigger types except manual', async () => {
+ const triggerTypes = ['api', 'webhook', 'schedule', 'chat'] as const
+
+ for (const triggerType of triggerTypes) {
+ // Mock select to return empty array (no existing record)
+ vi.mocked(db.select).mockReturnValue({
+ from: vi.fn().mockReturnValue({
+ where: vi.fn().mockReturnValue({
+ limit: vi.fn().mockResolvedValue([]), // No existing record
+ }),
+ }),
+ } as any)
+
+ // Mock insert to return the expected structure
+ vi.mocked(db.insert).mockReturnValue({
+ values: vi.fn().mockReturnValue({
+ onConflictDoUpdate: vi.fn().mockReturnValue({
+ returning: vi.fn().mockResolvedValue([
+ {
+ syncApiRequests: 1,
+ asyncApiRequests: 0,
+ windowStart: new Date(),
+ },
+ ]),
+ }),
+ }),
+ } as any)
+
+ const result = await rateLimiter.checkRateLimit(testUserId, 'free', triggerType, false)
+
+ expect(result.allowed).toBe(true)
+ expect(result.remaining).toBe(RATE_LIMITS.free.syncApiExecutionsPerMinute - 1)
+ }
+ })
+ })
+
+ describe('getRateLimitStatus', () => {
+ it('should return unlimited for manual trigger type', async () => {
+ const status = await rateLimiter.getRateLimitStatus(testUserId, 'free', 'manual', false)
+
+ expect(status.used).toBe(0)
+ expect(status.limit).toBe(999999)
+ expect(status.remaining).toBe(999999)
+ expect(status.resetAt).toBeInstanceOf(Date)
+ })
+
+ it('should return sync API limits for API trigger type', async () => {
+ const mockSelect = vi.fn().mockReturnThis()
+ const mockFrom = vi.fn().mockReturnThis()
+ const mockWhere = vi.fn().mockReturnThis()
+ const mockLimit = vi.fn().mockResolvedValue([])
+
+ vi.mocked(db.select).mockReturnValue({
+ from: mockFrom,
+ where: mockWhere,
+ limit: mockLimit,
+ } as any)
+
+ const status = await rateLimiter.getRateLimitStatus(testUserId, 'free', 'api', false)
+
+ expect(status.used).toBe(0)
+ expect(status.limit).toBe(RATE_LIMITS.free.syncApiExecutionsPerMinute)
+ expect(status.remaining).toBe(RATE_LIMITS.free.syncApiExecutionsPerMinute)
+ expect(status.resetAt).toBeInstanceOf(Date)
+ })
+ })
+
+ describe('resetRateLimit', () => {
+ it('should delete rate limit record for user', async () => {
+ vi.mocked(db.delete).mockReturnValue({
+ where: vi.fn().mockResolvedValue({}),
+ } as any)
+
+ await rateLimiter.resetRateLimit(testUserId)
+
+ expect(db.delete).toHaveBeenCalled()
+ })
+ })
+})
diff --git a/apps/sim/services/queue/RateLimiter.ts b/apps/sim/services/queue/RateLimiter.ts
new file mode 100644
index 0000000000..ebc8ac2de1
--- /dev/null
+++ b/apps/sim/services/queue/RateLimiter.ts
@@ -0,0 +1,246 @@
+import { eq, sql } from 'drizzle-orm'
+import { createLogger } from '@/lib/logs/console-logger'
+import { db } from '@/db'
+import { userRateLimits } from '@/db/schema'
+import { RATE_LIMITS, type SubscriptionPlan, type TriggerType } from './types'
+
+const logger = createLogger('RateLimiter')
+
+export class RateLimiter {
+ /**
+ * Check if user can execute a workflow
+ * Manual executions bypass rate limiting entirely
+ */
+ async checkRateLimit(
+ userId: string,
+ subscriptionPlan: SubscriptionPlan = 'free',
+ triggerType: TriggerType = 'manual',
+ isAsync = false
+ ): Promise<{ allowed: boolean; remaining: number; resetAt: Date }> {
+ try {
+ if (triggerType === 'manual') {
+ return {
+ allowed: true,
+ remaining: 999999,
+ resetAt: new Date(Date.now() + 60000),
+ }
+ }
+
+ const limit = RATE_LIMITS[subscriptionPlan]
+ const execLimit = isAsync
+ ? limit.asyncApiExecutionsPerMinute
+ : limit.syncApiExecutionsPerMinute
+
+ const now = new Date()
+ const windowStart = new Date(now.getTime() - 60000) // 1 minute ago
+
+ // Get or create rate limit record
+ const [rateLimitRecord] = await db
+ .select()
+ .from(userRateLimits)
+ .where(eq(userRateLimits.userId, userId))
+ .limit(1)
+
+ if (!rateLimitRecord || new Date(rateLimitRecord.windowStart) < windowStart) {
+ // Window expired - reset window with this request as the first one
+ const result = await db
+ .insert(userRateLimits)
+ .values({
+ userId,
+ syncApiRequests: isAsync ? 0 : 1,
+ asyncApiRequests: isAsync ? 1 : 0,
+ windowStart: now,
+ lastRequestAt: now,
+ isRateLimited: false,
+ })
+ .onConflictDoUpdate({
+ target: userRateLimits.userId,
+ set: {
+ // Only reset if window is still expired (avoid race condition)
+ syncApiRequests: sql`CASE WHEN ${userRateLimits.windowStart} < ${windowStart.toISOString()} THEN ${isAsync ? 0 : 1} ELSE ${userRateLimits.syncApiRequests} + ${isAsync ? 0 : 1} END`,
+ asyncApiRequests: sql`CASE WHEN ${userRateLimits.windowStart} < ${windowStart.toISOString()} THEN ${isAsync ? 1 : 0} ELSE ${userRateLimits.asyncApiRequests} + ${isAsync ? 1 : 0} END`,
+ windowStart: sql`CASE WHEN ${userRateLimits.windowStart} < ${windowStart.toISOString()} THEN ${now.toISOString()} ELSE ${userRateLimits.windowStart} END`,
+ lastRequestAt: now,
+ isRateLimited: false,
+ rateLimitResetAt: null,
+ },
+ })
+ .returning({
+ syncApiRequests: userRateLimits.syncApiRequests,
+ asyncApiRequests: userRateLimits.asyncApiRequests,
+ windowStart: userRateLimits.windowStart,
+ })
+
+ const insertedRecord = result[0]
+ const actualCount = isAsync
+ ? insertedRecord.asyncApiRequests
+ : insertedRecord.syncApiRequests
+
+ // Check if we exceeded the limit
+ if (actualCount > execLimit) {
+ const resetAt = new Date(new Date(insertedRecord.windowStart).getTime() + 60000)
+
+ await db
+ .update(userRateLimits)
+ .set({
+ isRateLimited: true,
+ rateLimitResetAt: resetAt,
+ })
+ .where(eq(userRateLimits.userId, userId))
+
+ return {
+ allowed: false,
+ remaining: 0,
+ resetAt,
+ }
+ }
+
+ return {
+ allowed: true,
+ remaining: execLimit - actualCount,
+ resetAt: new Date(new Date(insertedRecord.windowStart).getTime() + 60000),
+ }
+ }
+
+ // Simple atomic increment - increment first, then check if over limit
+ const updateResult = await db
+ .update(userRateLimits)
+ .set({
+ ...(isAsync
+ ? { asyncApiRequests: sql`${userRateLimits.asyncApiRequests} + 1` }
+ : { syncApiRequests: sql`${userRateLimits.syncApiRequests} + 1` }),
+ lastRequestAt: now,
+ })
+ .where(eq(userRateLimits.userId, userId))
+ .returning({
+ asyncApiRequests: userRateLimits.asyncApiRequests,
+ syncApiRequests: userRateLimits.syncApiRequests,
+ })
+
+ const updatedRecord = updateResult[0]
+ const actualNewRequests = isAsync
+ ? updatedRecord.asyncApiRequests
+ : updatedRecord.syncApiRequests
+
+ // Check if we exceeded the limit AFTER the atomic increment
+ if (actualNewRequests > execLimit) {
+ const resetAt = new Date(new Date(rateLimitRecord.windowStart).getTime() + 60000)
+
+ logger.info(
+ `Rate limit exceeded - request ${actualNewRequests} > limit ${execLimit} for user ${userId}`,
+ {
+ execLimit,
+ isAsync,
+ actualNewRequests,
+ }
+ )
+
+ // Update rate limited status
+ await db
+ .update(userRateLimits)
+ .set({
+ isRateLimited: true,
+ rateLimitResetAt: resetAt,
+ })
+ .where(eq(userRateLimits.userId, userId))
+
+ return {
+ allowed: false,
+ remaining: 0,
+ resetAt,
+ }
+ }
+
+ return {
+ allowed: true,
+ remaining: execLimit - actualNewRequests,
+ resetAt: new Date(new Date(rateLimitRecord.windowStart).getTime() + 60000),
+ }
+ } catch (error) {
+ logger.error('Error checking rate limit:', error)
+ // Allow execution on error to avoid blocking users
+ return {
+ allowed: true,
+ remaining: 0,
+ resetAt: new Date(Date.now() + 60000),
+ }
+ }
+ }
+
+ /**
+ * Get current rate limit status for user
+ * Only applies to API executions
+ */
+ async getRateLimitStatus(
+ userId: string,
+ subscriptionPlan: SubscriptionPlan = 'free',
+ triggerType: TriggerType = 'manual',
+ isAsync = false
+ ): Promise<{ used: number; limit: number; remaining: number; resetAt: Date }> {
+ try {
+ if (triggerType === 'manual') {
+ return {
+ used: 0,
+ limit: 999999,
+ remaining: 999999,
+ resetAt: new Date(Date.now() + 60000),
+ }
+ }
+
+ const limit = RATE_LIMITS[subscriptionPlan]
+ const execLimit = isAsync
+ ? limit.asyncApiExecutionsPerMinute
+ : limit.syncApiExecutionsPerMinute
+ const now = new Date()
+ const windowStart = new Date(now.getTime() - 60000)
+
+ const [rateLimitRecord] = await db
+ .select()
+ .from(userRateLimits)
+ .where(eq(userRateLimits.userId, userId))
+ .limit(1)
+
+ if (!rateLimitRecord || new Date(rateLimitRecord.windowStart) < windowStart) {
+ return {
+ used: 0,
+ limit: execLimit,
+ remaining: execLimit,
+ resetAt: new Date(now.getTime() + 60000),
+ }
+ }
+
+ const used = isAsync ? rateLimitRecord.asyncApiRequests : rateLimitRecord.syncApiRequests
+ return {
+ used,
+ limit: execLimit,
+ remaining: Math.max(0, execLimit - used),
+ resetAt: new Date(new Date(rateLimitRecord.windowStart).getTime() + 60000),
+ }
+ } catch (error) {
+ logger.error('Error getting rate limit status:', error)
+ const execLimit = isAsync
+ ? RATE_LIMITS[subscriptionPlan].asyncApiExecutionsPerMinute
+ : RATE_LIMITS[subscriptionPlan].syncApiExecutionsPerMinute
+ return {
+ used: 0,
+ limit: execLimit,
+ remaining: execLimit,
+ resetAt: new Date(Date.now() + 60000),
+ }
+ }
+ }
+
+ /**
+ * Reset rate limit for user (admin action)
+ */
+ async resetRateLimit(userId: string): Promise {
+ try {
+ await db.delete(userRateLimits).where(eq(userRateLimits.userId, userId))
+
+ logger.info(`Reset rate limit for user ${userId}`)
+ } catch (error) {
+ logger.error('Error resetting rate limit:', error)
+ throw error
+ }
+ }
+}
diff --git a/apps/sim/services/queue/index.ts b/apps/sim/services/queue/index.ts
new file mode 100644
index 0000000000..bc77ad2039
--- /dev/null
+++ b/apps/sim/services/queue/index.ts
@@ -0,0 +1,7 @@
+export { RateLimiter } from './RateLimiter'
+export type {
+ RateLimitConfig,
+ SubscriptionPlan,
+ TriggerType,
+} from './types'
+export { RATE_LIMITS, RateLimitError } from './types'
diff --git a/apps/sim/services/queue/types.ts b/apps/sim/services/queue/types.ts
new file mode 100644
index 0000000000..58eb7e5ff7
--- /dev/null
+++ b/apps/sim/services/queue/types.ts
@@ -0,0 +1,46 @@
+import type { InferSelectModel } from 'drizzle-orm'
+import type { userRateLimits } from '@/db/schema'
+
+// Database types
+export type UserRateLimit = InferSelectModel
+
+// Trigger types for rate limiting
+export type TriggerType = 'api' | 'webhook' | 'schedule' | 'manual' | 'chat'
+
+// Subscription plan types
+export type SubscriptionPlan = 'free' | 'pro' | 'team' | 'enterprise'
+
+// Rate limit configuration (applies to all non-manual trigger types: api, webhook, schedule, chat)
+export interface RateLimitConfig {
+ syncApiExecutionsPerMinute: number
+ asyncApiExecutionsPerMinute: number
+}
+
+export const RATE_LIMITS: Record = {
+ free: {
+ syncApiExecutionsPerMinute: 10,
+ asyncApiExecutionsPerMinute: 50,
+ },
+ pro: {
+ syncApiExecutionsPerMinute: 25,
+ asyncApiExecutionsPerMinute: 200,
+ },
+ team: {
+ syncApiExecutionsPerMinute: 75,
+ asyncApiExecutionsPerMinute: 500,
+ },
+ enterprise: {
+ syncApiExecutionsPerMinute: 150,
+ asyncApiExecutionsPerMinute: 1000,
+ },
+}
+
+// Custom error for rate limits
+export class RateLimitError extends Error {
+ statusCode: number
+ constructor(message: string, statusCode = 429) {
+ super(message)
+ this.name = 'RateLimitError'
+ this.statusCode = statusCode
+ }
+}
diff --git a/apps/sim/socket-server/database/operations.ts b/apps/sim/socket-server/database/operations.ts
index 7c1c296ee1..434e6829c7 100644
--- a/apps/sim/socket-server/database/operations.ts
+++ b/apps/sim/socket-server/database/operations.ts
@@ -130,7 +130,6 @@ export async function getWorkflowState(workflowId: string) {
const finalState = {
// Default values for expected properties
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
// Preserve any existing state properties
...existingState,
diff --git a/apps/sim/stores/index.ts b/apps/sim/stores/index.ts
index acf1afbc68..92696b9c5a 100644
--- a/apps/sim/stores/index.ts
+++ b/apps/sim/stores/index.ts
@@ -219,7 +219,6 @@ export const resetAllStores = () => {
})
useWorkflowStore.getState().clear()
useSubBlockStore.getState().clear()
- useSubBlockStore.getState().clearToolParams()
useEnvironmentStore.setState({
variables: {},
isLoading: false,
diff --git a/apps/sim/stores/settings/general/store.ts b/apps/sim/stores/settings/general/store.ts
index 16f90bd9d8..fbc77ee8c2 100644
--- a/apps/sim/stores/settings/general/store.ts
+++ b/apps/sim/stores/settings/general/store.ts
@@ -17,7 +17,6 @@ export const useGeneralStore = create()(
const store: General = {
isAutoConnectEnabled: true,
- isAutoFillEnvVarsEnabled: true,
isAutoPanEnabled: true,
isConsoleExpandedByDefault: true,
isDebugModeEnabled: false,
@@ -28,7 +27,6 @@ export const useGeneralStore = create()(
error: null,
// Individual loading states
isAutoConnectLoading: false,
- isAutoFillEnvVarsLoading: false,
isAutoPanLoading: false,
isConsoleExpandedByDefaultLoading: false,
isThemeLoading: false,
@@ -74,17 +72,6 @@ export const useGeneralStore = create()(
)
},
- toggleAutoFillEnvVars: async () => {
- if (get().isAutoFillEnvVarsLoading) return
- const newValue = !get().isAutoFillEnvVarsEnabled
- await updateSettingOptimistic(
- 'autoFillEnvVars',
- newValue,
- 'isAutoFillEnvVarsLoading',
- 'isAutoFillEnvVarsEnabled'
- )
- },
-
toggleAutoPan: async () => {
if (get().isAutoPanLoading) return
const newValue = !get().isAutoPanEnabled
@@ -166,7 +153,6 @@ export const useGeneralStore = create()(
set({
isAutoConnectEnabled: data.autoConnect,
- isAutoFillEnvVarsEnabled: data.autoFillEnvVars,
isAutoPanEnabled: data.autoPan ?? true, // Default to true if undefined
isConsoleExpandedByDefault: data.consoleExpandedByDefault ?? true, // Default to true if undefined
theme: data.theme,
diff --git a/apps/sim/stores/settings/general/types.ts b/apps/sim/stores/settings/general/types.ts
index 4bacfabaf6..f7f689f88a 100644
--- a/apps/sim/stores/settings/general/types.ts
+++ b/apps/sim/stores/settings/general/types.ts
@@ -1,6 +1,5 @@
export interface General {
isAutoConnectEnabled: boolean
- isAutoFillEnvVarsEnabled: boolean
isAutoPanEnabled: boolean
isConsoleExpandedByDefault: boolean
isDebugModeEnabled: boolean
@@ -11,7 +10,6 @@ export interface General {
error: string | null
// Individual loading states for optimistic updates
isAutoConnectLoading: boolean
- isAutoFillEnvVarsLoading: boolean
isAutoPanLoading: boolean
isConsoleExpandedByDefaultLoading: boolean
isThemeLoading: boolean
@@ -20,7 +18,7 @@ export interface General {
export interface GeneralActions {
toggleAutoConnect: () => Promise
- toggleAutoFillEnvVars: () => Promise
+
toggleAutoPan: () => Promise
toggleConsoleExpandedByDefault: () => Promise
toggleDebugMode: () => void
@@ -36,7 +34,6 @@ export type GeneralStore = General & GeneralActions
export type UserSettings = {
theme: 'system' | 'light' | 'dark'
autoConnect: boolean
- autoFillEnvVars: boolean
autoPan: boolean
consoleExpandedByDefault: boolean
telemetryEnabled: boolean
diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts
index 5fada790b0..c49741f7ce 100644
--- a/apps/sim/stores/workflows/registry/store.ts
+++ b/apps/sim/stores/workflows/registry/store.ts
@@ -188,7 +188,6 @@ function resetWorkflowStores() {
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {}, // Reset deployment statuses map
- hasActiveSchedule: false,
history: {
past: [],
present: {
@@ -212,7 +211,6 @@ function resetWorkflowStores() {
// Reset the subblock store
useSubBlockStore.setState({
workflowValues: {},
- toolParams: {},
})
}
@@ -442,7 +440,6 @@ export const useWorkflowRegistry = create()(
lastSaved: Date.now(),
marketplaceData: workflowData.marketplaceData || null,
deploymentStatuses: {},
- hasActiveSchedule: false,
history: {
past: [],
present: {
@@ -491,7 +488,6 @@ export const useWorkflowRegistry = create()(
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {},
- hasActiveSchedule: false,
history: {
past: [],
present: {
@@ -1254,7 +1250,6 @@ export const useWorkflowRegistry = create()(
parallels: {},
isDeployed: false,
deployedAt: undefined,
- hasActiveSchedule: false,
history: {
past: [],
present: {
diff --git a/apps/sim/stores/workflows/registry/utils.ts b/apps/sim/stores/workflows/registry/utils.ts
index fd41d21a75..10df11c9ad 100644
--- a/apps/sim/stores/workflows/registry/utils.ts
+++ b/apps/sim/stores/workflows/registry/utils.ts
@@ -1,93 +1,74 @@
// Available workflow colors
export const WORKFLOW_COLORS = [
- // Original colors
- '#3972F6', // Blue
- '#F639DD', // Pink/Magenta
- '#F6B539', // Orange/Yellow
- '#8139F6', // Purple
- '#39B54A', // Green
- '#39B5AB', // Teal
- '#F66839', // Red/Orange
+ // Blues - vibrant blue tones
+ '#3972F6', // Blue (original)
+ '#2E5BF5', // Deeper Blue
+ '#1E4BF4', // Royal Blue
+ '#0D3BF3', // Deep Royal Blue
- // Additional vibrant blues
- '#2E5BFF', // Bright Blue
- '#4A90FF', // Sky Blue
- '#1E40AF', // Deep Blue
- '#0EA5E9', // Cyan Blue
- '#3B82F6', // Royal Blue
- '#6366F1', // Indigo
- '#1D4ED8', // Electric Blue
+ // Pinks/Magentas - vibrant pink and magenta tones
+ '#F639DD', // Pink/Magenta (original)
+ '#F529CF', // Deep Magenta
+ '#F749E7', // Light Magenta
+ '#F419C1', // Hot Pink
- // Additional vibrant purples
- '#A855F7', // Bright Purple
- '#C084FC', // Light Purple
- '#7C3AED', // Deep Purple
- '#9333EA', // Violet
- '#8B5CF6', // Medium Purple
- '#6D28D9', // Dark Purple
- '#5B21B6', // Deep Violet
+ // Oranges/Yellows - vibrant orange and yellow tones
+ '#F6B539', // Orange/Yellow (original)
+ '#F5A529', // Deep Orange
+ '#F49519', // Burnt Orange
+ '#F38509', // Deep Burnt Orange
- // Additional vibrant pinks/magentas
- '#EC4899', // Hot Pink
- '#F97316', // Pink Orange
- '#E11D48', // Rose
- '#BE185D', // Deep Pink
- '#DB2777', // Pink Red
- '#F472B6', // Light Pink
- '#F59E0B', // Amber Pink
+ // Purples - vibrant purple tones
+ '#8139F6', // Purple (original)
+ '#7129F5', // Deep Purple
+ '#6119F4', // Royal Purple
+ '#5109F3', // Deep Royal Purple
- // Additional vibrant greens
- '#10B981', // Emerald
- '#059669', // Green Teal
- '#16A34A', // Forest Green
- '#22C55E', // Lime Green
- '#84CC16', // Yellow Green
- '#65A30D', // Olive Green
- '#15803D', // Dark Green
+ // Greens - vibrant green tones
+ '#39B54A', // Green (original)
+ '#29A53A', // Deep Green
+ '#19952A', // Forest Green
+ '#09851A', // Deep Forest Green
- // Additional vibrant teals/cyans
- '#06B6D4', // Cyan
- '#0891B2', // Dark Cyan
- '#0E7490', // Teal Blue
- '#14B8A6', // Turquoise
- '#0D9488', // Dark Teal
- '#047857', // Sea Green
- '#059669', // Mint Green
+ // Teals/Cyans - vibrant teal and cyan tones
+ '#39B5AB', // Teal (original)
+ '#29A59B', // Deep Teal
+ '#19958B', // Dark Teal
+ '#09857B', // Deep Dark Teal
- // Additional vibrant oranges/reds
- '#EA580C', // Bright Orange
- '#DC2626', // Red
- '#B91C1C', // Dark Red
- '#EF4444', // Light Red
- '#F97316', // Orange
- '#FB923C', // Light Orange
- '#FDBA74', // Peach
+ // Reds/Red-Oranges - vibrant red and red-orange tones
+ '#F66839', // Red/Orange (original)
+ '#F55829', // Deep Red-Orange
+ '#F44819', // Burnt Red
+ '#F33809', // Deep Burnt Red
- // Additional vibrant yellows/golds
- '#FBBF24', // Gold
- '#F59E0B', // Amber
- '#D97706', // Dark Amber
- '#92400E', // Bronze
- '#EAB308', // Yellow
- '#CA8A04', // Dark Yellow
- '#A16207', // Mustard
+ // Additional vibrant colors for variety
+ // Corals - warm coral tones
+ '#F6397A', // Coral
+ '#F5296A', // Deep Coral
+ '#F7498A', // Light Coral
- // Additional unique vibrant colors
- '#FF6B6B', // Coral
- '#4ECDC4', // Mint
- '#45B7D1', // Light Blue
- '#96CEB4', // Sage
- '#FFEAA7', // Cream
- '#DDA0DD', // Plum
- '#98D8C8', // Seafoam
- '#F7DC6F', // Banana
- '#BB8FCE', // Lavender
- '#85C1E9', // Baby Blue
- '#F8C471', // Peach
- '#82E0AA', // Light Green
- '#F1948A', // Salmon
- '#D7BDE2', // Lilac
- '#D7BDE2', // Lilac
+ // Crimsons - deep red tones
+ '#DC143C', // Crimson
+ '#CC042C', // Deep Crimson
+ '#EC243C', // Light Crimson
+ '#BC003C', // Dark Crimson
+ '#FC343C', // Bright Crimson
+
+ // Mint - fresh green tones
+ '#00FF7F', // Mint Green
+ '#00EF6F', // Deep Mint
+ '#00DF5F', // Dark Mint
+
+ // Slate - blue-gray tones
+ '#6A5ACD', // Slate Blue
+ '#5A4ABD', // Deep Slate
+ '#4A3AAD', // Dark Slate
+
+ // Amber - warm orange-yellow tones
+ '#FFBF00', // Amber
+ '#EFAF00', // Deep Amber
+ '#DF9F00', // Dark Amber
]
// Random adjectives and nouns for generating creative workflow names
diff --git a/apps/sim/stores/workflows/subblock/store.ts b/apps/sim/stores/workflows/subblock/store.ts
index 47d8fdbf7b..09fe68af54 100644
--- a/apps/sim/stores/workflows/subblock/store.ts
+++ b/apps/sim/stores/workflows/subblock/store.ts
@@ -1,12 +1,9 @@
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import type { SubBlockConfig } from '@/blocks/types'
-import { useEnvironmentStore } from '../../settings/environment/store'
-import { useGeneralStore } from '../../settings/general/store'
import { useWorkflowRegistry } from '../registry/store'
// Removed workflowSync import - Socket.IO handles real-time sync
import type { SubBlockStore } from './types'
-import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './utils'
// Removed debounce sync - Socket.IO handles real-time sync immediately
@@ -25,9 +22,6 @@ import { extractEnvVarName, findMatchingEnvVar, isEnvVarReference } from './util
export const useSubBlockStore = create()(
devtools((set, get) => ({
workflowValues: {},
- // Initialize tool params-related state
- toolParams: {},
- clearedParams: {},
setValue: (blockId: string, subBlockId: string, value: any) => {
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
@@ -93,179 +87,5 @@ export const useSubBlockStore = create()(
syncWithDB: () => {
// No-op: Socket.IO handles real-time sync
},
-
- // Tool params related functionality
- setToolParam: (toolId: string, paramId: string, value: string) => {
- // If setting a non-empty value, we should remove it from clearedParams if it exists
- if (value.trim() !== '') {
- set((state) => {
- const newClearedParams = { ...state.clearedParams }
- if (newClearedParams[toolId]?.[paramId]) {
- delete newClearedParams[toolId][paramId]
- // Clean up empty objects
- if (Object.keys(newClearedParams[toolId]).length === 0) {
- delete newClearedParams[toolId]
- }
- }
-
- return { clearedParams: newClearedParams }
- })
- }
-
- // Set the parameter value
- set((state) => ({
- toolParams: {
- ...state.toolParams,
- [toolId]: {
- ...(state.toolParams[toolId] || {}),
- [paramId]: value,
- },
- },
- }))
-
- // For API keys, also store under a normalized tool name for cross-referencing
- // This allows both blocks and tools to share the same parameters
- if (paramId.toLowerCase() === 'apikey' || paramId.toLowerCase() === 'api_key') {
- // Extract the tool name part (e.g., "exa" from "exa-search")
- const baseTool = toolId.split('-')[0].toLowerCase()
-
- if (baseTool !== toolId) {
- // Set the same value for the base tool to enable cross-referencing
- set((state) => ({
- toolParams: {
- ...state.toolParams,
- [baseTool]: {
- ...(state.toolParams[baseTool] || {}),
- [paramId]: value,
- },
- },
- }))
- }
- }
- },
-
- markParamAsCleared: (instanceId: string, paramId: string) => {
- // Mark this specific instance as cleared
- set((state) => ({
- clearedParams: {
- ...state.clearedParams,
- [instanceId]: {
- ...(state.clearedParams[instanceId] || {}),
- [paramId]: true,
- },
- },
- }))
- },
-
- unmarkParamAsCleared: (instanceId: string, paramId: string) => {
- // Remove the cleared flag for this parameter
- set((state) => {
- const newClearedParams = { ...state.clearedParams }
- if (newClearedParams[instanceId]?.[paramId]) {
- delete newClearedParams[instanceId][paramId]
- // Clean up empty objects
- if (Object.keys(newClearedParams[instanceId]).length === 0) {
- delete newClearedParams[instanceId]
- }
- }
- return { clearedParams: newClearedParams }
- })
- },
-
- isParamCleared: (instanceId: string, paramId: string) => {
- // Only check this specific instance
- return !!get().clearedParams[instanceId]?.[paramId]
- },
-
- getToolParam: (toolId: string, paramId: string) => {
- // Check for direct match first
- const directValue = get().toolParams[toolId]?.[paramId]
- if (directValue) return directValue
-
- // Try base tool name if it's a compound tool ID
- if (toolId.includes('-')) {
- const baseTool = toolId.split('-')[0].toLowerCase()
- return get().toolParams[baseTool]?.[paramId]
- }
-
- // Try matching against any stored tool that starts with this ID
- // This helps match "exa" with "exa-search" etc.
- const matchingToolIds = Object.keys(get().toolParams).filter(
- (id) => id.startsWith(toolId) || id.split('-')[0] === toolId
- )
-
- for (const id of matchingToolIds) {
- const value = get().toolParams[id]?.[paramId]
- if (value) return value
- }
-
- return undefined
- },
-
- getToolParams: (toolId: string) => {
- return get().toolParams[toolId] || {}
- },
-
- isEnvVarReference,
-
- resolveToolParamValue: (toolId: string, paramId: string, instanceId?: string) => {
- // If this is a specific instance that has been deliberately cleared, don't auto-fill it
- if (instanceId && get().isParamCleared(instanceId, paramId)) {
- return undefined
- }
-
- // Check if auto-fill environment variables is enabled
- const isAutoFillEnvVarsEnabled = useGeneralStore.getState().isAutoFillEnvVarsEnabled
- if (!isAutoFillEnvVarsEnabled) {
- // When auto-fill is disabled, we still return existing stored values, but don't
- // attempt to resolve environment variables or set new values
- return get().toolParams[toolId]?.[paramId]
- }
-
- const envStore = useEnvironmentStore.getState()
-
- // First check params store for previously entered value
- const storedValue = get().getToolParam(toolId, paramId)
-
- if (storedValue) {
- // If the stored value is an environment variable reference like {{EXA_API_KEY}}
- if (isEnvVarReference(storedValue)) {
- // Extract variable name from {{VAR_NAME}}
- const envVarName = extractEnvVarName(storedValue)
- if (!envVarName) return undefined
-
- // Check if this environment variable still exists
- const envValue = envStore.getVariable(envVarName)
-
- if (envValue) {
- // Environment variable exists, return the reference
- return storedValue
- }
- // Environment variable no longer exists
- return undefined
- }
-
- // Return the stored value directly if it's not an env var reference
- return storedValue
- }
-
- // If no stored value, try to guess based on parameter name
- // This handles cases where the user hasn't entered a value yet
- if (paramId.toLowerCase() === 'apikey' || paramId.toLowerCase() === 'api_key') {
- const matchingVar = findMatchingEnvVar(toolId)
- if (matchingVar) {
- const envReference = `{{${matchingVar}}}`
- get().setToolParam(toolId, paramId, envReference)
- return envReference
- }
- }
-
- // No value found
- return undefined
- },
-
- clearToolParams: () => {
- set({ toolParams: {}, clearedParams: {} })
- },
}))
)
diff --git a/apps/sim/stores/workflows/subblock/types.ts b/apps/sim/stores/workflows/subblock/types.ts
index a9e2d7fb14..1f9f61a148 100644
--- a/apps/sim/stores/workflows/subblock/types.ts
+++ b/apps/sim/stores/workflows/subblock/types.ts
@@ -1,7 +1,5 @@
export interface SubBlockState {
workflowValues: Record>> // Store values per workflow ID
- toolParams: Record>
- clearedParams: Record>
}
export interface SubBlockStore extends SubBlockState {
@@ -11,19 +9,4 @@ export interface SubBlockStore extends SubBlockState {
initializeFromWorkflow: (workflowId: string, blocks: Record) => void
// Add debounced sync function
syncWithDB: () => void
-
- // Tool params related functions
- setToolParam: (toolId: string, paramId: string, value: string) => void
- markParamAsCleared: (instanceId: string, paramId: string) => void
- unmarkParamAsCleared: (instanceId: string, paramId: string) => void
- isParamCleared: (instanceId: string, paramId: string) => boolean
- getToolParam: (toolId: string, paramId: string) => string | undefined
- getToolParams: (toolId: string) => Record
- isEnvVarReference: (value: string) => boolean
- resolveToolParamValue: (
- toolId: string,
- paramId: string,
- instanceId?: string
- ) => string | undefined
- clearToolParams: () => void
}
diff --git a/apps/sim/stores/workflows/subblock/utils.ts b/apps/sim/stores/workflows/subblock/utils.ts
index 5675942aba..578ebe1417 100644
--- a/apps/sim/stores/workflows/subblock/utils.ts
+++ b/apps/sim/stores/workflows/subblock/utils.ts
@@ -1,4 +1,4 @@
-import { useEnvironmentStore } from '../../settings/environment/store'
+// DEPRECATED: useEnvironmentStore import removed as autofill functions were removed
/**
* Checks if a value is an environment variable reference in the format {{ENV_VAR}}
@@ -15,38 +15,3 @@ export const extractEnvVarName = (value: string): string | null => {
if (!isEnvVarReference(value)) return null
return value.slice(2, -2)
}
-
-/**
- * Generates possible environment variable names for a tool ID
- * For example, "exa-search" could map to "EXA_API_KEY", "EXA_KEY", etc.
- */
-export const generatePossibleEnvVarNames = (toolId: string): string[] => {
- // Extract base tool name if it's a compound ID
- const baseTool = toolId.includes('-') ? toolId.split('-')[0] : toolId
- const toolPrefix = baseTool.toUpperCase()
-
- return [
- `${toolPrefix}_API_KEY`,
- `${toolPrefix.replace(/-/g, '_')}_API_KEY`,
- `${toolPrefix}_KEY`,
- `${toolPrefix}_TOKEN`,
- `${toolPrefix}`,
- ]
-}
-
-/**
- * Finds a matching environment variable for a tool ID
- */
-export const findMatchingEnvVar = (toolId: string): string | null => {
- const envStore = useEnvironmentStore.getState()
- const possibleVars = generatePossibleEnvVarNames(toolId)
-
- for (const varName of possibleVars) {
- const envValue = envStore.getVariable(varName)
- if (envValue) {
- return varName
- }
- }
-
- return null
-}
diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts
index b1308552de..3f624f0032 100644
--- a/apps/sim/stores/workflows/workflow/store.ts
+++ b/apps/sim/stores/workflows/workflow/store.ts
@@ -23,7 +23,6 @@ const initialState = {
// New field for per-workflow deployment tracking
deploymentStatuses: {},
needsRedeployment: false,
- hasActiveSchedule: false,
hasActiveWebhook: false,
history: {
past: [],
@@ -436,7 +435,6 @@ export const useWorkflowStore = create()(
lastSaved: Date.now(),
isDeployed: false,
isPublished: false,
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
set(newState)
@@ -799,23 +797,9 @@ export const useWorkflowStore = create()(
}))
},
- setScheduleStatus: (hasActiveSchedule: boolean) => {
- // Only update if the status has changed to avoid unnecessary rerenders
- if (get().hasActiveSchedule !== hasActiveSchedule) {
- set({ hasActiveSchedule })
- get().updateLastSaved()
- // Note: Socket.IO handles real-time sync automatically
- }
- },
-
setWebhookStatus: (hasActiveWebhook: boolean) => {
// Only update if the status has changed to avoid unnecessary rerenders
if (get().hasActiveWebhook !== hasActiveWebhook) {
- // If the workflow has an active schedule, disable it
- if (get().hasActiveSchedule) {
- get().setScheduleStatus(false)
- }
-
set({ hasActiveWebhook })
get().updateLastSaved()
// Note: Socket.IO handles real-time sync automatically
diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts
index 65ef362451..05b31bfbc2 100644
--- a/apps/sim/stores/workflows/workflow/types.ts
+++ b/apps/sim/stores/workflows/workflow/types.ts
@@ -140,7 +140,6 @@ export interface WorkflowState {
// New field for per-workflow deployment status
deploymentStatuses?: Record
needsRedeployment?: boolean
- hasActiveSchedule?: boolean
hasActiveWebhook?: boolean
}
@@ -189,7 +188,6 @@ export interface WorkflowActions {
generateLoopBlocks: () => Record
generateParallelBlocks: () => Record
setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void
- setScheduleStatus: (hasActiveSchedule: boolean) => void
setWebhookStatus: (hasActiveWebhook: boolean) => void
revertToDeployedState: (deployedState: WorkflowState) => void
toggleBlockAdvancedMode: (id: string) => void
diff --git a/apps/sim/stores/workflows/yaml/importer.ts b/apps/sim/stores/workflows/yaml/importer.ts
index 6ef4d3b84e..d1187d7a94 100644
--- a/apps/sim/stores/workflows/yaml/importer.ts
+++ b/apps/sim/stores/workflows/yaml/importer.ts
@@ -696,7 +696,6 @@ export async function importWorkflowFromYaml(
isDeployed: false,
deployedAt: undefined,
deploymentStatuses: {},
- hasActiveSchedule: false,
hasActiveWebhook: false,
}
diff --git a/apps/sim/tools/airtable/create_records.ts b/apps/sim/tools/airtable/create_records.ts
index 163f8e2474..7f7e8bee36 100644
--- a/apps/sim/tools/airtable/create_records.ts
+++ b/apps/sim/tools/airtable/create_records.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { AirtableCreateParams, AirtableCreateResponse } from './types'
+import type { AirtableCreateParams, AirtableCreateResponse } from '@/tools/airtable/types'
+import type { ToolConfig } from '@/tools/types'
export const airtableCreateRecordsTool: ToolConfig = {
id: 'airtable_create_records',
diff --git a/apps/sim/tools/airtable/get_record.ts b/apps/sim/tools/airtable/get_record.ts
index 2323fc5da5..a789575e8a 100644
--- a/apps/sim/tools/airtable/get_record.ts
+++ b/apps/sim/tools/airtable/get_record.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { AirtableGetParams, AirtableGetResponse } from './types'
+import type { AirtableGetParams, AirtableGetResponse } from '@/tools/airtable/types'
+import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
diff --git a/apps/sim/tools/airtable/index.ts b/apps/sim/tools/airtable/index.ts
index 9fe70e4649..d2f022c039 100644
--- a/apps/sim/tools/airtable/index.ts
+++ b/apps/sim/tools/airtable/index.ts
@@ -1,8 +1,8 @@
-import { airtableCreateRecordsTool } from './create_records'
-import { airtableGetRecordTool } from './get_record'
-import { airtableListRecordsTool } from './list_records'
-import { airtableUpdateMultipleRecordsTool } from './update_multiple_records'
-import { airtableUpdateRecordTool } from './update_record'
+import { airtableCreateRecordsTool } from '@/tools/airtable/create_records'
+import { airtableGetRecordTool } from '@/tools/airtable/get_record'
+import { airtableListRecordsTool } from '@/tools/airtable/list_records'
+import { airtableUpdateMultipleRecordsTool } from '@/tools/airtable/update_multiple_records'
+import { airtableUpdateRecordTool } from '@/tools/airtable/update_record'
export {
airtableCreateRecordsTool,
diff --git a/apps/sim/tools/airtable/list_records.ts b/apps/sim/tools/airtable/list_records.ts
index 15736fefd3..6fd04a94a7 100644
--- a/apps/sim/tools/airtable/list_records.ts
+++ b/apps/sim/tools/airtable/list_records.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { AirtableListParams, AirtableListResponse } from './types'
+import type { AirtableListParams, AirtableListResponse } from '@/tools/airtable/types'
+import type { ToolConfig } from '@/tools/types'
export const airtableListRecordsTool: ToolConfig = {
id: 'airtable_list_records',
diff --git a/apps/sim/tools/airtable/types.ts b/apps/sim/tools/airtable/types.ts
index 64914c3986..5c12658555 100644
--- a/apps/sim/tools/airtable/types.ts
+++ b/apps/sim/tools/airtable/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
// Common types
export interface AirtableRecord {
@@ -87,3 +87,10 @@ export interface AirtableUpdateMultipleResponse extends ToolResponse {
}
}
}
+
+export type AirtableResponse =
+ | AirtableListResponse
+ | AirtableGetResponse
+ | AirtableCreateResponse
+ | AirtableUpdateResponse
+ | AirtableUpdateMultipleResponse
diff --git a/apps/sim/tools/airtable/update_multiple_records.ts b/apps/sim/tools/airtable/update_multiple_records.ts
index 9e754f6c3b..3c00190c0b 100644
--- a/apps/sim/tools/airtable/update_multiple_records.ts
+++ b/apps/sim/tools/airtable/update_multiple_records.ts
@@ -1,5 +1,8 @@
-import type { ToolConfig } from '../types'
-import type { AirtableUpdateMultipleParams, AirtableUpdateMultipleResponse } from './types'
+import type {
+ AirtableUpdateMultipleParams,
+ AirtableUpdateMultipleResponse,
+} from '@/tools/airtable/types'
+import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
diff --git a/apps/sim/tools/airtable/update_record.ts b/apps/sim/tools/airtable/update_record.ts
index 9cf443aa0f..3f17cbb75d 100644
--- a/apps/sim/tools/airtable/update_record.ts
+++ b/apps/sim/tools/airtable/update_record.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { AirtableUpdateParams, AirtableUpdateResponse } from './types'
+import type { AirtableUpdateParams, AirtableUpdateResponse } from '@/tools/airtable/types'
+import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
diff --git a/apps/sim/tools/browser_use/index.ts b/apps/sim/tools/browser_use/index.ts
index dd87c2fbf8..26f38d8b40 100644
--- a/apps/sim/tools/browser_use/index.ts
+++ b/apps/sim/tools/browser_use/index.ts
@@ -1,3 +1,3 @@
-import { runTaskTool } from './run_task'
+import { runTaskTool } from '@/tools/browser_use/run_task'
export const browserUseRunTaskTool = runTaskTool
diff --git a/apps/sim/tools/browser_use/run_task.ts b/apps/sim/tools/browser_use/run_task.ts
index fc8aacf883..fe13d215db 100644
--- a/apps/sim/tools/browser_use/run_task.ts
+++ b/apps/sim/tools/browser_use/run_task.ts
@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
-import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from './types'
+import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('BrowserUseTool')
diff --git a/apps/sim/tools/browser_use/types.ts b/apps/sim/tools/browser_use/types.ts
index 24406b179d..293bcbfa7d 100644
--- a/apps/sim/tools/browser_use/types.ts
+++ b/apps/sim/tools/browser_use/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface BrowserUseRunTaskParams {
task: string
@@ -27,3 +27,12 @@ export interface BrowserUseTaskOutput {
export interface BrowserUseRunTaskResponse extends ToolResponse {
output: BrowserUseTaskOutput
}
+
+export interface BrowserUseResponse extends ToolResponse {
+ output: {
+ id: string
+ success: boolean
+ output: any
+ steps: BrowserUseTaskStep[]
+ }
+}
diff --git a/apps/sim/tools/clay/index.ts b/apps/sim/tools/clay/index.ts
index 6a3796cd14..0d4d1b78c8 100644
--- a/apps/sim/tools/clay/index.ts
+++ b/apps/sim/tools/clay/index.ts
@@ -1,3 +1,3 @@
-import { clayPopulateTool } from './populate'
+import { clayPopulateTool } from '@/tools/clay/populate'
export { clayPopulateTool }
diff --git a/apps/sim/tools/clay/populate.ts b/apps/sim/tools/clay/populate.ts
index e15ab64d20..4c5fe9372e 100644
--- a/apps/sim/tools/clay/populate.ts
+++ b/apps/sim/tools/clay/populate.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ClayPopulateParams, ClayPopulateResponse } from './types'
+import type { ClayPopulateParams, ClayPopulateResponse } from '@/tools/clay/types'
+import type { ToolConfig } from '@/tools/types'
export const clayPopulateTool: ToolConfig = {
id: 'clay_populate',
diff --git a/apps/sim/tools/clay/types.ts b/apps/sim/tools/clay/types.ts
index af38c97209..f5aeb2d552 100644
--- a/apps/sim/tools/clay/types.ts
+++ b/apps/sim/tools/clay/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface ClayPopulateParams {
webhookURL: string
diff --git a/apps/sim/tools/confluence/index.ts b/apps/sim/tools/confluence/index.ts
index 4d6173a30a..97f01e249c 100644
--- a/apps/sim/tools/confluence/index.ts
+++ b/apps/sim/tools/confluence/index.ts
@@ -1,5 +1,5 @@
-import { confluenceRetrieveTool } from './retrieve'
-import { confluenceUpdateTool } from './update'
+import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
+import { confluenceUpdateTool } from '@/tools/confluence/update'
export { confluenceRetrieveTool }
export { confluenceUpdateTool }
diff --git a/apps/sim/tools/confluence/retrieve.ts b/apps/sim/tools/confluence/retrieve.ts
index 8a7ff6939a..192aeaacca 100644
--- a/apps/sim/tools/confluence/retrieve.ts
+++ b/apps/sim/tools/confluence/retrieve.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ConfluenceRetrieveParams, ConfluenceRetrieveResponse } from './types'
+import type { ConfluenceRetrieveParams, ConfluenceRetrieveResponse } from '@/tools/confluence/types'
+import type { ToolConfig } from '@/tools/types'
export const confluenceRetrieveTool: ToolConfig<
ConfluenceRetrieveParams,
diff --git a/apps/sim/tools/confluence/types.ts b/apps/sim/tools/confluence/types.ts
index 4738c8b18e..617e31a473 100644
--- a/apps/sim/tools/confluence/types.ts
+++ b/apps/sim/tools/confluence/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface ConfluenceRetrieveParams {
accessToken: string
@@ -42,3 +42,5 @@ export interface ConfluenceUpdateResponse extends ToolResponse {
success: boolean
}
}
+
+export type ConfluenceResponse = ConfluenceRetrieveResponse | ConfluenceUpdateResponse
diff --git a/apps/sim/tools/confluence/update.ts b/apps/sim/tools/confluence/update.ts
index 14ba0feb1e..4e5e3f95a6 100644
--- a/apps/sim/tools/confluence/update.ts
+++ b/apps/sim/tools/confluence/update.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from './types'
+import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from '@/tools/confluence/types'
+import type { ToolConfig } from '@/tools/types'
export const confluenceUpdateTool: ToolConfig = {
id: 'confluence_update',
diff --git a/apps/sim/tools/discord/get_messages.ts b/apps/sim/tools/discord/get_messages.ts
index 9b2ba0d111..51e81b0bbe 100644
--- a/apps/sim/tools/discord/get_messages.ts
+++ b/apps/sim/tools/discord/get_messages.ts
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetMessagesParams,
DiscordGetMessagesResponse,
DiscordMessage,
-} from './types'
+} from '@/tools/discord/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetMessages')
diff --git a/apps/sim/tools/discord/get_server.ts b/apps/sim/tools/discord/get_server.ts
index 096850ff43..274478ca9c 100644
--- a/apps/sim/tools/discord/get_server.ts
+++ b/apps/sim/tools/discord/get_server.ts
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetServerParams,
DiscordGetServerResponse,
DiscordGuild,
-} from './types'
+} from '@/tools/discord/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetServer')
diff --git a/apps/sim/tools/discord/get_user.ts b/apps/sim/tools/discord/get_user.ts
index b8d9fa144e..917bc6ebb9 100644
--- a/apps/sim/tools/discord/get_user.ts
+++ b/apps/sim/tools/discord/get_user.ts
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetUserParams,
DiscordGetUserResponse,
DiscordUser,
-} from './types'
+} from '@/tools/discord/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetUser')
diff --git a/apps/sim/tools/discord/index.ts b/apps/sim/tools/discord/index.ts
index f0816e2aa1..ec9e992344 100644
--- a/apps/sim/tools/discord/index.ts
+++ b/apps/sim/tools/discord/index.ts
@@ -1,6 +1,6 @@
-import { discordGetMessagesTool } from './get_messages'
-import { discordGetServerTool } from './get_server'
-import { discordGetUserTool } from './get_user'
-import { discordSendMessageTool } from './send_message'
+import { discordGetMessagesTool } from '@/tools/discord/get_messages'
+import { discordGetServerTool } from '@/tools/discord/get_server'
+import { discordGetUserTool } from '@/tools/discord/get_user'
+import { discordSendMessageTool } from '@/tools/discord/send_message'
export { discordSendMessageTool, discordGetMessagesTool, discordGetServerTool, discordGetUserTool }
diff --git a/apps/sim/tools/discord/send_message.ts b/apps/sim/tools/discord/send_message.ts
index 5d27501432..756260f35e 100644
--- a/apps/sim/tools/discord/send_message.ts
+++ b/apps/sim/tools/discord/send_message.ts
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordMessage,
DiscordSendMessageParams,
DiscordSendMessageResponse,
-} from './types'
+} from '@/tools/discord/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordSendMessage')
diff --git a/apps/sim/tools/docs/search.ts b/apps/sim/tools/docs/search.ts
index 1ccbaf95c8..fc94d08ba5 100644
--- a/apps/sim/tools/docs/search.ts
+++ b/apps/sim/tools/docs/search.ts
@@ -1,4 +1,4 @@
-import type { ToolConfig, ToolResponse } from '../types'
+import type { ToolConfig, ToolResponse } from '@/tools/types'
interface DocsSearchParams {
query: string
diff --git a/apps/sim/tools/elevenlabs/index.ts b/apps/sim/tools/elevenlabs/index.ts
index 4469deb7e3..fe92c04d78 100644
--- a/apps/sim/tools/elevenlabs/index.ts
+++ b/apps/sim/tools/elevenlabs/index.ts
@@ -1,3 +1,3 @@
-import { elevenLabsTtsTool } from './tts'
+import { elevenLabsTtsTool } from '@/tools/elevenlabs/tts'
export { elevenLabsTtsTool }
diff --git a/apps/sim/tools/elevenlabs/types.ts b/apps/sim/tools/elevenlabs/types.ts
index 577881ab43..0648ee48cf 100644
--- a/apps/sim/tools/elevenlabs/types.ts
+++ b/apps/sim/tools/elevenlabs/types.ts
@@ -12,3 +12,9 @@ export interface ElevenLabsTtsResponse extends ToolResponse {
audioUrl: string
}
}
+
+export interface ElevenLabsBlockResponse extends ToolResponse {
+ output: {
+ audioUrl: string
+ }
+}
diff --git a/apps/sim/tools/exa/answer.ts b/apps/sim/tools/exa/answer.ts
index c3da9678aa..78876ed28a 100644
--- a/apps/sim/tools/exa/answer.ts
+++ b/apps/sim/tools/exa/answer.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ExaAnswerParams, ExaAnswerResponse } from './types'
+import type { ExaAnswerParams, ExaAnswerResponse } from '@/tools/exa/types'
+import type { ToolConfig } from '@/tools/types'
export const answerTool: ToolConfig = {
id: 'exa_answer',
diff --git a/apps/sim/tools/exa/find_similar_links.ts b/apps/sim/tools/exa/find_similar_links.ts
index e153b64089..45aafe8774 100644
--- a/apps/sim/tools/exa/find_similar_links.ts
+++ b/apps/sim/tools/exa/find_similar_links.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ExaFindSimilarLinksParams, ExaFindSimilarLinksResponse } from './types'
+import type { ExaFindSimilarLinksParams, ExaFindSimilarLinksResponse } from '@/tools/exa/types'
+import type { ToolConfig } from '@/tools/types'
export const findSimilarLinksTool: ToolConfig<
ExaFindSimilarLinksParams,
diff --git a/apps/sim/tools/exa/get_contents.ts b/apps/sim/tools/exa/get_contents.ts
index 84d9002189..67c552dbb1 100644
--- a/apps/sim/tools/exa/get_contents.ts
+++ b/apps/sim/tools/exa/get_contents.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ExaGetContentsParams, ExaGetContentsResponse } from './types'
+import type { ExaGetContentsParams, ExaGetContentsResponse } from '@/tools/exa/types'
+import type { ToolConfig } from '@/tools/types'
export const getContentsTool: ToolConfig = {
id: 'exa_get_contents',
diff --git a/apps/sim/tools/exa/index.ts b/apps/sim/tools/exa/index.ts
index 94b0107d79..b373dd13eb 100644
--- a/apps/sim/tools/exa/index.ts
+++ b/apps/sim/tools/exa/index.ts
@@ -1,9 +1,11 @@
-import { answerTool } from './answer'
-import { findSimilarLinksTool } from './find_similar_links'
-import { getContentsTool } from './get_contents'
-import { searchTool } from './search'
+import { answerTool } from '@/tools/exa/answer'
+import { findSimilarLinksTool } from '@/tools/exa/find_similar_links'
+import { getContentsTool } from '@/tools/exa/get_contents'
+import { researchTool } from '@/tools/exa/research'
+import { searchTool } from '@/tools/exa/search'
export const exaAnswerTool = answerTool
export const exaFindSimilarLinksTool = findSimilarLinksTool
export const exaGetContentsTool = getContentsTool
export const exaSearchTool = searchTool
+export const exaResearchTool = researchTool
diff --git a/apps/sim/tools/exa/research.ts b/apps/sim/tools/exa/research.ts
new file mode 100644
index 0000000000..0ac57b2d25
--- /dev/null
+++ b/apps/sim/tools/exa/research.ts
@@ -0,0 +1,177 @@
+import { createLogger } from '@/lib/logs/console-logger'
+import type { ExaResearchParams, ExaResearchResponse } from '@/tools/exa/types'
+import type { ToolConfig } from '@/tools/types'
+
+const logger = createLogger('ExaResearchTool')
+
+const POLL_INTERVAL_MS = 5000 // 5 seconds between polls
+const MAX_POLL_TIME_MS = 300000 // 5 minutes maximum polling time
+
+export const researchTool: ToolConfig = {
+ id: 'exa_research',
+ name: 'Exa Research',
+ description:
+ 'Perform comprehensive research using AI to generate detailed reports with citations',
+ version: '1.0.0',
+ params: {
+ query: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'Research query or topic',
+ },
+ includeText: {
+ type: 'boolean',
+ required: false,
+ visibility: 'user-only',
+ description: 'Include full text content in results',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Exa AI API Key',
+ },
+ },
+ request: {
+ url: 'https://api.exa.ai/research/v0/tasks',
+ method: 'POST',
+ isInternalRoute: false,
+ headers: (params) => ({
+ 'Content-Type': 'application/json',
+ 'x-api-key': params.apiKey,
+ }),
+ body: (params) => {
+ const body: any = {
+ instructions: params.query,
+ model: 'exa-research',
+ output: {
+ schema: {
+ type: 'object',
+ properties: {
+ results: {
+ type: 'array',
+ items: {
+ type: 'object',
+ properties: {
+ title: { type: 'string' },
+ url: { type: 'string' },
+ summary: { type: 'string' },
+ text: { type: 'string' },
+ publishedDate: { type: 'string' },
+ author: { type: 'string' },
+ score: { type: 'number' },
+ },
+ },
+ },
+ },
+ required: ['results'],
+ },
+ },
+ }
+
+ return body
+ },
+ },
+ transformResponse: async (response: Response) => {
+ const data = await response.json()
+
+ if (!response.ok) {
+ throw new Error(data.message || data.error || 'Failed to create research task')
+ }
+
+ return {
+ success: true,
+ output: {
+ taskId: data.id,
+ research: [],
+ },
+ }
+ },
+ postProcess: async (result, params) => {
+ if (!result.success) {
+ return result
+ }
+
+ const taskId = result.output.taskId
+ logger.info(`Exa research task ${taskId} created, polling for completion...`)
+
+ let elapsedTime = 0
+
+ while (elapsedTime < MAX_POLL_TIME_MS) {
+ try {
+ const statusResponse = await fetch(`https://api.exa.ai/research/v0/tasks/${taskId}`, {
+ method: 'GET',
+ headers: {
+ 'x-api-key': params.apiKey,
+ },
+ })
+
+ if (!statusResponse.ok) {
+ throw new Error(`Failed to get task status: ${statusResponse.statusText}`)
+ }
+
+ const taskData = await statusResponse.json()
+ logger.info(`Exa research task ${taskId} status: ${taskData.status}`)
+
+ if (taskData.status === 'completed') {
+ result.output = {
+ research: taskData.data?.results || [
+ {
+ title: 'Research Complete',
+ url: '',
+ summary: taskData.data || 'Research completed successfully',
+ text: undefined,
+ publishedDate: undefined,
+ author: undefined,
+ score: 1.0,
+ },
+ ],
+ }
+ return result
+ }
+
+ if (taskData.status === 'failed') {
+ return {
+ ...result,
+ success: false,
+ error: `Research task failed: ${taskData.error || 'Unknown error'}`,
+ }
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
+ elapsedTime += POLL_INTERVAL_MS
+ } catch (error: any) {
+ logger.error('Error polling for research task status:', {
+ message: error.message || 'Unknown error',
+ taskId,
+ })
+
+ return {
+ ...result,
+ success: false,
+ error: `Error polling for research task status: ${error.message || 'Unknown error'}`,
+ }
+ }
+ }
+
+ logger.warn(
+ `Research task ${taskId} did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`
+ )
+ return {
+ ...result,
+ success: false,
+ error: `Research task did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`,
+ }
+ },
+ transformError: (error) => {
+ const errorMessage = error?.message || ''
+ if (errorMessage.includes('401')) {
+ return new Error('Invalid API key. Please check your Exa AI API key.')
+ }
+ if (errorMessage.includes('429')) {
+ return new Error('Rate limit exceeded. Please try again later.')
+ }
+ return error
+ },
+}
diff --git a/apps/sim/tools/exa/search.ts b/apps/sim/tools/exa/search.ts
index d152ef6421..76fa3f5e1a 100644
--- a/apps/sim/tools/exa/search.ts
+++ b/apps/sim/tools/exa/search.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ExaSearchParams, ExaSearchResponse } from './types'
+import type { ExaSearchParams, ExaSearchResponse } from '@/tools/exa/types'
+import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig = {
id: 'exa_search',
diff --git a/apps/sim/tools/exa/types.ts b/apps/sim/tools/exa/types.ts
index ee25d55581..09106457e8 100644
--- a/apps/sim/tools/exa/types.ts
+++ b/apps/sim/tools/exa/types.ts
@@ -1,5 +1,5 @@
// Common types for Exa AI tools
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
// Common parameters for all Exa AI tools
export interface ExaBaseParams {
@@ -88,3 +88,31 @@ export interface ExaAnswerResponse extends ToolResponse {
}[]
}
}
+
+// Research tool types
+export interface ExaResearchParams extends ExaBaseParams {
+ query: string
+ includeText?: boolean
+}
+
+export interface ExaResearchResponse extends ToolResponse {
+ output: {
+ taskId?: string
+ research: {
+ title: string
+ url: string
+ summary: string
+ text?: string
+ publishedDate?: string
+ author?: string
+ score: number
+ }[]
+ }
+}
+
+export type ExaResponse =
+ | ExaSearchResponse
+ | ExaGetContentsResponse
+ | ExaFindSimilarLinksResponse
+ | ExaAnswerResponse
+ | ExaResearchResponse
diff --git a/apps/sim/tools/file/index.ts b/apps/sim/tools/file/index.ts
index 855c19b501..d6b6372e0e 100644
--- a/apps/sim/tools/file/index.ts
+++ b/apps/sim/tools/file/index.ts
@@ -1,3 +1,3 @@
-import { fileParserTool } from './parser'
+import { fileParserTool } from '@/tools/file/parser'
export const fileParseTool = fileParserTool
diff --git a/apps/sim/tools/file/parser.ts b/apps/sim/tools/file/parser.ts
index a673f0c805..e3be52e3c4 100644
--- a/apps/sim/tools/file/parser.ts
+++ b/apps/sim/tools/file/parser.ts
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
import type {
FileParseResult,
FileParserInput,
FileParserOutput,
FileParserOutputData,
-} from './types'
+} from '@/tools/file/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('FileParserTool')
diff --git a/apps/sim/tools/file/types.ts b/apps/sim/tools/file/types.ts
index 3f060379f4..e411d56115 100644
--- a/apps/sim/tools/file/types.ts
+++ b/apps/sim/tools/file/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface FileParserInput {
filePath: string | string[]
diff --git a/apps/sim/tools/firecrawl/crawl.ts b/apps/sim/tools/firecrawl/crawl.ts
new file mode 100644
index 0000000000..c0d4a684ed
--- /dev/null
+++ b/apps/sim/tools/firecrawl/crawl.ts
@@ -0,0 +1,156 @@
+import { createLogger } from '@/lib/logs/console-logger'
+import type { FirecrawlCrawlParams, FirecrawlCrawlResponse } from '@/tools/firecrawl/types'
+import type { ToolConfig } from '@/tools/types'
+
+const logger = createLogger('FirecrawlCrawlTool')
+
+const POLL_INTERVAL_MS = 5000 // 5 seconds between polls
+const MAX_POLL_TIME_MS = 300000 // 5 minutes maximum polling time
+
+export const crawlTool: ToolConfig = {
+ id: 'firecrawl_crawl',
+ name: 'Firecrawl Crawl',
+ description: 'Crawl entire websites and extract structured content from all accessible pages',
+ version: '1.0.0',
+ params: {
+ url: {
+ type: 'string',
+ required: true,
+ visibility: 'user-or-llm',
+ description: 'The website URL to crawl',
+ },
+ limit: {
+ type: 'number',
+ required: false,
+ visibility: 'user-only',
+ description: 'Maximum number of pages to crawl (default: 100)',
+ },
+ onlyMainContent: {
+ type: 'boolean',
+ required: false,
+ visibility: 'user-only',
+ description: 'Extract only main content from pages',
+ },
+ apiKey: {
+ type: 'string',
+ required: true,
+ visibility: 'user-only',
+ description: 'Firecrawl API Key',
+ },
+ },
+ request: {
+ url: 'https://api.firecrawl.dev/v1/crawl',
+ method: 'POST',
+ isInternalRoute: false,
+ headers: (params) => ({
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${params.apiKey}`,
+ }),
+ body: (params) => ({
+ url: params.url,
+ limit: Number(params.limit) || 100,
+ scrapeOptions: {
+ formats: ['markdown'],
+ onlyMainContent: params.onlyMainContent || false,
+ },
+ }),
+ },
+ transformResponse: async (response: Response) => {
+ const data = await response.json()
+
+ if (!response.ok) {
+ throw new Error(data.error || data.message || 'Failed to create crawl job')
+ }
+
+ return {
+ success: true,
+ output: {
+ jobId: data.jobId || data.id,
+ pages: [],
+ total: 0,
+ creditsUsed: 0,
+ },
+ }
+ },
+ postProcess: async (result, params) => {
+ if (!result.success) {
+ return result
+ }
+
+ const jobId = result.output.jobId
+ logger.info(`Firecrawl crawl job ${jobId} created, polling for completion...`)
+
+ let elapsedTime = 0
+
+ while (elapsedTime < MAX_POLL_TIME_MS) {
+ try {
+ const statusResponse = await fetch(`/api/tools/firecrawl/crawl/${jobId}`, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${params.apiKey}`,
+ },
+ })
+
+ if (!statusResponse.ok) {
+ throw new Error(`Failed to get crawl status: ${statusResponse.statusText}`)
+ }
+
+ const crawlData = await statusResponse.json()
+ logger.info(`Firecrawl crawl job ${jobId} status: ${crawlData.status}`)
+
+ if (crawlData.status === 'completed') {
+ result.output = {
+ pages: crawlData.data || [],
+ total: crawlData.total || 0,
+ creditsUsed: crawlData.creditsUsed || 0,
+ }
+ return result
+ }
+
+ if (crawlData.status === 'failed') {
+ return {
+ ...result,
+ success: false,
+ error: `Crawl job failed: ${crawlData.error || 'Unknown error'}`,
+ }
+ }
+
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
+ elapsedTime += POLL_INTERVAL_MS
+ } catch (error: any) {
+ logger.error('Error polling for crawl job status:', {
+ message: error.message || 'Unknown error',
+ jobId,
+ })
+
+ return {
+ ...result,
+ success: false,
+ error: `Error polling for crawl job status: ${error.message || 'Unknown error'}`,
+ }
+ }
+ }
+
+ logger.warn(
+ `Crawl job ${jobId} did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`
+ )
+ return {
+ ...result,
+ success: false,
+ error: `Crawl job did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`,
+ }
+ },
+ transformError: (error) => {
+ const errorMessage = error?.message || ''
+ if (errorMessage.includes('401')) {
+ return new Error('Invalid API key. Please check your Firecrawl API key.')
+ }
+ if (errorMessage.includes('429')) {
+ return new Error('Rate limit exceeded. Please try again later.')
+ }
+ if (errorMessage.includes('402')) {
+ return new Error('Insufficient credits. Please check your Firecrawl account.')
+ }
+ return error
+ },
+}
diff --git a/apps/sim/tools/firecrawl/index.ts b/apps/sim/tools/firecrawl/index.ts
index a8a60b7ff3..c7c173f34d 100644
--- a/apps/sim/tools/firecrawl/index.ts
+++ b/apps/sim/tools/firecrawl/index.ts
@@ -1,4 +1,5 @@
-import { scrapeTool } from './scrape'
-import { searchTool } from './search'
+import { crawlTool } from '@/tools/firecrawl/crawl'
+import { scrapeTool } from '@/tools/firecrawl/scrape'
+import { searchTool } from '@/tools/firecrawl/search'
-export { scrapeTool, searchTool }
+export { scrapeTool, searchTool, crawlTool }
diff --git a/apps/sim/tools/firecrawl/scrape.ts b/apps/sim/tools/firecrawl/scrape.ts
index 04392b5a0e..7d8496ba6a 100644
--- a/apps/sim/tools/firecrawl/scrape.ts
+++ b/apps/sim/tools/firecrawl/scrape.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { ScrapeParams, ScrapeResponse } from './types'
+import type { ScrapeParams, ScrapeResponse } from '@/tools/firecrawl/types'
+import type { ToolConfig } from '@/tools/types'
export const scrapeTool: ToolConfig = {
id: 'firecrawl_scrape',
diff --git a/apps/sim/tools/firecrawl/search.ts b/apps/sim/tools/firecrawl/search.ts
index a00aeefb3f..cb5e47b0bd 100644
--- a/apps/sim/tools/firecrawl/search.ts
+++ b/apps/sim/tools/firecrawl/search.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { SearchParams, SearchResponse } from './types'
+import type { SearchParams, SearchResponse } from '@/tools/firecrawl/types'
+import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig = {
id: 'firecrawl_search',
diff --git a/apps/sim/tools/firecrawl/types.ts b/apps/sim/tools/firecrawl/types.ts
index 3cb5c579ec..d61a268770 100644
--- a/apps/sim/tools/firecrawl/types.ts
+++ b/apps/sim/tools/firecrawl/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface ScrapeParams {
apiKey: string
@@ -14,6 +14,13 @@ export interface SearchParams {
query: string
}
+export interface FirecrawlCrawlParams {
+ apiKey: string
+ url: string
+ limit?: number
+ onlyMainContent?: boolean
+}
+
export interface ScrapeResponse extends ToolResponse {
output: {
markdown: string
@@ -58,3 +65,24 @@ export interface SearchResponse extends ToolResponse {
warning?: string
}
}
+
+export interface FirecrawlCrawlResponse extends ToolResponse {
+ output: {
+ jobId?: string
+ pages: Array<{
+ markdown: string
+ html?: string
+ metadata: {
+ title: string
+ description: string
+ language: string
+ sourceURL: string
+ statusCode: number
+ }
+ }>
+ total: number
+ creditsUsed: number
+ }
+}
+
+export type FirecrawlResponse = ScrapeResponse | SearchResponse | FirecrawlCrawlResponse
diff --git a/apps/sim/tools/function/execute.test.ts b/apps/sim/tools/function/execute.test.ts
index 7318e74818..333086a37d 100644
--- a/apps/sim/tools/function/execute.test.ts
+++ b/apps/sim/tools/function/execute.test.ts
@@ -7,8 +7,8 @@
* which runs JavaScript code in a secure sandbox.
*/
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
-import { ToolTester } from '../__test-utils__/test-tools'
-import { functionExecuteTool } from './execute'
+import { ToolTester } from '@/tools/__test-utils__/test-tools'
+import { functionExecuteTool } from '@/tools/function/execute'
describe('Function Execute Tool', () => {
let tester: ToolTester
diff --git a/apps/sim/tools/function/execute.ts b/apps/sim/tools/function/execute.ts
index 8b405e0b82..b80282e61f 100644
--- a/apps/sim/tools/function/execute.ts
+++ b/apps/sim/tools/function/execute.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { CodeExecutionInput, CodeExecutionOutput } from './types'
+import type { CodeExecutionInput, CodeExecutionOutput } from '@/tools/function/types'
+import type { ToolConfig } from '@/tools/types'
const DEFAULT_TIMEOUT = 10000 // 10 seconds
diff --git a/apps/sim/tools/function/index.ts b/apps/sim/tools/function/index.ts
index 9dc7bcbd95..3ba0a04c77 100644
--- a/apps/sim/tools/function/index.ts
+++ b/apps/sim/tools/function/index.ts
@@ -1,3 +1,3 @@
-import { functionExecuteTool } from './execute'
+import { functionExecuteTool } from '@/tools/function/execute'
export { functionExecuteTool }
diff --git a/apps/sim/tools/function/types.ts b/apps/sim/tools/function/types.ts
index 06e673d5ee..70d3ef6257 100644
--- a/apps/sim/tools/function/types.ts
+++ b/apps/sim/tools/function/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface CodeExecutionInput {
code: Array<{ content: string; id: string }> | string
diff --git a/apps/sim/tools/github/comment.ts b/apps/sim/tools/github/comment.ts
index 9128ddd2cc..2dbe2e3697 100644
--- a/apps/sim/tools/github/comment.ts
+++ b/apps/sim/tools/github/comment.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { CreateCommentParams, CreateCommentResponse } from './types'
+import type { CreateCommentParams, CreateCommentResponse } from '@/tools/github/types'
+import type { ToolConfig } from '@/tools/types'
export const commentTool: ToolConfig = {
id: 'github_comment',
diff --git a/apps/sim/tools/github/index.ts b/apps/sim/tools/github/index.ts
index 9cfe81dfbf..703c8ec6d0 100644
--- a/apps/sim/tools/github/index.ts
+++ b/apps/sim/tools/github/index.ts
@@ -1,7 +1,7 @@
-import { commentTool } from './comment'
-import { latestCommitTool } from './latest_commit'
-import { prTool } from './pr'
-import { repoInfoTool } from './repo_info'
+import { commentTool } from '@/tools/github/comment'
+import { latestCommitTool } from '@/tools/github/latest_commit'
+import { prTool } from '@/tools/github/pr'
+import { repoInfoTool } from '@/tools/github/repo_info'
export const githubCommentTool = commentTool
export const githubLatestCommitTool = latestCommitTool
diff --git a/apps/sim/tools/github/latest_commit.ts b/apps/sim/tools/github/latest_commit.ts
index 356f394055..bec7b0ded1 100644
--- a/apps/sim/tools/github/latest_commit.ts
+++ b/apps/sim/tools/github/latest_commit.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { LatestCommitParams, LatestCommitResponse } from './types'
+import type { LatestCommitParams, LatestCommitResponse } from '@/tools/github/types'
+import type { ToolConfig } from '@/tools/types'
export const latestCommitTool: ToolConfig = {
id: 'github_latest_commit',
diff --git a/apps/sim/tools/github/pr.ts b/apps/sim/tools/github/pr.ts
index 62b23f8000..8bd884b1be 100644
--- a/apps/sim/tools/github/pr.ts
+++ b/apps/sim/tools/github/pr.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { PROperationParams, PullRequestResponse } from './types'
+import type { PROperationParams, PullRequestResponse } from '@/tools/github/types'
+import type { ToolConfig } from '@/tools/types'
export const prTool: ToolConfig = {
id: 'github_pr',
diff --git a/apps/sim/tools/github/repo_info.ts b/apps/sim/tools/github/repo_info.ts
index af8e5c4cdd..b52dd8017a 100644
--- a/apps/sim/tools/github/repo_info.ts
+++ b/apps/sim/tools/github/repo_info.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { BaseGitHubParams, RepoInfoResponse } from './types'
+import type { BaseGitHubParams, RepoInfoResponse } from '@/tools/github/types'
+import type { ToolConfig } from '@/tools/types'
export const repoInfoTool: ToolConfig = {
id: 'github_repo_info',
diff --git a/apps/sim/tools/github/types.ts b/apps/sim/tools/github/types.ts
index 27eb3c2e8a..7ed2bcb4b8 100644
--- a/apps/sim/tools/github/types.ts
+++ b/apps/sim/tools/github/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
// Base parameters shared by all GitHub operations
export interface BaseGitHubParams {
@@ -147,3 +147,9 @@ export interface RepoInfoResponse extends ToolResponse {
metadata: RepoMetadata
}
}
+
+export type GitHubResponse =
+ | PullRequestResponse
+ | CreateCommentResponse
+ | LatestCommitResponse
+ | RepoInfoResponse
diff --git a/apps/sim/tools/gmail/draft.ts b/apps/sim/tools/gmail/draft.ts
index e16117fb34..70c8bd0531 100644
--- a/apps/sim/tools/gmail/draft.ts
+++ b/apps/sim/tools/gmail/draft.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GmailSendParams, GmailToolResponse } from './types'
+import type { GmailSendParams, GmailToolResponse } from '@/tools/gmail/types'
+import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
diff --git a/apps/sim/tools/gmail/index.ts b/apps/sim/tools/gmail/index.ts
index 3558d482c2..110147cad9 100644
--- a/apps/sim/tools/gmail/index.ts
+++ b/apps/sim/tools/gmail/index.ts
@@ -1,6 +1,6 @@
-import { gmailDraftTool } from './draft'
-import { gmailReadTool } from './read'
-import { gmailSearchTool } from './search'
-import { gmailSendTool } from './send'
+import { gmailDraftTool } from '@/tools/gmail/draft'
+import { gmailReadTool } from '@/tools/gmail/read'
+import { gmailSearchTool } from '@/tools/gmail/search'
+import { gmailSendTool } from '@/tools/gmail/send'
export { gmailSendTool, gmailReadTool, gmailSearchTool, gmailDraftTool }
diff --git a/apps/sim/tools/gmail/read.ts b/apps/sim/tools/gmail/read.ts
index 0633610623..c2494a631a 100644
--- a/apps/sim/tools/gmail/read.ts
+++ b/apps/sim/tools/gmail/read.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GmailMessage, GmailReadParams, GmailToolResponse } from './types'
+import type { GmailMessage, GmailReadParams, GmailToolResponse } from '@/tools/gmail/types'
+import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
diff --git a/apps/sim/tools/gmail/search.ts b/apps/sim/tools/gmail/search.ts
index d80dee57dd..78c0130b91 100644
--- a/apps/sim/tools/gmail/search.ts
+++ b/apps/sim/tools/gmail/search.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GmailSearchParams, GmailToolResponse } from './types'
+import type { GmailSearchParams, GmailToolResponse } from '@/tools/gmail/types'
+import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
diff --git a/apps/sim/tools/gmail/send.ts b/apps/sim/tools/gmail/send.ts
index 498f82d627..ed18f9ecbf 100644
--- a/apps/sim/tools/gmail/send.ts
+++ b/apps/sim/tools/gmail/send.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GmailSendParams, GmailToolResponse } from './types'
+import type { GmailSendParams, GmailToolResponse } from '@/tools/gmail/types'
+import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
diff --git a/apps/sim/tools/gmail/types.ts b/apps/sim/tools/gmail/types.ts
index f811d5a4dd..9449701071 100644
--- a/apps/sim/tools/gmail/types.ts
+++ b/apps/sim/tools/gmail/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
// Base parameters shared by all operations
interface BaseGmailParams {
diff --git a/apps/sim/tools/google/index.ts b/apps/sim/tools/google/index.ts
index 93b47653ad..baa2a16f5a 100644
--- a/apps/sim/tools/google/index.ts
+++ b/apps/sim/tools/google/index.ts
@@ -1,3 +1,3 @@
-import { searchTool } from './search'
+import { searchTool } from '@/tools/google/search'
export { searchTool }
diff --git a/apps/sim/tools/google/search.ts b/apps/sim/tools/google/search.ts
index 772cf25e68..7a870df840 100644
--- a/apps/sim/tools/google/search.ts
+++ b/apps/sim/tools/google/search.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleSearchParams, GoogleSearchResponse } from './types'
+import type { GoogleSearchParams, GoogleSearchResponse } from '@/tools/google/types'
+import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig = {
id: 'google_search',
diff --git a/apps/sim/tools/google/types.ts b/apps/sim/tools/google/types.ts
index 624b5c499e..1f44f82e42 100644
--- a/apps/sim/tools/google/types.ts
+++ b/apps/sim/tools/google/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface GoogleSearchParams {
query: string
diff --git a/apps/sim/tools/google_calendar/create.ts b/apps/sim/tools/google_calendar/create.ts
index b36eff211b..fe1f7d09d4 100644
--- a/apps/sim/tools/google_calendar/create.ts
+++ b/apps/sim/tools/google_calendar/create.ts
@@ -1,11 +1,11 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarCreateParams,
type GoogleCalendarCreateResponse,
type GoogleCalendarEventRequestBody,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const createTool: ToolConfig = {
id: 'google_calendar_create',
diff --git a/apps/sim/tools/google_calendar/get.ts b/apps/sim/tools/google_calendar/get.ts
index 62070bf1ed..da02234348 100644
--- a/apps/sim/tools/google_calendar/get.ts
+++ b/apps/sim/tools/google_calendar/get.ts
@@ -1,10 +1,10 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarGetParams,
type GoogleCalendarGetResponse,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const getTool: ToolConfig = {
id: 'google_calendar_get',
diff --git a/apps/sim/tools/google_calendar/index.ts b/apps/sim/tools/google_calendar/index.ts
index b6564bff0d..2cc7ab2a81 100644
--- a/apps/sim/tools/google_calendar/index.ts
+++ b/apps/sim/tools/google_calendar/index.ts
@@ -1,8 +1,8 @@
-import { createTool } from './create'
-import { getTool } from './get'
-import { inviteTool } from './invite'
-import { listTool } from './list'
-import { quickAddTool } from './quick_add'
+import { createTool } from '@/tools/google_calendar/create'
+import { getTool } from '@/tools/google_calendar/get'
+import { inviteTool } from '@/tools/google_calendar/invite'
+import { listTool } from '@/tools/google_calendar/list'
+import { quickAddTool } from '@/tools/google_calendar/quick_add'
export const googleCalendarCreateTool = createTool
export const googleCalendarGetTool = getTool
diff --git a/apps/sim/tools/google_calendar/invite.ts b/apps/sim/tools/google_calendar/invite.ts
index baaec7bb9b..08891c531a 100644
--- a/apps/sim/tools/google_calendar/invite.ts
+++ b/apps/sim/tools/google_calendar/invite.ts
@@ -1,9 +1,9 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarInviteParams,
type GoogleCalendarInviteResponse,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const inviteTool: ToolConfig = {
id: 'google_calendar_invite',
diff --git a/apps/sim/tools/google_calendar/list.ts b/apps/sim/tools/google_calendar/list.ts
index d454098f82..c4093efba3 100644
--- a/apps/sim/tools/google_calendar/list.ts
+++ b/apps/sim/tools/google_calendar/list.ts
@@ -1,11 +1,11 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarApiListResponse,
type GoogleCalendarListParams,
type GoogleCalendarListResponse,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const listTool: ToolConfig = {
id: 'google_calendar_list',
diff --git a/apps/sim/tools/google_calendar/quick_add.ts b/apps/sim/tools/google_calendar/quick_add.ts
index 46ec3de2b6..fa2c62beda 100644
--- a/apps/sim/tools/google_calendar/quick_add.ts
+++ b/apps/sim/tools/google_calendar/quick_add.ts
@@ -1,9 +1,9 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarQuickAddParams,
type GoogleCalendarQuickAddResponse,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const quickAddTool: ToolConfig<
GoogleCalendarQuickAddParams,
diff --git a/apps/sim/tools/google_calendar/types.ts b/apps/sim/tools/google_calendar/types.ts
index c5687c2fec..c6e5352597 100644
--- a/apps/sim/tools/google_calendar/types.ts
+++ b/apps/sim/tools/google_calendar/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3'
@@ -276,3 +276,11 @@ export interface GoogleCalendarApiListResponse {
nextSyncToken?: string
items: GoogleCalendarApiEventResponse[]
}
+
+export type GoogleCalendarResponse =
+ | GoogleCalendarCreateResponse
+ | GoogleCalendarListResponse
+ | GoogleCalendarGetResponse
+ | GoogleCalendarQuickAddResponse
+ | GoogleCalendarInviteResponse
+ | GoogleCalendarUpdateResponse
diff --git a/apps/sim/tools/google_calendar/update.ts b/apps/sim/tools/google_calendar/update.ts
index 4acf35ded8..ea2a8bd3b5 100644
--- a/apps/sim/tools/google_calendar/update.ts
+++ b/apps/sim/tools/google_calendar/update.ts
@@ -1,9 +1,9 @@
-import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarToolResponse,
type GoogleCalendarUpdateParams,
-} from './types'
+} from '@/tools/google_calendar/types'
+import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig = {
id: 'google_calendar_update',
diff --git a/apps/sim/tools/google_docs/create.ts b/apps/sim/tools/google_docs/create.ts
index 4d2eef2c48..fb6c4f3b25 100644
--- a/apps/sim/tools/google_docs/create.ts
+++ b/apps/sim/tools/google_docs/create.ts
@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
-import type { GoogleDocsCreateResponse, GoogleDocsToolParams } from './types'
+import type { GoogleDocsCreateResponse, GoogleDocsToolParams } from '@/tools/google_docs/types'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDocsCreateTool')
diff --git a/apps/sim/tools/google_docs/index.ts b/apps/sim/tools/google_docs/index.ts
index 9368fe7205..4b13d9b278 100644
--- a/apps/sim/tools/google_docs/index.ts
+++ b/apps/sim/tools/google_docs/index.ts
@@ -1,6 +1,6 @@
-import { createTool } from './create'
-import { readTool } from './read'
-import { writeTool } from './write'
+import { createTool } from '@/tools/google_docs/create'
+import { readTool } from '@/tools/google_docs/read'
+import { writeTool } from '@/tools/google_docs/write'
export const googleDocsReadTool = readTool
export const googleDocsWriteTool = writeTool
diff --git a/apps/sim/tools/google_docs/read.ts b/apps/sim/tools/google_docs/read.ts
index 3bb76d0372..02bfbc3d95 100644
--- a/apps/sim/tools/google_docs/read.ts
+++ b/apps/sim/tools/google_docs/read.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleDocsReadResponse, GoogleDocsToolParams } from './types'
+import type { GoogleDocsReadResponse, GoogleDocsToolParams } from '@/tools/google_docs/types'
+import type { ToolConfig } from '@/tools/types'
export const readTool: ToolConfig = {
id: 'google_docs_read',
diff --git a/apps/sim/tools/google_docs/types.ts b/apps/sim/tools/google_docs/types.ts
index aab22906be..9d0aa201ab 100644
--- a/apps/sim/tools/google_docs/types.ts
+++ b/apps/sim/tools/google_docs/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface GoogleDocsMetadata {
documentId: string
@@ -38,3 +38,8 @@ export interface GoogleDocsToolParams {
folderId?: string
folderSelector?: string
}
+
+export type GoogleDocsResponse =
+ | GoogleDocsReadResponse
+ | GoogleDocsWriteResponse
+ | GoogleDocsCreateResponse
diff --git a/apps/sim/tools/google_docs/write.ts b/apps/sim/tools/google_docs/write.ts
index 0adfca4e18..33fa654b2b 100644
--- a/apps/sim/tools/google_docs/write.ts
+++ b/apps/sim/tools/google_docs/write.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleDocsToolParams, GoogleDocsWriteResponse } from './types'
+import type { GoogleDocsToolParams, GoogleDocsWriteResponse } from '@/tools/google_docs/types'
+import type { ToolConfig } from '@/tools/types'
export const writeTool: ToolConfig = {
id: 'google_docs_write',
diff --git a/apps/sim/tools/google_drive/create_folder.ts b/apps/sim/tools/google_drive/create_folder.ts
index da001436c8..fca7624cea 100644
--- a/apps/sim/tools/google_drive/create_folder.ts
+++ b/apps/sim/tools/google_drive/create_folder.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
+import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
+import type { ToolConfig } from '@/tools/types'
export const createFolderTool: ToolConfig = {
id: 'google_drive_create_folder',
diff --git a/apps/sim/tools/google_drive/get_content.ts b/apps/sim/tools/google_drive/get_content.ts
index 21d5cbec34..50af9a182b 100644
--- a/apps/sim/tools/google_drive/get_content.ts
+++ b/apps/sim/tools/google_drive/get_content.ts
@@ -1,7 +1,10 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
-import type { GoogleDriveGetContentResponse, GoogleDriveToolParams } from './types'
-import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from './utils'
+import type {
+ GoogleDriveGetContentResponse,
+ GoogleDriveToolParams,
+} from '@/tools/google_drive/types'
+import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from '@/tools/google_drive/utils'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDriveGetContentTool')
diff --git a/apps/sim/tools/google_drive/index.ts b/apps/sim/tools/google_drive/index.ts
index e6a3652243..634223ccd5 100644
--- a/apps/sim/tools/google_drive/index.ts
+++ b/apps/sim/tools/google_drive/index.ts
@@ -1,7 +1,7 @@
-import { createFolderTool } from './create_folder'
-import { getContentTool } from './get_content'
-import { listTool } from './list'
-import { uploadTool } from './upload'
+import { createFolderTool } from '@/tools/google_drive/create_folder'
+import { getContentTool } from '@/tools/google_drive/get_content'
+import { listTool } from '@/tools/google_drive/list'
+import { uploadTool } from '@/tools/google_drive/upload'
export const googleDriveCreateFolderTool = createFolderTool
export const googleDriveGetContentTool = getContentTool
diff --git a/apps/sim/tools/google_drive/list.ts b/apps/sim/tools/google_drive/list.ts
index d2377ced9c..70a138a970 100644
--- a/apps/sim/tools/google_drive/list.ts
+++ b/apps/sim/tools/google_drive/list.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleDriveListResponse, GoogleDriveToolParams } from './types'
+import type { GoogleDriveListResponse, GoogleDriveToolParams } from '@/tools/google_drive/types'
+import type { ToolConfig } from '@/tools/types'
export const listTool: ToolConfig = {
id: 'google_drive_list',
diff --git a/apps/sim/tools/google_drive/types.ts b/apps/sim/tools/google_drive/types.ts
index 7973ff11f8..e497781a06 100644
--- a/apps/sim/tools/google_drive/types.ts
+++ b/apps/sim/tools/google_drive/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface GoogleDriveFile {
id: string
@@ -45,3 +45,8 @@ export interface GoogleDriveToolParams {
pageToken?: string
exportMimeType?: string
}
+
+export type GoogleDriveResponse =
+ | GoogleDriveUploadResponse
+ | GoogleDriveGetContentResponse
+ | GoogleDriveListResponse
diff --git a/apps/sim/tools/google_drive/upload.ts b/apps/sim/tools/google_drive/upload.ts
index b69b9f00a9..2a5e5562ef 100644
--- a/apps/sim/tools/google_drive/upload.ts
+++ b/apps/sim/tools/google_drive/upload.ts
@@ -1,7 +1,7 @@
import { createLogger } from '@/lib/logs/console-logger'
-import type { ToolConfig } from '../types'
-import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
-import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from './utils'
+import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
+import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from '@/tools/google_drive/utils'
+import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDriveUploadTool')
diff --git a/apps/sim/tools/google_sheets/append.ts b/apps/sim/tools/google_sheets/append.ts
index 532f3d3762..3a962d340c 100644
--- a/apps/sim/tools/google_sheets/append.ts
+++ b/apps/sim/tools/google_sheets/append.ts
@@ -1,5 +1,8 @@
-import type { ToolConfig } from '../types'
-import type { GoogleSheetsAppendResponse, GoogleSheetsToolParams } from './types'
+import type {
+ GoogleSheetsAppendResponse,
+ GoogleSheetsToolParams,
+} from '@/tools/google_sheets/types'
+import type { ToolConfig } from '@/tools/types'
export const appendTool: ToolConfig = {
id: 'google_sheets_append',
diff --git a/apps/sim/tools/google_sheets/index.ts b/apps/sim/tools/google_sheets/index.ts
index e751103f56..a47ebe4c94 100644
--- a/apps/sim/tools/google_sheets/index.ts
+++ b/apps/sim/tools/google_sheets/index.ts
@@ -1,7 +1,7 @@
-import { appendTool } from './append'
-import { readTool } from './read'
-import { updateTool } from './update'
-import { writeTool } from './write'
+import { appendTool } from '@/tools/google_sheets/append'
+import { readTool } from '@/tools/google_sheets/read'
+import { updateTool } from '@/tools/google_sheets/update'
+import { writeTool } from '@/tools/google_sheets/write'
export const googleSheetsReadTool = readTool
export const googleSheetsWriteTool = writeTool
diff --git a/apps/sim/tools/google_sheets/read.ts b/apps/sim/tools/google_sheets/read.ts
index 52d834ee24..4858391e58 100644
--- a/apps/sim/tools/google_sheets/read.ts
+++ b/apps/sim/tools/google_sheets/read.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleSheetsReadResponse, GoogleSheetsToolParams } from './types'
+import type { GoogleSheetsReadResponse, GoogleSheetsToolParams } from '@/tools/google_sheets/types'
+import type { ToolConfig } from '@/tools/types'
export const readTool: ToolConfig = {
id: 'google_sheets_read',
diff --git a/apps/sim/tools/google_sheets/types.ts b/apps/sim/tools/google_sheets/types.ts
index 5a16355710..2efc612dea 100644
--- a/apps/sim/tools/google_sheets/types.ts
+++ b/apps/sim/tools/google_sheets/types.ts
@@ -1,4 +1,4 @@
-import type { ToolResponse } from '../types'
+import type { ToolResponse } from '@/tools/types'
export interface GoogleSheetsRange {
sheetId?: number
@@ -69,3 +69,9 @@ export interface GoogleSheetsToolParams {
responseValueRenderOption?: 'FORMATTED_VALUE' | 'UNFORMATTED_VALUE' | 'FORMULA'
majorDimension?: 'ROWS' | 'COLUMNS'
}
+
+export type GoogleSheetsResponse =
+ | GoogleSheetsReadResponse
+ | GoogleSheetsWriteResponse
+ | GoogleSheetsUpdateResponse
+ | GoogleSheetsAppendResponse
diff --git a/apps/sim/tools/google_sheets/update.ts b/apps/sim/tools/google_sheets/update.ts
index 798c600fdb..bc706f0b8c 100644
--- a/apps/sim/tools/google_sheets/update.ts
+++ b/apps/sim/tools/google_sheets/update.ts
@@ -1,5 +1,8 @@
-import type { ToolConfig } from '../types'
-import type { GoogleSheetsToolParams, GoogleSheetsUpdateResponse } from './types'
+import type {
+ GoogleSheetsToolParams,
+ GoogleSheetsUpdateResponse,
+} from '@/tools/google_sheets/types'
+import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig = {
id: 'google_sheets_update',
diff --git a/apps/sim/tools/google_sheets/write.ts b/apps/sim/tools/google_sheets/write.ts
index 3c6e02c56a..ec07aeb5b6 100644
--- a/apps/sim/tools/google_sheets/write.ts
+++ b/apps/sim/tools/google_sheets/write.ts
@@ -1,5 +1,5 @@
-import type { ToolConfig } from '../types'
-import type { GoogleSheetsToolParams, GoogleSheetsWriteResponse } from './types'
+import type { GoogleSheetsToolParams, GoogleSheetsWriteResponse } from '@/tools/google_sheets/types'
+import type { ToolConfig } from '@/tools/types'
export const writeTool: ToolConfig = {
id: 'google_sheets_write',
diff --git a/apps/sim/tools/http/index.ts b/apps/sim/tools/http/index.ts
index c3c680334c..aa60451376 100644
--- a/apps/sim/tools/http/index.ts
+++ b/apps/sim/tools/http/index.ts
@@ -1,3 +1,3 @@
-import { requestTool } from './request'
+import { requestTool } from '@/tools/http/request'
export { requestTool }
diff --git a/apps/sim/tools/http/request.test.ts b/apps/sim/tools/http/request.test.ts
index ef9ce00f5c..7cf77704d2 100644
--- a/apps/sim/tools/http/request.test.ts
+++ b/apps/sim/tools/http/request.test.ts
@@ -7,9 +7,9 @@
* to make HTTP requests to external APIs and services.
*/
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
-import { mockHttpResponses } from '../__test-utils__/mock-data'
-import { ToolTester } from '../__test-utils__/test-tools'
-import { requestTool } from './request'
+import { mockHttpResponses } from '@/tools/__test-utils__/mock-data'
+import { ToolTester } from '@/tools/__test-utils__/test-tools'
+import { requestTool } from '@/tools/http/request'
process.env.VITEST = 'true'
diff --git a/apps/sim/tools/http/request.ts b/apps/sim/tools/http/request.ts
index 7d6e20e482..f132237a61 100644
--- a/apps/sim/tools/http/request.ts
+++ b/apps/sim/tools/http/request.ts
@@ -2,8 +2,8 @@ import { env } from '@/lib/env'
import { isTest } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
import { getBaseUrl } from '@/lib/urls/utils'
-import type { HttpMethod, TableRow, ToolConfig } from '../types'
-import type { RequestParams, RequestResponse } from './types'
+import type { RequestParams, RequestResponse } from '@/tools/http/types'
+import type { HttpMethod, TableRow, ToolConfig } from '@/tools/types'
const logger = createLogger('HTTPRequestTool')
diff --git a/apps/sim/tools/http/types.ts b/apps/sim/tools/http/types.ts
index 9bda757d48..68b455c6fe 100644
--- a/apps/sim/tools/http/types.ts
+++ b/apps/sim/tools/http/types.ts
@@ -1,4 +1,4 @@
-import type { HttpMethod, TableRow, ToolResponse } from '../types'
+import type { HttpMethod, TableRow, ToolResponse } from '@/tools/types'
export interface RequestParams {
url: string
diff --git a/apps/sim/tools/huggingface/chat.ts b/apps/sim/tools/huggingface/chat.ts
index dd6a01d923..82933c7ef3 100644
--- a/apps/sim/tools/huggingface/chat.ts
+++ b/apps/sim/tools/huggingface/chat.ts
@@ -1,10 +1,10 @@
-import type { ToolConfig } from '../types'
import type {
HuggingFaceChatParams,
HuggingFaceChatResponse,
HuggingFaceMessage,
HuggingFaceRequestBody,
-} from './types'
+} from '@/tools/huggingface/types'
+import type { ToolConfig } from '@/tools/types'
export const chatTool: ToolConfig