From 56f67d7ce5285b52a830e0ccb323b0be47bb64fc Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 23 Jun 2025 17:48:50 -0700 Subject: [PATCH 01/13] fix(sockets): updated CSP --- apps/sim/next.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index 7876cc3d4..3f67fb5b7 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -154,7 +154,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; 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' ${process.env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${process.env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} 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://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.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; 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' ${process.env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${process.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://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`, }, ], }, From 201cb10c0b1754803560e1b1da25ba1b831f2248 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Fri, 20 Jun 2025 11:59:45 -0700 Subject: [PATCH 02/13] improvement: connection blocks ui --- .../connection-blocks/connection-blocks.tsx | 120 ++++++++++-------- .../workflow-block/workflow-block.tsx | 6 +- .../app/w/[id]/hooks/use-block-connections.ts | 13 +- 3 files changed, 73 insertions(+), 66 deletions(-) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx index baf322f53..0c2a810ff 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx @@ -2,9 +2,11 @@ import { Card } from '@/components/ui/card' import { cn } from '@/lib/utils' import { type ConnectedBlock, useBlockConnections } from '@/app/w/[id]/hooks/use-block-connections' import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { getBlock } from '@/blocks' interface ConnectionBlocksProps { blockId: string + horizontalHandles: boolean setIsConnecting: (isConnecting: boolean) => void isDisabled?: boolean } @@ -17,7 +19,7 @@ interface ResponseField { export function ConnectionBlocks({ blockId, - setIsConnecting, + horizontalHandles, setIsConnecting, isDisabled = false, }: ConnectionBlocksProps) { const { incomingConnections, hasIncomingConnections } = useBlockConnections(blockId) @@ -117,86 +119,92 @@ export function ConnectionBlocks({ } } - // Deduplicate connections by ID - const connectionMap = incomingConnections.reduce( - (acc, connection) => { - acc[connection.id] = connection - return acc - }, - {} as Record - ) - - // Sort connections by name - const sortedConnections = Object.values(connectionMap).sort((a, b) => - a.name.localeCompare(b.name) + // Use connections in distance order (already sorted by the hook) + const sortedConnections = incomingConnections.filter((connection, index, arr) => + arr.findIndex(c => c.id === connection.id) === index ) // Helper function to render a connection card const renderConnectionCard = (connection: ConnectedBlock, field?: ResponseField) => { - const displayName = connection.name.replace(/\s+/g, '').toLowerCase() + // Get block configuration for icon and color + const blockConfig = getBlock(connection.type) + const displayName = connection.name // Use the actual block name instead of transforming it + const Icon = blockConfig?.icon + const bgColor = blockConfig?.bgColor || '#6B7280' // Fallback to gray return ( handleDragStart(e, connection, field)} onDragEnd={handleDragEnd} className={cn( - 'group flex w-max items-center rounded-lg border bg-card p-2 shadow-sm transition-colors', + 'group flex w-max items-center gap-2 rounded-lg border bg-card p-2 shadow-sm transition-colors', !isDisabled ? 'cursor-grab hover:bg-accent/50 active:cursor-grabbing' : 'cursor-not-allowed opacity-60' )} > + {/* Block icon with color */} + {Icon && ( +
+ +
+ )}
{displayName} - - {field - ? `.${field.name}` - : typeof connection.outputType === 'string' - ? `.${connection.outputType}` - : ''} -
) } - return ( -
- {sortedConnections.map((connection, index) => { - // Special handling for starter blocks with input format - if (connection.type === 'starter') { - const starterFields = extractFieldsFromStarterInput(connection) - - if (starterFields.length > 0) { - return ( -
- {starterFields.map((field) => renderConnectionCard(connection, field))} -
- ) - } + // Generate all connection cards + const connectionCards: React.ReactNode[] = [] + + sortedConnections.forEach((connection, index) => { + // Special handling for starter blocks with input format + if (connection.type === 'starter') { + const starterFields = extractFieldsFromStarterInput(connection) + + if (starterFields.length > 0) { + starterFields.forEach((field) => { + connectionCards.push(renderConnectionCard(connection, field)) + }) + return + } + } + + // Regular connection handling + if (Array.isArray(connection.outputType)) { + // Handle array of field names + connection.outputType.forEach((fieldName) => { + // Try to find field in response format + const fields = extractFieldsFromSchema(connection) + const field = fields.find((f) => f.name === fieldName) || { + name: fieldName, + type: 'string', } - // Regular connection handling - return ( -
- {Array.isArray(connection.outputType) - ? // Handle array of field names - connection.outputType.map((fieldName) => { - // Try to find field in response format - const fields = extractFieldsFromSchema(connection) - const field = fields.find((f) => f.name === fieldName) || { - name: fieldName, - type: 'string', - } - - return renderConnectionCard(connection, field) - }) - : renderConnectionCard(connection)} -
- ) - })} + connectionCards.push(renderConnectionCard(connection, field)) + }) + } else { + connectionCards.push(renderConnectionCard(connection)) + } + }) + + // Position and layout based on handle orientation - reverse of ports + // When ports are horizontal: connection blocks on top, aligned to left, closest blocks on bottom row + // When ports are vertical (default): connection blocks on left, stack vertically, aligned to right + const containerClasses = horizontalHandles + ? 'absolute bottom-full left-0 flex max-w-[600px] flex-wrap-reverse gap-2 pb-3' + : 'absolute top-0 right-full flex max-h-[400px] max-w-[200px] flex-col items-end gap-2 overflow-y-auto pr-3' + + return ( +
+ {connectionCards}
) } diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index 82cf37cfa..5433c6aac 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -433,11 +433,7 @@ export function WorkflowBlock({ id, data }: NodeProps) { )} - + {/* Input Handle - Don't show for starter blocks */} {type !== 'starter' && ( diff --git a/apps/sim/app/w/[id]/hooks/use-block-connections.ts b/apps/sim/app/w/[id]/hooks/use-block-connections.ts index 57e97320c..2c34ef0ed 100644 --- a/apps/sim/app/w/[id]/hooks/use-block-connections.ts +++ b/apps/sim/app/w/[id]/hooks/use-block-connections.ts @@ -59,9 +59,9 @@ function extractFieldsFromSchema(schema: any): Field[] { * along connected paths * @param edges - List of all edges in the graph * @param targetNodeId - ID of the target block we're finding connections for - * @returns Array of unique ancestor node IDs + * @returns Array of objects with node IDs and their distances from target */ -function findAllPathNodes(edges: any[], targetNodeId: string): string[] { +function findAllPathNodes(edges: any[], targetNodeId: string): Array<{ nodeId: string; distance: number }> { // We'll use a reverse topological sort approach by tracking "distance" from target const nodeDistances = new Map() const visited = new Set() @@ -107,7 +107,10 @@ function findAllPathNodes(edges: any[], targetNodeId: string): string[] { } } + // Return nodes sorted by distance (closest first) return Array.from(pathNodes) + .map(nodeId => ({ nodeId, distance: nodeDistances.get(nodeId) || 0 })) + .sort((a, b) => a.distance - b.distance) } export function useBlockConnections(blockId: string) { @@ -120,11 +123,11 @@ export function useBlockConnections(blockId: string) { ) // Find all blocks along paths leading to this block - const allPathNodeIds = findAllPathNodes(edges, blockId) + const allPathNodes = findAllPathNodes(edges, blockId) // Map each path node to a ConnectedBlock structure - const allPathConnections = allPathNodeIds - .map((sourceId) => { + const allPathConnections = allPathNodes + .map(({ nodeId: sourceId }) => { const sourceBlock = blocks[sourceId] if (!sourceBlock) return null From 8f38880921525e564d86477c834e045f97229cf1 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Fri, 20 Jun 2025 12:23:18 -0700 Subject: [PATCH 03/13] fix: truncating workflow block name --- .../connection-blocks/connection-blocks.tsx | 19 ++++++++++--------- .../workflow-block/workflow-block.tsx | 11 +++++++++-- .../app/w/[id]/hooks/use-block-connections.ts | 7 +++++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx index 0c2a810ff..733127b1b 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx @@ -1,8 +1,8 @@ import { Card } from '@/components/ui/card' import { cn } from '@/lib/utils' import { type ConnectedBlock, useBlockConnections } from '@/app/w/[id]/hooks/use-block-connections' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { getBlock } from '@/blocks' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' interface ConnectionBlocksProps { blockId: string @@ -19,8 +19,13 @@ interface ResponseField { export function ConnectionBlocks({ blockId, +<<<<<<< HEAD horizontalHandles, setIsConnecting, isDisabled = false, +======= + horizontalHandles, + setIsConnecting, +>>>>>>> 0ebc0b67 (fix: truncating workflow block name) }: ConnectionBlocksProps) { const { incomingConnections, hasIncomingConnections } = useBlockConnections(blockId) @@ -120,8 +125,8 @@ export function ConnectionBlocks({ } // Use connections in distance order (already sorted by the hook) - const sortedConnections = incomingConnections.filter((connection, index, arr) => - arr.findIndex(c => c.id === connection.id) === index + const sortedConnections = incomingConnections.filter( + (connection, index, arr) => arr.findIndex((c) => c.id === connection.id) === index ) // Helper function to render a connection card @@ -163,7 +168,7 @@ export function ConnectionBlocks({ // Generate all connection cards const connectionCards: React.ReactNode[] = [] - + sortedConnections.forEach((connection, index) => { // Special handling for starter blocks with input format if (connection.type === 'starter') { @@ -202,9 +207,5 @@ export function ConnectionBlocks({ ? 'absolute bottom-full left-0 flex max-w-[600px] flex-wrap-reverse gap-2 pb-3' : 'absolute top-0 right-full flex max-h-[400px] max-w-[200px] flex-col items-end gap-2 overflow-y-auto pr-3' - return ( -
- {connectionCards} -
- ) + return
{connectionCards}
} diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index 5433c6aac..e260eefb8 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -495,13 +495,20 @@ export function WorkflowBlock({ id, data }: NodeProps) { ) : ( {name} diff --git a/apps/sim/app/w/[id]/hooks/use-block-connections.ts b/apps/sim/app/w/[id]/hooks/use-block-connections.ts index 2c34ef0ed..4a9b238b4 100644 --- a/apps/sim/app/w/[id]/hooks/use-block-connections.ts +++ b/apps/sim/app/w/[id]/hooks/use-block-connections.ts @@ -61,7 +61,10 @@ function extractFieldsFromSchema(schema: any): Field[] { * @param targetNodeId - ID of the target block we're finding connections for * @returns Array of objects with node IDs and their distances from target */ -function findAllPathNodes(edges: any[], targetNodeId: string): Array<{ nodeId: string; distance: number }> { +function findAllPathNodes( + edges: any[], + targetNodeId: string +): Array<{ nodeId: string; distance: number }> { // We'll use a reverse topological sort approach by tracking "distance" from target const nodeDistances = new Map() const visited = new Set() @@ -109,7 +112,7 @@ function findAllPathNodes(edges: any[], targetNodeId: string): Array<{ nodeId: s // Return nodes sorted by distance (closest first) return Array.from(pathNodes) - .map(nodeId => ({ nodeId, distance: nodeDistances.get(nodeId) || 0 })) + .map((nodeId) => ({ nodeId, distance: nodeDistances.get(nodeId) || 0 })) .sort((a, b) => a.distance - b.distance) } From 358fd7d5b667fe8a645532ede2c0d55244134d5e Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Fri, 20 Jun 2025 15:14:10 -0700 Subject: [PATCH 04/13] improvement: tag dropdown ui and refactor --- apps/sim/components/ui/tag-dropdown.tsx | 492 ++++++++++++++---------- 1 file changed, 293 insertions(+), 199 deletions(-) diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index 78c9bf0a1..910801d9c 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -12,6 +12,7 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store' const logger = createLogger('TagDropdown') +// Type definitions for component data structures interface Field { name: string type: string @@ -27,6 +28,14 @@ interface Metric { } } +interface BlockTagGroup { + blockName: string + blockId: string + blockType: string + tags: string[] + distance: number +} + interface TagDropdownProps { visible: boolean onSelect: (newValue: string) => void @@ -39,7 +48,7 @@ interface TagDropdownProps { style?: React.CSSProperties } -// Add a helper function to extract fields from JSON Schema +// Extract fields from JSON Schema or legacy format export const extractFieldsFromSchema = (responseFormat: any): Field[] => { if (!responseFormat) return [] @@ -67,6 +76,21 @@ export const extractFieldsFromSchema = (responseFormat: any): Field[] => { })) } +// Check if tag trigger '<' should show dropdown +export const checkTagTrigger = (text: string, cursorPosition: number): { show: boolean } => { + if (cursorPosition >= 1) { + const textBeforeCursor = text.slice(0, cursorPosition) + const lastOpenBracket = textBeforeCursor.lastIndexOf('<') + const lastCloseBracket = textBeforeCursor.lastIndexOf('>') + + // Show if we have an unclosed '<' that's not part of a completed tag + if (lastOpenBracket !== -1 && (lastCloseBracket === -1 || lastCloseBracket < lastOpenBracket)) { + return { show: true } + } + } + return { show: false } +} + export const TagDropdown: React.FC = ({ visible, onSelect, @@ -78,41 +102,46 @@ export const TagDropdown: React.FC = ({ onClose, style, }) => { + // Component state const [selectedIndex, setSelectedIndex] = useState(0) - // Get available tags from workflow state + // Store hooks for workflow data const blocks = useWorkflowStore((state) => state.blocks) const loops = useWorkflowStore((state) => state.loops) const parallels = useWorkflowStore((state) => state.parallels) - const _edges = useWorkflowStore((state) => state.edges) + const edges = useWorkflowStore((state) => state.edges) const workflowId = useWorkflowRegistry((state) => state.activeWorkflowId) - // Get variables from variables store + // Store hooks for variables const getVariablesByWorkflowId = useVariablesStore((state) => state.getVariablesByWorkflowId) const loadVariables = useVariablesStore((state) => state.loadVariables) const variables = useVariablesStore((state) => state.variables) const workflowVariables = workflowId ? getVariablesByWorkflowId(workflowId) : [] - // Get all connected blocks using useBlockConnections + // Get block connections const { incomingConnections } = useBlockConnections(blockId) - // Load variables when workflowId changes + // Load variables when workflow changes useEffect(() => { if (workflowId) { loadVariables(workflowId) } }, [workflowId, loadVariables]) - // Extract search term from input + // Extract current search term from input const searchTerm = useMemo(() => { const textBeforeCursor = inputValue.slice(0, cursorPosition) const match = textBeforeCursor.match(/<([^>]*)$/) return match ? match[1].toLowerCase() : '' }, [inputValue, cursorPosition]) - // Get source block and compute tags - const { tags, variableInfoMap = {} } = useMemo(() => { - // Helper function to get output paths + // Compute available tags and metadata + const { + tags, + variableInfoMap = {}, + blockTagGroups = [], + } = useMemo(() => { + // Get output paths from block outputs recursively const getOutputPaths = (obj: any, prefix = '', isStarterBlock = false): string[] => { if (typeof obj !== 'object' || obj === null) { return prefix ? [prefix] : [] @@ -156,12 +185,45 @@ export const TagDropdown: React.FC = ({ }) } - // Variables as tags - format as variable.{variableName} + // Calculate distances from starter block using BFS + const blockDistances: Record = {} + const starterBlock = Object.values(blocks).find((block) => block.type === 'starter') + const starterBlockId = starterBlock?.id + + if (starterBlockId) { + // Build adjacency list for efficient traversal + const adjList: Record = {} + for (const edge of edges) { + if (!adjList[edge.source]) { + adjList[edge.source] = [] + } + adjList[edge.source].push(edge.target) + } + + // BFS to find distances from starter block + const visited = new Set() + const queue: [string, number][] = [[starterBlockId, 0]] + + while (queue.length > 0) { + const [currentNodeId, distance] = queue.shift()! + + if (visited.has(currentNodeId)) continue + visited.add(currentNodeId) + blockDistances[currentNodeId] = distance + + // Add outgoing nodes to queue with incremented distance + const outgoingNodeIds = adjList[currentNodeId] || [] + for (const targetId of outgoingNodeIds) { + queue.push([targetId, distance + 1]) + } + } + } + + // Create variable tags with type information const variableTags = workflowVariables.map( (variable: Variable) => `variable.${variable.name.replace(/\s+/g, '')}` ) - // Create a map of variable tags to their type information const variableInfoMap = workflowVariables.reduce( (acc, variable) => { const tagName = `variable.${variable.name.replace(/\s+/g, '')}` @@ -174,111 +236,112 @@ export const TagDropdown: React.FC = ({ {} as Record ) - // Loop tags - Add if this block is in a loop + // Generate loop tags if current block is in a loop const loopTags: string[] = [] - - // Check if the current block is part of a loop const containingLoop = Object.entries(loops).find(([_, loop]) => loop.nodes.includes(blockId)) if (containingLoop) { const [_loopId, loop] = containingLoop const loopType = loop.loopType || 'for' - // Add loop.index for all loop types loopTags.push('loop.index') - // Add forEach specific properties if (loopType === 'forEach') { - // Add loop.currentItem and loop.items loopTags.push('loop.currentItem') loopTags.push('loop.items') } } - // Parallel tags - Add if this block is in a parallel + // Generate parallel tags if current block is in parallel const parallelTags: string[] = [] - - // Check if the current block is part of a parallel const containingParallel = Object.entries(parallels || {}).find(([_, parallel]) => parallel.nodes.includes(blockId) ) if (containingParallel) { - // Add parallel.index for all parallel blocks parallelTags.push('parallel.index') - - // Add parallel.currentItem and parallel.items parallelTags.push('parallel.currentItem') parallelTags.push('parallel.items') } - // If we have an active source block ID from a drop, use that specific block only + // Handle active source block (from drag & drop) if (activeSourceBlockId) { const sourceBlock = blocks[activeSourceBlockId] - if (!sourceBlock) return { tags: [...variableTags] } + if (!sourceBlock) + return { tags: [...variableTags, ...loopTags, ...parallelTags], variableInfoMap } const blockName = sourceBlock.name || sourceBlock.type const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() - // First check for evaluator metrics + let blockTags: string[] = [] + + // Check for evaluator metrics first if (sourceBlock.type === 'evaluator') { try { const metricsValue = useSubBlockStore .getState() .getValue(activeSourceBlockId, 'metrics') as unknown as Metric[] if (Array.isArray(metricsValue)) { - return { - tags: [ - ...variableTags, - ...metricsValue.map( - (metric) => `${normalizedBlockName}.response.${metric.name.toLowerCase()}` - ), - ], - } + blockTags = metricsValue.map( + (metric) => `${normalizedBlockName}.response.${metric.name.toLowerCase()}` + ) } } catch (e) { logger.error('Error parsing metrics:', { e }) } } - // Then check for response format - try { - const responseFormatValue = useSubBlockStore - .getState() - .getValue(activeSourceBlockId, 'responseFormat') - if (responseFormatValue) { - const responseFormat = - typeof responseFormatValue === 'string' - ? JSON.parse(responseFormatValue) - : responseFormatValue - - if (responseFormat) { - const fields = extractFieldsFromSchema(responseFormat) - if (fields.length > 0) { - return { - tags: [ - ...variableTags, - ...fields.map((field: Field) => `${normalizedBlockName}.response.${field.name}`), - ], + // Check for response format if no metrics + if (blockTags.length === 0) { + try { + const responseFormatValue = useSubBlockStore + .getState() + .getValue(activeSourceBlockId, 'responseFormat') + if (responseFormatValue) { + const responseFormat = + typeof responseFormatValue === 'string' + ? JSON.parse(responseFormatValue) + : responseFormatValue + + if (responseFormat) { + const fields = extractFieldsFromSchema(responseFormat) + if (fields.length > 0) { + blockTags = fields.map( + (field: Field) => `${normalizedBlockName}.response.${field.name}` + ) } } } + } catch (e) { + logger.error('Error parsing response format:', { e }) } - } catch (e) { - logger.error('Error parsing response format:', { e }) } - // Fall back to default outputs if no response format - const outputPaths = getOutputPaths(sourceBlock.outputs, '', sourceBlock.type === 'starter') + // Fall back to default outputs + if (blockTags.length === 0) { + const outputPaths = getOutputPaths(sourceBlock.outputs, '', sourceBlock.type === 'starter') + blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) + } + + const blockTagGroups: BlockTagGroup[] = [ + { + blockName, + blockId: activeSourceBlockId, + blockType: sourceBlock.type, + tags: blockTags, + distance: blockDistances[activeSourceBlockId] || 0, + }, + ] + return { - tags: [...variableTags, ...outputPaths.map((path) => `${normalizedBlockName}.${path}`)], + tags: [...variableTags, ...loopTags, ...parallelTags, ...blockTags], + variableInfoMap, + blockTagGroups, } } - // Find parallel and loop blocks connected via end-source handles + // Find parallel/loop end-source connections const endSourceConnections: ConnectedBlock[] = [] - - // Get all edges that connect to this block const incomingEdges = useWorkflowStore .getState() .edges.filter((edge) => edge.target === blockId) @@ -287,12 +350,10 @@ export const TagDropdown: React.FC = ({ const sourceBlock = blocks[edge.source] if (!sourceBlock) continue - // Check if this is a parallel-end-source or loop-end-source connection + // Handle parallel-end-source connections if (edge.sourceHandle === 'parallel-end-source' && sourceBlock.type === 'parallel') { const blockName = sourceBlock.name || sourceBlock.type - const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() - // Add the parallel block as a referenceable block with its aggregated results endSourceConnections.push({ id: sourceBlock.id, type: sourceBlock.type, @@ -316,9 +377,7 @@ export const TagDropdown: React.FC = ({ }) } else if (edge.sourceHandle === 'loop-end-source' && sourceBlock.type === 'loop') { const blockName = sourceBlock.name || sourceBlock.type - const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() - // Add the loop block as a referenceable block with its aggregated results endSourceConnections.push({ id: sourceBlock.id, type: sourceBlock.type, @@ -343,47 +402,90 @@ export const TagDropdown: React.FC = ({ } } - // Use all incoming connections plus end-source connections + // Combine all connections const allConnections = [...incomingConnections, ...endSourceConnections] - const sourceTags = allConnections.flatMap((connection: ConnectedBlock) => { + // Group block tags by source block + const blockTagsMap = new Map< + string, + { + blockName: string + blockId: string + blockType: string + tags: string[] + distance: number + } + >() + + allConnections.forEach((connection: ConnectedBlock) => { const blockName = connection.name || connection.type const normalizedBlockName = blockName.replace(/\s+/g, '').toLowerCase() + let connectionTags: string[] = [] // Extract fields from response format if (connection.responseFormat) { const fields = extractFieldsFromSchema(connection.responseFormat) if (fields.length > 0) { - return fields.map((field: Field) => `${normalizedBlockName}.response.${field.name}`) + connectionTags = fields.map( + (field: Field) => `${normalizedBlockName}.response.${field.name}` + ) } } - // For evaluator blocks, use metrics + // Handle evaluator blocks with metrics if (connection.type === 'evaluator') { try { const metricsValue = useSubBlockStore .getState() .getValue(connection.id, 'metrics') as unknown as Metric[] if (Array.isArray(metricsValue)) { - return metricsValue.map( + connectionTags = metricsValue.map( (metric) => `${normalizedBlockName}.response.${metric.name.toLowerCase()}` ) } } catch (e) { logger.error('Error parsing metrics:', { e }) - return [] + connectionTags = [] } } - // Fall back to default outputs if no response format - const sourceBlock = blocks[connection.id] - if (!sourceBlock) return [] + // Fall back to default outputs + if (connectionTags.length === 0) { + const sourceBlock = blocks[connection.id] + if (sourceBlock) { + const outputPaths = getOutputPaths( + sourceBlock.outputs, + '', + sourceBlock.type === 'starter' + ) + connectionTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) + } + } - const outputPaths = getOutputPaths(sourceBlock.outputs, '', sourceBlock.type === 'starter') - return outputPaths.map((path) => `${normalizedBlockName}.${path}`) + // Add to map, combining tags if block already exists + const existingEntry = blockTagsMap.get(connection.id) + if (existingEntry) { + existingEntry.tags.push(...connectionTags) + } else { + blockTagsMap.set(connection.id, { + blockName, + blockId: connection.id, + blockType: connection.type, + tags: connectionTags, + distance: blockDistances[connection.id] || 0, + }) + } }) - return { tags: [...variableTags, ...loopTags, ...parallelTags, ...sourceTags], variableInfoMap } + // Sort by distance (furthest first) and flatten tags + const blockTagGroups = Array.from(blockTagsMap.values()).sort((a, b) => b.distance - a.distance) + const allBlockTags = blockTagGroups.flatMap((group) => group.tags) + + return { + tags: [...variableTags, ...loopTags, ...parallelTags, ...allBlockTags], + variableInfoMap, + blockTagGroups, + } }, [ blocks, incomingConnections, @@ -392,6 +494,7 @@ export const TagDropdown: React.FC = ({ workflowVariables, loops, parallels, + edges, ]) // Filter tags based on search term @@ -400,12 +503,11 @@ export const TagDropdown: React.FC = ({ return tags.filter((tag: string) => tag.toLowerCase().includes(searchTerm)) }, [tags, searchTerm]) - // Group tags into variables, loops, and blocks - const { variableTags, loopTags, parallelTags, blockTags } = useMemo(() => { + // Group filtered tags by category + const { variableTags, loopTags, parallelTags, filteredBlockTagGroups } = useMemo(() => { const varTags: string[] = [] const loopTags: string[] = [] const parTags: string[] = [] - const blkTags: string[] = [] filteredTags.forEach((tag) => { if (tag.startsWith('variable.')) { @@ -414,20 +516,32 @@ export const TagDropdown: React.FC = ({ loopTags.push(tag) } else if (tag.startsWith('parallel.')) { parTags.push(tag) - } else { - blkTags.push(tag) } }) - return { variableTags: varTags, loopTags: loopTags, parallelTags: parTags, blockTags: blkTags } - }, [filteredTags]) + // Filter block tag groups based on search term + const filteredBlockTagGroups = blockTagGroups + .map((group) => ({ + ...group, + tags: group.tags.filter((tag) => !searchTerm || tag.toLowerCase().includes(searchTerm)), + })) + .filter((group) => group.tags.length > 0) + + return { + variableTags: varTags, + loopTags: loopTags, + parallelTags: parTags, + filteredBlockTagGroups, + } + }, [filteredTags, blockTagGroups, searchTerm]) - // Create ordered tags array that matches the display order for keyboard navigation + // Create ordered tags for keyboard navigation const orderedTags = useMemo(() => { - return [...variableTags, ...loopTags, ...parallelTags, ...blockTags] - }, [variableTags, loopTags, parallelTags, blockTags]) + const allBlockTags = filteredBlockTagGroups.flatMap((group) => group.tags) + return [...variableTags, ...loopTags, ...parallelTags, ...allBlockTags] + }, [variableTags, loopTags, parallelTags, filteredBlockTagGroups]) - // Create a map for efficient tag index lookups + // Create efficient tag index lookup map const tagIndexMap = useMemo(() => { const map = new Map() orderedTags.forEach((tag, index) => { @@ -436,19 +550,7 @@ export const TagDropdown: React.FC = ({ return map }, [orderedTags]) - // Reset selection when filtered results change - useEffect(() => { - setSelectedIndex(0) - }, [searchTerm]) - - // Ensure selectedIndex stays within bounds when orderedTags changes - useEffect(() => { - if (selectedIndex >= orderedTags.length) { - setSelectedIndex(Math.max(0, orderedTags.length - 1)) - } - }, [orderedTags.length, selectedIndex]) - - // Handle tag selection + // Handle tag selection and text replacement const handleTagSelect = useCallback( (tag: string) => { const textBeforeCursor = inputValue.slice(0, cursorPosition) @@ -458,34 +560,26 @@ export const TagDropdown: React.FC = ({ const lastOpenBracket = textBeforeCursor.lastIndexOf('<') if (lastOpenBracket === -1) return - // Process the tag if it's a variable tag + // Process variable tags to maintain compatibility let processedTag = tag if (tag.startsWith('variable.')) { - // Get the variable name from the tag (after 'variable.') const variableName = tag.substring('variable.'.length) - - // Find the variable in the store by name const variableObj = Object.values(variables).find( (v) => v.name.replace(/\s+/g, '') === variableName ) - // We still use the full tag format internally to maintain compatibility if (variableObj) { processedTag = tag } } - // Check if there's a closing bracket in textAfterCursor that belongs to the current tag - // Find the first '>' in textAfterCursor (if any) + // Handle existing closing bracket const nextCloseBracket = textAfterCursor.indexOf('>') let remainingTextAfterCursor = textAfterCursor - // If there's a '>' right after the cursor or with only whitespace/tag content in between, - // it's likely part of the existing tag being edited, so we should skip it if (nextCloseBracket !== -1) { const textBetween = textAfterCursor.slice(0, nextCloseBracket) - // If the text between cursor and '>' contains only tag-like characters (letters, dots, numbers) - // then it's likely part of the current tag being edited + // If text between cursor and '>' contains only tag-like characters, skip it if (/^[a-zA-Z0-9._]*$/.test(textBetween)) { remainingTextAfterCursor = textAfterCursor.slice(nextCloseBracket + 1) } @@ -499,7 +593,19 @@ export const TagDropdown: React.FC = ({ [inputValue, cursorPosition, variables, onSelect, onClose] ) - // Add and remove keyboard event listener + // Reset selection when search results change + useEffect(() => { + setSelectedIndex(0) + }, [searchTerm]) + + // Keep selection within bounds when tags change + useEffect(() => { + if (selectedIndex >= orderedTags.length) { + setSelectedIndex(Math.max(0, orderedTags.length - 1)) + } + }, [orderedTags.length, selectedIndex]) + + // Handle keyboard navigation useEffect(() => { if (visible) { const handleKeyboardEvent = (e: KeyboardEvent) => { @@ -536,7 +642,7 @@ export const TagDropdown: React.FC = ({ } }, [visible, selectedIndex, orderedTags, handleTagSelect, onClose]) - // Don't render if not visible or no tags + // Early return if dropdown should not be visible if (!visible || tags.length === 0 || orderedTags.length === 0) return null return ( @@ -552,6 +658,7 @@ export const TagDropdown: React.FC = ({
No matching tags found
) : ( <> + {/* Variables section */} {variableTags.length > 0 && ( <>
@@ -575,8 +682,8 @@ export const TagDropdown: React.FC = ({ )} onMouseEnter={() => setSelectedIndex(tagIndex >= 0 ? tagIndex : 0)} onMouseDown={(e) => { - e.preventDefault() // Prevent input blur - e.stopPropagation() // Prevent event bubbling + e.preventDefault() + e.stopPropagation() handleTagSelect(tag) }} onClick={(e) => { @@ -606,6 +713,7 @@ export const TagDropdown: React.FC = ({ )} + {/* Loop section */} {loopTags.length > 0 && ( <> {variableTags.length > 0 &&
} @@ -617,10 +725,10 @@ export const TagDropdown: React.FC = ({ const tagIndex = tagIndexMap.get(tag) ?? -1 const loopProperty = tag.split('.')[1] - // Choose appropriate icon/label based on type + // Choose appropriate icon and description based on loop property let tagIcon = 'L' let tagDescription = '' - const bgColor = '#8857E6' // Purple for loop variables + const bgColor = '#8857E6' if (loopProperty === 'currentItem') { tagIcon = 'i' @@ -646,8 +754,8 @@ export const TagDropdown: React.FC = ({ )} onMouseEnter={() => setSelectedIndex(tagIndex >= 0 ? tagIndex : 0)} onMouseDown={(e) => { - e.preventDefault() // Prevent input blur - e.stopPropagation() // Prevent event bubbling + e.preventDefault() + e.stopPropagation() handleTagSelect(tag) }} onClick={(e) => { @@ -673,6 +781,7 @@ export const TagDropdown: React.FC = ({ )} + {/* Parallel section */} {parallelTags.length > 0 && ( <> {loopTags.length > 0 &&
} @@ -684,10 +793,10 @@ export const TagDropdown: React.FC = ({ const tagIndex = tagIndexMap.get(tag) ?? -1 const parallelProperty = tag.split('.')[1] - // Choose appropriate icon/label based on type + // Choose appropriate icon and description based on parallel property let tagIcon = 'P' let tagDescription = '' - const bgColor = '#FF5757' // Red for parallel variables + const bgColor = '#FF5757' if (parallelProperty === 'currentItem') { tagIcon = 'i' @@ -713,8 +822,8 @@ export const TagDropdown: React.FC = ({ )} onMouseEnter={() => setSelectedIndex(tagIndex >= 0 ? tagIndex : 0)} onMouseDown={(e) => { - e.preventDefault() // Prevent input blur - e.stopPropagation() // Prevent event bubbling + e.preventDefault() + e.stopPropagation() handleTagSelect(tag) }} onClick={(e) => { @@ -740,68 +849,68 @@ export const TagDropdown: React.FC = ({ )} - {blockTags.length > 0 && ( + {/* Block sections */} + {filteredBlockTagGroups.length > 0 && ( <> {(variableTags.length > 0 || loopTags.length > 0 || parallelTags.length > 0) && (
)} -
- Blocks -
-
- {blockTags.map((tag: string) => { - const tagIndex = tagIndexMap.get(tag) ?? -1 - - // Get block name from tag (first part before the dot) - const blockName = tag.split('.')[0] - - // Get block type from blocks - const blockType = Object.values(blocks).find( - (block) => - (block.name || block.type || '').replace(/\s+/g, '').toLowerCase() === - blockName - )?.type - - // Get block color from block config - const blockConfig = blockType ? getBlock(blockType) : null - const blockColor = blockConfig?.bgColor || '#2F55FF' // Default to blue if not found - - return ( - - ) - })} -
+ {filteredBlockTagGroups.map((group) => { + // Get block color from configuration + const blockConfig = getBlock(group.blockType) + const blockColor = blockConfig?.bgColor || '#2F55FF' + + return ( +
+
+ {group.blockName} +
+
+ {group.tags.map((tag: string) => { + const tagIndex = tagIndexMap.get(tag) ?? -1 + // Extract path after block name (e.g., "response.field" from "blockname.response.field") + const tagParts = tag.split('.') + const path = tagParts.slice(1).join('.') + + return ( + + ) + })} +
+
+ ) + })} )} @@ -810,18 +919,3 @@ export const TagDropdown: React.FC = ({
) } - -// Helper function to check for '<' trigger -export const checkTagTrigger = (text: string, cursorPosition: number): { show: boolean } => { - if (cursorPosition >= 1) { - const textBeforeCursor = text.slice(0, cursorPosition) - const lastOpenBracket = textBeforeCursor.lastIndexOf('<') - const lastCloseBracket = textBeforeCursor.lastIndexOf('>') - - // Show if we have an unclosed '<' that's not part of a completed tag - if (lastOpenBracket !== -1 && (lastCloseBracket === -1 || lastCloseBracket < lastOpenBracket)) { - return { show: true } - } - } - return { show: false } -} From e6895897c39fc1db13d66e18c5f5505044e62eb7 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Fri, 20 Jun 2025 20:33:44 -0700 Subject: [PATCH 05/13] feat: pan on workflow run --- apps/sim/app/api/user/settings/route.ts | 3 + apps/sim/app/w/[id]/workflow.tsx | 113 +++++++++++++++++- .../components/general/general.tsx | 38 ++++++ .../migrations/0046_cuddly_killer_shrike.sql | 1 + apps/sim/db/schema.ts | 1 + apps/sim/executor/index.ts | 4 +- apps/sim/stores/execution/store.ts | 61 +++++++++- apps/sim/stores/execution/types.ts | 7 ++ apps/sim/stores/settings/general/store.ts | 8 ++ apps/sim/stores/settings/general/types.ts | 3 + 10 files changed, 228 insertions(+), 11 deletions(-) create mode 100644 apps/sim/db/migrations/0046_cuddly_killer_shrike.sql diff --git a/apps/sim/app/api/user/settings/route.ts b/apps/sim/app/api/user/settings/route.ts index 5204aa730..3515f9afa 100644 --- a/apps/sim/app/api/user/settings/route.ts +++ b/apps/sim/app/api/user/settings/route.ts @@ -14,6 +14,7 @@ const SettingsSchema = z.object({ debugMode: z.boolean().optional(), autoConnect: z.boolean().optional(), autoFillEnvVars: z.boolean().optional(), + autoPan: z.boolean().optional(), telemetryEnabled: z.boolean().optional(), telemetryNotifiedUser: z.boolean().optional(), emailPreferences: z @@ -32,6 +33,7 @@ const defaultSettings = { debugMode: false, autoConnect: true, autoFillEnvVars: true, + autoPan: true, telemetryEnabled: true, telemetryNotifiedUser: false, emailPreferences: {}, @@ -65,6 +67,7 @@ export async function GET() { debugMode: userSettings.debugMode, autoConnect: userSettings.autoConnect, autoFillEnvVars: userSettings.autoFillEnvVars, + autoPan: userSettings.autoPan, telemetryEnabled: userSettings.telemetryEnabled, telemetryNotifiedUser: userSettings.telemetryNotifiedUser, emailPreferences: userSettings.emailPreferences ?? {}, diff --git a/apps/sim/app/w/[id]/workflow.tsx b/apps/sim/app/w/[id]/workflow.tsx index e656b3fb1..6c56e022f 100644 --- a/apps/sim/app/w/[id]/workflow.tsx +++ b/apps/sim/app/w/[id]/workflow.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import ReactFlow, { Background, @@ -21,7 +21,7 @@ import { getBlock } from '@/blocks' import { useSocket } from '@/contexts/socket-context' import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow' import { useWorkspacePermissions } from '@/hooks/use-workspace-permissions' -import { useExecutionStore } from '@/stores/execution/store' +import { setPanToBlockCallback, useExecutionStore } from '@/stores/execution/store' import { useNotificationStore } from '@/stores/notifications/store' import { useVariablesStore } from '@/stores/panel/variables/store' import { useGeneralStore } from '@/stores/settings/general/store' @@ -92,7 +92,7 @@ const WorkflowContent = React.memo(() => { // Hooks const params = useParams() const router = useRouter() - const { project, getNodes, fitView } = useReactFlow() + const { project, getNodes, fitView, getNode, setCenter } = useReactFlow() // Get workspace ID from current workflow const workflowId = params.id as string @@ -128,7 +128,7 @@ const WorkflowContent = React.memo(() => { const { resetLoaded: resetVariablesLoaded } = useVariablesStore() // Execution and debug mode state - const { activeBlockIds, pendingBlocks } = useExecutionStore() + const { activeBlockIds, pendingBlocks, setAutoPanDisabled } = useExecutionStore() const { isDebugModeEnabled } = useGeneralStore() const [dragStartParentId, setDragStartParentId] = useState(null) @@ -1427,6 +1427,109 @@ const WorkflowContent = React.memo(() => { } }, [emitSubblockUpdate, isConnected, currentWorkflowId, activeWorkflowId]) + // Refs for efficient pan state tracking + const lastAutoPanTimeRef = useRef(0) + const panTimeoutRef = useRef(null) + const lastPanTimeRef = useRef(0) + const isAutoPanningRef = useRef(false) + + // Create smooth panning callback with optimized performance + const createPanToBlockCallback = useCallback(() => { + const DEBOUNCE_MS = 150 // Prevent rapid-fire panning + + return (blockId: string) => { + // Only pan if workflow is ready + if (!isWorkflowReady) return + + const now = performance.now() + + // Clear any pending pan operation + if (panTimeoutRef.current) { + clearTimeout(panTimeoutRef.current) + panTimeoutRef.current = null + } + + // Debounce rapid successive calls + const timeSinceLastPan = now - lastPanTimeRef.current + const delay = timeSinceLastPan < DEBOUNCE_MS ? DEBOUNCE_MS - timeSinceLastPan : 50 + + panTimeoutRef.current = setTimeout(() => { + const node = getNode(blockId) + if (!node) return + + // Calculate the center position of the block + const nodeWidth = node.width || (blocks[blockId]?.isWide ? 480 : 320) + const nodeHeight = node.height || Math.max(blocks[blockId]?.height || 100, 100) + + const centerX = node.position.x + nodeWidth / 2 + const centerY = node.position.y + nodeHeight / 2 + + // Track auto-pan timing and state efficiently + lastAutoPanTimeRef.current = performance.now() + isAutoPanningRef.current = true + + // Smooth pan with optimized duration for quick transitions + setCenter(centerX, centerY, { + zoom: 0.7, + duration: 400, // Shorter duration for smoother rapid transitions + }) + + // Reset auto-panning flag after animation completes + buffer + setTimeout(() => { + isAutoPanningRef.current = false + }, 500) // 100ms buffer after 400ms animation + + lastPanTimeRef.current = now + panTimeoutRef.current = null + + logger.info('Auto-panned to block', { + blockId, + position: { x: centerX, y: centerY }, + debounced: timeSinceLastPan < DEBOUNCE_MS, + }) + }, delay) + } + }, [isWorkflowReady, getNode, setCenter, blocks]) + + // Handle React Flow initialization + const handleReactFlowInit = useCallback(() => { + // Register the panning callback when ReactFlow initializes + const panCallback = createPanToBlockCallback() + setPanToBlockCallback(panCallback) + + // Return the callback for accessing isAutoPanning state + return panCallback + }, [createPanToBlockCallback]) + + // Handle manual viewport changes (pan/zoom) - optimized for performance + const handleMove = useCallback(() => { + // Ignore moves during auto-pan animations + if (isAutoPanningRef.current) { + return + } + + // Check if this move is from user interaction vs auto-pan + const now = performance.now() + const timeSinceAutoPan = now - lastAutoPanTimeRef.current + + // If onMove is called >600ms after auto-pan and we're not auto-panning, it's manual + if (timeSinceAutoPan > 600) { + setAutoPanDisabled(true) + logger.info('Manual pan detected - disabling auto-pan for this execution') + } + }, [setAutoPanDisabled]) + + // Cleanup timeouts on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (panTimeoutRef.current) { + clearTimeout(panTimeoutRef.current) + } + // Reset auto-panning flag on cleanup + isAutoPanningRef.current = false + } + }, []) + // Show skeleton UI while loading, then smoothly transition to real content const showSkeletonUI = !isWorkflowReady @@ -1476,6 +1579,8 @@ const WorkflowContent = React.memo(() => { edgeTypes={edgeTypes} onDrop={userPermissions.canEdit ? onDrop : undefined} onDragOver={userPermissions.canEdit ? onDragOver : undefined} + onInit={handleReactFlowInit} + onMove={handleMove} fitView minZoom={0.1} maxZoom={1.3} diff --git a/apps/sim/app/w/components/sidebar/components/settings-modal/components/general/general.tsx b/apps/sim/app/w/components/sidebar/components/settings-modal/components/general/general.tsx index 28844be07..f0e7b71b7 100644 --- a/apps/sim/app/w/components/sidebar/components/settings-modal/components/general/general.tsx +++ b/apps/sim/app/w/components/sidebar/components/settings-modal/components/general/general.tsx @@ -19,6 +19,7 @@ const TOOLTIPS = { debugMode: 'Enable visual debugging information during execution.', autoConnect: 'Automatically connect nodes.', autoFillEnvVars: 'Automatically fill API keys.', + autoPan: 'Automatically pan to active blocks during workflow execution.', } export function General() { @@ -30,11 +31,13 @@ export function General() { const isAutoConnectEnabled = useGeneralStore((state) => state.isAutoConnectEnabled) const isDebugModeEnabled = useGeneralStore((state) => state.isDebugModeEnabled) const isAutoFillEnvVarsEnabled = useGeneralStore((state) => state.isAutoFillEnvVarsEnabled) + const isAutoPanEnabled = useGeneralStore((state) => state.isAutoPanEnabled) const setTheme = useGeneralStore((state) => state.setTheme) const toggleAutoConnect = useGeneralStore((state) => state.toggleAutoConnect) const toggleDebugMode = useGeneralStore((state) => state.toggleDebugMode) const toggleAutoFillEnvVars = useGeneralStore((state) => state.toggleAutoFillEnvVars) + const toggleAutoPan = useGeneralStore((state) => state.toggleAutoPan) const loadSettings = useGeneralStore((state) => state.loadSettings) useEffect(() => { @@ -66,6 +69,12 @@ export function General() { } } + const handleAutoPanChange = (checked: boolean) => { + if (checked !== isAutoPanEnabled) { + toggleAutoPan() + } + } + const handleRetry = () => { setRetryCount((prev) => prev + 1) } @@ -200,6 +209,35 @@ export function General() { disabled={isLoading} />
+
+
+ + + + + + +

{TOOLTIPS.autoPan}

+
+
+
+ +
)}
diff --git a/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql b/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql new file mode 100644 index 000000000..5e08f6229 --- /dev/null +++ b/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql @@ -0,0 +1 @@ +ALTER TABLE "settings" ADD COLUMN "auto_pan" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts index 8a5d74042..344f7f03f 100644 --- a/apps/sim/db/schema.ts +++ b/apps/sim/db/schema.ts @@ -301,6 +301,7 @@ export const settings = pgTable('settings', { debugMode: boolean('debug_mode').notNull().default(false), autoConnect: boolean('auto_connect').notNull().default(true), autoFillEnvVars: boolean('auto_fill_env_vars').notNull().default(true), + autoPan: boolean('auto_pan').notNull().default(true), // Privacy settings telemetryEnabled: boolean('telemetry_enabled').notNull().default(true), diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index 211015c51..7c06d69b6 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -1043,7 +1043,7 @@ export class Executor { } }) - useExecutionStore.setState({ activeBlockIds }) + setActiveBlocks(activeBlockIds) const results = await Promise.all( blockIds.map((blockId) => this.executeBlock(blockId, context)) @@ -1058,7 +1058,7 @@ export class Executor { return results } catch (error) { // If there's an uncaught error, clear all active blocks as a safety measure - useExecutionStore.setState({ activeBlockIds: new Set() }) + setActiveBlocks(new Set()) throw error } } diff --git a/apps/sim/stores/execution/store.ts b/apps/sim/stores/execution/store.ts index ab09c84fa..5261bacba 100644 --- a/apps/sim/stores/execution/store.ts +++ b/apps/sim/stores/execution/store.ts @@ -1,14 +1,65 @@ import { create } from 'zustand' -import { type ExecutionActions, type ExecutionState, initialState } from './types' +import { useGeneralStore } from '@/stores/settings/general/store' +import { + type ExecutionActions, + type ExecutionState, + initialState, + type PanToBlockCallback, + type SetPanToBlockCallback, +} from './types' -export const useExecutionStore = create()((set) => ({ +// Global callback for panning to active blocks +let panToBlockCallback: PanToBlockCallback | null = null + +export const setPanToBlockCallback: SetPanToBlockCallback = (callback) => { + panToBlockCallback = callback +} + +export const useExecutionStore = create()((set, get) => ({ ...initialState, - setActiveBlocks: (blockIds) => set({ activeBlockIds: new Set(blockIds) }), - setIsExecuting: (isExecuting) => set({ isExecuting }), + setActiveBlocks: (blockIds) => { + set({ activeBlockIds: new Set(blockIds) }) + + // Pan to the first active block if auto-pan is enabled and we have a callback and blocks are active + const { autoPanDisabled } = get() + const isAutoPanEnabled = useGeneralStore.getState().isAutoPanEnabled + + if (panToBlockCallback && !autoPanDisabled && isAutoPanEnabled && blockIds.size > 0) { + const firstActiveBlockId = Array.from(blockIds)[0] + panToBlockCallback(firstActiveBlockId) + } + }, + + setPendingBlocks: (pendingBlocks) => { + set({ pendingBlocks }) + + // Pan to the first pending block if auto-pan is enabled, we have a callback, blocks are pending, and we're in debug mode + const { isDebugging, autoPanDisabled } = get() + const isAutoPanEnabled = useGeneralStore.getState().isAutoPanEnabled + + if ( + panToBlockCallback && + !autoPanDisabled && + isAutoPanEnabled && + pendingBlocks.length > 0 && + isDebugging + ) { + const firstPendingBlockId = pendingBlocks[0] + panToBlockCallback(firstPendingBlockId) + } + }, + + setIsExecuting: (isExecuting) => { + set({ isExecuting }) + // Reset auto-pan disabled state when starting execution + if (isExecuting) { + set({ autoPanDisabled: false }) + } + }, setIsDebugging: (isDebugging) => set({ isDebugging }), - setPendingBlocks: (pendingBlocks) => set({ pendingBlocks }), setExecutor: (executor) => set({ executor }), setDebugContext: (debugContext) => set({ debugContext }), + setAutoPanDisabled: (disabled) => set({ autoPanDisabled: disabled }), reset: () => set(initialState), })) diff --git a/apps/sim/stores/execution/types.ts b/apps/sim/stores/execution/types.ts index 6e387678e..48578d445 100644 --- a/apps/sim/stores/execution/types.ts +++ b/apps/sim/stores/execution/types.ts @@ -8,6 +8,7 @@ export interface ExecutionState { pendingBlocks: string[] executor: Executor | null debugContext: ExecutionContext | null + autoPanDisabled: boolean } export interface ExecutionActions { @@ -17,6 +18,7 @@ export interface ExecutionActions { setPendingBlocks: (blockIds: string[]) => void setExecutor: (executor: Executor | null) => void setDebugContext: (context: ExecutionContext | null) => void + setAutoPanDisabled: (disabled: boolean) => void reset: () => void } @@ -27,4 +29,9 @@ export const initialState: ExecutionState = { pendingBlocks: [], executor: null, debugContext: null, + autoPanDisabled: false, } + +// Types for panning functionality +export type PanToBlockCallback = (blockId: string) => void +export type SetPanToBlockCallback = (callback: PanToBlockCallback | null) => void diff --git a/apps/sim/stores/settings/general/store.ts b/apps/sim/stores/settings/general/store.ts index 7a526f9c3..573611e81 100644 --- a/apps/sim/stores/settings/general/store.ts +++ b/apps/sim/stores/settings/general/store.ts @@ -19,6 +19,7 @@ export const useGeneralStore = create()( isAutoConnectEnabled: true, isDebugModeEnabled: false, isAutoFillEnvVarsEnabled: true, + isAutoPanEnabled: true, theme: 'system', telemetryEnabled: true, telemetryNotifiedUser: false, @@ -44,6 +45,12 @@ export const useGeneralStore = create()( get().updateSetting('autoFillEnvVars', newValue) }, + toggleAutoPan: () => { + const newValue = !get().isAutoPanEnabled + set({ isAutoPanEnabled: newValue }) + get().updateSetting('autoPan', newValue) + }, + setTheme: (theme) => { set({ theme }) get().updateSetting('theme', theme) @@ -96,6 +103,7 @@ export const useGeneralStore = create()( isAutoConnectEnabled: data.autoConnect, isDebugModeEnabled: data.debugMode, isAutoFillEnvVarsEnabled: data.autoFillEnvVars, + isAutoPanEnabled: data.autoPan ?? true, // Default to true if undefined theme: data.theme, telemetryEnabled: data.telemetryEnabled, telemetryNotifiedUser: data.telemetryNotifiedUser, diff --git a/apps/sim/stores/settings/general/types.ts b/apps/sim/stores/settings/general/types.ts index 665f7eab8..2a6e6e1bb 100644 --- a/apps/sim/stores/settings/general/types.ts +++ b/apps/sim/stores/settings/general/types.ts @@ -2,6 +2,7 @@ export interface General { isAutoConnectEnabled: boolean isDebugModeEnabled: boolean isAutoFillEnvVarsEnabled: boolean + isAutoPanEnabled: boolean theme: 'system' | 'light' | 'dark' telemetryEnabled: boolean telemetryNotifiedUser: boolean @@ -13,6 +14,7 @@ export interface GeneralActions { toggleAutoConnect: () => void toggleDebugMode: () => void toggleAutoFillEnvVars: () => void + toggleAutoPan: () => void setTheme: (theme: 'system' | 'light' | 'dark') => void setTelemetryEnabled: (enabled: boolean) => void setTelemetryNotifiedUser: (notified: boolean) => void @@ -27,6 +29,7 @@ export type UserSettings = { debugMode: boolean autoConnect: boolean autoFillEnvVars: boolean + autoPan: boolean telemetryEnabled: boolean telemetryNotifiedUser: boolean } From 6e91b523ebb195b465a178983cb73c439eedc926 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 16:57:25 -0700 Subject: [PATCH 06/13] refactor: flattened output of blocks across entire codebase --- apps/docs/content/docs/blocks/workflow.mdx | 28 +- apps/sim/app/api/__test-utils__/utils.ts | 4 +- apps/sim/app/api/chat/utils.ts | 6 +- apps/sim/app/api/codegen/route.ts | 4 +- apps/sim/app/api/providers/route.ts | 32 +-- .../components/panel/components/chat/chat.tsx | 13 +- .../output-select/output-select.tsx | 50 +++- .../connection-blocks/connection-blocks.tsx | 122 ++------- .../workflow-block/workflow-block.tsx | 32 ++- .../app/w/[id]/hooks/use-block-connections.ts | 225 ++++++++------- .../w/[id]/hooks/use-workflow-execution.ts | 20 +- apps/sim/blocks/blocks/agent.ts | 30 +- apps/sim/blocks/blocks/airtable.ts | 10 +- apps/sim/blocks/blocks/api.ts | 10 +- apps/sim/blocks/blocks/autoblocks.ts | 12 +- apps/sim/blocks/blocks/browser_use.ts | 12 +- apps/sim/blocks/blocks/clay.ts | 6 +- apps/sim/blocks/blocks/condition.ts | 12 +- apps/sim/blocks/blocks/confluence.ts | 14 +- apps/sim/blocks/blocks/discord.ts | 8 +- apps/sim/blocks/blocks/elevenlabs.ts | 6 +- apps/sim/blocks/blocks/evaluator.ts | 26 +- apps/sim/blocks/blocks/exa.ts | 18 +- apps/sim/blocks/blocks/file.ts | 8 +- apps/sim/blocks/blocks/firecrawl.ts | 18 +- apps/sim/blocks/blocks/function.ts | 8 +- apps/sim/blocks/blocks/github.ts | 8 +- apps/sim/blocks/blocks/gmail.ts | 8 +- apps/sim/blocks/blocks/google.ts | 8 +- apps/sim/blocks/blocks/google_calendar.ts | 8 +- apps/sim/blocks/blocks/google_docs.ts | 10 +- apps/sim/blocks/blocks/google_drive.ts | 8 +- apps/sim/blocks/blocks/google_sheets.ts | 18 +- apps/sim/blocks/blocks/guesty.ts | 20 +- apps/sim/blocks/blocks/huggingface.ts | 10 +- apps/sim/blocks/blocks/image_generator.ts | 10 +- apps/sim/blocks/blocks/jina.ts | 6 +- apps/sim/blocks/blocks/jira.ts | 20 +- apps/sim/blocks/blocks/knowledge.ts | 10 +- apps/sim/blocks/blocks/linear.ts | 8 +- apps/sim/blocks/blocks/linkup.ts | 8 +- apps/sim/blocks/blocks/mem0.ts | 10 +- apps/sim/blocks/blocks/memory.ts | 8 +- apps/sim/blocks/blocks/microsoft_excel.ts | 20 +- apps/sim/blocks/blocks/microsoft_teams.ts | 10 +- apps/sim/blocks/blocks/mistral_parse.ts | 8 +- apps/sim/blocks/blocks/notion.ts | 8 +- apps/sim/blocks/blocks/openai.ts | 10 +- apps/sim/blocks/blocks/outlook.ts | 8 +- apps/sim/blocks/blocks/perplexity.ts | 10 +- apps/sim/blocks/blocks/pinecone.ts | 16 +- apps/sim/blocks/blocks/reddit.ts | 12 +- apps/sim/blocks/blocks/router.ts | 14 +- apps/sim/blocks/blocks/s3.ts | 8 +- apps/sim/blocks/blocks/serper.ts | 6 +- apps/sim/blocks/blocks/slack.ts | 8 +- apps/sim/blocks/blocks/stagehand.ts | 6 +- apps/sim/blocks/blocks/stagehand_agent.ts | 8 +- apps/sim/blocks/blocks/starter.ts | 6 +- apps/sim/blocks/blocks/supabase.ts | 8 +- apps/sim/blocks/blocks/tavily.ts | 16 +- apps/sim/blocks/blocks/telegram.ts | 8 +- apps/sim/blocks/blocks/thinking.ts | 6 +- apps/sim/blocks/blocks/translate.ts | 10 +- apps/sim/blocks/blocks/twilio.ts | 12 +- apps/sim/blocks/blocks/typeform.ts | 28 +- apps/sim/blocks/blocks/vision.ts | 10 +- apps/sim/blocks/blocks/whatsapp.ts | 10 +- apps/sim/blocks/blocks/workflow.ts | 14 +- apps/sim/blocks/blocks/x.ts | 20 +- apps/sim/blocks/blocks/youtube.ts | 8 +- apps/sim/blocks/types.ts | 23 +- apps/sim/blocks/utils.ts | 21 +- apps/sim/components/ui/tag-dropdown.test.tsx | 106 ++++---- apps/sim/components/ui/tag-dropdown.tsx | 126 ++++----- .../migrations/0046_cuddly_killer_shrike.sql | 1 - .../executor/__test-utils__/executor-mocks.ts | 42 ++- .../executor/__test-utils__/test-executor.ts | 4 +- .../handlers/agent/agent-handler.test.ts | 105 +++---- .../executor/handlers/agent/agent-handler.ts | 24 +- .../executor/handlers/api/api-handler.test.ts | 4 +- apps/sim/executor/handlers/api/api-handler.ts | 7 +- .../condition/condition-handler.test.ts | 58 ++-- .../handlers/condition/condition-handler.ts | 16 +- .../evaluator/evaluator-handler.test.ts | 43 ++- .../handlers/evaluator/evaluator-handler.ts | 26 +- .../function/function-handler.test.ts | 5 +- .../handlers/function/function-handler.ts | 5 +- .../handlers/generic/generic-handler.test.ts | 3 +- .../handlers/generic/generic-handler.ts | 5 +- .../handlers/loop/loop-handler.test.ts | 20 +- .../executor/handlers/loop/loop-handler.ts | 32 +-- .../parallel/parallel-handler.test.ts | 55 ++-- .../handlers/parallel/parallel-handler.ts | 62 ++--- .../handlers/router/router-handler.test.ts | 35 +-- .../handlers/router/router-handler.ts | 34 ++- .../workflow/workflow-handler.test.ts | 28 +- .../handlers/workflow/workflow-handler.ts | 26 +- apps/sim/executor/index.test.ts | 76 ++---- apps/sim/executor/index.ts | 257 +++--------------- apps/sim/executor/loops.test.ts | 39 ++- apps/sim/executor/loops.ts | 19 +- apps/sim/executor/parallels.test.ts | 2 +- apps/sim/executor/parallels.ts | 2 +- apps/sim/executor/path.test.ts | 16 +- apps/sim/executor/path.ts | 8 +- apps/sim/executor/resolver.test.ts | 44 +-- apps/sim/executor/resolver.ts | 7 +- apps/sim/executor/types.ts | 60 ++-- apps/sim/lib/logs/execution-logger.ts | 211 +++++--------- apps/sim/lib/logs/trace-spans.ts | 28 +- apps/sim/lib/uploads/s3/s3-client.test.ts | 18 +- apps/sim/lib/workflows/db-helpers.test.ts | 6 +- apps/sim/providers/anthropic/index.ts | 106 ++++---- apps/sim/providers/azure-openai/index.ts | 108 ++++---- apps/sim/providers/cerebras/index.ts | 106 ++++---- apps/sim/providers/deepseek/index.ts | 106 ++++---- apps/sim/providers/google/index.ts | 108 ++++---- apps/sim/providers/groq/index.ts | 104 ++++--- apps/sim/providers/ollama/index.ts | 4 - apps/sim/providers/openai/index.ts | 108 ++++---- apps/sim/providers/utils.test.ts | 36 --- apps/sim/providers/utils.ts | 61 +---- apps/sim/providers/xai/index.ts | 106 ++++---- apps/sim/stores/panel/console/store.test.ts | 34 ++- apps/sim/stores/panel/console/store.ts | 14 +- apps/sim/stores/workflows/registry/store.ts | 240 ++++++---------- .../stores/workflows/workflow/utils.test.ts | 4 +- 128 files changed, 1644 insertions(+), 2530 deletions(-) delete mode 100644 apps/sim/db/migrations/0046_cuddly_killer_shrike.sql diff --git a/apps/docs/content/docs/blocks/workflow.mdx b/apps/docs/content/docs/blocks/workflow.mdx index f45e0ce41..3724cb7e4 100644 --- a/apps/docs/content/docs/blocks/workflow.mdx +++ b/apps/docs/content/docs/blocks/workflow.mdx @@ -66,17 +66,17 @@ Define the data to pass to the child workflow: - **Single Variable Input**: Select a variable or block output to pass to the child workflow - **Variable References**: Use `` to reference workflow variables -- **Block References**: Use `` to reference outputs from previous blocks -- **Automatic Mapping**: The selected data is automatically available as `start.response.input` in the child workflow +- **Block References**: Use `` to reference outputs from previous blocks +- **Automatic Mapping**: The selected data is automatically available as `start.input` in the child workflow - **Optional**: The input field is optional - child workflows can run without input data - **Type Preservation**: Variable types (strings, numbers, objects, etc.) are preserved when passed to the child workflow ### Examples of Input References - `` - Pass a workflow variable -- `` - Pass the result from a previous block -- `` - Pass the original workflow input -- `` - Pass a specific field from an API response +- `` - Pass the result from a previous block +- `` - Pass the original workflow input +- `` - Pass a specific field from an API response ### Execution Context @@ -109,7 +109,7 @@ To prevent infinite recursion and ensure system stability, the Workflow block in Workflow ID: The identifier of the workflow to execute
  • - Input Variable: Variable or block reference to pass to the child workflow (e.g., `` or ``) + Input Variable: Variable or block reference to pass to the child workflow (e.g., `` or ``)
  • @@ -150,23 +150,23 @@ blocks: - type: workflow name: "Setup Customer Account" workflowId: "account-setup-workflow" - input: "" + input: "" - type: workflow name: "Send Welcome Email" workflowId: "welcome-email-workflow" - input: "" + input: "" ``` ### Child Workflow: Customer Validation ```yaml # Reusable customer validation workflow -# Access the input data using: start.response.input +# Access the input data using: start.input blocks: - type: function name: "Validate Email" code: | - const customerData = start.response.input; + const customerData = start.input; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(customerData.email); @@ -174,7 +174,7 @@ blocks: name: "Check Credit Score" url: "https://api.creditcheck.com/score" method: "POST" - body: "" + body: "" ``` ### Variable Reference Examples @@ -184,13 +184,13 @@ blocks: input: "" # Using block outputs -input: "" + input: "" # Using nested object properties -input: "" + input: "" # Using array elements (if supported by the resolver) -input: "" + input: "" ``` ## Access Control and Permissions diff --git a/apps/sim/app/api/__test-utils__/utils.ts b/apps/sim/app/api/__test-utils__/utils.ts index a93d240de..be9fac6ad 100644 --- a/apps/sim/app/api/__test-utils__/utils.ts +++ b/apps/sim/app/api/__test-utils__/utils.ts @@ -93,7 +93,7 @@ export const sampleWorkflowState = { webhookPath: { id: 'webhookPath', type: 'short-input', value: '' }, }, outputs: { - response: { type: { input: 'any' } }, + input: 'any', }, enabled: true, horizontalHandles: true, @@ -111,7 +111,7 @@ export const sampleWorkflowState = { type: 'long-input', value: 'You are a helpful assistant', }, - context: { id: 'context', type: 'short-input', value: '' }, + context: { id: 'context', type: 'short-input', value: '' }, model: { id: 'model', type: 'dropdown', value: 'gpt-4o' }, apiKey: { id: 'apiKey', type: 'short-input', value: '{{OPENAI_API_KEY}}' }, }, diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index 99bad6f89..dec6c5862 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -211,7 +211,7 @@ export async function validateChatAuth( /** * Executes a workflow for a chat request and returns the formatted output. * - * When workflows reference , they receive a structured JSON + * When workflows reference , they receive a structured JSON * containing both the message and conversationId for maintaining chat context. * * @param chatId - Chat deployment identifier @@ -461,8 +461,8 @@ export async function executeWorkflowForChat( if (result && 'success' in result) { result.logs?.forEach((log: BlockLog) => { if (streamedContent.has(log.blockId)) { - if (log.output?.response) { - log.output.response.content = streamedContent.get(log.blockId) + if (log.output) { + log.output.content = streamedContent.get(log.blockId) } } }) diff --git a/apps/sim/app/api/codegen/route.ts b/apps/sim/app/api/codegen/route.ts index 49aabdaec..71e8ff6f8 100644 --- a/apps/sim/app/api/codegen/route.ts +++ b/apps/sim/app/api/codegen/route.ts @@ -239,7 +239,7 @@ Example Scenario: User Prompt: "Fetch user data from an API. Use the User ID passed in as 'userId' and an API Key stored as the 'SERVICE_API_KEY' environment variable." Generated Code: -const userId = ; // Correct: Accessing input parameter without quotes +const userId = ; // Correct: Accessing input parameter without quotes const apiKey = {{SERVICE_API_KEY}}; // Correct: Accessing environment variable without quotes const url = \`https://api.example.com/users/\${userId}\`; @@ -273,7 +273,7 @@ Do not include import/require statements unless absolutely necessary and they ar Do not include markdown formatting or explanations. Output only the raw TypeScript code. Use modern TypeScript features where appropriate. Do not use semicolons. Example: -const userId = as string +const userId = as string const apiKey = {{SERVICE_API_KEY}} const response = await fetch(\`https://api.example.com/users/\${userId}\`, { headers: { Authorization: \`Bearer \${apiKey}\` } }) if (!response.ok) { diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index 5f116bc9c..f0962d1ba 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -137,24 +137,22 @@ export async function POST(request: NextRequest) { const safeExecutionData = { success: executionData.success, output: { - response: { - // Sanitize content to remove non-ASCII characters that would cause ByteString errors - content: executionData.output?.response?.content - ? String(executionData.output.response.content).replace(/[\u0080-\uFFFF]/g, '') - : '', - model: executionData.output?.response?.model, - tokens: executionData.output?.response?.tokens || { - prompt: 0, - completion: 0, - total: 0, - }, - // Sanitize any potential Unicode characters in tool calls - toolCalls: executionData.output?.response?.toolCalls - ? sanitizeToolCalls(executionData.output.response.toolCalls) - : undefined, - providerTiming: executionData.output?.response?.providerTiming, - cost: executionData.output?.response?.cost, + // Sanitize content to remove non-ASCII characters that would cause ByteString errors + content: executionData.output?.content + ? String(executionData.output.content).replace(/[\u0080-\uFFFF]/g, '') + : '', + model: executionData.output?.model, + tokens: executionData.output?.tokens || { + prompt: 0, + completion: 0, + total: 0, }, + // Sanitize any potential Unicode characters in tool calls + toolCalls: executionData.output?.toolCalls + ? sanitizeToolCalls(executionData.output.toolCalls) + : undefined, + providerTiming: executionData.output?.providerTiming, + cost: executionData.output?.cost, }, error: executionData.error, logs: [], // Strip logs from header to avoid encoding issues diff --git a/apps/sim/app/w/[id]/components/panel/components/chat/chat.tsx b/apps/sim/app/w/[id]/components/panel/components/chat/chat.tsx index 9bb91ad2a..a599c7d54 100644 --- a/apps/sim/app/w/[id]/components/panel/components/chat/chat.tsx +++ b/apps/sim/app/w/[id]/components/panel/components/chat/chat.tsx @@ -258,17 +258,12 @@ export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) { if (typeof output === 'string') { content = output } else if (output && typeof output === 'object') { - // Handle cases where output is { response: ... } + // Handle flattened output structure const outputObj = output as Record - const response = outputObj.response - if (response) { - if (typeof response.content === 'string') { - content = response.content - } else { - // Pretty print for better readability - content = `\`\`\`json\n${JSON.stringify(response, null, 2)}\n\`\`\`` - } + if (typeof outputObj.content === 'string') { + content = outputObj.content } else { + // Pretty print for better readability content = `\`\`\`json\n${JSON.stringify(output, null, 2)}\n\`\`\`` } } diff --git a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx index c3642e0d3..bd349f493 100644 --- a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx @@ -53,13 +53,45 @@ export function OutputSelect({ const addOutput = (path: string, outputObj: any, prefix = '') => { const fullPath = prefix ? `${prefix}.${path}` : path - if (typeof outputObj === 'object' && outputObj !== null) { - // For objects, recursively add each property + // If not an object or is null, treat as leaf node + if (typeof outputObj !== 'object' || outputObj === null) { + const output = { + id: `${block.id}_${fullPath}`, + label: `${blockName}.${fullPath}`, + blockId: block.id, + blockName: block.name || `Block ${block.id}`, + blockType: block.type, + path: fullPath, + } + outputs.push(output) + return + } + + // If has 'type' property, treat as schema definition (leaf node) + if ('type' in outputObj && typeof outputObj.type === 'string') { + const output = { + id: `${block.id}_${fullPath}`, + label: `${blockName}.${fullPath}`, + blockId: block.id, + blockName: block.name || `Block ${block.id}`, + blockType: block.type, + path: fullPath, + } + outputs.push(output) + return + } + + // For objects without type, recursively add each property + if (!Array.isArray(outputObj)) { Object.entries(outputObj).forEach(([key, value]) => { + // Skip configuration properties that shouldn't be available as outputs + if (key === 'dependsOn') { + return + } addOutput(key, value, fullPath) }) } else { - // Add leaf node as output option + // For arrays, treat as leaf node outputs.push({ id: `${block.id}_${fullPath}`, label: `${blockName}.${fullPath}`, @@ -71,10 +103,14 @@ export function OutputSelect({ } } - // Start with the response object - if (block.outputs.response) { - addOutput('response', block.outputs.response) - } + // Process all output properties directly (flattened structure) + Object.entries(block.outputs).forEach(([key, value]) => { + // Skip configuration properties that shouldn't be available as outputs + if (key === 'dependsOn') { + return + } + addOutput(key, value) + }) } }) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx index 733127b1b..1d93c3d2c 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx @@ -2,7 +2,6 @@ import { Card } from '@/components/ui/card' import { cn } from '@/lib/utils' import { type ConnectedBlock, useBlockConnections } from '@/app/w/[id]/hooks/use-block-connections' import { getBlock } from '@/blocks' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' interface ConnectionBlocksProps { blockId: string @@ -19,13 +18,8 @@ interface ResponseField { export function ConnectionBlocks({ blockId, -<<<<<<< HEAD horizontalHandles, setIsConnecting, isDisabled = false, -======= - horizontalHandles, - setIsConnecting, ->>>>>>> 0ebc0b67 (fix: truncating workflow block name) }: ConnectionBlocksProps) { const { incomingConnections, hasIncomingConnections } = useBlockConnections(blockId) @@ -43,6 +37,10 @@ export function ConnectionBlocks({ e.stopPropagation() // Prevent parent drag handlers from firing setIsConnecting(true) + + // If no specific field is provided, use all available output types + const outputType = field ? field.name : connection.outputType + e.dataTransfer.setData( 'application/json', JSON.stringify({ @@ -50,9 +48,13 @@ export function ConnectionBlocks({ connectionData: { id: connection.id, name: connection.name, - outputType: field ? field.name : connection.outputType, + outputType: outputType, sourceBlockId: connection.id, fieldType: field?.type, + // Include all available output types for reference + allOutputTypes: Array.isArray(connection.outputType) + ? connection.outputType + : [connection.outputType], }, }) ) @@ -63,74 +65,11 @@ export function ConnectionBlocks({ setIsConnecting(false) } - // Helper function to extract fields from JSON Schema - const extractFieldsFromSchema = (connection: ConnectedBlock): ResponseField[] => { - // Handle legacy format with fields array - if (connection.responseFormat?.fields) { - return connection.responseFormat.fields - } - - // Handle new JSON Schema format - const schema = connection.responseFormat?.schema || connection.responseFormat - // Safely check if schema and properties exist - if ( - !schema || - typeof schema !== 'object' || - !('properties' in schema) || - typeof schema.properties !== 'object' - ) { - return [] - } - return Object.entries(schema.properties).map(([name, prop]: [string, any]) => ({ - name, - type: Array.isArray(prop) ? 'array' : prop.type || 'string', - description: prop.description, - })) - } - - // Extract fields from starter block input format - const extractFieldsFromStarterInput = (connection: ConnectedBlock): ResponseField[] => { - // Only process for starter blocks - if (connection.type !== 'starter') return [] - - try { - // Get input format from subblock store - const inputFormat = useSubBlockStore.getState().getValue(connection.id, 'inputFormat') - - // Make sure we have a valid input format - if (!inputFormat || !Array.isArray(inputFormat) || inputFormat.length === 0) { - return [{ name: 'input', type: 'any' }] - } - - // Check if any fields have been configured with names - const hasConfiguredFields = inputFormat.some( - (field: any) => field.name && field.name.trim() !== '' - ) - - // If no fields have been configured, return the default input field - if (!hasConfiguredFields) { - return [{ name: 'input', type: 'any' }] - } - - // Map input fields to response fields - return inputFormat.map((field: any) => ({ - name: `input.${field.name}`, - type: field.type || 'string', - description: field.description, - })) - } catch (e) { - console.error('Error extracting fields from starter input format:', e) - return [{ name: 'input', type: 'any' }] - } - } - - // Use connections in distance order (already sorted by the hook) - const sortedConnections = incomingConnections.filter( - (connection, index, arr) => arr.findIndex((c) => c.id === connection.id) === index - ) + // Use connections in distance order (already sorted and deduplicated by the hook) + const sortedConnections = incomingConnections // Helper function to render a connection card - const renderConnectionCard = (connection: ConnectedBlock, field?: ResponseField) => { + const renderConnectionCard = (connection: ConnectedBlock) => { // Get block configuration for icon and color const blockConfig = getBlock(connection.type) const displayName = connection.name // Use the actual block name instead of transforming it @@ -139,9 +78,9 @@ export function ConnectionBlocks({ return ( handleDragStart(e, connection, field)} + onDragStart={(e) => handleDragStart(e, connection)} onDragEnd={handleDragEnd} className={cn( 'group flex w-max items-center gap-2 rounded-lg border bg-card p-2 shadow-sm transition-colors', @@ -166,38 +105,11 @@ export function ConnectionBlocks({ ) } - // Generate all connection cards + // Generate all connection cards - one per block, not per output field const connectionCards: React.ReactNode[] = [] - sortedConnections.forEach((connection, index) => { - // Special handling for starter blocks with input format - if (connection.type === 'starter') { - const starterFields = extractFieldsFromStarterInput(connection) - - if (starterFields.length > 0) { - starterFields.forEach((field) => { - connectionCards.push(renderConnectionCard(connection, field)) - }) - return - } - } - - // Regular connection handling - if (Array.isArray(connection.outputType)) { - // Handle array of field names - connection.outputType.forEach((fieldName) => { - // Try to find field in response format - const fields = extractFieldsFromSchema(connection) - const field = fields.find((f) => f.name === fieldName) || { - name: fieldName, - type: 'string', - } - - connectionCards.push(renderConnectionCard(connection, field)) - }) - } else { - connectionCards.push(renderConnectionCard(connection)) - } + sortedConnections.forEach((connection) => { + connectionCards.push(renderConnectionCard(connection)) }) // Position and layout based on handle orientation - reverse of ports diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index e260eefb8..68ff22ad9 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -674,21 +674,25 @@ export function WorkflowBlock({ id, data }: NodeProps) { {Object.entries(config.outputs).map(([key, value]) => (
    {key}{' '} - {typeof value.type === 'object' ? ( -
    - {Object.entries(value.type).map(([typeKey, typeValue]) => ( -
    - - {typeKey}: - - - {typeValue as string} - -
    - ))} -
    + {typeof value === 'object' && value !== null && 'type' in value ? ( + typeof value.type === 'object' ? ( +
    + {Object.entries(value.type).map(([typeKey, typeValue]) => ( +
    + + {typeKey}: + + + {typeValue as string} + +
    + ))} +
    + ) : ( + {value.type as string} + ) ) : ( - {value.type as string} + {value as string} )}
    ))} diff --git a/apps/sim/app/w/[id]/hooks/use-block-connections.ts b/apps/sim/app/w/[id]/hooks/use-block-connections.ts index 4a9b238b4..3f0721423 100644 --- a/apps/sim/app/w/[id]/hooks/use-block-connections.ts +++ b/apps/sim/app/w/[id]/hooks/use-block-connections.ts @@ -17,9 +17,6 @@ export interface ConnectedBlock { outputType: string | string[] name: string responseFormat?: { - // Support both formats - fields?: Field[] - name?: string schema?: { type: string properties: Record @@ -28,29 +25,92 @@ export interface ConnectedBlock { } } -// Helper function to extract fields from JSON Schema -function extractFieldsFromSchema(schema: any): Field[] { - if (!schema || typeof schema !== 'object') { +function extractFieldsFromSchema(responseFormat: any): Field[] { + if (!responseFormat) return [] + + const schema = responseFormat.schema || responseFormat + if ( + !schema || + typeof schema !== 'object' || + !('properties' in schema) || + typeof schema.properties !== 'object' || + schema.properties === null + ) { return [] } - // Handle legacy format with fields array - if (Array.isArray(schema.fields)) { - return schema.fields - } + return Object.entries(schema.properties).map(([name, prop]: [string, any]) => ({ + name, + type: Array.isArray(prop) ? 'array' : prop.type || 'string', + description: prop.description, + })) +} - // Handle new JSON Schema format - const schemaObj = schema.schema || schema - if (!schemaObj || !schemaObj.properties || typeof schemaObj.properties !== 'object') { - return [] +/** + * Creates a ConnectedBlock object from a block with proper response format handling + */ +function createConnectedBlock(sourceBlock: any, sourceId: string): ConnectedBlock | null { + if (!sourceBlock) return null + + // Get the response format from the subblock store + const responseFormatValue = useSubBlockStore.getState().getValue(sourceId, 'responseFormat') + + let responseFormat + + try { + responseFormat = + typeof responseFormatValue === 'string' && responseFormatValue + ? JSON.parse(responseFormatValue) + : responseFormatValue // Handle case where it's already an object + } catch (e) { + logger.error('Failed to parse response format:', { e }) + responseFormat = undefined } - // Extract fields from schema properties - return Object.entries(schemaObj.properties).map(([name, prop]: [string, any]) => ({ - name, - type: prop.type || 'string', - description: prop.description, + // Get the default output type from the block's outputs + const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({ + name: key, + type: 'string', })) + + // Extract fields from the response format using our helper function + const outputFields = responseFormat ? extractFieldsFromSchema(responseFormat) : defaultOutputs + + return { + id: sourceBlock.id, + type: sourceBlock.type, + outputType: outputFields.map((field: Field) => field.name), + name: sourceBlock.name, + responseFormat, + } +} + +/** + * Merges multiple ConnectedBlock instances for the same block ID + */ +function mergeConnectedBlocks(blocks: ConnectedBlock[]): ConnectedBlock { + if (blocks.length === 1) return blocks[0] + + const firstBlock = blocks[0] + const allOutputTypes = new Set() + + // Collect all unique output types from all instances + blocks.forEach((block) => { + if (Array.isArray(block.outputType)) { + block.outputType.forEach((type) => allOutputTypes.add(type)) + } else if (block.outputType) { + allOutputTypes.add(block.outputType) + } + }) + + // Use the response format from the first block that has one, or merge if needed + const responseFormat = blocks.find((block) => block.responseFormat)?.responseFormat + + return { + ...firstBlock, + outputType: Array.from(allOutputTypes), + responseFormat, + } } /** @@ -65,11 +125,9 @@ function findAllPathNodes( edges: any[], targetNodeId: string ): Array<{ nodeId: string; distance: number }> { - // We'll use a reverse topological sort approach by tracking "distance" from target const nodeDistances = new Map() const visited = new Set() const queue: [string, number][] = [[targetNodeId, 0]] // [nodeId, distance] - const pathNodes = new Set() // Build a reverse adjacency list for faster traversal const reverseAdjList: Record = {} @@ -96,11 +154,6 @@ function findAllPathNodes( visited.add(currentNodeId) nodeDistances.set(currentNodeId, distance) - // Don't add the target node itself to the results - if (currentNodeId !== targetNodeId) { - pathNodes.add(currentNodeId) - } - // Get all incoming edges from the reverse adjacency list const incomingNodeIds = reverseAdjList[currentNodeId] || [] @@ -110,8 +163,10 @@ function findAllPathNodes( } } - // Return nodes sorted by distance (closest first) - return Array.from(pathNodes) + // Remove the target node itself and return sorted by distance (closest first) + visited.delete(targetNodeId) + + return Array.from(visited) .map((nodeId) => ({ nodeId, distance: nodeDistances.get(nodeId) || 0 })) .sort((a, b) => a.distance - b.distance) } @@ -128,90 +183,54 @@ export function useBlockConnections(blockId: string) { // Find all blocks along paths leading to this block const allPathNodes = findAllPathNodes(edges, blockId) - // Map each path node to a ConnectedBlock structure - const allPathConnections = allPathNodes - .map(({ nodeId: sourceId }) => { - const sourceBlock = blocks[sourceId] - if (!sourceBlock) return null - - // Get the response format from the subblock store - const responseFormatValue = useSubBlockStore.getState().getValue(sourceId, 'responseFormat') - - let responseFormat - - try { - responseFormat = - typeof responseFormatValue === 'string' && responseFormatValue - ? JSON.parse(responseFormatValue) - : responseFormatValue // Handle case where it's already an object - } catch (e) { - logger.error('Failed to parse response format:', { e }) - responseFormat = undefined - } + // Create ConnectedBlock objects for all path nodes + const pathConnections = allPathNodes + .map(({ nodeId: sourceId }) => createConnectedBlock(blocks[sourceId], sourceId)) + .filter(Boolean) as ConnectedBlock[] - // Get the default output type from the block's outputs - const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({ - name: key, - type: 'string', - })) - - // Extract fields from the response format using our helper function - const outputFields = responseFormat ? extractFieldsFromSchema(responseFormat) : defaultOutputs - - return { - id: sourceBlock.id, - type: sourceBlock.type, - outputType: outputFields.map((field: Field) => field.name), - name: sourceBlock.name, - responseFormat, - } + // Deduplicate and merge blocks with the same ID + const connectionMap = new Map() + + pathConnections.forEach((connection) => { + if (!connectionMap.has(connection.id)) { + connectionMap.set(connection.id, []) + } + connectionMap.get(connection.id)!.push(connection) + }) + + // Merge blocks with the same ID and sort by distance + const allPathConnections = Array.from(connectionMap.entries()) + .map(([blockId, blockInstances]) => mergeConnectedBlocks(blockInstances)) + .sort((a, b) => { + // Sort by distance (maintain original order based on first occurrence) + const aDistance = allPathNodes.find((node) => node.nodeId === a.id)?.distance || 0 + const bDistance = allPathNodes.find((node) => node.nodeId === b.id)?.distance || 0 + return aDistance - bDistance }) - .filter(Boolean) as ConnectedBlock[] - // Keep the original incoming connections for compatibility + // Keep the original direct incoming connections for compatibility const directIncomingConnections = edges .filter((edge) => edge.target === blockId) - .map((edge) => { - const sourceBlock = blocks[edge.source] - - // Get the response format from the subblock store instead - const responseFormatValue = useSubBlockStore - .getState() - .getValue(edge.source, 'responseFormat') - - let responseFormat - - try { - responseFormat = - typeof responseFormatValue === 'string' && responseFormatValue - ? JSON.parse(responseFormatValue) - : responseFormatValue // Handle case where it's already an object - } catch (e) { - logger.error('Failed to parse response format:', { e }) - responseFormat = undefined - } + .map((edge) => createConnectedBlock(blocks[edge.source], edge.source)) + .filter(Boolean) as ConnectedBlock[] - // Get the default output type from the block's outputs - const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({ - name: key, - type: 'string', - })) - - // Extract fields from the response format using our helper function - const outputFields = responseFormat ? extractFieldsFromSchema(responseFormat) : defaultOutputs - - return { - id: sourceBlock.id, - type: sourceBlock.type, - outputType: outputFields.map((field: Field) => field.name), - name: sourceBlock.name, - responseFormat, - } - }) + // Deduplicate direct connections as well + const directConnectionMap = new Map() + + directIncomingConnections.forEach((connection) => { + if (!directConnectionMap.has(connection.id)) { + directConnectionMap.set(connection.id, []) + } + directConnectionMap.get(connection.id)!.push(connection) + }) + + const deduplicatedDirectConnections = Array.from(directConnectionMap.entries()).map( + ([blockId, blockInstances]) => mergeConnectedBlocks(blockInstances) + ) return { incomingConnections: allPathConnections, - directIncomingConnections, + directIncomingConnections: deduplicatedDirectConnections, hasIncomingConnections: allPathConnections.length > 0, } } diff --git a/apps/sim/app/w/[id]/hooks/use-workflow-execution.ts b/apps/sim/app/w/[id]/hooks/use-workflow-execution.ts index 31e399410..84503f56f 100644 --- a/apps/sim/app/w/[id]/hooks/use-workflow-execution.ts +++ b/apps/sim/app/w/[id]/hooks/use-workflow-execution.ts @@ -82,9 +82,9 @@ export function useWorkflowExecution() { } // If this was a streaming response and we have the final content, update it - if (streamContent && result.output?.response && typeof streamContent === 'string') { + if (streamContent && result.output && typeof streamContent === 'string') { // Update the content with the final streaming content - enrichedResult.output.response.content = streamContent + enrichedResult.output.content = streamContent // Also update any block logs to include the content where appropriate if (enrichedResult.logs) { @@ -97,9 +97,9 @@ export function useWorkflowExecution() { if ( isStreamingBlock && (log.blockType === 'agent' || log.blockType === 'router') && - log.output?.response + log.output ) { - log.output.response.content = streamContent + log.output.content = streamContent } } } @@ -215,8 +215,8 @@ export function useWorkflowExecution() { result.logs?.forEach((log: BlockLog) => { if (streamedContent.has(log.blockId)) { const content = streamedContent.get(log.blockId) || '' - if (log.output?.response) { - log.output.response.content = content + if (log.output) { + log.output.content = content } useConsoleStore.getState().updateConsole(log.blockId, content) } @@ -437,7 +437,7 @@ export function useWorkflowExecution() { const errorResult: ExecutionResult = { success: false, - output: { response: {} }, + output: {}, error: errorMessage, logs: [], } @@ -560,7 +560,7 @@ export function useWorkflowExecution() { // Create error result const errorResult = { success: false, - output: { response: {} }, + output: {}, error: errorMessage, logs: debugContext.blockLogs, } @@ -647,7 +647,7 @@ export function useWorkflowExecution() { let currentResult: ExecutionResult = { success: true, - output: { response: {} }, + output: {}, logs: debugContext.blockLogs, } @@ -743,7 +743,7 @@ export function useWorkflowExecution() { // Create error result const errorResult = { success: false, - output: { response: {} }, + output: {}, error: errorMessage, logs: debugContext.blockLogs, } diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index 44f950300..a811301ea 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -304,24 +304,20 @@ export const AgentBlock: BlockConfig = { tools: { type: 'json', required: false }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - tokens: 'any', - toolCalls: 'any', - }, - dependsOn: { - subBlockId: 'responseFormat', - condition: { - whenEmpty: { - content: 'string', - model: 'string', - tokens: 'any', - toolCalls: 'any', - }, - whenFilled: 'json', + content: 'string', + model: 'string', + tokens: 'any', + toolCalls: 'any', + dependsOn: { + subBlockId: 'responseFormat', + condition: { + whenEmpty: { + content: 'string', + model: 'string', + tokens: 'any', + toolCalls: 'any', }, + whenFilled: 'json', }, }, }, diff --git a/apps/sim/blocks/blocks/airtable.ts b/apps/sim/blocks/blocks/airtable.ts index c2a2b9c59..676c69d3e 100644 --- a/apps/sim/blocks/blocks/airtable.ts +++ b/apps/sim/blocks/blocks/airtable.ts @@ -179,12 +179,8 @@ export const AirtableBlock: BlockConfig = { }, // Output structure depends on the operation, covered by AirtableResponse union type outputs: { - response: { - type: { - records: 'json', // Optional: for list, create, updateMultiple - record: 'json', // Optional: for get, update single - metadata: 'json', // Required: present in all responses - }, - }, + records: 'json', // Optional: for list, create, updateMultiple + record: 'json', // Optional: for get, update single + metadata: 'json', // Required: present in all responses }, } diff --git a/apps/sim/blocks/blocks/api.ts b/apps/sim/blocks/blocks/api.ts index f6fd424eb..68dbaca29 100644 --- a/apps/sim/blocks/blocks/api.ts +++ b/apps/sim/blocks/blocks/api.ts @@ -62,12 +62,8 @@ export const ApiBlock: BlockConfig = { params: { type: 'json', required: false }, }, outputs: { - response: { - type: { - data: 'any', - status: 'number', - headers: 'json', - }, - }, + data: 'any', + status: 'number', + headers: 'json', }, } diff --git a/apps/sim/blocks/blocks/autoblocks.ts b/apps/sim/blocks/blocks/autoblocks.ts index bd87f186e..502585c33 100644 --- a/apps/sim/blocks/blocks/autoblocks.ts +++ b/apps/sim/blocks/blocks/autoblocks.ts @@ -112,13 +112,9 @@ export const AutoblocksBlock: BlockConfig = { environment: { type: 'string', required: true }, }, outputs: { - response: { - type: { - promptId: 'string', - version: 'string', - renderedPrompt: 'string', - templates: 'json', - }, - }, + promptId: 'string', + version: 'string', + renderedPrompt: 'string', + templates: 'json', }, } diff --git a/apps/sim/blocks/blocks/browser_use.ts b/apps/sim/blocks/blocks/browser_use.ts index e713b3221..33bc2feab 100644 --- a/apps/sim/blocks/blocks/browser_use.ts +++ b/apps/sim/blocks/blocks/browser_use.ts @@ -76,13 +76,9 @@ export const BrowserUseBlock: BlockConfig = { save_browser_data: { type: 'boolean', required: false }, }, outputs: { - response: { - type: { - id: 'string', - success: 'boolean', - output: 'any', - steps: 'json', - }, - }, + id: 'string', + success: 'boolean', + output: 'any', + steps: 'json', }, } diff --git a/apps/sim/blocks/blocks/clay.ts b/apps/sim/blocks/blocks/clay.ts index 646cfd3ba..092ce98aa 100644 --- a/apps/sim/blocks/blocks/clay.ts +++ b/apps/sim/blocks/blocks/clay.ts @@ -50,10 +50,6 @@ Plain Text: Best for populating a table in free-form style. data: { type: 'json', required: true }, }, outputs: { - response: { - type: { - data: 'any', - }, - }, + data: 'any', }, } diff --git a/apps/sim/blocks/blocks/condition.ts b/apps/sim/blocks/blocks/condition.ts index 944d5c753..91601094d 100644 --- a/apps/sim/blocks/blocks/condition.ts +++ b/apps/sim/blocks/blocks/condition.ts @@ -37,13 +37,9 @@ export const ConditionBlock: BlockConfig = { }, inputs: {}, outputs: { - response: { - type: { - content: 'string', - conditionResult: 'boolean', - selectedPath: 'json', - selectedConditionId: 'string', - }, - }, + content: 'string', + conditionResult: 'boolean', + selectedPath: 'json', + selectedConditionId: 'string', }, } diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index b3c0d13fa..c6bfd743c 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -109,14 +109,10 @@ export const ConfluenceBlock: BlockConfig = { content: { type: 'string', required: false }, }, outputs: { - response: { - type: { - ts: 'string', - pageId: 'string', - content: 'string', - title: 'string', - success: 'boolean', - }, - }, + ts: 'string', + pageId: 'string', + content: 'string', + title: 'string', + success: 'boolean', }, } diff --git a/apps/sim/blocks/blocks/discord.ts b/apps/sim/blocks/blocks/discord.ts index 38862fee7..9206c2a6c 100644 --- a/apps/sim/blocks/blocks/discord.ts +++ b/apps/sim/blocks/blocks/discord.ts @@ -149,11 +149,7 @@ export const DiscordBlock: BlockConfig = { userId: { type: 'string', required: false }, }, outputs: { - response: { - type: { - message: 'string', - data: 'any', - }, - }, + message: 'string', + data: 'any', }, } diff --git a/apps/sim/blocks/blocks/elevenlabs.ts b/apps/sim/blocks/blocks/elevenlabs.ts index 39f7cd449..61fe71c5f 100644 --- a/apps/sim/blocks/blocks/elevenlabs.ts +++ b/apps/sim/blocks/blocks/elevenlabs.ts @@ -39,11 +39,7 @@ export const ElevenLabsBlock: BlockConfig = { }, outputs: { - response: { - type: { - audioUrl: 'string', - }, - }, + audioUrl: 'string', }, subBlocks: [ diff --git a/apps/sim/blocks/blocks/evaluator.ts b/apps/sim/blocks/blocks/evaluator.ts index a10dfd409..218629145 100644 --- a/apps/sim/blocks/blocks/evaluator.ts +++ b/apps/sim/blocks/blocks/evaluator.ts @@ -307,25 +307,9 @@ export const EvaluatorBlock: BlockConfig = { content: { type: 'string' as ParamType, required: true }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - tokens: 'any', - cost: 'any', - }, - dependsOn: { - subBlockId: 'metrics', - condition: { - whenEmpty: { - content: 'string', - model: 'string', - tokens: 'any', - cost: 'any', - }, - whenFilled: 'json', - }, - }, - }, - }, + content: 'string', + model: 'string', + tokens: 'any', + cost: 'any', + } as any, } diff --git a/apps/sim/blocks/blocks/exa.ts b/apps/sim/blocks/blocks/exa.ts index 6e7ad1b97..754d3ea2d 100644 --- a/apps/sim/blocks/blocks/exa.ts +++ b/apps/sim/blocks/blocks/exa.ts @@ -190,16 +190,12 @@ export const ExaBlock: BlockConfig = { url: { type: 'string', required: false }, }, outputs: { - response: { - type: { - // Search output - results: 'json', - // Find Similar Links output - similarLinks: 'json', - // Answer output - answer: 'string', - citations: 'json', - }, - }, + // Search output + results: 'json', + // Find Similar Links output + similarLinks: 'json', + // Answer output + answer: 'string', + citations: 'json', }, } diff --git a/apps/sim/blocks/blocks/file.ts b/apps/sim/blocks/blocks/file.ts index 97d10b8ac..bee1f381e 100644 --- a/apps/sim/blocks/blocks/file.ts +++ b/apps/sim/blocks/blocks/file.ts @@ -130,11 +130,7 @@ export const FileBlock: BlockConfig = { file: { type: 'json', required: false }, }, outputs: { - response: { - type: { - files: 'json', - combinedContent: 'string', - }, - }, + files: 'json', + combinedContent: 'string', }, } diff --git a/apps/sim/blocks/blocks/firecrawl.ts b/apps/sim/blocks/blocks/firecrawl.ts index 20c0ab17e..3eb3213cf 100644 --- a/apps/sim/blocks/blocks/firecrawl.ts +++ b/apps/sim/blocks/blocks/firecrawl.ts @@ -90,16 +90,12 @@ export const FirecrawlBlock: BlockConfig = { scrapeOptions: { type: 'json', required: false }, }, outputs: { - response: { - type: { - // Scrape output - markdown: 'string', - html: 'any', - metadata: 'json', - // Search output - data: 'json', - warning: 'any', - }, - }, + // Scrape output + markdown: 'string', + html: 'any', + metadata: 'json', + // Search output + data: 'json', + warning: 'any', }, } diff --git a/apps/sim/blocks/blocks/function.ts b/apps/sim/blocks/blocks/function.ts index 124f1054e..f8706924a 100644 --- a/apps/sim/blocks/blocks/function.ts +++ b/apps/sim/blocks/blocks/function.ts @@ -27,11 +27,7 @@ export const FunctionBlock: BlockConfig = { timeout: { type: 'number', required: false }, }, outputs: { - response: { - type: { - result: 'any', - stdout: 'string', - }, - }, + result: 'any', + stdout: 'string', }, } diff --git a/apps/sim/blocks/blocks/github.ts b/apps/sim/blocks/blocks/github.ts index 06fc0b784..db5725968 100644 --- a/apps/sim/blocks/blocks/github.ts +++ b/apps/sim/blocks/blocks/github.ts @@ -167,11 +167,7 @@ export const GitHubBlock: BlockConfig = { branch: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - }, - }, + content: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/gmail.ts b/apps/sim/blocks/blocks/gmail.ts index a5e047613..d7be4e604 100644 --- a/apps/sim/blocks/blocks/gmail.ts +++ b/apps/sim/blocks/blocks/gmail.ts @@ -179,11 +179,7 @@ export const GmailBlock: BlockConfig = { maxResults: { type: 'number', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - }, - }, + content: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/google.ts b/apps/sim/blocks/blocks/google.ts index 6224a7b19..4c8b3b18d 100644 --- a/apps/sim/blocks/blocks/google.ts +++ b/apps/sim/blocks/blocks/google.ts @@ -87,11 +87,7 @@ export const GoogleSearchBlock: BlockConfig = { }, outputs: { - response: { - type: { - items: 'json', - searchInformation: 'json', - } as any, - }, + items: 'json', + searchInformation: 'json', }, } diff --git a/apps/sim/blocks/blocks/google_calendar.ts b/apps/sim/blocks/blocks/google_calendar.ts index 8c3b7997c..02f3cd2ad 100644 --- a/apps/sim/blocks/blocks/google_calendar.ts +++ b/apps/sim/blocks/blocks/google_calendar.ts @@ -284,11 +284,7 @@ export const GoogleCalendarBlock: BlockConfig = { sendUpdates: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - }, - }, + content: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/google_docs.ts b/apps/sim/blocks/blocks/google_docs.ts index 27e978447..7c012cf2b 100644 --- a/apps/sim/blocks/blocks/google_docs.ts +++ b/apps/sim/blocks/blocks/google_docs.ts @@ -181,12 +181,8 @@ export const GoogleDocsBlock: BlockConfig = { content: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - updatedContent: 'boolean', - }, - }, + content: 'string', + metadata: 'json', + updatedContent: 'boolean', }, } diff --git a/apps/sim/blocks/blocks/google_drive.ts b/apps/sim/blocks/blocks/google_drive.ts index 1ad6e708d..6ca9ccabd 100644 --- a/apps/sim/blocks/blocks/google_drive.ts +++ b/apps/sim/blocks/blocks/google_drive.ts @@ -265,11 +265,7 @@ export const GoogleDriveBlock: BlockConfig = { pageSize: { type: 'number', required: false }, }, outputs: { - response: { - type: { - file: 'json', - files: 'json', - }, - }, + file: 'json', + files: 'json', }, } diff --git a/apps/sim/blocks/blocks/google_sheets.ts b/apps/sim/blocks/blocks/google_sheets.ts index 9ae2ed9ee..4d8c959d2 100644 --- a/apps/sim/blocks/blocks/google_sheets.ts +++ b/apps/sim/blocks/blocks/google_sheets.ts @@ -211,16 +211,12 @@ export const GoogleSheetsBlock: BlockConfig = { insertDataOption: { type: 'string', required: false }, }, outputs: { - response: { - type: { - data: 'json', - metadata: 'json', - updatedRange: 'string', - updatedRows: 'number', - updatedColumns: 'number', - updatedCells: 'number', - tableRange: 'string', - }, - }, + data: 'json', + metadata: 'json', + updatedRange: 'string', + updatedRows: 'number', + updatedColumns: 'number', + updatedCells: 'number', + tableRange: 'string', }, } diff --git a/apps/sim/blocks/blocks/guesty.ts b/apps/sim/blocks/blocks/guesty.ts index 5814d8db1..a8e0346b0 100644 --- a/apps/sim/blocks/blocks/guesty.ts +++ b/apps/sim/blocks/blocks/guesty.ts @@ -82,17 +82,13 @@ export const GuestyBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - usage: 'json', - }, - }, + content: 'string', + model: 'string', + usage: 'json', }, } diff --git a/apps/sim/blocks/blocks/image_generator.ts b/apps/sim/blocks/blocks/image_generator.ts index 63cea4354..2f6cffba4 100644 --- a/apps/sim/blocks/blocks/image_generator.ts +++ b/apps/sim/blocks/blocks/image_generator.ts @@ -153,12 +153,8 @@ export const ImageGeneratorBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - image: 'string', - metadata: 'json', - }, - }, + content: 'string', + image: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/jina.ts b/apps/sim/blocks/blocks/jina.ts index a884e7331..0275dd150 100644 --- a/apps/sim/blocks/blocks/jina.ts +++ b/apps/sim/blocks/blocks/jina.ts @@ -51,10 +51,6 @@ export const JinaBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - }, - }, + content: 'string', }, } diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts index e0493868d..3ec546d1f 100644 --- a/apps/sim/blocks/blocks/jira.ts +++ b/apps/sim/blocks/blocks/jira.ts @@ -187,17 +187,13 @@ export const JiraBlock: BlockConfig = { issueType: { type: 'string', required: false }, }, outputs: { - response: { - type: { - ts: 'string', - issueKey: 'string', - summary: 'string', - description: 'string', - created: 'string', - updated: 'string', - success: 'boolean', - url: 'string', - }, - }, + ts: 'string', + issueKey: 'string', + summary: 'string', + description: 'string', + created: 'string', + updated: 'string', + success: 'boolean', + url: 'string', }, } diff --git a/apps/sim/blocks/blocks/knowledge.ts b/apps/sim/blocks/blocks/knowledge.ts index d43cef05f..4d29ef9bb 100644 --- a/apps/sim/blocks/blocks/knowledge.ts +++ b/apps/sim/blocks/blocks/knowledge.ts @@ -36,13 +36,9 @@ export const KnowledgeBlock: BlockConfig = { content: { type: 'string', required: false }, }, outputs: { - response: { - type: { - results: 'json', - query: 'string', - totalResults: 'number', - }, - }, + results: 'json', + query: 'string', + totalResults: 'number', }, subBlocks: [ { diff --git a/apps/sim/blocks/blocks/linear.ts b/apps/sim/blocks/blocks/linear.ts index f4eacc5c8..8e2545811 100644 --- a/apps/sim/blocks/blocks/linear.ts +++ b/apps/sim/blocks/blocks/linear.ts @@ -99,11 +99,7 @@ export const LinearBlock: BlockConfig = { description: { type: 'string', required: false }, }, outputs: { - response: { - type: { - issues: 'json', - issue: 'json', - }, - }, + issues: 'json', + issue: 'json', }, } diff --git a/apps/sim/blocks/blocks/linkup.ts b/apps/sim/blocks/blocks/linkup.ts index 34408d420..ed984776a 100644 --- a/apps/sim/blocks/blocks/linkup.ts +++ b/apps/sim/blocks/blocks/linkup.ts @@ -63,11 +63,7 @@ export const LinkupBlock: BlockConfig = { }, outputs: { - response: { - type: { - answer: 'string', - sources: 'json', - }, - }, + answer: 'string', + sources: 'json', }, } diff --git a/apps/sim/blocks/blocks/mem0.ts b/apps/sim/blocks/blocks/mem0.ts index 38a64ee4e..e85bf0797 100644 --- a/apps/sim/blocks/blocks/mem0.ts +++ b/apps/sim/blocks/blocks/mem0.ts @@ -290,12 +290,8 @@ export const Mem0Block: BlockConfig = { limit: { type: 'number', required: false }, }, outputs: { - response: { - type: { - ids: 'any', - memories: 'any', - searchResults: 'any', - }, - }, + ids: 'any', + memories: 'any', + searchResults: 'any', }, } diff --git a/apps/sim/blocks/blocks/memory.ts b/apps/sim/blocks/blocks/memory.ts index 946769624..59c3ff970 100644 --- a/apps/sim/blocks/blocks/memory.ts +++ b/apps/sim/blocks/blocks/memory.ts @@ -105,12 +105,8 @@ export const MemoryBlock: BlockConfig = { content: { type: 'string', required: false }, }, outputs: { - response: { - type: { - memories: 'any', - id: 'string', - }, - }, + memories: 'any', + id: 'string', }, subBlocks: [ { diff --git a/apps/sim/blocks/blocks/microsoft_excel.ts b/apps/sim/blocks/blocks/microsoft_excel.ts index 4eb2939a8..9cd294d36 100644 --- a/apps/sim/blocks/blocks/microsoft_excel.ts +++ b/apps/sim/blocks/blocks/microsoft_excel.ts @@ -199,17 +199,13 @@ export const MicrosoftExcelBlock: BlockConfig = { valueInputOption: { type: 'string', required: false }, }, outputs: { - response: { - type: { - data: 'json', - metadata: 'json', - updatedRange: 'string', - updatedRows: 'number', - updatedColumns: 'number', - updatedCells: 'number', - index: 'number', - values: 'json', - }, - }, + data: 'json', + metadata: 'json', + updatedRange: 'string', + updatedRows: 'number', + updatedColumns: 'number', + updatedCells: 'number', + index: 'number', + values: 'json', }, } diff --git a/apps/sim/blocks/blocks/microsoft_teams.ts b/apps/sim/blocks/blocks/microsoft_teams.ts index ae2809a49..9296e7549 100644 --- a/apps/sim/blocks/blocks/microsoft_teams.ts +++ b/apps/sim/blocks/blocks/microsoft_teams.ts @@ -169,12 +169,8 @@ export const MicrosoftTeamsBlock: BlockConfig = { content: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - updatedContent: 'boolean', - }, - }, + content: 'string', + metadata: 'json', + updatedContent: 'boolean', }, } diff --git a/apps/sim/blocks/blocks/mistral_parse.ts b/apps/sim/blocks/blocks/mistral_parse.ts index 49adde5f4..8b13b3253 100644 --- a/apps/sim/blocks/blocks/mistral_parse.ts +++ b/apps/sim/blocks/blocks/mistral_parse.ts @@ -202,11 +202,7 @@ export const MistralParseBlock: BlockConfig = { // imageMinSize: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'json', - }, - }, + content: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/notion.ts b/apps/sim/blocks/blocks/notion.ts index 87c8149c0..8f3596cdf 100644 --- a/apps/sim/blocks/blocks/notion.ts +++ b/apps/sim/blocks/blocks/notion.ts @@ -174,11 +174,7 @@ export const NotionBlock: BlockConfig = { properties: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - metadata: 'any', - }, - }, + content: 'string', + metadata: 'any', }, } diff --git a/apps/sim/blocks/blocks/openai.ts b/apps/sim/blocks/blocks/openai.ts index 16eefd4da..7a67bd9c1 100644 --- a/apps/sim/blocks/blocks/openai.ts +++ b/apps/sim/blocks/blocks/openai.ts @@ -49,12 +49,8 @@ export const OpenAIBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - embeddings: 'json', - model: 'string', - usage: 'json', - }, - }, + embeddings: 'json', + model: 'string', + usage: 'json', }, } diff --git a/apps/sim/blocks/blocks/outlook.ts b/apps/sim/blocks/blocks/outlook.ts index 4c2c82473..67d0b5732 100644 --- a/apps/sim/blocks/blocks/outlook.ts +++ b/apps/sim/blocks/blocks/outlook.ts @@ -140,11 +140,7 @@ export const OutlookBlock: BlockConfig< maxResults: { type: 'number', required: false }, }, outputs: { - response: { - type: { - message: 'string', - results: 'json', - }, - }, + message: 'string', + results: 'json', }, } diff --git a/apps/sim/blocks/blocks/perplexity.ts b/apps/sim/blocks/blocks/perplexity.ts index 2c6fb5ce7..3035b3d5a 100644 --- a/apps/sim/blocks/blocks/perplexity.ts +++ b/apps/sim/blocks/blocks/perplexity.ts @@ -106,12 +106,8 @@ export const PerplexityBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - usage: 'json', - }, - }, + content: 'string', + model: 'string', + usage: 'json', }, } diff --git a/apps/sim/blocks/blocks/pinecone.ts b/apps/sim/blocks/blocks/pinecone.ts index 0b12f4abc..fea14bf69 100644 --- a/apps/sim/blocks/blocks/pinecone.ts +++ b/apps/sim/blocks/blocks/pinecone.ts @@ -268,15 +268,11 @@ export const PineconeBlock: BlockConfig = { }, outputs: { - response: { - type: { - matches: 'any', - upsertedCount: 'any', - data: 'any', - model: 'any', - vector_type: 'any', - usage: 'any', - }, - }, + matches: 'any', + upsertedCount: 'any', + data: 'any', + model: 'any', + vector_type: 'any', + usage: 'any', }, } diff --git a/apps/sim/blocks/blocks/reddit.ts b/apps/sim/blocks/blocks/reddit.ts index f4eb9700c..b901f4eed 100644 --- a/apps/sim/blocks/blocks/reddit.ts +++ b/apps/sim/blocks/blocks/reddit.ts @@ -181,13 +181,9 @@ export const RedditBlock: BlockConfig< commentLimit: { type: 'number', required: false }, }, outputs: { - response: { - type: { - subreddit: 'string', - posts: 'json', - post: 'json', - comments: 'json', - }, - }, + subreddit: 'string', + posts: 'json', + post: 'json', + comments: 'json', }, } diff --git a/apps/sim/blocks/blocks/router.ts b/apps/sim/blocks/blocks/router.ts index 56f195153..20221f331 100644 --- a/apps/sim/blocks/blocks/router.ts +++ b/apps/sim/blocks/blocks/router.ts @@ -180,14 +180,10 @@ export const RouterBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - tokens: 'any', - cost: 'any', - selectedPath: 'json', - }, - }, + content: 'string', + model: 'string', + tokens: 'any', + cost: 'any', + selectedPath: 'json', }, } diff --git a/apps/sim/blocks/blocks/s3.ts b/apps/sim/blocks/blocks/s3.ts index 83c34ac1e..a5d4e86bd 100644 --- a/apps/sim/blocks/blocks/s3.ts +++ b/apps/sim/blocks/blocks/s3.ts @@ -96,11 +96,7 @@ export const S3Block: BlockConfig = { s3Uri: { type: 'string', required: true }, }, outputs: { - response: { - type: { - url: 'string', - metadata: 'json', - }, - }, + url: 'string', + metadata: 'json', }, } diff --git a/apps/sim/blocks/blocks/serper.ts b/apps/sim/blocks/blocks/serper.ts index 6b298bab8..92a94b1e7 100644 --- a/apps/sim/blocks/blocks/serper.ts +++ b/apps/sim/blocks/blocks/serper.ts @@ -69,10 +69,6 @@ export const SerperBlock: BlockConfig = { type: { type: 'string', required: false }, }, outputs: { - response: { - type: { - searchResults: 'json', - }, - }, + searchResults: 'json', }, } diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index 4fb5a338f..c87c5e683 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -138,11 +138,7 @@ export const SlackBlock: BlockConfig = { text: { type: 'string', required: true }, }, outputs: { - response: { - type: { - ts: 'string', - channel: 'string', - }, - }, + ts: 'string', + channel: 'string', }, } diff --git a/apps/sim/blocks/blocks/stagehand.ts b/apps/sim/blocks/blocks/stagehand.ts index fa08c00b9..bd66fe4e9 100644 --- a/apps/sim/blocks/blocks/stagehand.ts +++ b/apps/sim/blocks/blocks/stagehand.ts @@ -64,10 +64,6 @@ export const StagehandBlock: BlockConfig = { apiKey: { type: 'string', required: true }, }, outputs: { - response: { - type: { - data: 'json', - }, - }, + data: 'json', }, } diff --git a/apps/sim/blocks/blocks/stagehand_agent.ts b/apps/sim/blocks/blocks/stagehand_agent.ts index f7f802234..bd8ddd368 100644 --- a/apps/sim/blocks/blocks/stagehand_agent.ts +++ b/apps/sim/blocks/blocks/stagehand_agent.ts @@ -83,11 +83,7 @@ export const StagehandAgentBlock: BlockConfig = { outputSchema: { type: 'json', required: false }, }, outputs: { - response: { - type: { - agentResult: 'json', - structuredOutput: 'any', - }, - }, + agentResult: 'json', + structuredOutput: 'any', }, } diff --git a/apps/sim/blocks/blocks/starter.ts b/apps/sim/blocks/blocks/starter.ts index 0dff75af3..ce1e9b520 100644 --- a/apps/sim/blocks/blocks/starter.ts +++ b/apps/sim/blocks/blocks/starter.ts @@ -190,10 +190,6 @@ export const StarterBlock: BlockConfig = { input: { type: 'json', required: false }, }, outputs: { - response: { - type: { - input: 'any', - }, - }, + input: 'any', }, } diff --git a/apps/sim/blocks/blocks/supabase.ts b/apps/sim/blocks/blocks/supabase.ts index 8d5949e8a..5f2f88598 100644 --- a/apps/sim/blocks/blocks/supabase.ts +++ b/apps/sim/blocks/blocks/supabase.ts @@ -109,11 +109,7 @@ export const SupabaseBlock: BlockConfig = { data: { type: 'string', required: false, requiredForToolCall: true }, }, outputs: { - response: { - type: { - message: 'string', - results: 'json', - }, - }, + message: 'string', + results: 'json', }, } diff --git a/apps/sim/blocks/blocks/tavily.ts b/apps/sim/blocks/blocks/tavily.ts index f0f682555..b33e08d83 100644 --- a/apps/sim/blocks/blocks/tavily.ts +++ b/apps/sim/blocks/blocks/tavily.ts @@ -98,15 +98,11 @@ export const TavilyBlock: BlockConfig = { extract_depth: { type: 'string', required: false }, }, outputs: { - response: { - type: { - results: 'json', - answer: 'any', - query: 'string', - content: 'string', - title: 'string', - url: 'string', - }, - }, + results: 'json', + answer: 'any', + query: 'string', + content: 'string', + title: 'string', + url: 'string', }, } diff --git a/apps/sim/blocks/blocks/telegram.ts b/apps/sim/blocks/blocks/telegram.ts index d2321f02f..0e1180abf 100644 --- a/apps/sim/blocks/blocks/telegram.ts +++ b/apps/sim/blocks/blocks/telegram.ts @@ -55,11 +55,7 @@ export const TelegramBlock: BlockConfig = { text: { type: 'string', required: true }, }, outputs: { - response: { - type: { - ok: 'boolean', - result: 'json', - }, - }, + ok: 'boolean', + result: 'json', }, } diff --git a/apps/sim/blocks/blocks/thinking.ts b/apps/sim/blocks/blocks/thinking.ts index a6139c64c..63231c96f 100644 --- a/apps/sim/blocks/blocks/thinking.ts +++ b/apps/sim/blocks/blocks/thinking.ts @@ -36,11 +36,7 @@ export const ThinkingBlock: BlockConfig = { }, outputs: { - response: { - type: { - acknowledgedThought: 'string', - }, - }, + acknowledgedThought: 'string', }, tools: { diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts index f8361c7e1..6579588ba 100644 --- a/apps/sim/blocks/blocks/translate.ts +++ b/apps/sim/blocks/blocks/translate.ts @@ -93,12 +93,8 @@ export const TranslateBlock: BlockConfig = { systemPrompt: { type: 'string', required: true }, }, outputs: { - response: { - type: { - content: 'string', - model: 'string', - tokens: 'any', - }, - }, + content: 'string', + model: 'string', + tokens: 'any', }, } diff --git a/apps/sim/blocks/blocks/twilio.ts b/apps/sim/blocks/blocks/twilio.ts index 67fbcf6ea..6d08dd02b 100644 --- a/apps/sim/blocks/blocks/twilio.ts +++ b/apps/sim/blocks/blocks/twilio.ts @@ -62,13 +62,9 @@ export const TwilioSMSBlock: BlockConfig = { fromNumber: { type: 'string', required: true }, }, outputs: { - response: { - type: { - success: 'boolean', - messageId: 'any', - status: 'any', - error: 'any', - }, - }, + success: 'boolean', + messageId: 'any', + status: 'any', + error: 'any', }, } diff --git a/apps/sim/blocks/blocks/typeform.ts b/apps/sim/blocks/blocks/typeform.ts index 7cc7d3fe6..c2bc374f6 100644 --- a/apps/sim/blocks/blocks/typeform.ts +++ b/apps/sim/blocks/blocks/typeform.ts @@ -215,23 +215,13 @@ export const TypeformBlock: BlockConfig = { inline: { type: 'boolean', required: false }, }, outputs: { - response: { - type: { - total_items: 'number', - page_count: 'number', - items: 'json', - }, - dependsOn: { - subBlockId: 'operation', - condition: { - whenEmpty: { - total_items: 'number', - page_count: 'number', - items: 'json', - }, - whenFilled: 'json', - }, - }, - }, - }, + total_items: 'number', + page_count: 'number', + items: 'json', + fileUrl: 'string', + contentType: 'string', + filename: 'string', + fields: 'json', + form: 'json', + } as any, } diff --git a/apps/sim/blocks/blocks/vision.ts b/apps/sim/blocks/blocks/vision.ts index 8037561b1..53279a48e 100644 --- a/apps/sim/blocks/blocks/vision.ts +++ b/apps/sim/blocks/blocks/vision.ts @@ -53,12 +53,8 @@ export const VisionBlock: BlockConfig = { prompt: { type: 'string', required: false }, }, outputs: { - response: { - type: { - content: 'string', - model: 'any', - tokens: 'any', - }, - }, + content: 'string', + model: 'any', + tokens: 'any', }, } diff --git a/apps/sim/blocks/blocks/whatsapp.ts b/apps/sim/blocks/blocks/whatsapp.ts index d00f0bdb0..47485883c 100644 --- a/apps/sim/blocks/blocks/whatsapp.ts +++ b/apps/sim/blocks/blocks/whatsapp.ts @@ -64,12 +64,8 @@ export const WhatsAppBlock: BlockConfig = { accessToken: { type: 'string', required: true }, }, outputs: { - response: { - type: { - success: 'boolean', - messageId: 'any', - error: 'any', - }, - }, + success: 'boolean', + messageId: 'any', + error: 'any', }, } diff --git a/apps/sim/blocks/blocks/workflow.ts b/apps/sim/blocks/blocks/workflow.ts index 854bc814c..fac3ae58e 100644 --- a/apps/sim/blocks/blocks/workflow.ts +++ b/apps/sim/blocks/blocks/workflow.ts @@ -55,7 +55,7 @@ export const WorkflowBlock: BlockConfig = { title: 'Input Variable (Optional)', type: 'short-input', placeholder: 'Select a variable to pass to the child workflow', - description: 'This variable will be available as start.response.input in the child workflow', + description: 'This variable will be available as start.input in the child workflow', }, ], tools: { @@ -74,13 +74,9 @@ export const WorkflowBlock: BlockConfig = { }, }, outputs: { - response: { - type: { - success: 'boolean', - childWorkflowName: 'string', - result: 'json', - error: 'string', - }, - }, + success: 'boolean', + childWorkflowName: 'string', + result: 'json', + error: 'string', }, } diff --git a/apps/sim/blocks/blocks/x.ts b/apps/sim/blocks/blocks/x.ts index 6c9db2e30..6fac15183 100644 --- a/apps/sim/blocks/blocks/x.ts +++ b/apps/sim/blocks/blocks/x.ts @@ -211,17 +211,13 @@ export const XBlock: BlockConfig = { includeRecentTweets: { type: 'boolean', required: false }, }, outputs: { - response: { - type: { - tweet: 'json', - replies: 'any', - context: 'any', - tweets: 'json', - includes: 'any', - meta: 'json', - user: 'json', - recentTweets: 'any', - }, - }, + tweet: 'json', + replies: 'any', + context: 'any', + tweets: 'json', + includes: 'any', + meta: 'json', + user: 'json', + recentTweets: 'any', }, } diff --git a/apps/sim/blocks/blocks/youtube.ts b/apps/sim/blocks/blocks/youtube.ts index d40baf3b8..5ff45080d 100644 --- a/apps/sim/blocks/blocks/youtube.ts +++ b/apps/sim/blocks/blocks/youtube.ts @@ -46,11 +46,7 @@ export const YouTubeBlock: BlockConfig = { maxResults: { type: 'number', required: false }, }, outputs: { - response: { - type: { - items: 'json', - totalResults: 'number', - }, - }, + items: 'json', + totalResults: 'number', }, } diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 56baf6159..7c0b2605b 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -154,21 +154,18 @@ export interface BlockConfig { } } inputs: Record - outputs: { - response: { - type: ToolOutputToValueType> - dependsOn?: { - subBlockId: string - condition: { - whenEmpty: ToolOutputToValueType> - whenFilled: 'json' - } - } - visualization?: { - type: 'image' - url: string + outputs: ToolOutputToValueType> & { + dependsOn?: { + subBlockId: string + condition: { + whenEmpty: ToolOutputToValueType> + whenFilled: 'json' } } + visualization?: { + type: 'image' + url: string + } } hideFromToolbar?: boolean } diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index 6d3c7a2e1..3b21a2c5a 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -26,12 +26,29 @@ function isCodeEditorValue(value: any[]): value is CodeLine[] { } export function resolveOutputType( - outputs: Record, + outputs: Record, subBlocks: Record ): Record { const resolvedOutputs: Record = {} - for (const [key, outputConfig] of Object.entries(outputs)) { + for (const [key, outputValue] of Object.entries(outputs)) { + // Handle backward compatibility: Check if the output is a primitive value or object (old format) + // If it's a string OR an object without 'type' and 'dependsOn' properties, it's the old format + if ( + typeof outputValue === 'string' || + (typeof outputValue === 'object' && + outputValue !== null && + !('type' in outputValue) && + !('dependsOn' in outputValue)) + ) { + // This is a primitive BlockOutput value (old format like 'string', 'any', etc.) + resolvedOutputs[key] = outputValue as BlockOutput + continue + } + + // Handle new format: OutputConfig with type and optional dependsOn + const outputConfig = outputValue as OutputConfig + // If no dependencies, use the type directly if (!outputConfig.dependsOn) { resolvedOutputs[key] = outputConfig.type diff --git a/apps/sim/components/ui/tag-dropdown.test.tsx b/apps/sim/components/ui/tag-dropdown.test.tsx index f21fc8cd5..839970e95 100644 --- a/apps/sim/components/ui/tag-dropdown.test.tsx +++ b/apps/sim/components/ui/tag-dropdown.test.tsx @@ -274,7 +274,7 @@ describe('TagDropdown Search and Filtering', () => { 'loop.index', 'loop.currentItem', 'parallel.index', - 'block.response.data', + 'block.data', ] const searchTerm = 'user' @@ -288,7 +288,7 @@ describe('TagDropdown Search and Filtering', () => { 'variable.userName', 'loop.index', 'parallel.currentItem', - 'block.response.data', + 'block.data', 'variable.userAge', 'loop.currentItem', ] @@ -313,7 +313,7 @@ describe('TagDropdown Search and Filtering', () => { expect(variableTags).toEqual(['variable.userName', 'variable.userAge']) expect(loopTags).toEqual(['loop.index', 'loop.currentItem']) expect(parallelTags).toEqual(['parallel.currentItem']) - expect(blockTags).toEqual(['block.response.data']) + expect(blockTags).toEqual(['block.data']) }) }) @@ -358,22 +358,6 @@ describe('checkTagTrigger helper function', () => { }) describe('extractFieldsFromSchema helper function logic', () => { - test('should extract fields from legacy format with fields array', () => { - const responseFormat = { - fields: [ - { name: 'name', type: 'string', description: 'User name' }, - { name: 'age', type: 'number', description: 'User age' }, - ], - } - - const fields = extractFieldsFromSchema(responseFormat) - - expect(fields).toEqual([ - { name: 'name', type: 'string', description: 'User name' }, - { name: 'age', type: 'number', description: 'User age' }, - ]) - }) - test('should extract fields from JSON Schema format', () => { const responseFormat = { schema: { @@ -450,6 +434,26 @@ describe('extractFieldsFromSchema helper function logic', () => { { name: 'age', type: 'number', description: undefined }, ]) }) + + test('should handle flattened response format (new format)', () => { + const responseFormat = { + schema: { + properties: { + name: { type: 'string', description: 'User name' }, + age: { type: 'number', description: 'User age' }, + status: { type: 'boolean', description: 'Active status' }, + }, + }, + } + + const fields = extractFieldsFromSchema(responseFormat) + + expect(fields).toEqual([ + { name: 'name', type: 'string', description: 'User name' }, + { name: 'age', type: 'number', description: 'User age' }, + { name: 'status', type: 'boolean', description: 'Active status' }, + ]) + }) }) describe('TagDropdown Tag Ordering', () => { @@ -457,7 +461,7 @@ describe('TagDropdown Tag Ordering', () => { const variableTags = ['variable.userName', 'variable.userAge'] const loopTags = ['loop.index', 'loop.currentItem'] const parallelTags = ['parallel.index'] - const blockTags = ['block.response.data'] + const blockTags = ['block.data'] const orderedTags = [...variableTags, ...loopTags, ...parallelTags, ...blockTags] @@ -467,12 +471,12 @@ describe('TagDropdown Tag Ordering', () => { 'loop.index', 'loop.currentItem', 'parallel.index', - 'block.response.data', + 'block.data', ]) }) test('should create tag index map correctly', () => { - const orderedTags = ['variable.userName', 'loop.index', 'block.response.data'] + const orderedTags = ['variable.userName', 'loop.index', 'block.data'] const tagIndexMap = new Map() orderedTags.forEach((tag, index) => { @@ -481,7 +485,7 @@ describe('TagDropdown Tag Ordering', () => { expect(tagIndexMap.get('variable.userName')).toBe(0) expect(tagIndexMap.get('loop.index')).toBe(1) - expect(tagIndexMap.get('block.response.data')).toBe(2) + expect(tagIndexMap.get('block.data')).toBe(2) expect(tagIndexMap.get('nonexistent')).toBeUndefined() }) }) @@ -491,39 +495,39 @@ describe('TagDropdown Tag Selection Logic', () => { const testCases = [ { description: 'should remove existing closing bracket from incomplete tag', - inputValue: 'Hello ', - cursorPosition: 21, // cursor after the dot - tag: 'start.response.input', - expectedResult: 'Hello ', + inputValue: 'Hello ', + cursorPosition: 13, // cursor after the dot + tag: 'start.input', + expectedResult: 'Hello ', }, { description: 'should remove existing closing bracket when replacing tag content', - inputValue: 'Hello ', - cursorPosition: 22, // cursor after 'response.' - tag: 'start.response.data', - expectedResult: 'Hello ', + inputValue: 'Hello ', + cursorPosition: 12, // cursor after 'start.' + tag: 'start.data', + expectedResult: 'Hello ', }, { description: 'should preserve content after closing bracket', - inputValue: 'Hello world', - cursorPosition: 21, - tag: 'start.response.input', - expectedResult: 'Hello world', + inputValue: 'Hello world', + cursorPosition: 13, + tag: 'start.input', + expectedResult: 'Hello world', }, { description: 'should not affect closing bracket if text between contains invalid characters', - inputValue: 'Hello and ', - cursorPosition: 22, - tag: 'start.response.data', - expectedResult: 'Hello and ', + inputValue: 'Hello and ', + cursorPosition: 12, + tag: 'start.data', + expectedResult: 'Hello and ', }, { description: 'should handle case with no existing closing bracket', - inputValue: 'Hello ', + inputValue: 'Hello ', }, ] @@ -556,25 +560,25 @@ describe('TagDropdown Tag Selection Logic', () => { // Valid tag-like text expect(regex.test('')).toBe(true) // empty string expect(regex.test('input')).toBe(true) - expect(regex.test('response.data')).toBe(true) + expect(regex.test('content.data')).toBe(true) expect(regex.test('user_name')).toBe(true) expect(regex.test('item123')).toBe(true) - expect(regex.test('response.data.item_1')).toBe(true) + expect(regex.test('content.data.item_1')).toBe(true) // Invalid tag-like text (should not remove closing bracket) expect(regex.test('input> and more')).toBe(false) - expect(regex.test('response data')).toBe(false) // space + expect(regex.test('content data')).toBe(false) // space expect(regex.test('user-name')).toBe(false) // hyphen expect(regex.test('data[')).toBe(false) // bracket - expect(regex.test('response.data!')).toBe(false) // exclamation + expect(regex.test('content.data!')).toBe(false) // exclamation }) test('should find correct position of last open bracket', () => { const testCases = [ - { input: 'Hello and and { test('should find correct position of next closing bracket', () => { const testCases = [ { input: 'input>', expected: 5 }, - { input: 'response.data> more text', expected: 13 }, + { input: 'content.data> more text', expected: 12 }, { input: 'no closing bracket', expected: -1 }, { input: '>', expected: 0 }, { input: 'multiple > > > >last', expected: 9 }, diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index 910801d9c..058bc3871 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -48,16 +48,9 @@ interface TagDropdownProps { style?: React.CSSProperties } -// Extract fields from JSON Schema or legacy format export const extractFieldsFromSchema = (responseFormat: any): Field[] => { if (!responseFormat) return [] - // Handle legacy format with fields array - if (Array.isArray(responseFormat.fields)) { - return responseFormat.fields - } - - // Handle new JSON Schema format const schema = responseFormat.schema || responseFormat if ( !schema || @@ -142,46 +135,23 @@ export const TagDropdown: React.FC = ({ blockTagGroups = [], } = useMemo(() => { // Get output paths from block outputs recursively - const getOutputPaths = (obj: any, prefix = '', isStarterBlock = false): string[] => { + const getOutputPaths = (obj: any, prefix = ''): string[] => { if (typeof obj !== 'object' || obj === null) { return prefix ? [prefix] : [] } - // Special handling for starter block with input format - if (isStarterBlock && prefix === 'response') { - try { - // Check if there's an input format defined - const inputFormatValue = useSubBlockStore - .getState() - .getValue(activeSourceBlockId || blockId, 'inputFormat') - if (inputFormatValue && Array.isArray(inputFormatValue) && inputFormatValue.length > 0) { - // Check if any fields have been configured with names - const hasConfiguredFields = inputFormatValue.some( - (field: any) => field.name && field.name.trim() !== '' - ) - - // If no fields have been configured, return the default input path - if (!hasConfiguredFields) { - return ['response.input'] - } - - // Return fields from input format - return inputFormatValue.map((field: any) => `response.input.${field.name}`) - } - } catch (e) { - logger.error('Error parsing input format:', { e }) - } - - return ['response.input'] - } - if ('type' in obj && typeof obj.type === 'string') { return [prefix] } return Object.entries(obj).flatMap(([key, value]) => { + // Skip configuration properties that shouldn't be available as tags + if (key === 'dependsOn') { + return [] + } + const newPrefix = prefix ? `${prefix}.${key}` : key - return getOutputPaths(value, newPrefix, isStarterBlock) + return getOutputPaths(value, newPrefix) }) } @@ -283,7 +253,7 @@ export const TagDropdown: React.FC = ({ .getValue(activeSourceBlockId, 'metrics') as unknown as Metric[] if (Array.isArray(metricsValue)) { blockTags = metricsValue.map( - (metric) => `${normalizedBlockName}.response.${metric.name.toLowerCase()}` + (metric) => `${normalizedBlockName}.${metric.name.toLowerCase()}` ) } } catch (e) { @@ -306,9 +276,7 @@ export const TagDropdown: React.FC = ({ if (responseFormat) { const fields = extractFieldsFromSchema(responseFormat) if (fields.length > 0) { - blockTags = fields.map( - (field: Field) => `${normalizedBlockName}.response.${field.name}` - ) + blockTags = fields.map((field: Field) => `${normalizedBlockName}.${field.name}`) } } } @@ -319,7 +287,7 @@ export const TagDropdown: React.FC = ({ // Fall back to default outputs if (blockTags.length === 0) { - const outputPaths = getOutputPaths(sourceBlock.outputs, '', sourceBlock.type === 'starter') + const outputPaths = getOutputPaths(sourceBlock.outputs, '') blockTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } @@ -357,22 +325,26 @@ export const TagDropdown: React.FC = ({ endSourceConnections.push({ id: sourceBlock.id, type: sourceBlock.type, - outputType: ['response'], + outputType: ['completed', 'results', 'message'], name: blockName, responseFormat: { - fields: [ - { - name: 'completed', - type: 'boolean', - description: 'Whether all executions completed', + schema: { + type: 'object', + properties: { + completed: { + type: 'boolean', + description: 'Whether all executions completed', + }, + results: { + type: 'array', + description: 'Aggregated results from all parallel executions', + }, + message: { + type: 'string', + description: 'Status message', + }, }, - { - name: 'results', - type: 'array', - description: 'Aggregated results from all parallel executions', - }, - { name: 'message', type: 'string', description: 'Status message' }, - ], + }, }, }) } else if (edge.sourceHandle === 'loop-end-source' && sourceBlock.type === 'loop') { @@ -381,22 +353,26 @@ export const TagDropdown: React.FC = ({ endSourceConnections.push({ id: sourceBlock.id, type: sourceBlock.type, - outputType: ['response'], + outputType: ['completed', 'results', 'message'], name: blockName, responseFormat: { - fields: [ - { - name: 'completed', - type: 'boolean', - description: 'Whether all iterations completed', - }, - { - name: 'results', - type: 'array', - description: 'Aggregated results from all loop iterations', + schema: { + type: 'object', + properties: { + completed: { + type: 'boolean', + description: 'Whether all iterations completed', + }, + results: { + type: 'array', + description: 'Aggregated results from all loop iterations', + }, + message: { + type: 'string', + description: 'Status message', + }, }, - { name: 'message', type: 'string', description: 'Status message' }, - ], + }, }, }) } @@ -426,9 +402,7 @@ export const TagDropdown: React.FC = ({ if (connection.responseFormat) { const fields = extractFieldsFromSchema(connection.responseFormat) if (fields.length > 0) { - connectionTags = fields.map( - (field: Field) => `${normalizedBlockName}.response.${field.name}` - ) + connectionTags = fields.map((field: Field) => `${normalizedBlockName}.${field.name}`) } } @@ -440,7 +414,7 @@ export const TagDropdown: React.FC = ({ .getValue(connection.id, 'metrics') as unknown as Metric[] if (Array.isArray(metricsValue)) { connectionTags = metricsValue.map( - (metric) => `${normalizedBlockName}.response.${metric.name.toLowerCase()}` + (metric) => `${normalizedBlockName}.${metric.name.toLowerCase()}` ) } } catch (e) { @@ -453,11 +427,7 @@ export const TagDropdown: React.FC = ({ if (connectionTags.length === 0) { const sourceBlock = blocks[connection.id] if (sourceBlock) { - const outputPaths = getOutputPaths( - sourceBlock.outputs, - '', - sourceBlock.type === 'starter' - ) + const outputPaths = getOutputPaths(sourceBlock.outputs, '') connectionTags = outputPaths.map((path) => `${normalizedBlockName}.${path}`) } } @@ -868,7 +838,7 @@ export const TagDropdown: React.FC = ({
    {group.tags.map((tag: string) => { const tagIndex = tagIndexMap.get(tag) ?? -1 - // Extract path after block name (e.g., "response.field" from "blockname.response.field") + // Extract path after block name (e.g., "field" from "blockname.field") const tagParts = tag.split('.') const path = tagParts.slice(1).join('.') diff --git a/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql b/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql deleted file mode 100644 index 5e08f6229..000000000 --- a/apps/sim/db/migrations/0046_cuddly_killer_shrike.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "settings" ADD COLUMN "auto_pan" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/apps/sim/executor/__test-utils__/executor-mocks.ts b/apps/sim/executor/__test-utils__/executor-mocks.ts index a37187ae5..6a058981b 100644 --- a/apps/sim/executor/__test-utils__/executor-mocks.ts +++ b/apps/sim/executor/__test-utils__/executor-mocks.ts @@ -15,7 +15,7 @@ export const createMockHandler = ( block.metadata?.id === handlerName || handlerName === 'generic' const defaultExecuteResult = { - response: { result: `${handlerName} executed` }, + result: `${handlerName} executed`, } return vi.fn().mockImplementation(() => ({ @@ -614,12 +614,8 @@ export const createFunctionBlockHandler = vi.fn().mockImplementation(() => ({ canHandle: (block: any) => block.metadata?.id === 'function', execute: vi.fn().mockImplementation(async (block, inputs) => { return { - response: { - result: inputs.code - ? new Function(inputs.code)() - : { key: inputs.key, value: inputs.value }, - stdout: '', - }, + result: inputs.code ? new Function(inputs.code)() : { key: inputs.key, value: inputs.value }, + stdout: '', } }), })) @@ -679,13 +675,11 @@ export const createParallelBlockHandler = vi.fn().mockImplementation(() => { } return { - response: { - parallelId, - parallelCount, - distributionType: 'distributed', - started: true, - message: `Initialized ${parallelCount} parallel executions`, - }, + parallelId, + parallelCount, + distributionType: 'distributed', + started: true, + message: `Initialized ${parallelCount} parallel executions`, } } @@ -714,22 +708,18 @@ export const createParallelBlockHandler = vi.fn().mockImplementation(() => { } return { - response: { - parallelId, - parallelCount: parallelState.parallelCount, - completed: true, - message: `Completed all ${parallelState.parallelCount} executions`, - }, + parallelId, + parallelCount: parallelState.parallelCount, + completed: true, + message: `Completed all ${parallelState.parallelCount} executions`, } } return { - response: { - parallelId, - parallelCount: parallelState.parallelCount, - waiting: true, - message: 'Waiting for iterations to complete', - }, + parallelId, + parallelCount: parallelState.parallelCount, + waiting: true, + message: 'Waiting for iterations to complete', } }), } diff --git a/apps/sim/executor/__test-utils__/test-executor.ts b/apps/sim/executor/__test-utils__/test-executor.ts index 61ce054d3..973da3f0c 100644 --- a/apps/sim/executor/__test-utils__/test-executor.ts +++ b/apps/sim/executor/__test-utils__/test-executor.ts @@ -26,7 +26,7 @@ export class TestExecutor extends Executor { return { success: true, output: { - response: { result: 'Test execution completed' }, + result: 'Test execution completed', } as NormalizedBlockOutput, logs: [], metadata: { @@ -39,7 +39,7 @@ export class TestExecutor extends Executor { // If validation fails, return a failure result return { success: false, - output: { response: {} } as NormalizedBlockOutput, + output: {} as NormalizedBlockOutput, error: error.message, logs: [], } diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index 404bf092d..b2e0235b8 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -210,14 +210,12 @@ describe('AgentBlockHandler', () => { mockGetProviderFromModel.mockReturnValue('openai') const expectedOutput = { - response: { - content: 'Mocked response content', - model: 'mock-model', - tokens: { prompt: 10, completion: 20, total: 30 }, - toolCalls: { list: [], count: 0 }, - providerTiming: { total: 100 }, - cost: 0.001, - }, + content: 'Mocked response content', + model: 'mock-model', + tokens: { prompt: 10, completion: 20, total: 30 }, + toolCalls: { list: [], count: 0 }, + providerTiming: { total: 100 }, + cost: 0.001, } const result = await handler.execute(mockBlock, inputs, mockContext) @@ -587,14 +585,12 @@ describe('AgentBlockHandler', () => { mockGetProviderFromModel.mockReturnValue('openai') const expectedOutput = { - response: { - content: 'Mocked response content', - model: 'mock-model', - tokens: { prompt: 10, completion: 20, total: 30 }, - toolCalls: { list: [], count: 0 }, // Assuming no tool calls in this mock response - providerTiming: { total: 100 }, - cost: 0.001, - }, + content: 'Mocked response content', + model: 'mock-model', + tokens: { prompt: 10, completion: 20, total: 30 }, + toolCalls: { list: [], count: 0 }, // Assuming no tool calls in this mock response + providerTiming: { total: 100 }, + cost: 0.001, } const result = await handler.execute(mockBlock, inputs, mockContext) @@ -691,14 +687,12 @@ describe('AgentBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) expect(result).toEqual({ - response: { - result: 'Success', - score: 0.95, - tokens: { prompt: 10, completion: 20, total: 30 }, - toolCalls: { list: [], count: 0 }, - providerTiming: { total: 100 }, - cost: undefined, - }, + result: 'Success', + score: 0.95, + tokens: { prompt: 10, completion: 20, total: 30 }, + toolCalls: { list: [], count: 0 }, + providerTiming: { total: 100 }, + cost: undefined, }) }) @@ -733,13 +727,12 @@ describe('AgentBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) expect(result).toEqual({ - response: { - content: 'Regular text response', - model: 'mock-model', - tokens: { prompt: 10, completion: 20, total: 30 }, - toolCalls: { list: [], count: 0 }, - providerTiming: { total: 100 }, - }, + content: 'Regular text response', + model: 'mock-model', + tokens: { prompt: 10, completion: 20, total: 30 }, + toolCalls: { list: [], count: 0 }, + providerTiming: { total: 100 }, + cost: undefined, }) }) @@ -793,7 +786,7 @@ describe('AgentBlockHandler', () => { stream: mockStreamBody, execution: { success: true, - output: { response: {} }, + output: {}, logs: [], metadata: { duration: 0, @@ -821,7 +814,7 @@ describe('AgentBlockHandler', () => { expect((result as StreamingExecution).execution).toHaveProperty('success', true) expect((result as StreamingExecution).execution).toHaveProperty('output') - expect((result as StreamingExecution).execution.output).toHaveProperty('response') + expect((result as StreamingExecution).execution.output).toBeDefined() expect((result as StreamingExecution).execution).toHaveProperty('logs') }) @@ -835,11 +828,9 @@ describe('AgentBlockHandler', () => { const mockExecutionData = { success: true, output: { - response: { - content: '', - model: 'mock-model', - tokens: { prompt: 10, completion: 20, total: 30 }, - }, + content: '', + model: 'mock-model', + tokens: { prompt: 10, completion: 20, total: 30 }, }, logs: [ { @@ -891,7 +882,7 @@ describe('AgentBlockHandler', () => { expect(result).toHaveProperty('execution') expect((result as StreamingExecution).execution.success).toBe(true) - expect((result as StreamingExecution).execution.output.response.model).toBe('mock-model') + expect((result as StreamingExecution).execution.output.model).toBe('mock-model') const logs = (result as StreamingExecution).execution.logs expect(logs?.length).toBe(1) if (logs && logs.length > 0 && logs[0]) { @@ -918,11 +909,9 @@ describe('AgentBlockHandler', () => { execution: { success: true, output: { - response: { - content: 'Test streaming content', - model: 'gpt-4o', - tokens: { prompt: 10, completion: 5, total: 15 }, - }, + content: 'Test streaming content', + model: 'gpt-4o', + tokens: { prompt: 10, completion: 5, total: 15 }, }, logs: [], metadata: { @@ -950,10 +939,8 @@ describe('AgentBlockHandler', () => { expect(result).toHaveProperty('execution') expect((result as StreamingExecution).execution.success).toBe(true) - expect((result as StreamingExecution).execution.output.response.content).toBe( - 'Test streaming content' - ) - expect((result as StreamingExecution).execution.output.response.model).toBe('gpt-4o') + expect((result as StreamingExecution).execution.output.content).toBe('Test streaming content') + expect((result as StreamingExecution).execution.output.model).toBe('gpt-4o') }) it('should process memories in advanced mode with system prompt and user prompt', async () => { @@ -1006,18 +993,16 @@ describe('AgentBlockHandler', () => { systemPrompt: 'You are a helpful assistant.', userPrompt: 'Continue our conversation.', memories: { - response: { - memories: [ - { - key: 'conversation-1', - type: 'agent', - data: [ - { role: 'user', content: 'Hi there!' }, - { role: 'assistant', content: 'Hello! How can I help you?' }, - ], - }, - ], - }, + memories: [ + { + key: 'conversation-1', + type: 'agent', + data: [ + { role: 'user', content: 'Hi there!' }, + { role: 'assistant', content: 'Hello! How can I help you?' }, + ], + }, + ], }, apiKey: 'test-api-key', } diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 3bae10043..aeb2fae04 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -194,9 +194,7 @@ export class AgentBlockHandler implements BlockHandler { if (!memories) return [] let memoryArray: any[] = [] - if (memories?.response?.memories && Array.isArray(memories.response.memories)) { - memoryArray = memories.response.memories - } else if (memories?.memories && Array.isArray(memories.memories)) { + if (memories?.memories && Array.isArray(memories.memories)) { memoryArray = memories.memories } else if (Array.isArray(memories)) { memoryArray = memories @@ -473,7 +471,7 @@ export class AgentBlockHandler implements BlockHandler { stream: response.body!, execution: { success: executionData.success, - output: executionData.output || { response: {} }, + output: executionData.output || {}, error: executionData.error, logs: [], // Logs are stripped from headers, will be populated by executor metadata: executionData.metadata || { @@ -621,7 +619,7 @@ export class AgentBlockHandler implements BlockHandler { const streamingExec = response as StreamingExecution logger.info(`Received StreamingExecution for block ${block.id}`) - if (streamingExec.execution.output?.response) { + if (streamingExec.execution.output) { const execution = streamingExec.execution as any if (block.metadata?.name) execution.blockName = block.metadata.name if (block.metadata?.id) execution.blockType = block.metadata.id @@ -637,7 +635,7 @@ export class AgentBlockHandler implements BlockHandler { stream, execution: { success: true, - output: { response: {} }, + output: {}, logs: [], metadata: { duration: 0, @@ -667,10 +665,8 @@ export class AgentBlockHandler implements BlockHandler { try { const parsedContent = JSON.parse(result.content) return { - response: { - ...parsedContent, - ...this.createResponseMetadata(result), - }, + ...parsedContent, + ...this.createResponseMetadata(result), } } catch (error) { logger.error('Failed to parse response content:', { error }) @@ -680,11 +676,9 @@ export class AgentBlockHandler implements BlockHandler { private processStandardResponse(result: any): BlockOutput { return { - response: { - content: result.content, - model: result.model, - ...this.createResponseMetadata(result), - }, + content: result.content, + model: result.model, + ...this.createResponseMetadata(result), } } diff --git a/apps/sim/executor/handlers/api/api-handler.test.ts b/apps/sim/executor/handlers/api/api-handler.test.ts index 32d258df1..54927db80 100644 --- a/apps/sim/executor/handlers/api/api-handler.test.ts +++ b/apps/sim/executor/handlers/api/api-handler.test.ts @@ -92,7 +92,7 @@ describe('ApiBlockHandler', () => { body: JSON.stringify({ key: 'value' }), } - const expectedOutput = { response: { data: 'Success' } } + const expectedOutput = { data: 'Success' } mockExecuteTool.mockResolvedValue({ success: true, output: { data: 'Success' } }) @@ -113,7 +113,7 @@ describe('ApiBlockHandler', () => { method: 'GET', } - const expectedOutput = { response: { content: '', success: true } } + const expectedOutput = { data: null, status: 200, headers: {} } const result = await handler.execute(mockBlock, inputs, mockContext) diff --git a/apps/sim/executor/handlers/api/api-handler.ts b/apps/sim/executor/handlers/api/api-handler.ts index 89740c8ca..31c7df53d 100644 --- a/apps/sim/executor/handlers/api/api-handler.ts +++ b/apps/sim/executor/handlers/api/api-handler.ts @@ -1,5 +1,4 @@ import { createLogger } from '@/lib/logs/console-logger' -import type { BlockOutput } from '@/blocks/types' import type { SerializedBlock } from '@/serializer/types' import { executeTool } from '@/tools' import { getTool } from '@/tools/utils' @@ -19,7 +18,7 @@ export class ApiBlockHandler implements BlockHandler { block: SerializedBlock, inputs: Record, context: ExecutionContext - ): Promise { + ): Promise { const tool = getTool(block.config.tool) if (!tool) { throw new Error(`Tool not found: ${block.config.tool}`) @@ -27,7 +26,7 @@ export class ApiBlockHandler implements BlockHandler { // Early return with empty success response if URL is not provided or empty if (tool.name?.includes('HTTP') && (!inputs.url || inputs.url.trim() === '')) { - return { response: { content: '', success: true } } + return { data: null, status: 200, headers: {} } } // Pre-validate common HTTP request issues to provide better error messages @@ -154,7 +153,7 @@ export class ApiBlockHandler implements BlockHandler { throw error } - return { response: result.output } + return result.output } catch (error: any) { // Ensure we have a meaningful error message if (!error.message || error.message === 'undefined (undefined)') { diff --git a/apps/sim/executor/handlers/condition/condition-handler.test.ts b/apps/sim/executor/handlers/condition/condition-handler.test.ts index 040184ecd..9d7e91cbc 100644 --- a/apps/sim/executor/handlers/condition/condition-handler.test.ts +++ b/apps/sim/executor/handlers/condition/condition-handler.test.ts @@ -96,7 +96,7 @@ describe('ConditionBlockHandler', () => { [ mockSourceBlock.id, { - output: { response: { value: 10, text: 'hello' } }, + output: { value: 10, text: 'hello' }, executed: true, executionTime: 100, }, @@ -129,32 +129,30 @@ describe('ConditionBlockHandler', () => { it('should execute condition block correctly and select first path', async () => { const conditions = [ - { id: 'cond1', title: 'if', value: 'context.response.value > 5' }, + { id: 'cond1', title: 'if', value: 'context.value > 5' }, { id: 'else1', title: 'else', value: '' }, ] const inputs = { conditions: JSON.stringify(conditions) } const expectedOutput = { - response: { - value: 10, - text: 'hello', - conditionResult: true, - selectedPath: { - blockId: mockTargetBlock1.id, - blockType: 'target', - blockTitle: 'Target Block 1', - }, - selectedConditionId: 'cond1', + value: 10, + text: 'hello', + conditionResult: true, + selectedPath: { + blockId: mockTargetBlock1.id, + blockType: 'target', + blockTitle: 'Target Block 1', }, + selectedConditionId: 'cond1', } // Mock directly in the test - mockResolver.resolveBlockReferences.mockReturnValue('context.response.value > 5') + mockResolver.resolveBlockReferences.mockReturnValue('context.value > 5') - const result = (await handler.execute(mockBlock, inputs, mockContext)) as { response: any } + const result = await handler.execute(mockBlock, inputs, mockContext) expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith( - 'context.response.value > 5', + 'context.value > 5', mockContext, mockBlock ) @@ -170,23 +168,21 @@ describe('ConditionBlockHandler', () => { const inputs = { conditions: JSON.stringify(conditions) } const expectedOutput = { - response: { - value: 10, - text: 'hello', - conditionResult: true, - selectedPath: { - blockId: mockTargetBlock2.id, - blockType: 'target', - blockTitle: 'Target Block 2', - }, - selectedConditionId: 'else1', + value: 10, + text: 'hello', + conditionResult: true, + selectedPath: { + blockId: mockTargetBlock2.id, + blockType: 'target', + blockTitle: 'Target Block 2', }, + selectedConditionId: 'else1', } // Mock directly in the test mockResolver.resolveBlockReferences.mockReturnValue('context.value < 0') - const result = (await handler.execute(mockBlock, inputs, mockContext)) as { response: any } + const result = await handler.execute(mockBlock, inputs, mockContext) expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith( 'context.value < 0', @@ -207,7 +203,7 @@ describe('ConditionBlockHandler', () => { it('should resolve references in conditions before evaluation', async () => { const conditions = [ - { id: 'cond1', title: 'if', value: '{{source-block-1.response.value}} > 5' }, + { id: 'cond1', title: 'if', value: '{{source-block-1.value}} > 5' }, { id: 'else1', title: 'else', value: '' }, ] const inputs = { conditions: JSON.stringify(conditions) } @@ -215,10 +211,10 @@ describe('ConditionBlockHandler', () => { // Mock directly in the test mockResolver.resolveBlockReferences.mockReturnValue('10 > 5') - const _result = (await handler.execute(mockBlock, inputs, mockContext)) as { response: any } + const _result = await handler.execute(mockBlock, inputs, mockContext) expect(mockResolver.resolveBlockReferences).toHaveBeenCalledWith( - '{{source-block-1.response.value}} > 5', + '{{source-block-1.value}} > 5', mockContext, mockBlock ) @@ -320,9 +316,9 @@ describe('ConditionBlockHandler', () => { // Mock directly in the test mockResolver.resolveBlockReferences.mockReturnValue('context.item === "apple"') - const result = (await handler.execute(mockBlock, inputs, mockContext)) as { response: any } + const result = await handler.execute(mockBlock, inputs, mockContext) expect(mockContext.decisions.condition.get(mockBlock.id)).toBe('cond1') - expect(result.response.selectedConditionId).toBe('cond1') + expect((result as any).selectedConditionId).toBe('cond1') }) }) diff --git a/apps/sim/executor/handlers/condition/condition-handler.ts b/apps/sim/executor/handlers/condition/condition-handler.ts index 2d90ffc00..3f479fdf2 100644 --- a/apps/sim/executor/handlers/condition/condition-handler.ts +++ b/apps/sim/executor/handlers/condition/condition-handler.ts @@ -199,16 +199,14 @@ export class ConditionBlockHandler implements BlockHandler { // Return output, preserving source output structure if possible return { - response: { - ...((sourceOutput as any)?.response || {}), // Keep original response fields if they exist - conditionResult: true, // Indicate a path was successfully chosen - selectedPath: { - blockId: targetBlock.id, - blockType: targetBlock.metadata?.id || 'unknown', - blockTitle: targetBlock.metadata?.name || 'Untitled Block', - }, - selectedConditionId: selectedCondition.id, + ...((sourceOutput as any) || {}), // Keep original fields if they exist + conditionResult: true, // Indicate a path was successfully chosen + selectedPath: { + blockId: targetBlock.id, + blockType: targetBlock.metadata?.id || 'unknown', + blockTitle: targetBlock.metadata?.name || 'Untitled Block', }, + selectedConditionId: selectedCondition.id, } } } diff --git a/apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts b/apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts index cd436c457..096f3ca6a 100644 --- a/apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts +++ b/apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts @@ -1,7 +1,6 @@ import '../../__test-utils__/mock-dependencies' import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' -import type { BlockOutput } from '@/blocks/types' import { getProviderFromModel } from '@/providers/utils' import type { SerializedBlock } from '@/serializer/types' import type { ExecutionContext } from '../../types' @@ -86,21 +85,6 @@ describe('EvaluatorBlockHandler', () => { temperature: 0.1, } - const expectedOutput: BlockOutput = { - response: { - content: 'This is the content to evaluate.', - model: 'mock-model', - tokens: { prompt: 50, completion: 10, total: 60 }, - cost: { - input: 0, - output: 0, - total: 0, - }, - score1: 5, - score2: 8, - }, - } - const result = await handler.execute(mockBlock, inputs, mockContext) expect(mockGetProviderFromModel).toHaveBeenCalledWith('gpt-4o') @@ -134,7 +118,18 @@ describe('EvaluatorBlockHandler', () => { temperature: 0.1, }) - expect(result).toEqual(expectedOutput) + expect(result).toEqual({ + content: 'This is the content to evaluate.', + model: 'mock-model', + tokens: { prompt: 50, completion: 10, total: 60 }, + cost: { + input: 0, + output: 0, + total: 0, + }, + score1: 5, + score2: 8, + }) }) it('should process JSON string content correctly', async () => { @@ -221,7 +216,7 @@ describe('EvaluatorBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) - expect((result as any).response.quality).toBe(9) + expect((result as any).quality).toBe(9) }) it('should handle invalid/non-JSON response gracefully (scores = 0)', async () => { @@ -246,7 +241,7 @@ describe('EvaluatorBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) - expect((result as any).response.score).toBe(0) + expect((result as any).score).toBe(0) }) it('should handle partially valid JSON response (extracts what it can)', async () => { @@ -273,8 +268,8 @@ describe('EvaluatorBlockHandler', () => { }) const result = await handler.execute(mockBlock, inputs, mockContext) - expect((result as any).response.accuracy).toBe(0) - expect((result as any).response.fluency).toBe(0) + expect((result as any).accuracy).toBe(0) + expect((result as any).fluency).toBe(0) }) it('should extract metric scores ignoring case', async () => { @@ -299,7 +294,7 @@ describe('EvaluatorBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) - expect((result as any).response.camelcasescore).toBe(7) + expect((result as any).camelcasescore).toBe(7) }) it('should handle missing metrics in response (score = 0)', async () => { @@ -327,8 +322,8 @@ describe('EvaluatorBlockHandler', () => { const result = await handler.execute(mockBlock, inputs, mockContext) - expect((result as any).response.presentscore).toBe(4) - expect((result as any).response.missingscore).toBe(0) + expect((result as any).presentscore).toBe(4) + expect((result as any).missingscore).toBe(0) }) it('should handle server error responses', async () => { diff --git a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts index 642a7b134..8d41c5bd7 100644 --- a/apps/sim/executor/handlers/evaluator/evaluator-handler.ts +++ b/apps/sim/executor/handlers/evaluator/evaluator-handler.ts @@ -251,21 +251,19 @@ export class EvaluatorBlockHandler implements BlockHandler { // Create result with metrics as direct fields for easy access const outputResult = { - response: { - content: inputs.content, - model: result.model, - tokens: { - prompt: result.tokens?.prompt || 0, - completion: result.tokens?.completion || 0, - total: result.tokens?.total || 0, - }, - cost: { - input: costCalculation.input, - output: costCalculation.output, - total: costCalculation.total, - }, - ...metricScores, + content: inputs.content, + model: result.model, + tokens: { + prompt: result.tokens?.prompt || 0, + completion: result.tokens?.completion || 0, + total: result.tokens?.total || 0, + }, + cost: { + input: costCalculation.input, + output: costCalculation.output, + total: costCalculation.total, }, + ...metricScores, } return outputResult diff --git a/apps/sim/executor/handlers/function/function-handler.test.ts b/apps/sim/executor/handlers/function/function-handler.test.ts index 92d2ba7d4..7d1332975 100644 --- a/apps/sim/executor/handlers/function/function-handler.test.ts +++ b/apps/sim/executor/handlers/function/function-handler.test.ts @@ -1,5 +1,4 @@ import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' -import type { BlockOutput } from '@/blocks/types' import type { SerializedBlock } from '@/serializer/types' import { executeTool } from '@/tools' import type { ExecutionContext } from '../../types' @@ -79,7 +78,7 @@ describe('FunctionBlockHandler', () => { envVars: {}, _context: { workflowId: mockContext.workflowId }, } - const expectedOutput: BlockOutput = { response: { result: 'Success' } } + const expectedOutput: any = { result: 'Success' } const result = await handler.execute(mockBlock, inputs, mockContext) @@ -102,7 +101,7 @@ describe('FunctionBlockHandler', () => { envVars: {}, _context: { workflowId: mockContext.workflowId }, } - const expectedOutput: BlockOutput = { response: { result: 'Success' } } + const expectedOutput: any = { result: 'Success' } const result = await handler.execute(mockBlock, inputs, mockContext) diff --git a/apps/sim/executor/handlers/function/function-handler.ts b/apps/sim/executor/handlers/function/function-handler.ts index c63c80d2f..399d52095 100644 --- a/apps/sim/executor/handlers/function/function-handler.ts +++ b/apps/sim/executor/handlers/function/function-handler.ts @@ -1,5 +1,4 @@ import { createLogger } from '@/lib/logs/console-logger' -import type { BlockOutput } from '@/blocks/types' import type { SerializedBlock } from '@/serializer/types' import { executeTool } from '@/tools' import type { BlockHandler, ExecutionContext } from '../../types' @@ -18,7 +17,7 @@ export class FunctionBlockHandler implements BlockHandler { block: SerializedBlock, inputs: Record, context: ExecutionContext - ): Promise { + ): Promise { const codeContent = Array.isArray(inputs.code) ? inputs.code.map((c: { content: string }) => c.content).join('\n') : inputs.code @@ -36,6 +35,6 @@ export class FunctionBlockHandler implements BlockHandler { throw new Error(result.error || 'Function execution failed') } - return { response: result.output } + return result.output } } diff --git a/apps/sim/executor/handlers/generic/generic-handler.test.ts b/apps/sim/executor/handlers/generic/generic-handler.test.ts index 75109192a..34a74842a 100644 --- a/apps/sim/executor/handlers/generic/generic-handler.test.ts +++ b/apps/sim/executor/handlers/generic/generic-handler.test.ts @@ -1,7 +1,6 @@ import '../../__test-utils__/mock-dependencies' import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' -import type { BlockOutput } from '@/blocks/types' import type { SerializedBlock } from '@/serializer/types' import { executeTool } from '@/tools' import type { ToolConfig } from '@/tools/types' @@ -88,7 +87,7 @@ describe('GenericBlockHandler', () => { ...inputs, _context: { workflowId: mockContext.workflowId }, } - const expectedOutput: BlockOutput = { response: { customResult: 'OK' } } + const expectedOutput: any = { customResult: 'OK' } const result = await handler.execute(mockBlock, inputs, mockContext) diff --git a/apps/sim/executor/handlers/generic/generic-handler.ts b/apps/sim/executor/handlers/generic/generic-handler.ts index b4e392cae..0c45b11b1 100644 --- a/apps/sim/executor/handlers/generic/generic-handler.ts +++ b/apps/sim/executor/handlers/generic/generic-handler.ts @@ -1,5 +1,4 @@ import { createLogger } from '@/lib/logs/console-logger' -import type { BlockOutput } from '@/blocks/types' import type { SerializedBlock } from '@/serializer/types' import { executeTool } from '@/tools' import { getTool } from '@/tools/utils' @@ -22,7 +21,7 @@ export class GenericBlockHandler implements BlockHandler { block: SerializedBlock, inputs: Record, context: ExecutionContext - ): Promise { + ): Promise { logger.info(`Executing block: ${block.id} (Type: ${block.metadata?.id})`) const tool = getTool(block.config.tool) if (!tool) { @@ -60,7 +59,7 @@ export class GenericBlockHandler implements BlockHandler { throw error } - return { response: result.output } + return result.output } catch (error: any) { // Ensure we have a meaningful error message if (!error.message || error.message === 'undefined (undefined)') { diff --git a/apps/sim/executor/handlers/loop/loop-handler.test.ts b/apps/sim/executor/handlers/loop/loop-handler.test.ts index 7e1ef005e..3c7e7a13e 100644 --- a/apps/sim/executor/handlers/loop/loop-handler.test.ts +++ b/apps/sim/executor/handlers/loop/loop-handler.test.ts @@ -81,8 +81,8 @@ describe('LoopBlockHandler', () => { expect(mockContext.activeExecutionPath.has('inner-block')).toBe(true) // Type guard to check if result has the expected structure - if (typeof result === 'object' && result !== null && 'response' in result) { - const response = result.response as any + if (typeof result === 'object' && result !== null) { + const response = result as any expect(response.currentIteration).toBe(0) // Still shows current iteration as 0 expect(response.maxIterations).toBe(3) expect(response.completed).toBe(false) @@ -102,8 +102,8 @@ describe('LoopBlockHandler', () => { // But it should not activate the inner block either since we're at max iterations expect(mockContext.activeExecutionPath.has('inner-block')).toBe(false) - if (typeof result === 'object' && result !== null && 'response' in result) { - const response = result.response as any + if (typeof result === 'object' && result !== null) { + const response = result as any expect(response.completed).toBe(false) // Not completed until all blocks execute expect(response.message).toContain('Final iteration') } @@ -122,8 +122,8 @@ describe('LoopBlockHandler', () => { expect(mockContext.loopItems.get('loop-1')).toBe('item1') - if (typeof result === 'object' && result !== null && 'response' in result) { - const response = result.response as any + if (typeof result === 'object' && result !== null) { + const response = result as any expect(response.loopType).toBe('forEach') expect(response.maxIterations).toBe(3) // Limited by items length } @@ -162,8 +162,8 @@ describe('LoopBlockHandler', () => { expect(mockContext.loopIterations.get('loop-1')).toBe(1) expect(mockContext.loopItems.get('loop-1')).toBe('a') - if (typeof result === 'object' && result !== null && 'response' in result) { - const response = result.response as any + if (typeof result === 'object' && result !== null) { + const response = result as any expect(response.maxIterations).toBe(2) // Should be limited to 2, not 10 expect(response.completed).toBe(false) } @@ -173,8 +173,8 @@ describe('LoopBlockHandler', () => { expect(mockContext.loopIterations.get('loop-1')).toBe(2) expect(mockContext.loopItems.get('loop-1')).toBe('b') - if (typeof result === 'object' && result !== null && 'response' in result) { - const response = result.response as any + if (typeof result === 'object' && result !== null) { + const response = result as any expect(response.completed).toBe(false) } diff --git a/apps/sim/executor/handlers/loop/loop-handler.ts b/apps/sim/executor/handlers/loop/loop-handler.ts index 7d805e098..25d1a2b35 100644 --- a/apps/sim/executor/handlers/loop/loop-handler.ts +++ b/apps/sim/executor/handlers/loop/loop-handler.ts @@ -92,15 +92,13 @@ export class LoopBlockHandler implements BlockHandler { // Don't mark as completed here - let the loop manager handle it after all blocks execute // Just return that this is the final iteration return { - response: { - loopId: block.id, - currentIteration: currentIteration - 1, // Report the actual last iteration number - maxIterations, - loopType: loop.loopType || 'for', - completed: false, // Not completed until all blocks in this iteration execute - message: `Final iteration ${currentIteration} of ${maxIterations}`, - }, - } + loopId: block.id, + currentIteration: currentIteration - 1, // Report the actual last iteration number + maxIterations, + loopType: loop.loopType || 'for', + completed: false, // Not completed until all blocks in this iteration execute + message: `Final iteration ${currentIteration} of ${maxIterations}`, + } as Record } // For forEach loops, set the current item BEFORE incrementing @@ -137,15 +135,13 @@ export class LoopBlockHandler implements BlockHandler { } return { - response: { - loopId: block.id, - currentIteration, - maxIterations, - loopType: loop.loopType || 'for', - completed: false, - message: `Starting iteration ${currentIteration + 1} of ${maxIterations}`, - }, - } + loopId: block.id, + currentIteration, + maxIterations, + loopType: loop.loopType || 'for', + completed: false, + message: `Starting iteration ${currentIteration + 1} of ${maxIterations}`, + } as Record } /** diff --git a/apps/sim/executor/handlers/parallel/parallel-handler.test.ts b/apps/sim/executor/handlers/parallel/parallel-handler.test.ts index fa69b3189..3eaa01ea3 100644 --- a/apps/sim/executor/handlers/parallel/parallel-handler.test.ts +++ b/apps/sim/executor/handlers/parallel/parallel-handler.test.ts @@ -71,8 +71,7 @@ describe('ParallelBlockHandler', () => { // First execution - initialize parallel and set up iterations const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 3, distributionType: 'distributed', @@ -128,8 +127,7 @@ describe('ParallelBlockHandler', () => { // Second execution - check waiting state const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 2, completedExecutions: 0, @@ -157,8 +155,8 @@ describe('ParallelBlockHandler', () => { distributionItems: ['item1', 'item2'], completedExecutions: 0, executionResults: new Map([ - ['iteration_0', { 'agent-1': { response: { result: 'result1' } } }], - ['iteration_1', { 'agent-1': { response: { result: 'result2' } } }], + ['iteration_0', { 'agent-1': { result: 'result1' } }], + ['iteration_1', { 'agent-1': { result: 'result2' } }], ]), activeIterations: new Set(), currentIteration: 1, @@ -182,15 +180,11 @@ describe('ParallelBlockHandler', () => { // Execution after all iterations complete const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 2, completed: true, - results: [ - { 'agent-1': { response: { result: 'result1' } } }, - { 'agent-1': { response: { result: 'result2' } } }, - ], + results: [{ 'agent-1': { result: 'result1' } }, { 'agent-1': { result: 'result2' } }], message: 'Completed all 2 executions', }) @@ -214,8 +208,7 @@ describe('ParallelBlockHandler', () => { const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 2, distributionType: 'distributed', @@ -243,8 +236,7 @@ describe('ParallelBlockHandler', () => { const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 3, distributionType: 'distributed', @@ -267,8 +259,7 @@ describe('ParallelBlockHandler', () => { const result = await handler.execute(block, {}, context) - expect(result).toHaveProperty('response') - expect((result as any).response).toMatchObject({ + expect(result as any).toMatchObject({ parallelId: 'parallel-1', parallelCount: 1, distributionType: 'count', @@ -316,8 +307,8 @@ describe('ParallelBlockHandler', () => { // Initialize parallel const initResult = await handler.execute(parallelBlock, {}, context) - expect((initResult as any).response.started).toBe(true) - expect((initResult as any).response.parallelCount).toBe(3) + expect((initResult as any).started).toBe(true) + expect((initResult as any).parallelCount).toBe(3) // Simulate all virtual blocks being executed const parallelState = context.parallelExecutions?.get('parallel-1') @@ -343,13 +334,13 @@ describe('ParallelBlockHandler', () => { const aggregatedResult = await handler.execute(parallelBlock, {}, context) // Verify results are aggregated - expect((aggregatedResult as any).response.completed).toBe(true) - expect((aggregatedResult as any).response.results).toHaveLength(3) + expect((aggregatedResult as any).completed).toBe(true) + expect((aggregatedResult as any).results).toHaveLength(3) // Verify block state is stored const blockState = context.blockStates.get('parallel-1') expect(blockState).toBeDefined() - expect(blockState?.output.response.results).toHaveLength(3) + expect(blockState?.output.results).toHaveLength(3) // Verify both downstream blocks are activated expect(context.activeExecutionPath.has('function-1')).toBe(true) @@ -360,7 +351,7 @@ describe('ParallelBlockHandler', () => { // Simulate downstream blocks trying to access results // This should work without errors - const storedResults = context.blockStates.get('parallel-1')?.output.response.results + const storedResults = context.blockStates.get('parallel-1')?.output.results expect(storedResults).toBeDefined() expect(storedResults).toHaveLength(3) }) @@ -379,7 +370,7 @@ describe('ParallelBlockHandler', () => { const parallel2Block = createMockBlock('parallel-2') parallel2Block.config.params = { parallelType: 'collection', - collection: '', // This references the first parallel + collection: '', // This references the first parallel } // Set up context with both parallels @@ -415,7 +406,7 @@ describe('ParallelBlockHandler', () => { config: { tool: 'function', params: { - code: 'return ;', + code: 'return ;', }, }, inputs: {}, @@ -451,7 +442,7 @@ describe('ParallelBlockHandler', () => { 'parallel-2': { id: 'parallel-2', nodes: [], - distribution: '', + distribution: '', }, }, }, @@ -465,26 +456,26 @@ describe('ParallelBlockHandler', () => { for (let i = 0; i < 2; i++) { context.executedBlocks.add(`agent-1_parallel_parallel-1_iteration_${i}`) parallelState!.executionResults.set(`iteration_${i}`, { - 'agent-1': { response: { content: `Result ${i}` } }, + 'agent-1': { content: `Result ${i}` }, }) } // Re-execute first parallel to aggregate results const result = await handler.execute(parallel1Block, {}, context) - expect((result as any).response.completed).toBe(true) + expect((result as any).completed).toBe(true) // Verify the block state is available const blockState = context.blockStates.get('parallel-1') expect(blockState).toBeDefined() - expect(blockState?.output.response.results).toHaveLength(2) + expect(blockState?.output.results).toHaveLength(2) - // Now when function block tries to resolve , it should work + // Now when function block tries to resolve , it should work // even though parallel-2 exists on the canvas expect(() => { // This simulates what the resolver would do const state = context.blockStates.get('parallel-1') if (!state) throw new Error('No state found for block parallel-1') - const results = state.output?.response?.results + const results = state.output?.results if (!results) throw new Error('No results found') return results }).not.toThrow() diff --git a/apps/sim/executor/handlers/parallel/parallel-handler.ts b/apps/sim/executor/handlers/parallel/parallel-handler.ts index f133cf99b..9aaf17b8a 100644 --- a/apps/sim/executor/handlers/parallel/parallel-handler.ts +++ b/apps/sim/executor/handlers/parallel/parallel-handler.ts @@ -56,7 +56,7 @@ export class ParallelBlockHandler implements BlockHandler { // Check if we already have aggregated results stored (from a previous completion check) const existingBlockState = context.blockStates.get(block.id) - if (existingBlockState?.output?.response?.results) { + if (existingBlockState?.output?.results) { logger.info(`Parallel ${block.id} already has aggregated results, returning them`) return existingBlockState.output } @@ -72,14 +72,12 @@ export class ParallelBlockHandler implements BlockHandler { // Store the aggregated results in the block state so subsequent blocks can reference them const aggregatedOutput = { - response: { - parallelId: block.id, - parallelCount: parallelState.parallelCount, - completed: true, - results, - message: `Completed all ${parallelState.parallelCount} executions`, - }, - } + parallelId: block.id, + parallelCount: parallelState.parallelCount, + completed: true, + results, + message: `Completed all ${parallelState.parallelCount} executions`, + } as Record // Store the aggregated results in context so blocks connected to parallel-end-source can access them context.blockStates.set(block.id, { @@ -199,14 +197,12 @@ export class ParallelBlockHandler implements BlockHandler { } return { - response: { - parallelId: block.id, - parallelCount, - distributionType: parallelType === 'count' ? 'count' : 'distributed', - started: true, - message: `Initialized ${parallelCount} parallel execution${parallelCount > 1 ? 's' : ''}`, - }, - } + parallelId: block.id, + parallelCount, + distributionType: parallelType === 'count' ? 'count' : 'distributed', + started: true, + message: `Initialized ${parallelCount} parallel execution${parallelCount > 1 ? 's' : ''}`, + } as Record } // Check if all virtual blocks have completed @@ -222,7 +218,7 @@ export class ParallelBlockHandler implements BlockHandler { // Check if we already have aggregated results stored (from a previous completion check) const existingBlockState = context.blockStates.get(block.id) - if (existingBlockState?.output?.response?.results) { + if (existingBlockState?.output?.results) { logger.info(`Parallel ${block.id} already has aggregated results, returning them`) return existingBlockState.output } @@ -238,14 +234,12 @@ export class ParallelBlockHandler implements BlockHandler { // Store the aggregated results in the block state so subsequent blocks can reference them const aggregatedOutput = { - response: { - parallelId: block.id, - parallelCount: parallelState.parallelCount, - completed: true, - results, - message: `Completed all ${parallelState.parallelCount} executions`, - }, - } + parallelId: block.id, + parallelCount: parallelState.parallelCount, + completed: true, + results, + message: `Completed all ${parallelState.parallelCount} executions`, + } as Record // Store the aggregated results in context so blocks connected to parallel-end-source can access them context.blockStates.set(block.id, { @@ -288,15 +282,13 @@ export class ParallelBlockHandler implements BlockHandler { // Still waiting for iterations to complete const completedCount = this.countCompletedIterations(block.id, context) return { - response: { - parallelId: block.id, - parallelCount: parallelState.parallelCount, - completedExecutions: completedCount, - activeIterations: parallelState.parallelCount - completedCount, - waiting: true, - message: `${completedCount} of ${parallelState.parallelCount} iterations completed`, - }, - } + parallelId: block.id, + parallelCount: parallelState.parallelCount, + completedExecutions: completedCount, + activeIterations: parallelState.parallelCount - completedCount, + waiting: true, + message: `${completedCount} of ${parallelState.parallelCount} iterations completed`, + } as Record } /** diff --git a/apps/sim/executor/handlers/router/router-handler.test.ts b/apps/sim/executor/handlers/router/router-handler.test.ts index 2ad38cf71..435e28c44 100644 --- a/apps/sim/executor/handlers/router/router-handler.test.ts +++ b/apps/sim/executor/handlers/router/router-handler.test.ts @@ -11,7 +11,6 @@ import { vi, } from 'vitest' import { generateRouterPrompt } from '@/blocks/blocks/router' -import type { BlockOutput } from '@/blocks/types' import { getProviderFromModel } from '@/providers/utils' import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types' import { PathTracker } from '../../path' @@ -147,24 +146,6 @@ describe('RouterBlockHandler', () => { }, ] - const expectedOutput: BlockOutput = { - response: { - content: 'Choose the best option.', - model: 'mock-model', - tokens: { prompt: 100, completion: 5, total: 105 }, - cost: { - input: 0, - output: 0, - total: 0, - }, - selectedPath: { - blockId: 'target-block-1', - blockType: 'target', - blockTitle: 'Option A', - }, - }, - } - const result = await handler.execute(mockBlock, inputs, mockContext) expect(mockGenerateRouterPrompt).toHaveBeenCalledWith(inputs.prompt, expectedTargetBlocks) @@ -189,7 +170,21 @@ describe('RouterBlockHandler', () => { temperature: 0.5, }) - expect(result).toEqual(expectedOutput) + expect(result).toEqual({ + content: 'Choose the best option.', + model: 'mock-model', + tokens: { prompt: 100, completion: 5, total: 105 }, + cost: { + input: 0, + output: 0, + total: 0, + }, + selectedPath: { + blockId: 'target-block-1', + blockType: 'target', + blockTitle: 'Option A', + }, + }) }) it('should throw error if target block is missing', async () => { diff --git a/apps/sim/executor/handlers/router/router-handler.ts b/apps/sim/executor/handlers/router/router-handler.ts index 40a88781e..66fb3cd42 100644 --- a/apps/sim/executor/handlers/router/router-handler.ts +++ b/apps/sim/executor/handlers/router/router-handler.ts @@ -101,24 +101,22 @@ export class RouterBlockHandler implements BlockHandler { ) return { - response: { - content: inputs.prompt, - model: result.model, - tokens: { - prompt: tokens.prompt || 0, - completion: tokens.completion || 0, - total: tokens.total || 0, - }, - cost: { - input: cost.input, - output: cost.output, - total: cost.total, - }, - selectedPath: { - blockId: chosenBlock.id, - blockType: chosenBlock.type || 'unknown', - blockTitle: chosenBlock.title || 'Untitled Block', - }, + content: inputs.prompt, + model: result.model, + tokens: { + prompt: tokens.prompt || 0, + completion: tokens.completion || 0, + total: tokens.total || 0, + }, + cost: { + input: cost.input, + output: cost.output, + total: cost.total, + }, + selectedPath: { + blockId: chosenBlock.id, + blockType: chosenBlock.type || 'unknown', + blockTitle: chosenBlock.title || 'Untitled Block', }, } } catch (error) { diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts index 69c6a6f2e..dd2a1ff98 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts @@ -208,7 +208,7 @@ describe('WorkflowBlockHandler', () => { it('should map successful child output correctly', () => { const childResult = { success: true, - output: { response: { data: 'test result' } }, + output: { data: 'test result' }, } const result = (handler as any).mapChildOutputToParent( @@ -219,11 +219,9 @@ describe('WorkflowBlockHandler', () => { ) expect(result).toEqual({ - response: { - success: true, - childWorkflowName: 'Child Workflow', - result: { data: 'test result' }, - }, + success: true, + childWorkflowName: 'Child Workflow', + result: { data: 'test result' }, }) }) @@ -241,17 +239,15 @@ describe('WorkflowBlockHandler', () => { ) expect(result).toEqual({ - response: { - success: false, - childWorkflowName: 'Child Workflow', - error: 'Child workflow failed', - }, + success: false, + childWorkflowName: 'Child Workflow', + error: 'Child workflow failed', }) }) it('should handle nested response structures', () => { const childResult = { - response: { response: { nested: 'data' } }, + output: { nested: 'data' }, } const result = (handler as any).mapChildOutputToParent( @@ -262,11 +258,9 @@ describe('WorkflowBlockHandler', () => { ) expect(result).toEqual({ - response: { - success: true, - childWorkflowName: 'Child Workflow', - result: { nested: 'data' }, - }, + success: true, + childWorkflowName: 'Child Workflow', + result: { nested: 'data' }, }) }) }) diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index 3aa0cca26..4081448c6 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -69,7 +69,7 @@ export class WorkflowBlockHandler implements BlockHandler { ) // Prepare the input for the child workflow - // The input from this block should be passed as start.response.input to the child workflow + // The input from this block should be passed as start.input to the child workflow let childWorkflowInput = {} if (inputs.input !== undefined) { @@ -186,29 +186,23 @@ export class WorkflowBlockHandler implements BlockHandler { if (!success) { logger.warn(`Child workflow ${childWorkflowName} failed`) return { - response: { - success: false, - childWorkflowName, - error: childResult.error || 'Child workflow execution failed', - }, + success: false, + childWorkflowName, + error: childResult.error || 'Child workflow execution failed', } as Record } - // Extract the actual result content from the nested structure + // Extract the actual result content from the flattened structure let result = childResult - if (childResult?.output?.response) { - result = childResult.output.response - } else if (childResult?.response?.response) { - result = childResult.response.response + if (childResult?.output) { + result = childResult.output } // Return a properly structured response with all required fields return { - response: { - success: true, - childWorkflowName, - result, - }, + success: true, + childWorkflowName, + result, } as Record } } diff --git a/apps/sim/executor/index.test.ts b/apps/sim/executor/index.test.ts index bfd53d45a..db3adf899 100644 --- a/apps/sim/executor/index.test.ts +++ b/apps/sim/executor/index.test.ts @@ -66,7 +66,7 @@ describe('Executor', () => { test('should create an executor instance with new options object format', () => { const workflow = createMinimalWorkflow() const initialStates = { - block1: { response: { result: 'Initial state' } }, + block1: { result: { value: 'Initial state' } }, } const envVars = { API_KEY: 'test-key', BASE_URL: 'https://example.com' } const workflowInput = { query: 'test query' } @@ -111,7 +111,7 @@ describe('Executor', () => { test('should handle legacy constructor with individual parameters', () => { const workflow = createMinimalWorkflow() const initialStates = { - block1: { response: { result: 'Initial state' } }, + block1: { result: { value: 'Initial state' } }, } const envVars = { API_KEY: 'test-key' } const workflowInput = { query: 'test query' } @@ -225,7 +225,6 @@ describe('Executor', () => { if ('success' in result) { expect(result).toHaveProperty('success') expect(result).toHaveProperty('output') - expect(result.output).toHaveProperty('response') // Our mocked implementation results in a false success value // In real usage, this would be true for successful executions @@ -373,7 +372,7 @@ describe('Executor', () => { // Create a mock context for debug continuation const mockContext = createMockContext() mockContext.blockStates.set('starter', { - output: { response: { input: {} } }, + output: { input: {} }, executed: true, executionTime: 0, }) @@ -389,61 +388,27 @@ describe('Executor', () => { /** * Additional tests to improve coverage */ - describe('normalizeBlockOutput', () => { - test('should normalize different block outputs correctly', () => { + describe('block output handling', () => { + test('should handle different block outputs correctly', () => { const workflow = createMinimalWorkflow() const executor = new Executor(workflow) - // Access the private method for testing - const normalizeOutput = (executor as any).normalizeBlockOutput.bind(executor) - - // Test normalizing agent block output - const agentBlock = { metadata: { id: 'agent' } } - const agentOutput = { response: { content: 'Agent response' } } - expect(normalizeOutput(agentOutput, agentBlock)).toEqual(agentOutput) - - // Test normalizing router block output - const routerBlock = { metadata: { id: 'router' } } - const routerOutput = { selectedPath: { blockId: 'target' } } - const normalizedRouterOutput = normalizeOutput(routerOutput, routerBlock) - expect(normalizedRouterOutput.response.selectedPath).toEqual(routerOutput.selectedPath) - - // Test normalizing function block output - const functionBlock = { metadata: { id: 'function' } } - const functionOutput = { result: 'Function result', stdout: 'Output' } - const normalizedFunctionOutput = normalizeOutput(functionOutput, functionBlock) - expect(normalizedFunctionOutput.response.result).toEqual(functionOutput.result) - expect(normalizedFunctionOutput.response.stdout).toEqual(functionOutput.stdout) - - // Test generic output normalization - const genericBlock = { metadata: { id: 'unknown' } } - const genericOutput = 'Simple string result' - const normalizedGenericOutput = normalizeOutput(genericOutput, genericBlock) - expect(normalizedGenericOutput.response.result).toEqual(genericOutput) + // Test basic workflow execution + expect(executor).toBeDefined() + expect(typeof executor.execute).toBe('function') }) - test('should normalize error outputs correctly', () => { + test('should handle error outputs correctly', () => { const workflow = createMinimalWorkflow() const executor = new Executor(workflow) - const normalizeOutput = (executor as any).normalizeBlockOutput.bind(executor) - - // Test error output with error property - const errorOutput = { error: 'Test error message', status: 400 } - const normalizedErrorOutput = normalizeOutput(errorOutput, { metadata: { id: 'api' } }) - - expect(normalizedErrorOutput).toHaveProperty('error', 'Test error message') - expect(normalizedErrorOutput.response).toHaveProperty('error', 'Test error message') - expect(normalizedErrorOutput.response).toHaveProperty('status', 400) - // Test object with response.error - const responseErrorOutput = { response: { error: 'Response error', data: 'test' } } - const normalizedResponseError = normalizeOutput(responseErrorOutput, { - metadata: { id: 'api' }, - }) + // Test error handling functionality + const extractErrorMessage = (executor as any).extractErrorMessage.bind(executor) - expect(normalizedResponseError).toHaveProperty('error', 'Response error') - expect(normalizedResponseError.response).toHaveProperty('error', 'Response error') - expect(normalizedResponseError.response).toHaveProperty('data', 'test') + // Test error message extraction + const error = new Error('Test error message') + const errorMessage = extractErrorMessage(error) + expect(errorMessage).toBe('Test error message') }) }) @@ -467,7 +432,6 @@ describe('Executor', () => { context.blockStates.set('block1', { output: { error: 'Test error', - response: { error: 'Test error' }, }, executed: true, }) @@ -579,17 +543,13 @@ describe('Executor', () => { // Create an error output manually const errorOutput = { - response: { - error: errorMessage, - status: testError.status || 500, - }, error: errorMessage, + status: testError.status || 500, } // Verify the error output structure expect(errorOutput).toHaveProperty('error') - expect(errorOutput.response).toHaveProperty('error') - expect(errorOutput.response).toHaveProperty('status') + expect(errorOutput).toHaveProperty('status') }) test('should handle "undefined (undefined)" error case', () => { @@ -625,7 +585,7 @@ describe('Executor', () => { }), execution: { blockId: 'agent-1', - output: { response: { content: 'Final content' } }, + output: { content: 'Final content' }, }, } diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index 7c06d69b6..de6a08f30 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -161,7 +161,7 @@ export class Executor { async execute(workflowId: string): Promise { const { setIsExecuting, setIsDebugging, setPendingBlocks, reset } = useExecutionStore.getState() const startTime = new Date() - let finalOutput: NormalizedBlockOutput = { response: {} } + let finalOutput: NormalizedBlockOutput = {} // Track workflow execution start trackWorkflowTelemetry('workflow_execution_started', { @@ -258,16 +258,16 @@ export class Executor { const blockId = (streamingExec.execution as any).blockId const blockState = context.blockStates.get(blockId) - if (blockState?.output?.response) { - blockState.output.response.content = fullContent + if (blockState?.output) { + blockState.output.content = fullContent } } catch (readerError: any) { logger.error('Error reading stream for executor:', readerError) // Set partial content if available const blockId = (streamingExec.execution as any).blockId const blockState = context.blockStates.get(blockId) - if (blockState?.output?.response && fullContent) { - blockState.output.response.content = fullContent + if (blockState?.output && fullContent) { + blockState.output.content = fullContent } } finally { try { @@ -376,7 +376,7 @@ export class Executor { */ async continueExecution(blockIds: string[], context: ExecutionContext): Promise { const { setPendingBlocks } = useExecutionStore.getState() - let finalOutput: NormalizedBlockOutput = { response: {} } + let finalOutput: NormalizedBlockOutput = {} try { // Execute the current layer - using the original context, not a clone @@ -621,14 +621,11 @@ 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 - // Ensure both input and direct fields are available + + // Initialize the starter block with structured input (flattened) const starterOutput = { - response: { - input: finalInput, - ...finalInput, // Add input fields directly at response level too - }, + input: finalInput, + ...finalInput, // Add input fields directly at top level } logger.info(`[Executor] Starter output:`, JSON.stringify(starterOutput, null, 2)) @@ -639,32 +636,26 @@ export class Executor { executionTime: 0, }) } else { - // Handle structured input (like API calls or chat messages) - if (this.workflowInput && typeof this.workflowInput === 'object') { - // Preserve complete workflowInput structure to maintain JSON format - // when referenced through - - const starterOutput = { - response: { - input: this.workflowInput, - // Add top-level fields for backward compatibility - message: this.workflowInput.input, - conversationId: this.workflowInput.conversationId, - }, - } + // Handle structured input (like API calls or chat messages) + if (this.workflowInput && typeof this.workflowInput === 'object') { + // Flatten structure: input is directly accessible as + const starterOutput = { + input: this.workflowInput, + // Add compatibility fields directly at top level + message: this.workflowInput.input, + conversationId: this.workflowInput.conversationId, + } - context.blockStates.set(starterBlock.id, { - output: starterOutput, - executed: true, - executionTime: 0, - }) - } else { - // Fallback for primitive input values - const starterOutput = { - response: { - input: this.workflowInput, - }, - } + context.blockStates.set(starterBlock.id, { + output: starterOutput, + executed: true, + executionTime: 0, + }) + } else { + // Fallback for primitive input values + const starterOutput = { + input: this.workflowInput, + } context.blockStates.set(starterBlock.id, { output: starterOutput, @@ -676,13 +667,11 @@ export class Executor { } catch (e) { logger.warn('Error processing starter block input format:', e) - // Error handler fallback - preserve structure for both direct access and backward compatibility + // Error handler fallback - flatten structure for direct access const starterOutput = { - response: { - input: this.workflowInput, - message: this.workflowInput?.input, - conversationId: this.workflowInput?.conversationId, - }, + input: this.workflowInput, + message: this.workflowInput?.input, + conversationId: this.workflowInput?.conversationId, } logger.info('[Executor] Fallback starter output:', JSON.stringify(starterOutput, null, 2)) @@ -888,9 +877,7 @@ export class Executor { return incomingConnections.every((conn) => { const sourceExecuted = executedBlocks.has(conn.source) const sourceBlockState = context.blockStates.get(conn.source) - const hasSourceError = - sourceBlockState?.output?.error !== undefined || - sourceBlockState?.output?.response?.error !== undefined + const hasSourceError = sourceBlockState?.output?.error !== undefined // For error connections, check if the source had an error if (conn.sourceHandle === 'error') { @@ -930,9 +917,7 @@ export class Executor { const sourceBlock = this.actualWorkflow.blocks.find((b) => b.id === conn.source) const sourceBlockState = context.blockStates.get(sourceId) || context.blockStates.get(conn.source) - const hasSourceError = - sourceBlockState?.output?.error !== undefined || - sourceBlockState?.output?.response?.error !== undefined + const hasSourceError = sourceBlockState?.output?.error !== undefined // Special handling for loop-start-source connections if (conn.sourceHandle === 'loop-start-source') { @@ -1128,7 +1113,7 @@ export class Executor { } // Check if this block needs the starter block's output - // This is especially relevant for API, function, and conditions that might reference + // This is especially relevant for API, function, and conditions that might reference const starterBlock = this.actualWorkflow.blocks.find((b) => b.metadata?.id === 'starter') if (starterBlock) { const starterState = context.blockStates.get(starterBlock.id) @@ -1259,8 +1244,13 @@ export class Executor { return streamingExec } - // Normalize the output - const output = this.normalizeBlockOutput(rawOutput, block) + // Handle error outputs and ensure object structure + const output: NormalizedBlockOutput = + rawOutput && typeof rawOutput === 'object' && rawOutput.error + ? { error: rawOutput.error, status: rawOutput.status || 500 } + : typeof rawOutput === 'object' && rawOutput !== null + ? rawOutput + : { result: rawOutput } // Update the context with the execution result // Use virtual block ID for parallel executions @@ -1358,7 +1348,7 @@ export class Executor { // Skip console logging for infrastructure blocks like loops and parallels if (block.metadata?.id !== 'loop' && block.metadata?.id !== 'parallel') { addConsole({ - output: { response: {} }, + output: {}, success: false, error: error.message || @@ -1386,11 +1376,8 @@ export class Executor { // Create error output with appropriate structure const errorOutput: NormalizedBlockOutput = { - response: { - error: this.extractErrorMessage(error), - status: error.status || 500, - }, error: this.extractErrorMessage(error), + status: error.status || 500, } // Set block state with error output @@ -1475,160 +1462,6 @@ export class Executor { return true } - /** - * Normalizes a block output to ensure it has the expected structure. - * Handles different block types with appropriate response formats. - * - * @param output - Raw output from block execution - * @param block - Block that produced the output - * @returns Normalized output with consistent structure - */ - private normalizeBlockOutput(output: any, block: SerializedBlock): NormalizedBlockOutput { - // Handle error outputs - if (output && typeof output === 'object' && output.error) { - return { - response: { - error: output.error, - status: output.status || 500, - }, - error: output.error, - } - } - - if (output && typeof output === 'object' && 'response' in output) { - // If response already contains an error, maintain it - if (output.response?.error) { - return { - ...output, - error: output.response.error, - } - } - return output as NormalizedBlockOutput - } - - const blockType = block.metadata?.id - - if (blockType === 'agent') { - return output - } - - if (blockType === 'router') { - return { - response: { - content: '', - model: '', - tokens: { prompt: 0, completion: 0, total: 0 }, - selectedPath: output?.selectedPath || { - blockId: '', - blockType: '', - blockTitle: '', - }, - }, - } - } - - if (blockType === 'condition') { - if (output && typeof output === 'object' && 'response' in output) { - return { - response: { - ...output.response, - conditionResult: output.response.conditionResult || false, - selectedPath: output.response.selectedPath || { - blockId: '', - blockType: '', - blockTitle: '', - }, - selectedConditionId: output.response.selectedConditionId || '', - }, - } - } - - return { - response: { - conditionResult: output?.conditionResult || false, - selectedPath: output?.selectedPath || { - blockId: '', - blockType: '', - blockTitle: '', - }, - selectedConditionId: output?.selectedConditionId || '', - }, - } - } - - if (blockType === 'function') { - return { - response: { - result: output?.result, - stdout: output?.stdout || '', - }, - } - } - - if (blockType === 'api') { - return { - response: { - data: output?.data, - status: output?.status || 0, - headers: output?.headers || {}, - }, - } - } - - if (blockType === 'evaluator') { - const evaluatorResponse: { - content: string - model: string - [key: string]: any - } = { - content: output?.content || '', - model: output?.model || '', - } - - if (output && typeof output === 'object') { - Object.keys(output).forEach((key) => { - if (key !== 'content' && key !== 'model') { - evaluatorResponse[key] = output[key] - } - }) - } - - return { response: evaluatorResponse } - } - - if (blockType === 'loop') { - return { - response: { - loopId: output?.loopId || block.id, - currentIteration: output?.currentIteration || 0, - maxIterations: output?.maxIterations || 0, - loopType: output?.loopType || 'for', - completed: output?.completed || false, - results: output?.results || [], - message: output?.message || '', - }, - } - } - - if (blockType === 'parallel') { - return { - response: { - parallelId: output?.parallelId || block.id, - parallelCount: output?.parallelCount || 1, - distributionType: output?.distributionType || 'simple', - completed: output?.completed || false, - completedCount: output?.completedCount || 0, - results: output?.results || [], - message: output?.message || '', - }, - } - } - - return { - response: { result: output }, - } - } - /** * Creates a new block log entry with initial values. * diff --git a/apps/sim/executor/loops.test.ts b/apps/sim/executor/loops.test.ts index 7dd43350d..3de1e4923 100644 --- a/apps/sim/executor/loops.test.ts +++ b/apps/sim/executor/loops.test.ts @@ -172,12 +172,12 @@ describe('LoopManager', () => { // Add some block states to verify they get reset mockContext.blockStates.set('block-1', { - output: { response: { result: 'test' } }, + output: { result: 'test' }, executed: true, executionTime: 100, }) mockContext.blockStates.set('block-2', { - output: { response: { result: 'test2' } }, + output: { result: 'test2' }, executed: true, executionTime: 200, }) @@ -215,9 +215,9 @@ describe('LoopManager', () => { loopType: 'for', forEachItems: null, executionResults: new Map([ - ['iteration_0', { iteration: { 'block-1': { response: { result: 'result1' } } } }], - ['iteration_1', { iteration: { 'block-1': { response: { result: 'result2' } } } }], - ['iteration_2', { iteration: { 'block-1': { response: { result: 'result3' } } } }], + ['iteration_0', { iteration: { 'block-1': { result: 'result1' } } }], + ['iteration_1', { iteration: { 'block-1': { result: 'result2' } } }], + ['iteration_2', { iteration: { 'block-1': { result: 'result3' } } }], ]), currentIteration: 3, }) @@ -232,8 +232,8 @@ describe('LoopManager', () => { // Verify loop block state was updated with aggregated results const loopBlockState = mockContext.blockStates.get('loop-1') expect(loopBlockState).toBeDefined() - expect(loopBlockState?.output.response.completed).toBe(true) - expect(loopBlockState?.output.response.results).toHaveLength(3) + expect(loopBlockState?.output.completed).toBe(true) + expect(loopBlockState?.output.results).toHaveLength(3) // Verify end connection was activated expect(mockContext.activeExecutionPath.has('after-loop')).toBe(true) @@ -259,8 +259,8 @@ describe('LoopManager', () => { expect(mockContext.completedLoops.has('loop-1')).toBe(true) const loopBlockState = mockContext.blockStates.get('loop-1') - expect(loopBlockState?.output.response.loopType).toBe('forEach') - expect(loopBlockState?.output.response.maxIterations).toBe(3) + expect(loopBlockState?.output.loopType).toBe('forEach') + expect(loopBlockState?.output.maxIterations).toBe(3) }) test('should handle forEach loops with object items', async () => { @@ -284,7 +284,7 @@ describe('LoopManager', () => { expect(mockContext.completedLoops.has('loop-1')).toBe(true) const loopBlockState = mockContext.blockStates.get('loop-1') - expect(loopBlockState?.output.response.maxIterations).toBe(2) + expect(loopBlockState?.output.maxIterations).toBe(2) }) test('should handle forEach loops with string items', async () => { @@ -307,7 +307,7 @@ describe('LoopManager', () => { describe('storeIterationResult', () => { test('should create new loop state if none exists', () => { - const output = { response: { result: 'test result' } } + const output = { result: 'test result' } manager.storeIterationResult(mockContext, 'loop-1', 0, 'block-1', output) @@ -330,8 +330,8 @@ describe('LoopManager', () => { currentIteration: 0, }) - const output1 = { response: { result: 'result1' } } - const output2 = { response: { result: 'result2' } } + const output1 = { result: 'result1' } + const output2 = { result: 'result2' } manager.storeIterationResult(mockContext, 'loop-1', 0, 'block-1', output1) manager.storeIterationResult(mockContext, 'loop-1', 0, 'block-2', output2) @@ -346,7 +346,7 @@ describe('LoopManager', () => { const forEachLoop = createForEachLoop(['item1', 'item2']) manager = new LoopManager({ 'loop-1': forEachLoop }) - const output = { response: { result: 'test result' } } + const output = { result: 'test result' } manager.storeIterationResult(mockContext, 'loop-1', 0, 'block-1', output) @@ -391,11 +391,11 @@ describe('LoopManager', () => { describe('getCurrentItem', () => { test('should return current item for loop', () => { - mockContext.loopItems.set('loop-1', 'current-item') + mockContext.loopItems.set('loop-1', ['current-item']) const item = manager.getCurrentItem('loop-1', mockContext) - expect(item).toBe('current-item') + expect(item).toEqual(['current-item']) }) test('should return undefined for non-existent loop item', () => { @@ -477,7 +477,7 @@ describe('LoopManager', () => { // Set block-1 to have no error (successful execution) mockContext.blockStates.set('block-1', { - output: { response: { result: 'success' } }, + output: { result: 'success' }, executed: true, executionTime: 100, }) @@ -521,7 +521,6 @@ describe('LoopManager', () => { // Set block-1 to have an error mockContext.blockStates.set('block-1', { output: { - response: { error: 'Something went wrong' }, error: 'Something went wrong', }, executed: true, @@ -633,8 +632,8 @@ describe('LoopManager', () => { loopType: 'for', forEachItems: null, executionResults: new Map([ - ['iteration_0', { iteration: { 'block-1': { response: { result: 'result1' } } } }], - ['iteration_1', { iteration: { 'block-1': { response: { result: 'result2' } } } }], + ['iteration_0', { iteration: { 'block-1': { result: 'result1' } } }], + ['iteration_1', { iteration: { 'block-1': { result: 'result2' } } }], ]), currentIteration: 2, }) diff --git a/apps/sim/executor/loops.ts b/apps/sim/executor/loops.ts index 7f61aa8ed..6bf50c6ad 100644 --- a/apps/sim/executor/loops.ts +++ b/apps/sim/executor/loops.ts @@ -131,15 +131,13 @@ export class LoopManager { // Store the aggregated results in the loop block's state so subsequent blocks can reference them const aggregatedOutput = { - response: { - loopId, - currentIteration: maxIterations - 1, // Last iteration index - maxIterations, - loopType: loop.loopType || 'for', - completed: true, - results, - message: `Completed all ${maxIterations} iterations`, - }, + loopId, + currentIteration: maxIterations - 1, // Last iteration index + maxIterations, + loopType: loop.loopType || 'for', + completed: true, + results, + message: `Completed all ${maxIterations} iterations`, } // Store the aggregated results in context so blocks connected to loop-end-source can access them @@ -444,8 +442,7 @@ export class LoopManager { ): void { // For regular blocks, check if they had an error const blockState = context.blockStates.get(blockId) - const hasError = - blockState?.output?.error !== undefined || blockState?.output?.response?.error !== undefined + const hasError = blockState?.output?.error !== undefined // Follow appropriate connections based on error state for (const conn of outgoing) { diff --git a/apps/sim/executor/parallels.test.ts b/apps/sim/executor/parallels.test.ts index fdfdf2867..51d4c84e5 100644 --- a/apps/sim/executor/parallels.test.ts +++ b/apps/sim/executor/parallels.test.ts @@ -273,7 +273,7 @@ describe('ParallelManager', () => { context.parallelExecutions?.set('parallel-1', state) - const output = { response: { result: 'test result' } } + const output = { result: 'test result' } manager.storeIterationResult(context, 'parallel-1', 1, output) diff --git a/apps/sim/executor/parallels.ts b/apps/sim/executor/parallels.ts index 0530c694c..a9d3598ff 100644 --- a/apps/sim/executor/parallels.ts +++ b/apps/sim/executor/parallels.ts @@ -112,7 +112,7 @@ export class ParallelManager { if (allVirtualBlocksExecuted && !context.completedLoops.has(parallelId)) { // Check if the parallel block already has aggregated results stored const blockState = context.blockStates.get(parallelId) - if (blockState?.output?.response?.completed && blockState?.output?.response?.results) { + if (blockState?.output?.completed && blockState?.output?.results) { logger.info( `Parallel ${parallelId} already has aggregated results, marking as completed without re-execution` ) diff --git a/apps/sim/executor/path.test.ts b/apps/sim/executor/path.test.ts index 8f03d49b1..fb33edcb9 100644 --- a/apps/sim/executor/path.test.ts +++ b/apps/sim/executor/path.test.ts @@ -168,7 +168,7 @@ describe('PathTracker', () => { describe('router blocks', () => { it('should update router decision and activate selected path', () => { const blockState: BlockState = { - output: { response: { selectedPath: { blockId: 'block1' } } }, + output: { selectedPath: { blockId: 'block1' } }, executed: true, executionTime: 100, } @@ -182,7 +182,7 @@ describe('PathTracker', () => { it('should not update if no selected path', () => { const blockState: BlockState = { - output: { response: {} }, + output: {}, executed: true, executionTime: 100, } @@ -198,7 +198,7 @@ describe('PathTracker', () => { describe('condition blocks', () => { it('should update condition decision and activate selected connection', () => { const blockState: BlockState = { - output: { response: { selectedConditionId: 'if' } }, + output: { selectedConditionId: 'if' }, executed: true, executionTime: 100, } @@ -212,7 +212,7 @@ describe('PathTracker', () => { it('should not activate if no matching connection', () => { const blockState: BlockState = { - output: { response: { selectedConditionId: 'unknown' } }, + output: { selectedConditionId: 'unknown' }, executed: true, executionTime: 100, } @@ -237,7 +237,7 @@ describe('PathTracker', () => { describe('regular blocks', () => { it('should activate outgoing connections on success', () => { const blockState: BlockState = { - output: { response: { data: 'success' } }, + output: { data: 'success' }, executed: true, executionTime: 100, } @@ -259,7 +259,7 @@ describe('PathTracker', () => { sourceHandle: 'error', }) const blockState: BlockState = { - output: { error: 'Something failed', response: { error: 'Something failed' } }, + output: { error: 'Something failed' }, executed: true, executionTime: 100, } @@ -332,12 +332,12 @@ describe('PathTracker', () => { it('should handle multiple blocks in one update', () => { const blockState1: BlockState = { - output: { response: { data: 'success' } }, + output: { data: 'success' }, executed: true, executionTime: 100, } const blockState2: BlockState = { - output: { response: { selectedPath: { blockId: 'block1' } } }, + output: { selectedPath: { blockId: 'block1' } }, executed: true, executionTime: 150, } diff --git a/apps/sim/executor/path.ts b/apps/sim/executor/path.ts index dcbfcfd49..7e1226426 100644 --- a/apps/sim/executor/path.ts +++ b/apps/sim/executor/path.ts @@ -160,7 +160,7 @@ export class PathTracker { */ private updateRouterPaths(block: SerializedBlock, context: ExecutionContext): void { const routerOutput = context.blockStates.get(block.id)?.output - const selectedPath = routerOutput?.response?.selectedPath?.blockId + const selectedPath = routerOutput?.selectedPath?.blockId if (selectedPath) { context.decisions.router.set(block.id, selectedPath) @@ -174,7 +174,7 @@ export class PathTracker { */ private updateConditionPaths(block: SerializedBlock, context: ExecutionContext): void { const conditionOutput = context.blockStates.get(block.id)?.output - const selectedConditionId = conditionOutput?.response?.selectedConditionId + const selectedConditionId = conditionOutput?.selectedConditionId if (!selectedConditionId) return @@ -231,9 +231,7 @@ export class PathTracker { * Check if a block has an error */ private blockHasError(blockState: BlockState | undefined): boolean { - return ( - blockState?.output?.error !== undefined || blockState?.output?.response?.error !== undefined - ) + return blockState?.output?.error !== undefined } /** diff --git a/apps/sim/executor/resolver.test.ts b/apps/sim/executor/resolver.test.ts index fd3d0db58..1cb191173 100644 --- a/apps/sim/executor/resolver.test.ts +++ b/apps/sim/executor/resolver.test.ts @@ -79,8 +79,8 @@ describe('InputResolver', () => { mockContext = { workflowId: 'test-workflow', blockStates: new Map([ - ['starter-block', { output: { response: { input: 'Hello World', type: 'text' } } }], - ['function-block', { output: { response: { result: '42' } } }], // String value as it would be in real app + ['starter-block', { output: { input: 'Hello World', type: 'text' } }], + ['function-block', { output: { result: '42' } }], // String value as it would be in real app ]), activeExecutionPath: new Set(['starter-block', 'function-block']), loopIterations: new Map(), @@ -284,9 +284,9 @@ describe('InputResolver', () => { config: { tool: 'generic', params: { - starterRef: '', - functionRef: '', - nameRef: '', // Reference by name + starterRef: '', + functionRef: '', + nameRef: '', // Reference by name }, }, inputs: { @@ -313,8 +313,8 @@ describe('InputResolver', () => { config: { tool: 'generic', params: { - startRef: '', - startType: '', + startRef: '', + startType: '', }, }, inputs: { @@ -339,7 +339,7 @@ describe('InputResolver', () => { config: { tool: 'generic', params: { - inactiveRef: '', // Not in activeExecutionPath + inactiveRef: '', // Not in activeExecutionPath }, }, inputs: { @@ -367,7 +367,7 @@ describe('InputResolver', () => { config: { tool: 'generic', params: { - disabledRef: '', + disabledRef: '', }, }, inputs: { @@ -520,14 +520,14 @@ describe('InputResolver', () => { id: 'row1', cells: { Key: 'inputKey', - Value: '', + Value: '', }, }, { id: 'row2', cells: { Key: 'resultKey', - Value: '', + Value: '', }, }, ], @@ -640,7 +640,7 @@ describe('InputResolver', () => { config: { tool: 'condition', params: { - conditions: ' === "Hello World"', + conditions: ' === "Hello World"', }, }, inputs: { @@ -653,7 +653,7 @@ describe('InputResolver', () => { const result = resolver.resolveInputs(block, mockContext) // Conditions should be passed through without parsing for condition blocks - expect(result.conditions).toBe(' === "Hello World"') + expect(result.conditions).toBe(' === "Hello World"') }) }) @@ -736,7 +736,7 @@ describe('InputResolver', () => { environmentVariables: {}, decisions: { router: new Map(), condition: new Map() }, loopIterations: new Map([['loop-1', 1]]), - loopItems: new Map([['loop-1', 'item1']]), + loopItems: new Map([['loop-1', ['item1']]]), completedLoops: new Set(), executedBlocks: new Set(), activeExecutionPath: new Set(['function-1']), @@ -745,7 +745,7 @@ describe('InputResolver', () => { const resolvedInputs = resolver.resolveInputs(functionBlock, context) - expect(resolvedInputs.item).toBe('item1') // Direct value, not quoted + expect(resolvedInputs.item).toEqual(['item1']) // Current loop items }) it('should resolve direct loop.index reference without quotes', () => { @@ -989,7 +989,7 @@ describe('InputResolver', () => { environmentVariables: {}, decisions: { router: new Map(), condition: new Map() }, loopIterations: new Map(), - loopItems: new Map([['parallel-1', 'test-item']]), + loopItems: new Map([['parallel-1', ['test-item']]]), completedLoops: new Set(), executedBlocks: new Set(), activeExecutionPath: new Set(['function-1']), @@ -999,7 +999,7 @@ describe('InputResolver', () => { const block = workflow.blocks[1] const result = resolver.resolveInputs(block, context) - expect(result.code).toBe('test-item') + expect(result.code).toEqual(['test-item']) }) it('should resolve parallel references by block name when multiple parallels exist', () => { @@ -1027,7 +1027,7 @@ describe('InputResolver', () => { { id: 'function-1', position: { x: 0, y: 0 }, - config: { tool: 'function', params: { code: '' } }, + config: { tool: 'function', params: { code: '' } }, inputs: {}, outputs: {}, metadata: { id: 'function', name: 'Function 1' }, @@ -1055,7 +1055,7 @@ describe('InputResolver', () => { [ 'parallel-1', { - output: { response: { results: ['result1', 'result2'] } }, + output: { results: ['result1', 'result2'] }, executed: true, executionTime: 0, }, @@ -1063,7 +1063,7 @@ describe('InputResolver', () => { [ 'parallel-2', { - output: { response: { results: ['result3', 'result4'] } }, + output: { results: ['result3', 'result4'] }, executed: true, executionTime: 0, }, @@ -1104,7 +1104,7 @@ describe('InputResolver', () => { { id: 'function-1', position: { x: 0, y: 0 }, - config: { tool: 'function', params: { code: '' } }, + config: { tool: 'function', params: { code: '' } }, inputs: {}, outputs: {}, metadata: { id: 'function', name: 'Function 1' }, @@ -1128,7 +1128,7 @@ describe('InputResolver', () => { [ 'parallel-1', { - output: { response: { results: ['result1', 'result2'] } }, + output: { results: ['result1', 'result2'] }, executed: true, executionTime: 0, }, diff --git a/apps/sim/executor/resolver.ts b/apps/sim/executor/resolver.ts index b3fd7ab5d..08f8e55fa 100644 --- a/apps/sim/executor/resolver.ts +++ b/apps/sim/executor/resolver.ts @@ -379,7 +379,7 @@ export class InputResolver { } // Special case for "start" references - // This allows users to reference the starter block using + // This allows users to reference the starter block using // regardless of the actual name of the starter block if (blockRef.toLowerCase() === 'start') { // Find the starter block @@ -387,7 +387,8 @@ export class InputResolver { if (starterBlock) { const blockState = context.blockStates.get(starterBlock.id) if (blockState) { - // Navigate through the path parts + // For starter block, start directly with the flattened output (skip nested .output) + // This enables instead of let replacementValue: any = blockState.output for (const part of pathParts) { @@ -890,7 +891,7 @@ export class InputResolver { // As a fallback, look for the most recent array or object in any block's output // This is less reliable but might help in some cases for (const [_blockId, blockState] of context.blockStates.entries()) { - const output = blockState.output?.response + const output = blockState.output if (output) { for (const [_key, value] of Object.entries(output)) { if (Array.isArray(value) && value.length > 0) { diff --git a/apps/sim/executor/types.ts b/apps/sim/executor/types.ts index b361fc3ed..ac76a3538 100644 --- a/apps/sim/executor/types.ts +++ b/apps/sim/executor/types.ts @@ -5,37 +5,37 @@ import type { SerializedBlock, SerializedWorkflow } from '@/serializer/types' * Standardized block output format that ensures compatibility with the execution engine. */ export interface NormalizedBlockOutput { - /** Primary response data from the block execution */ - response: { - [key: string]: any - content?: string // Text content from LLM responses - model?: string // Model identifier used for generation - tokens?: { - prompt?: number - completion?: number - total?: number - } - toolCalls?: { - list: any[] - count: number - } - selectedPath?: { - blockId: string - blockType?: string - blockTitle?: string - } - selectedConditionId?: string // ID of selected condition - conditionResult?: boolean // Whether condition evaluated to true - result?: any // Generic result value - stdout?: string // Standard output from function execution - executionTime?: number // Time taken to execute - data?: any // Response data from API calls - status?: number // HTTP status code - headers?: Record // HTTP headers - error?: string // Error message if block execution failed + [key: string]: any + // Content fields + content?: string // Text content from LLM responses + model?: string // Model identifier used for generation + tokens?: { + prompt?: number + completion?: number + total?: number + } + toolCalls?: { + list: any[] + count: number + } + // Path selection fields + selectedPath?: { + blockId: string + blockType?: string + blockTitle?: string } - error?: string // Top-level error field for easy error checking - [key: string]: any // Additional properties + selectedConditionId?: string // ID of selected condition + conditionResult?: boolean // Whether condition evaluated to true + // Generic result fields + result?: any // Generic result value + stdout?: string // Standard output from function execution + executionTime?: number // Time taken to execute + // API response fields + data?: any // Response data from API calls + status?: number // HTTP status code + headers?: Record // HTTP headers + // Error handling + error?: string // Error message if block execution failed } /** diff --git a/apps/sim/lib/logs/execution-logger.ts b/apps/sim/lib/logs/execution-logger.ts index a726d7d21..b1086090a 100644 --- a/apps/sim/lib/logs/execution-logger.ts +++ b/apps/sim/lib/logs/execution-logger.ts @@ -115,46 +115,45 @@ export async function persistExecutionLogs( blockType: log.blockType, outputKeys: Object.keys(log.output), hasToolCalls: !!log.output.toolCalls, - hasResponse: !!log.output.response, + hasResponse: !!log.output, }) // FIRST PASS - Check if this is a no-tool scenario with tokens data not propagated // In some cases, the token data from the streaming callback doesn't properly get into // the agent block response. This ensures we capture it. if ( - log.output.response && - (!log.output.response.tokens?.completion || - log.output.response.tokens.completion === 0) && - (!log.output.response.toolCalls || - !log.output.response.toolCalls.list || - log.output.response.toolCalls.list.length === 0) + log.output && + (!log.output.tokens?.completion || log.output.tokens.completion === 0) && + (!log.output.toolCalls || + !log.output.toolCalls.list || + log.output.toolCalls.list.length === 0) ) { - // Check if output response has providerTiming - this indicates it's a streaming response - if (log.output.response.providerTiming) { + // Check if output has providerTiming - this indicates it's a streaming response + if (log.output.providerTiming) { logger.debug('Processing streaming response without tool calls for token extraction', { blockId: log.blockId, - hasTokens: !!log.output.response.tokens, - hasProviderTiming: !!log.output.response.providerTiming, + hasTokens: !!log.output.tokens, + hasProviderTiming: !!log.output.providerTiming, }) // Only for no-tool streaming cases, extract content length and estimate token count - const contentLength = log.output.response.content?.length || 0 + const contentLength = log.output.content?.length || 0 if (contentLength > 0) { // Estimate completion tokens based on content length as a fallback const estimatedCompletionTokens = Math.ceil(contentLength / 4) - const promptTokens = log.output.response.tokens?.prompt || 8 + const promptTokens = log.output.tokens?.prompt || 8 // Update the tokens object - log.output.response.tokens = { + log.output.tokens = { prompt: promptTokens, completion: estimatedCompletionTokens, total: promptTokens + estimatedCompletionTokens, } // Update cost information using the provider's cost model - const model = log.output.response.model || 'gpt-4o' + const model = log.output.model || 'gpt-4o' const costInfo = calculateCost(model, promptTokens, estimatedCompletionTokens) - log.output.response.cost = { + log.output.cost = { input: costInfo.input, output: costInfo.output, total: costInfo.total, @@ -165,7 +164,7 @@ export async function persistExecutionLogs( blockId: log.blockId, contentLength, estimatedCompletionTokens, - tokens: log.output.response.tokens, + tokens: log.output.tokens, }) } } @@ -185,74 +184,54 @@ export async function persistExecutionLogs( // Extract the executionData and use it as our primary source of information const executionData = log.output.executionData - // If executionData has output with response, use that as our response + // If executionData has output, merge it with our output // This is especially important for streaming responses where the final content // is set in the executionData structure by the executor - if (executionData.output?.response) { - log.output.response = executionData.output.response - logger.debug('Using response from executionData', { - responseKeys: Object.keys(log.output.response), - hasContent: !!log.output.response.content, - contentLength: log.output.response.content?.length || 0, - hasToolCalls: !!log.output.response.toolCalls, - hasTokens: !!log.output.response.tokens, - hasCost: !!log.output.response.cost, + if (executionData.output) { + log.output = { ...log.output, ...executionData.output } + logger.debug('Using output from executionData', { + outputKeys: Object.keys(log.output), + hasContent: !!log.output.content, + contentLength: log.output.content?.length || 0, + hasToolCalls: !!log.output.toolCalls, + hasTokens: !!log.output.tokens, + hasCost: !!log.output.cost, }) } } - // Extract tool calls and other metadata - if (log.output.response) { - const response = log.output.response - - // Process tool calls - if (response.toolCalls?.list) { - metadata = { - toolCalls: response.toolCalls.list.map((tc: any) => ({ - name: stripCustomToolPrefix(tc.name), - duration: tc.duration || 0, - startTime: tc.startTime || new Date().toISOString(), - endTime: tc.endTime || new Date().toISOString(), - status: tc.error ? 'error' : 'success', - input: tc.input || tc.arguments, - output: tc.output || tc.result, - error: tc.error, - })), - } + // Add cost information if available + if (log.output?.cost) { + const output = log.output + if (!metadata) metadata = {} + metadata.cost = { + model: output.model, + input: output.cost.input, + output: output.cost.output, + total: output.cost.total, + tokens: output.tokens, + pricing: output.cost.pricing, } - // Add cost information if available - if (response.cost) { - if (!metadata) metadata = {} - metadata.cost = { - model: response.model, - input: response.cost.input, - output: response.cost.output, - total: response.cost.total, - tokens: response.tokens, - pricing: response.cost.pricing, + // Accumulate costs for workflow-level summary + if (output.cost.total) { + totalCost += output.cost.total + totalInputCost += output.cost.input || 0 + totalOutputCost += output.cost.output || 0 + + // Track tokens + if (output.tokens) { + totalPromptTokens += output.tokens.prompt || 0 + totalCompletionTokens += output.tokens.completion || 0 + totalTokens += output.tokens.total || 0 } - // Accumulate costs for workflow-level summary - if (response.cost.total) { - totalCost += response.cost.total - totalInputCost += response.cost.input || 0 - totalOutputCost += response.cost.output || 0 - - // Track tokens - if (response.tokens) { - totalPromptTokens += response.tokens.prompt || 0 - totalCompletionTokens += response.tokens.completion || 0 - totalTokens += response.tokens.total || 0 - } - - // Track model usage - if (response.model) { - modelCounts[response.model] = (modelCounts[response.model] || 0) + 1 - // Set the most frequently used model as primary - if (!primaryModel || modelCounts[response.model] > modelCounts[primaryModel]) { - primaryModel = response.model - } + // Track model usage + if (output.model) { + modelCounts[output.model] = (modelCounts[output.model] || 0) + 1 + // Set the most frequently used model as primary + if (!primaryModel || modelCounts[output.model] > modelCounts[primaryModel]) { + primaryModel = output.model } } } @@ -342,50 +321,7 @@ export async function persistExecutionLogs( } }) } - // Case 3: Response has toolCalls - else if (log.output.response?.toolCalls) { - const toolCalls = Array.isArray(log.output.response.toolCalls) - ? log.output.response.toolCalls - : log.output.response.toolCalls.list || [] - - logger.debug('Found toolCalls in response', { - count: toolCalls.length, - }) - - // Log raw timing data for debugging - toolCalls.forEach((tc: any, idx: number) => { - logger.debug(`Response tool call ${idx} raw timing data:`, { - name: stripCustomToolPrefix(tc.name), - startTime: tc.startTime, - endTime: tc.endTime, - duration: tc.duration, - timing: tc.timing, - argumentKeys: tc.arguments ? Object.keys(tc.arguments) : undefined, - }) - }) - - toolCallData = toolCalls.map((toolCall: any) => { - // Extract timing info - try various formats that providers might use - const duration = extractDuration(toolCall) - const timing = extractTimingInfo( - toolCall, - blockStartTime ? new Date(blockStartTime) : undefined, - blockEndTime ? new Date(blockEndTime) : undefined - ) - - return { - name: toolCall.name, - duration: duration, - startTime: timing.startTime, - endTime: timing.endTime, - status: toolCall.error ? 'error' : 'success', - input: toolCall.arguments || toolCall.input, - output: toolCall.result || toolCall.output, - error: toolCall.error, - } - }) - } - // Case 4: toolCalls is an object and has a list property + // Case 3: toolCalls is an object and has a list property else if ( log.output.toolCalls && typeof log.output.toolCalls === 'object' && @@ -438,9 +374,9 @@ export async function persistExecutionLogs( } }) } - // Case 5: Look in executionData.output.response for streaming responses - else if (log.output.executionData?.output?.response?.toolCalls) { - const toolCallsObj = log.output.executionData.output.response.toolCalls + // Case 4: Look in executionData.output for streaming responses + else if (log.output.executionData?.output?.toolCalls) { + const toolCallsObj = log.output.executionData.output.toolCalls const list = Array.isArray(toolCallsObj) ? toolCallsObj : toolCallsObj.list || [] logger.debug('Found toolCalls in executionData output response', { @@ -480,9 +416,9 @@ export async function persistExecutionLogs( } }) } - // Case 6: Parse the response string for toolCalls as a last resort - else if (typeof log.output.response === 'string') { - const match = log.output.response.match(/"toolCalls"\s*:\s*({[^}]*}|(\[.*?\]))/s) + // Case 5: Parse the output string for toolCalls as a last resort + else if (typeof log.output === 'string') { + const match = log.output.match(/"toolCalls"\s*:\s*({[^}]*}|(\[.*?\]))/s) if (match) { try { const toolCallsJson = JSON.parse(`{${match[0]}}`) @@ -535,9 +471,9 @@ export async function persistExecutionLogs( } }) } catch (error) { - logger.error('Error parsing toolCalls from response string', { + logger.error('Error parsing toolCalls from output string', { error, - response: log.output.response, + output: log.output, }) } } @@ -549,7 +485,7 @@ export async function persistExecutionLogs( }) } - // Fill in missing timing information + // Fill in missing timing information and merge with existing metadata if (toolCallData.length > 0) { const getToolCalls = getToolCallTimings( toolCallData, @@ -563,12 +499,13 @@ export async function persistExecutionLogs( input: redactApiKeys(toolCall.input), })) - metadata = { - toolCalls: redactedToolCalls, - } + // Merge with existing metadata instead of overwriting + if (!metadata) metadata = {} + metadata.toolCalls = redactedToolCalls - logger.debug('Created metadata with tool calls', { + logger.debug('Added tool calls to metadata', { count: redactedToolCalls.length, + existingMetadata: Object.keys(metadata).filter((k) => k !== 'toolCalls'), }) } } @@ -580,9 +517,9 @@ export async function persistExecutionLogs( level: log.success ? 'info' : 'error', message: log.success ? `Block ${log.blockName || log.blockId} (${log.blockType || 'unknown'}): ${ - log.output?.response?.content || - log.output?.executionData?.output?.response?.content || - JSON.stringify(log.output?.response || {}) + log.output?.content || + log.output?.executionData?.output?.content || + JSON.stringify(log.output || {}) }` : `Block ${log.blockName || log.blockId} (${log.blockType || 'unknown'}): ${log.error || 'Failed'}`, duration: log.success ? `${log.durationMs}ms` : 'NA', @@ -646,8 +583,8 @@ export async function persistExecutionLogs( if (primaryModel && result.logs && result.logs.length > 0) { // Find the first agent log with pricing info for (const log of result.logs) { - if (log.output?.response?.cost?.pricing) { - workflowMetadata.cost.pricing = log.output.response.cost.pricing + if (log.output?.cost?.pricing) { + workflowMetadata.cost.pricing = log.output.cost.pricing break } } diff --git a/apps/sim/lib/logs/trace-spans.ts b/apps/sim/lib/logs/trace-spans.ts index eed9418a5..a083a4850 100644 --- a/apps/sim/lib/logs/trace-spans.ts +++ b/apps/sim/lib/logs/trace-spans.ts @@ -57,8 +57,8 @@ export function buildTraceSpans(result: ExecutionResult): { } // Add provider timing data if it exists - if (log.output?.response?.providerTiming) { - const providerTiming = log.output.response.providerTiming + if (log.output?.providerTiming) { + const providerTiming = log.output.providerTiming // If we have time segments, use them to create a more detailed timeline if (providerTiming.timeSegments && providerTiming.timeSegments.length > 0) { @@ -149,18 +149,18 @@ export function buildTraceSpans(result: ExecutionResult): { // Create a child span for the provider execution const providerSpan: TraceSpan = { id: `${spanId}-provider`, - name: log.output.response.model || 'AI Provider', + name: log.output.model || 'AI Provider', type: 'provider', duration: providerTiming.duration || 0, startTime: providerTiming.startTime || log.startedAt, endTime: providerTiming.endTime || log.endedAt, status: 'success', - tokens: log.output.response.tokens?.total, + tokens: log.output.tokens?.total, } // If we have model time, create a child span for just the model processing if (providerTiming.modelTime) { - const modelName = log.output.response.model || '' + const modelName = log.output.model || '' const modelSpan: TraceSpan = { id: `${spanId}-model`, name: `Model Generation${modelName ? ` (${modelName})` : ''}`, @@ -169,7 +169,7 @@ export function buildTraceSpans(result: ExecutionResult): { startTime: providerTiming.startTime, // Approximate endTime: providerTiming.endTime, // Approximate status: 'success', - tokens: log.output.response.tokens?.completion, + tokens: log.output.tokens?.completion, } if (!providerSpan.children) providerSpan.children = [] @@ -180,8 +180,8 @@ export function buildTraceSpans(result: ExecutionResult): { span.children.push(providerSpan) // When using provider timing without segments, still add tool calls if they exist - if (log.output?.response?.toolCalls?.list) { - span.toolCalls = log.output.response.toolCalls.list.map((tc: any) => ({ + if (log.output?.toolCalls?.list) { + span.toolCalls = log.output.toolCalls.list.map((tc: any) => ({ name: stripCustomToolPrefix(tc.name), duration: tc.duration || 0, startTime: tc.startTime || log.startedAt, @@ -205,15 +205,15 @@ export function buildTraceSpans(result: ExecutionResult): { // Wrap extraction in try-catch to handle unexpected toolCalls formats try { - if (log.output?.response?.toolCalls?.list) { + if (log.output?.toolCalls?.list) { // Standard format with list property - toolCallsList = log.output.response.toolCalls.list - } else if (Array.isArray(log.output?.response?.toolCalls)) { + toolCallsList = log.output.toolCalls.list + } else if (Array.isArray(log.output?.toolCalls)) { // Direct array format - toolCallsList = log.output.response.toolCalls - } else if (log.output?.executionData?.output?.response?.toolCalls) { + toolCallsList = log.output.toolCalls + } else if (log.output?.executionData?.output?.toolCalls) { // Streaming format with executionData - const tcObj = log.output.executionData.output.response.toolCalls + const tcObj = log.output.executionData.output.toolCalls toolCallsList = Array.isArray(tcObj) ? tcObj : tcObj.list || [] } diff --git a/apps/sim/lib/uploads/s3/s3-client.test.ts b/apps/sim/lib/uploads/s3/s3-client.test.ts index 3a3a3d4e9..eaadc7fdf 100644 --- a/apps/sim/lib/uploads/s3/s3-client.test.ts +++ b/apps/sim/lib/uploads/s3/s3-client.test.ts @@ -38,6 +38,14 @@ describe('S3 Client', () => { }, })) + // Mock environment variables - use fake credentials for testing + vi.doMock('../../env', () => ({ + env: { + AWS_ACCESS_KEY_ID: 'AKIA_FAKE_KEY_FOR_TESTING', + AWS_SECRET_ACCESS_KEY: 'fake-secret-key-for-testing-only', + }, + })) + vi.spyOn(Date, 'now').mockReturnValue(1672603200000) vi.spyOn(Date.prototype, 'toISOString').mockReturnValue('2025-06-16T01:13:10.765Z') }) @@ -286,8 +294,14 @@ describe('S3 Client', () => { const client = getS3Client() expect(client).toBeDefined() - // Verify the client was constructed with the right configuration - expect(S3Client).toHaveBeenCalledWith({ region: 'test-region' }) + // Verify the client was constructed with the right configuration including fake credentials + expect(S3Client).toHaveBeenCalledWith({ + region: 'test-region', + credentials: { + accessKeyId: 'AKIA_FAKE_KEY_FOR_TESTING', + secretAccessKey: 'fake-secret-key-for-testing-only', + }, + }) }) }) }) diff --git a/apps/sim/lib/workflows/db-helpers.test.ts b/apps/sim/lib/workflows/db-helpers.test.ts index be9d23734..66035a06b 100644 --- a/apps/sim/lib/workflows/db-helpers.test.ts +++ b/apps/sim/lib/workflows/db-helpers.test.ts @@ -92,7 +92,7 @@ const mockBlocksFromDb = [ isWide: false, height: 150, subBlocks: { input: { id: 'input', type: 'short-input', value: 'test' } }, - outputs: { response: { type: 'string' } }, + outputs: { result: { type: 'string' } }, data: { parentId: null, extent: null, width: 350 }, parentId: null, extent: null, @@ -159,7 +159,7 @@ const mockWorkflowState: WorkflowState = { name: 'Start Block', position: { x: 100, y: 100 }, subBlocks: { input: { id: 'input', type: 'short-input', value: 'test' } }, - outputs: { response: { type: 'string' } }, + outputs: { result: { type: 'string' } }, enabled: true, horizontalHandles: true, isWide: false, @@ -268,7 +268,7 @@ describe('Database Helpers', () => { isWide: false, height: 150, subBlocks: { input: { id: 'input', type: 'short-input', value: 'test' } }, - outputs: { response: { type: 'string' } }, + outputs: { result: { type: 'string' } }, data: { parentId: null, extent: null, width: 350 }, parentId: null, extent: null, diff --git a/apps/sim/providers/anthropic/index.ts b/apps/sim/providers/anthropic/index.ts index 59e9c19e6..41476db91 100644 --- a/apps/sim/providers/anthropic/index.ts +++ b/apps/sim/providers/anthropic/index.ts @@ -289,31 +289,29 @@ ${fieldDescriptions} execution: { success: true, output: { - response: { - content: '', // Will be filled by streaming content in chat component - model: request.model, - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Estimate token cost based on typical Claude pricing - cost: { - total: 0.0, - input: 0.0, - output: 0.0, - }, + content: '', // Will be filled by streaming content in chat component + model: request.model, + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], + }, + // Estimate token cost based on typical Claude pricing + cost: { + total: 0.0, + input: 0.0, + output: 0.0, }, }, logs: [], // No block logs for direct streaming @@ -641,36 +639,34 @@ ${fieldDescriptions} execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model || 'claude-3-7-sonnet-20250219', - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, // Estimate cost based on tokens - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', // Will be filled by the callback + model: request.model || 'claude-3-7-sonnet-20250219', + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, // Estimate cost based on tokens + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], // No block logs at provider level diff --git a/apps/sim/providers/azure-openai/index.ts b/apps/sim/providers/azure-openai/index.ts index 1c7dfa660..91639241d 100644 --- a/apps/sim/providers/azure-openai/index.ts +++ b/apps/sim/providers/azure-openai/index.ts @@ -212,22 +212,22 @@ export const azureOpenAIProvider: ProviderConfig = { stream: createReadableStreamFromAzureOpenAIStream(streamResponse, (content, usage) => { // Update the execution data with the final content and token usage _streamContent = content - streamingResult.execution.output.response.content = content + streamingResult.execution.output.content = content // Update the timing information with the actual completion time const streamEndTime = Date.now() const streamEndTimeISO = new Date(streamEndTime).toISOString() - if (streamingResult.execution.output.response.providerTiming) { - streamingResult.execution.output.response.providerTiming.endTime = streamEndTimeISO - streamingResult.execution.output.response.providerTiming.duration = + if (streamingResult.execution.output.providerTiming) { + streamingResult.execution.output.providerTiming.endTime = streamEndTimeISO + streamingResult.execution.output.providerTiming.duration = streamEndTime - providerStartTime // Update the time segment as well - if (streamingResult.execution.output.response.providerTiming.timeSegments?.[0]) { - streamingResult.execution.output.response.providerTiming.timeSegments[0].endTime = + if (streamingResult.execution.output.providerTiming.timeSegments?.[0]) { + streamingResult.execution.output.providerTiming.timeSegments[0].endTime = streamEndTime - streamingResult.execution.output.response.providerTiming.timeSegments[0].duration = + streamingResult.execution.output.providerTiming.timeSegments[0].duration = streamEndTime - providerStartTime } } @@ -240,34 +240,32 @@ export const azureOpenAIProvider: ProviderConfig = { total: usage.total_tokens || tokenUsage.total, } - streamingResult.execution.output.response.tokens = newTokens + streamingResult.execution.output.tokens = newTokens } // We don't need to estimate tokens here as execution-logger.ts will handle that }), execution: { success: true, output: { - response: { - content: '', // Will be filled by the stream completion callback - model: request.model, - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Cost will be calculated in execution-logger.ts + content: '', // Will be filled by the stream completion callback + model: request.model, + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], }, + // Cost will be calculated in execution-logger.ts }, logs: [], // No block logs for direct streaming metadata: { @@ -527,7 +525,7 @@ export const azureOpenAIProvider: ProviderConfig = { stream: createReadableStreamFromAzureOpenAIStream(streamResponse, (content, usage) => { // Update the execution data with the final content and token usage _streamContent = content - streamingResult.execution.output.response.content = content + streamingResult.execution.output.content = content // Update token usage if available from the stream if (usage) { @@ -537,39 +535,37 @@ export const azureOpenAIProvider: ProviderConfig = { total: usage.total_tokens || tokens.total, } - streamingResult.execution.output.response.tokens = newTokens + streamingResult.execution.output.tokens = newTokens } }), execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model, - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - // Cost will be calculated in execution-logger.ts + content: '', // Will be filled by the callback + model: request.model, + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, }, + // Cost will be calculated in execution-logger.ts }, logs: [], // No block logs at provider level metadata: { diff --git a/apps/sim/providers/cerebras/index.ts b/apps/sim/providers/cerebras/index.ts index d1cd664ee..5fdca4c74 100644 --- a/apps/sim/providers/cerebras/index.ts +++ b/apps/sim/providers/cerebras/index.ts @@ -157,31 +157,29 @@ export const cerebrasProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by streaming content in chat component - model: request.model || 'cerebras/llama-3.3-70b', - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Estimate token cost - cost: { - total: 0.0, - input: 0.0, - output: 0.0, - }, + content: '', // Will be filled by streaming content in chat component + model: request.model || 'cerebras/llama-3.3-70b', + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], + }, + // Estimate token cost + cost: { + total: 0.0, + input: 0.0, + output: 0.0, }, }, logs: [], // No block logs for direct streaming @@ -461,36 +459,34 @@ export const cerebrasProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model || 'cerebras/llama-3.3-70b', - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', // Will be filled by the callback + model: request.model || 'cerebras/llama-3.3-70b', + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], // No block logs at provider level diff --git a/apps/sim/providers/deepseek/index.ts b/apps/sim/providers/deepseek/index.ts index 6b18551ba..d91b7008d 100644 --- a/apps/sim/providers/deepseek/index.ts +++ b/apps/sim/providers/deepseek/index.ts @@ -151,31 +151,29 @@ export const deepseekProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by streaming content in chat component - model: request.model || 'deepseek-chat', - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Estimate token cost - cost: { - total: 0.0, - input: 0.0, - output: 0.0, - }, + content: '', // Will be filled by streaming content in chat component + model: request.model || 'deepseek-chat', + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], + }, + // Estimate token cost + cost: { + total: 0.0, + input: 0.0, + output: 0.0, }, }, logs: [], // No block logs for direct streaming @@ -461,36 +459,34 @@ export const deepseekProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model || 'deepseek-chat', - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', // Will be filled by the callback + model: request.model || 'deepseek-chat', + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], // No block logs at provider level diff --git a/apps/sim/providers/google/index.ts b/apps/sim/providers/google/index.ts index 594799e33..f2413d27a 100644 --- a/apps/sim/providers/google/index.ts +++ b/apps/sim/providers/google/index.ts @@ -215,36 +215,34 @@ export const googleProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', - model: request.model, - tokens: { - prompt: 0, - completion: 0, - total: 0, - }, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: firstResponseTime, - modelTime: firstResponseTime, - toolsTime: 0, - firstResponseTime, - iterations: 1, - timeSegments: [ - { - type: 'model', - name: 'Initial streaming response', - startTime: initialCallTime, - endTime: initialCallTime + firstResponseTime, - duration: firstResponseTime, - }, - ], - cost: { - total: 0.0, // Initial estimate, updated as tokens are processed - input: 0.0, - output: 0.0, + content: '', + model: request.model, + tokens: { + prompt: 0, + completion: 0, + total: 0, + }, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: firstResponseTime, + modelTime: firstResponseTime, + toolsTime: 0, + firstResponseTime, + iterations: 1, + timeSegments: [ + { + type: 'model', + name: 'Initial streaming response', + startTime: initialCallTime, + endTime: initialCallTime + firstResponseTime, + duration: firstResponseTime, }, + ], + cost: { + total: 0.0, // Initial estimate, updated as tokens are processed + input: 0.0, + output: 0.0, }, }, }, @@ -527,33 +525,31 @@ export const googleProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', - model: request.model, - tokens, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - toolResults, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime, - toolsTime, - firstResponseTime, - iterations: iterationCount + 1, - timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, // Estimate cost based on tokens - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', + model: request.model, + tokens, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + toolResults, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime, + toolsTime, + firstResponseTime, + iterations: iterationCount + 1, + timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, // Estimate cost based on tokens + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], diff --git a/apps/sim/providers/groq/index.ts b/apps/sim/providers/groq/index.ts index b67f7fdc3..d928924ea 100644 --- a/apps/sim/providers/groq/index.ts +++ b/apps/sim/providers/groq/index.ts @@ -153,30 +153,28 @@ export const groqProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by streaming content in chat component - model: request.model || 'groq/meta-llama/llama-4-scout-17b-16e-instruct', - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - cost: { - total: 0.0, - input: 0.0, - output: 0.0, - }, + content: '', // Will be filled by streaming content in chat component + model: request.model || 'groq/meta-llama/llama-4-scout-17b-16e-instruct', + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], + }, + cost: { + total: 0.0, + input: 0.0, + output: 0.0, }, }, logs: [], // No block logs for direct streaming @@ -380,36 +378,34 @@ export const groqProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model || 'groq/meta-llama/llama-4-scout-17b-16e-instruct', - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', // Will be filled by the callback + model: request.model || 'groq/meta-llama/llama-4-scout-17b-16e-instruct', + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], // No block logs at provider level diff --git a/apps/sim/providers/ollama/index.ts b/apps/sim/providers/ollama/index.ts index e71c1de42..845830786 100644 --- a/apps/sim/providers/ollama/index.ts +++ b/apps/sim/providers/ollama/index.ts @@ -53,7 +53,6 @@ export const ollamaProvider: ProviderConfig = { }) const startTime = Date.now() - const _timeSegments: TimeSegment[] = [] try { // Prepare messages array @@ -126,9 +125,6 @@ export const ollamaProvider: ProviderConfig = { } } - // Track the original tool_choice for forced tool tracking - const _originalToolChoice = payload.tool_choice - let currentResponse = await ollama.chat.completions.create(payload) const firstResponseTime = Date.now() - startTime diff --git a/apps/sim/providers/openai/index.ts b/apps/sim/providers/openai/index.ts index 9c6624574..43452c8b7 100644 --- a/apps/sim/providers/openai/index.ts +++ b/apps/sim/providers/openai/index.ts @@ -194,22 +194,22 @@ export const openaiProvider: ProviderConfig = { stream: createReadableStreamFromOpenAIStream(streamResponse, (content, usage) => { // Update the execution data with the final content and token usage _streamContent = content - streamingResult.execution.output.response.content = content + streamingResult.execution.output.content = content // Update the timing information with the actual completion time const streamEndTime = Date.now() const streamEndTimeISO = new Date(streamEndTime).toISOString() - if (streamingResult.execution.output.response.providerTiming) { - streamingResult.execution.output.response.providerTiming.endTime = streamEndTimeISO - streamingResult.execution.output.response.providerTiming.duration = + if (streamingResult.execution.output.providerTiming) { + streamingResult.execution.output.providerTiming.endTime = streamEndTimeISO + streamingResult.execution.output.providerTiming.duration = streamEndTime - providerStartTime // Update the time segment as well - if (streamingResult.execution.output.response.providerTiming.timeSegments?.[0]) { - streamingResult.execution.output.response.providerTiming.timeSegments[0].endTime = + if (streamingResult.execution.output.providerTiming.timeSegments?.[0]) { + streamingResult.execution.output.providerTiming.timeSegments[0].endTime = streamEndTime - streamingResult.execution.output.response.providerTiming.timeSegments[0].duration = + streamingResult.execution.output.providerTiming.timeSegments[0].duration = streamEndTime - providerStartTime } } @@ -222,34 +222,32 @@ export const openaiProvider: ProviderConfig = { total: usage.total_tokens || tokenUsage.total, } - streamingResult.execution.output.response.tokens = newTokens + streamingResult.execution.output.tokens = newTokens } // We don't need to estimate tokens here as execution-logger.ts will handle that }), execution: { success: true, output: { - response: { - content: '', // Will be filled by the stream completion callback - model: request.model, - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Cost will be calculated in execution-logger.ts + content: '', // Will be filled by the stream completion callback + model: request.model, + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], }, + // Cost will be calculated in execution-logger.ts }, logs: [], // No block logs for direct streaming metadata: { @@ -509,7 +507,7 @@ export const openaiProvider: ProviderConfig = { stream: createReadableStreamFromOpenAIStream(streamResponse, (content, usage) => { // Update the execution data with the final content and token usage _streamContent = content - streamingResult.execution.output.response.content = content + streamingResult.execution.output.content = content // Update token usage if available from the stream if (usage) { @@ -519,39 +517,37 @@ export const openaiProvider: ProviderConfig = { total: usage.total_tokens || tokens.total, } - streamingResult.execution.output.response.tokens = newTokens + streamingResult.execution.output.tokens = newTokens } }), execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model, - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - // Cost will be calculated in execution-logger.ts + content: '', // Will be filled by the callback + model: request.model, + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, }, + // Cost will be calculated in execution-logger.ts }, logs: [], // No block logs at provider level metadata: { diff --git a/apps/sim/providers/utils.test.ts b/apps/sim/providers/utils.test.ts index 4672df358..4c1364cab 100644 --- a/apps/sim/providers/utils.test.ts +++ b/apps/sim/providers/utils.test.ts @@ -549,42 +549,6 @@ describe('JSON and Structured Output', () => { expect(generateStructuredOutputInstructions(objectFormat)).toBe('') }) - it.concurrent('should generate instructions for legacy fields format', () => { - const fieldsFormat = { - fields: [ - { name: 'score', type: 'number', description: 'A score from 1-10' }, - { name: 'comment', type: 'string', description: 'A comment' }, - ], - } - const result = generateStructuredOutputInstructions(fieldsFormat) - - expect(result).toContain('JSON format') - expect(result).toContain('score') - expect(result).toContain('comment') - expect(result).toContain('A score from 1-10') - }) - - it.concurrent('should handle object fields with properties', () => { - const fieldsFormat = { - fields: [ - { - name: 'metadata', - type: 'object', - properties: { - version: { type: 'string', description: 'Version number' }, - count: { type: 'number', description: 'Item count' }, - }, - }, - ], - } - const result = generateStructuredOutputInstructions(fieldsFormat) - - expect(result).toContain('metadata') - expect(result).toContain('Properties:') - expect(result).toContain('version') - expect(result).toContain('count') - }) - it.concurrent('should return empty string for missing fields', () => { expect(generateStructuredOutputInstructions({})).toBe('') expect(generateStructuredOutputInstructions(null)).toBe('') diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index b48c3c40f..619b22cc7 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -179,66 +179,7 @@ export function getProviderModels(providerId: ProviderId): string[] { } export function generateStructuredOutputInstructions(responseFormat: any): string { - // Handle null/undefined input - if (!responseFormat) return '' - - // If using the new JSON Schema format, don't add additional instructions - // This is necessary because providers now handle the schema directly - if (responseFormat.schema || (responseFormat.type === 'object' && responseFormat.properties)) { - return '' - } - - // Handle legacy format with fields array - if (!responseFormat.fields) return '' - - function generateFieldStructure(field: any): string { - if (field.type === 'object' && field.properties) { - return `{ - ${Object.entries(field.properties) - .map(([key, prop]: [string, any]) => `"${key}": ${prop.type === 'number' ? '0' : '"value"'}`) - .join(',\n ')} - }` - } - return field.type === 'string' - ? '"value"' - : field.type === 'number' - ? '0' - : field.type === 'boolean' - ? 'true/false' - : '[]' - } - - const exampleFormat = responseFormat.fields - .map((field: any) => ` "${field.name}": ${generateFieldStructure(field)}`) - .join(',\n') - - const fieldDescriptions = responseFormat.fields - .map((field: any) => { - let desc = `${field.name} (${field.type})` - if (field.description) desc += `: ${field.description}` - if (field.type === 'object' && field.properties) { - desc += '\nProperties:' - Object.entries(field.properties).forEach(([key, prop]: [string, any]) => { - desc += `\n - ${key} (${(prop as any).type}): ${(prop as any).description || ''}` - }) - } - return desc - }) - .join('\n') - - logger.info(`Generated structured output instructions for ${responseFormat.fields.length} fields`) - - return ` -Please provide your response in the following JSON format: -{ -${exampleFormat} -} - -Field descriptions: -${fieldDescriptions} - -Your response MUST be valid JSON and include all the specified fields with their correct types. -Each metric should be an object containing 'score' (number) and 'reasoning' (string).` + return '' } export function extractAndParseJSON(content: string): any { diff --git a/apps/sim/providers/xai/index.ts b/apps/sim/providers/xai/index.ts index a4f2f9be9..957d49574 100644 --- a/apps/sim/providers/xai/index.ts +++ b/apps/sim/providers/xai/index.ts @@ -169,31 +169,29 @@ export const xAIProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by streaming content in chat component - model: request.model || 'grok-3-latest', - tokens: tokenUsage, - toolCalls: undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - timeSegments: [ - { - type: 'model', - name: 'Streaming response', - startTime: providerStartTime, - endTime: Date.now(), - duration: Date.now() - providerStartTime, - }, - ], - }, - // Estimate token cost - cost: { - total: 0.0, - input: 0.0, - output: 0.0, - }, + content: '', // Will be filled by streaming content in chat component + model: request.model || 'grok-3-latest', + tokens: tokenUsage, + toolCalls: undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + timeSegments: [ + { + type: 'model', + name: 'Streaming response', + startTime: providerStartTime, + endTime: Date.now(), + duration: Date.now() - providerStartTime, + }, + ], + }, + // Estimate token cost + cost: { + total: 0.0, + input: 0.0, + output: 0.0, }, }, logs: [], // No block logs for direct streaming @@ -514,36 +512,34 @@ export const xAIProvider: ProviderConfig = { execution: { success: true, output: { - response: { - content: '', // Will be filled by the callback - model: request.model || 'grok-3-latest', - tokens: { - prompt: tokens.prompt, - completion: tokens.completion, - total: tokens.total, - }, - toolCalls: - toolCalls.length > 0 - ? { - list: toolCalls, - count: toolCalls.length, - } - : undefined, - providerTiming: { - startTime: providerStartTimeISO, - endTime: new Date().toISOString(), - duration: Date.now() - providerStartTime, - modelTime: modelTime, - toolsTime: toolsTime, - firstResponseTime: firstResponseTime, - iterations: iterationCount + 1, - timeSegments: timeSegments, - }, - cost: { - total: (tokens.total || 0) * 0.0001, - input: (tokens.prompt || 0) * 0.0001, - output: (tokens.completion || 0) * 0.0001, - }, + content: '', // Will be filled by the callback + model: request.model || 'grok-3-latest', + tokens: { + prompt: tokens.prompt, + completion: tokens.completion, + total: tokens.total, + }, + toolCalls: + toolCalls.length > 0 + ? { + list: toolCalls, + count: toolCalls.length, + } + : undefined, + providerTiming: { + startTime: providerStartTimeISO, + endTime: new Date().toISOString(), + duration: Date.now() - providerStartTime, + modelTime: modelTime, + toolsTime: toolsTime, + firstResponseTime: firstResponseTime, + iterations: iterationCount + 1, + timeSegments: timeSegments, + }, + cost: { + total: (tokens.total || 0) * 0.0001, + input: (tokens.prompt || 0) * 0.0001, + output: (tokens.completion || 0) * 0.0001, }, }, logs: [], // No block logs at provider level diff --git a/apps/sim/stores/panel/console/store.test.ts b/apps/sim/stores/panel/console/store.test.ts index ebdbe636f..975ac8593 100644 --- a/apps/sim/stores/panel/console/store.test.ts +++ b/apps/sim/stores/panel/console/store.test.ts @@ -29,7 +29,7 @@ describe('Console Store', () => { blockName: 'Test Block', blockType: 'agent', success: true, - output: { response: { content: 'Test output' } }, + output: { content: 'Test output' }, durationMs: 100, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', @@ -78,7 +78,7 @@ describe('Console Store', () => { blockName: 'Test Block', blockType: 'agent', success: true, - output: { response: { content: 'Initial content' } }, + output: { content: 'Initial content' }, durationMs: 100, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', @@ -92,7 +92,7 @@ describe('Console Store', () => { const state = useConsoleStore.getState() expect(state.entries).toHaveLength(1) - expect(state.entries[0].output?.response?.content).toBe('Updated content') + expect(state.entries[0].output?.content).toBe('Updated content') }) it('should update console entry with object update', () => { @@ -111,7 +111,7 @@ describe('Console Store', () => { const state = useConsoleStore.getState() const entry = state.entries[0] - expect(entry.output?.response?.content).toBe('New content') + expect(entry.output?.content).toBe('New content') expect(entry.success).toBe(false) expect(entry.error).toBe('Update error') expect(entry.durationMs).toBe(200) @@ -123,10 +123,8 @@ describe('Console Store', () => { const update: ConsoleUpdate = { output: { - response: { - content: 'Direct output update', - status: 200, - }, + content: 'Direct output update', + status: 200, }, } @@ -135,8 +133,8 @@ describe('Console Store', () => { const state = useConsoleStore.getState() const entry = state.entries[0] - expect(entry.output?.response?.content).toBe('Direct output update') - expect(entry.output?.response?.status).toBe(200) + expect(entry.output?.content).toBe('Direct output update') + expect(entry.output?.status).toBe(200) }) it('should not update non-matching block IDs', () => { @@ -145,7 +143,7 @@ describe('Console Store', () => { store.updateConsole('non-existent-block', 'Should not update') const newState = useConsoleStore.getState() - expect(newState.entries[0].output?.response?.content).toBe('Initial content') + expect(newState.entries[0].output?.content).toBe('Initial content') }) it('should handle partial updates correctly', () => { @@ -156,14 +154,14 @@ describe('Console Store', () => { let state = useConsoleStore.getState() expect(state.entries[0].success).toBe(false) - expect(state.entries[0].output?.response?.content).toBe('Initial content') // Should remain unchanged + expect(state.entries[0].output?.content).toBe('Initial content') // Should remain unchanged // Then update only content store.updateConsole('block-123', { content: 'Partial update' }) state = useConsoleStore.getState() expect(state.entries[0].success).toBe(false) // Should remain false - expect(state.entries[0].output?.response?.content).toBe('Partial update') + expect(state.entries[0].output?.content).toBe('Partial update') }) }) @@ -178,7 +176,7 @@ describe('Console Store', () => { blockName: 'Block 1', blockType: 'agent', success: true, - output: { response: {} }, + output: {}, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', }) @@ -189,7 +187,7 @@ describe('Console Store', () => { blockName: 'Block 2', blockType: 'api', success: true, - output: { response: {} }, + output: {}, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', }) @@ -230,7 +228,7 @@ describe('Console Store', () => { blockName: 'Block 1', blockType: 'agent', success: true, - output: { response: {} }, + output: {}, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', }) @@ -241,7 +239,7 @@ describe('Console Store', () => { blockName: 'Block 2', blockType: 'api', success: true, - output: { response: {} }, + output: {}, startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', }) @@ -252,7 +250,7 @@ describe('Console Store', () => { blockName: 'Block 3', blockType: 'function', success: false, - output: { response: {} }, + output: {}, error: 'Test error', startedAt: '2023-01-01T00:00:00.000Z', endedAt: '2023-01-01T00:00:01.000Z', diff --git a/apps/sim/stores/panel/console/store.ts b/apps/sim/stores/panel/console/store.ts index b315e0a96..396666823 100644 --- a/apps/sim/stores/panel/console/store.ts +++ b/apps/sim/stores/panel/console/store.ts @@ -14,15 +14,11 @@ const updateBlockOutput = ( existingOutput: NormalizedBlockOutput | undefined, contentUpdate: string ): NormalizedBlockOutput => { - const defaultOutput: NormalizedBlockOutput = { response: {} } - const baseOutput = existingOutput || defaultOutput + const baseOutput = existingOutput || {} return { ...baseOutput, - response: { - ...baseOutput.response, - content: contentUpdate, - }, + content: contentUpdate, } } @@ -198,14 +194,10 @@ export const useConsoleStore = create()( } if (update.output !== undefined) { - const existingOutput = entry.output || { response: {} } + const existingOutput = entry.output || {} updatedEntry.output = { ...existingOutput, ...update.output, - response: { - ...(existingOutput.response || {}), - ...(update.output.response || {}), - }, } } diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index 0ce08f959..c96ca185c 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -1,6 +1,8 @@ import { create } from 'zustand' import { devtools } from 'zustand/middleware' import { createLogger } from '@/lib/logs/console-logger' +import { StarterBlock } from '@/blocks/blocks/starter' +import type { SubBlockConfig } from '@/blocks/types' import { clearWorkflowVariablesTracking } from '@/stores/panel/variables/store' import { API_ENDPOINTS } from '../../constants' import { useSubBlockStore } from '../subblock/store' @@ -662,7 +664,38 @@ export const useWorkflowRegistry = create()( }, })) } else { - // If no state in DB, use empty state - server should have created start block + // If no state in DB, initialize with starter block using proper configuration + const starterId = crypto.randomUUID() + + // Create subBlocks from StarterBlock configuration with default values + const subBlocks: Record = {} + StarterBlock.subBlocks.forEach((subBlock: SubBlockConfig) => { + subBlocks[subBlock.id] = { + id: subBlock.id, + type: subBlock.type, + value: subBlock.value + ? subBlock.value({}) + : subBlock.type === 'dropdown' + ? Array.isArray(subBlock.options) + ? subBlock.options[0] + : 'manual' + : '', + } + }) + + const starterBlock = { + id: starterId, + type: 'starter' as const, + name: StarterBlock.name, + position: { x: 100, y: 100 }, + subBlocks, + outputs: StarterBlock.outputs, + enabled: true, + horizontalHandles: true, + isWide: false, + height: 0, + } + workflowState = { blocks: {}, edges: [], @@ -793,92 +826,32 @@ export const useWorkflowRegistry = create()( logger.info(`Created workflow from marketplace: ${options.marketplaceId}`) } else { - // Create starter block for new workflow + // Create starter block for new workflow using StarterBlock configuration const starterId = crypto.randomUUID() + + // Create subBlocks from StarterBlock configuration with default values + const subBlocks: Record = {} + StarterBlock.subBlocks.forEach((subBlock: SubBlockConfig) => { + subBlocks[subBlock.id] = { + id: subBlock.id, + type: subBlock.type, + value: subBlock.value + ? subBlock.value({}) + : subBlock.type === 'dropdown' + ? Array.isArray(subBlock.options) + ? subBlock.options[0] + : 'manual' + : '', + } + }) + const starterBlock = { id: starterId, type: 'starter' as const, - name: 'Start', + name: StarterBlock.name, position: { x: 100, y: 100 }, - subBlocks: { - startWorkflow: { - id: 'startWorkflow', - type: 'dropdown' as const, - value: 'manual', - }, - webhookPath: { - id: 'webhookPath', - type: 'short-input' as const, - value: '', - }, - webhookSecret: { - id: 'webhookSecret', - type: 'short-input' as const, - value: '', - }, - scheduleType: { - id: 'scheduleType', - type: 'dropdown' as const, - value: 'daily', - }, - minutesInterval: { - id: 'minutesInterval', - type: 'short-input' as const, - value: '', - }, - minutesStartingAt: { - id: 'minutesStartingAt', - type: 'short-input' as const, - value: '', - }, - hourlyMinute: { - id: 'hourlyMinute', - type: 'short-input' as const, - value: '', - }, - dailyTime: { - id: 'dailyTime', - type: 'short-input' as const, - value: '', - }, - weeklyDay: { - id: 'weeklyDay', - type: 'dropdown' as const, - value: 'MON', - }, - weeklyDayTime: { - id: 'weeklyDayTime', - type: 'short-input' as const, - value: '', - }, - monthlyDay: { - id: 'monthlyDay', - type: 'short-input' as const, - value: '', - }, - monthlyTime: { - id: 'monthlyTime', - type: 'short-input' as const, - value: '', - }, - cronExpression: { - id: 'cronExpression', - type: 'short-input' as const, - value: '', - }, - timezone: { - id: 'timezone', - type: 'dropdown' as const, - value: 'UTC', - }, - }, - outputs: { - response: { - type: { - input: 'any', - }, - }, - }, + subBlocks, + outputs: StarterBlock.outputs, enabled: true, horizontalHandles: true, isWide: false, @@ -1226,93 +1199,32 @@ export const useWorkflowRegistry = create()( parallels: currentWorkflowState.parallels || {}, } } else { - // Source is not active workflow, create with starter block for now - // In a future enhancement, we could fetch from DB + // Source is not active workflow, create with starter block using StarterBlock configuration const starterId = crypto.randomUUID() + + // Create subBlocks from StarterBlock configuration with default values + const subBlocks: Record = {} + StarterBlock.subBlocks.forEach((subBlock: SubBlockConfig) => { + subBlocks[subBlock.id] = { + id: subBlock.id, + type: subBlock.type, + value: subBlock.value + ? subBlock.value({}) + : subBlock.type === 'dropdown' + ? Array.isArray(subBlock.options) + ? subBlock.options[0] + : 'manual' + : '', + } + }) + const starterBlock = { id: starterId, type: 'starter' as const, - name: 'Start', + name: StarterBlock.name, position: { x: 100, y: 100 }, - subBlocks: { - startWorkflow: { - id: 'startWorkflow', - type: 'dropdown' as const, - value: 'manual', - }, - webhookPath: { - id: 'webhookPath', - type: 'short-input' as const, - value: '', - }, - webhookSecret: { - id: 'webhookSecret', - type: 'short-input' as const, - value: '', - }, - scheduleType: { - id: 'scheduleType', - type: 'dropdown' as const, - value: 'daily', - }, - minutesInterval: { - id: 'minutesInterval', - type: 'short-input' as const, - value: '', - }, - minutesStartingAt: { - id: 'minutesStartingAt', - type: 'short-input' as const, - value: '', - }, - hourlyMinute: { - id: 'hourlyMinute', - type: 'short-input' as const, - value: '', - }, - dailyTime: { - id: 'dailyTime', - type: 'short-input' as const, - value: '', - }, - weeklyDay: { - id: 'weeklyDay', - type: 'dropdown' as const, - value: 'MON', - }, - weeklyDayTime: { - id: 'weeklyDayTime', - type: 'short-input' as const, - value: '', - }, - monthlyDay: { - id: 'monthlyDay', - type: 'short-input' as const, - value: '', - }, - monthlyTime: { - id: 'monthlyTime', - type: 'short-input' as const, - value: '', - }, - cronExpression: { - id: 'cronExpression', - type: 'short-input' as const, - value: '', - }, - timezone: { - id: 'timezone', - type: 'dropdown' as const, - value: 'UTC', - }, - }, - outputs: { - response: { - type: { - input: 'any', - }, - }, - }, + subBlocks, + outputs: StarterBlock.outputs, enabled: true, horizontalHandles: true, isWide: false, diff --git a/apps/sim/stores/workflows/workflow/utils.test.ts b/apps/sim/stores/workflows/workflow/utils.test.ts index faaa1fb70..96ceb4131 100644 --- a/apps/sim/stores/workflows/workflow/utils.test.ts +++ b/apps/sim/stores/workflows/workflow/utils.test.ts @@ -67,7 +67,7 @@ describe('convertLoopBlockToLoop', () => { data: { loopType: 'forEach', count: 5, - collection: '', + collection: '', }, }, } @@ -75,7 +75,7 @@ describe('convertLoopBlockToLoop', () => { const result = convertLoopBlockToLoop('loop1', blocks) expect(result).toBeDefined() - expect(result?.forEachItems).toBe('') + expect(result?.forEachItems).toBe('') }) test('should handle empty collection', () => { From 0703e82389a3075fb9616f3a4be42e1be0f68c50 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 17:40:40 -0700 Subject: [PATCH 07/13] refactor: remove depends on from block config --- .../output-select/output-select.tsx | 8 --- apps/sim/blocks/blocks/agent.ts | 12 ----- apps/sim/blocks/types.ts | 14 ------ apps/sim/blocks/utils.ts | 49 ++----------------- apps/sim/components/ui/tag-dropdown.tsx | 5 -- 5 files changed, 4 insertions(+), 84 deletions(-) diff --git a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx index bd349f493..71b24b790 100644 --- a/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx +++ b/apps/sim/app/w/[id]/components/panel/components/chat/components/output-select/output-select.tsx @@ -84,10 +84,6 @@ export function OutputSelect({ // For objects without type, recursively add each property if (!Array.isArray(outputObj)) { Object.entries(outputObj).forEach(([key, value]) => { - // Skip configuration properties that shouldn't be available as outputs - if (key === 'dependsOn') { - return - } addOutput(key, value, fullPath) }) } else { @@ -105,10 +101,6 @@ export function OutputSelect({ // Process all output properties directly (flattened structure) Object.entries(block.outputs).forEach(([key, value]) => { - // Skip configuration properties that shouldn't be available as outputs - if (key === 'dependsOn') { - return - } addOutput(key, value) }) } diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index a811301ea..f05b73682 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -308,17 +308,5 @@ export const AgentBlock: BlockConfig = { model: 'string', tokens: 'any', toolCalls: 'any', - dependsOn: { - subBlockId: 'responseFormat', - condition: { - whenEmpty: { - content: 'string', - model: 'string', - tokens: 'any', - toolCalls: 'any', - }, - whenFilled: 'json', - }, - }, }, } diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 7c0b2605b..aa7c12029 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -155,13 +155,6 @@ export interface BlockConfig { } inputs: Record outputs: ToolOutputToValueType> & { - dependsOn?: { - subBlockId: string - condition: { - whenEmpty: ToolOutputToValueType> - whenFilled: 'json' - } - } visualization?: { type: 'image' url: string @@ -173,11 +166,4 @@ export interface BlockConfig { // Output configuration rules export interface OutputConfig { type: BlockOutput - dependsOn?: { - subBlockId: string - condition: { - whenEmpty: BlockOutput - whenFilled: BlockOutput - } - } } diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index 3b21a2c5a..52480bb43 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -1,65 +1,24 @@ import type { BlockOutput, OutputConfig } from '@/blocks/types' -import type { SubBlockState } from '@/stores/workflows/workflow/types' - -interface CodeLine { - id: string - content: string -} - -function isEmptyValue(value: SubBlockState['value']): boolean { - if (value === null || value === undefined) return true - if (typeof value === 'string') return value.trim() === '' - if (typeof value === 'number') return false - if (Array.isArray(value)) { - // Handle code editor's array of lines format - if (value.length === 0) return true - if (isCodeEditorValue(value)) { - return value.every((line: any) => !line.content.trim()) - } - return value.length === 0 - } - return false -} - -function isCodeEditorValue(value: any[]): value is CodeLine[] { - return value.length > 0 && 'id' in value[0] && 'content' in value[0] -} export function resolveOutputType( - outputs: Record, - subBlocks: Record + outputs: Record ): Record { const resolvedOutputs: Record = {} for (const [key, outputValue] of Object.entries(outputs)) { // Handle backward compatibility: Check if the output is a primitive value or object (old format) - // If it's a string OR an object without 'type' and 'dependsOn' properties, it's the old format if ( typeof outputValue === 'string' || - (typeof outputValue === 'object' && - outputValue !== null && - !('type' in outputValue) && - !('dependsOn' in outputValue)) + (typeof outputValue === 'object' && outputValue !== null && !('type' in outputValue)) ) { // This is a primitive BlockOutput value (old format like 'string', 'any', etc.) resolvedOutputs[key] = outputValue as BlockOutput continue } - // Handle new format: OutputConfig with type and optional dependsOn + // OutputConfig with type const outputConfig = outputValue as OutputConfig - - // If no dependencies, use the type directly - if (!outputConfig.dependsOn) { - resolvedOutputs[key] = outputConfig.type - continue - } - - // Handle dependent output types - const subBlock = subBlocks[outputConfig.dependsOn.subBlockId] - resolvedOutputs[key] = isEmptyValue(subBlock?.value) - ? outputConfig.dependsOn.condition.whenEmpty - : outputConfig.dependsOn.condition.whenFilled + resolvedOutputs[key] = outputConfig.type } return resolvedOutputs diff --git a/apps/sim/components/ui/tag-dropdown.tsx b/apps/sim/components/ui/tag-dropdown.tsx index 058bc3871..799cc3645 100644 --- a/apps/sim/components/ui/tag-dropdown.tsx +++ b/apps/sim/components/ui/tag-dropdown.tsx @@ -145,11 +145,6 @@ export const TagDropdown: React.FC = ({ } return Object.entries(obj).flatMap(([key, value]) => { - // Skip configuration properties that shouldn't be available as tags - if (key === 'dependsOn') { - return [] - } - const newPrefix = prefix ? `${prefix}.${key}` : key return getOutputPaths(value, newPrefix) }) From 70b5619306d14f1c05bbb2839f21c49584185de9 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 17:48:58 -0700 Subject: [PATCH 08/13] fix: build error --- apps/sim/stores/workflows/workflow/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index 9a7ff7406..f2ba38a48 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -143,7 +143,7 @@ export const useWorkflowStore = create()( } }) - const outputs = resolveOutputType(blockConfig.outputs, subBlocks) + const outputs = resolveOutputType(blockConfig.outputs) const newState = { blocks: { From 83222d6b6323a098646d56906817c2455a27ae5e Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 18:43:45 -0700 Subject: [PATCH 09/13] fix: run lint --- .../connection-blocks/connection-blocks.tsx | 3 +- .../workflow-block/workflow-block.tsx | 7 +++- apps/sim/executor/index.ts | 40 +++++++++---------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx index 1d93c3d2c..ea392e994 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/components/connection-blocks/connection-blocks.tsx @@ -18,7 +18,8 @@ interface ResponseField { export function ConnectionBlocks({ blockId, - horizontalHandles, setIsConnecting, + horizontalHandles, + setIsConnecting, isDisabled = false, }: ConnectionBlocksProps) { const { incomingConnections, hasIncomingConnections } = useBlockConnections(blockId) diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index 68ff22ad9..c354ae882 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -433,7 +433,12 @@ export function WorkflowBlock({ id, data }: NodeProps) { )} - + {/* Input Handle - Don't show for starter blocks */} {type !== 'starter' && ( diff --git a/apps/sim/executor/index.ts b/apps/sim/executor/index.ts index de6a08f30..cf8980d19 100644 --- a/apps/sim/executor/index.ts +++ b/apps/sim/executor/index.ts @@ -621,7 +621,7 @@ 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 = { input: finalInput, @@ -636,26 +636,26 @@ export class Executor { executionTime: 0, }) } else { - // Handle structured input (like API calls or chat messages) - if (this.workflowInput && typeof this.workflowInput === 'object') { - // Flatten structure: input is directly accessible as - const starterOutput = { - input: this.workflowInput, - // Add compatibility fields directly at top level - message: this.workflowInput.input, - conversationId: this.workflowInput.conversationId, - } + // Handle structured input (like API calls or chat messages) + if (this.workflowInput && typeof this.workflowInput === 'object') { + // Flatten structure: input is directly accessible as + const starterOutput = { + input: this.workflowInput, + // Add compatibility fields directly at top level + message: this.workflowInput.input, + conversationId: this.workflowInput.conversationId, + } - context.blockStates.set(starterBlock.id, { - output: starterOutput, - executed: true, - executionTime: 0, - }) - } else { - // Fallback for primitive input values - const starterOutput = { - input: this.workflowInput, - } + context.blockStates.set(starterBlock.id, { + output: starterOutput, + executed: true, + executionTime: 0, + }) + } else { + // Fallback for primitive input values + const starterOutput = { + input: this.workflowInput, + } context.blockStates.set(starterBlock.id, { output: starterOutput, From a17d9e09f531748af808ecb3bbc315a31a55805b Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 19:11:25 -0700 Subject: [PATCH 10/13] fix: old starter structure in workflow/workspace route --- apps/sim/app/api/workflows/route.ts | 12 +---- apps/sim/app/api/workspaces/route.ts | 8 +-- apps/sim/app/w/[id]/workflow.tsx | 49 ++++++++++--------- .../settings-modal/settings-modal.tsx | 4 +- apps/sim/components/icons.tsx | 2 +- 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 3e698468c..6af3f73f1 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -118,11 +118,7 @@ export async function POST(req: NextRequest) { }, }, outputs: { - response: { - type: { - input: 'any', - }, - }, + input: 'any', }, enabled: true, horizontalHandles: true, @@ -248,11 +244,7 @@ export async function POST(req: NextRequest) { }, }, outputs: { - response: { - type: { - input: 'any', - }, - }, + input: 'any', }, createdAt: now, updatedAt: now, diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index e6aac33f5..80fdcd014 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -146,7 +146,7 @@ async function createWorkspace(userId: string, name: string) { }, }, outputs: { - response: { type: { input: 'any' } }, + input: 'any', }, enabled: true, horizontalHandles: true, @@ -230,11 +230,7 @@ async function createWorkspace(userId: string, name: string) { }, }, outputs: { - response: { - type: { - input: 'any', - }, - }, + input: 'any', }, createdAt: now, updatedAt: now, diff --git a/apps/sim/app/w/[id]/workflow.tsx b/apps/sim/app/w/[id]/workflow.tsx index 6c56e022f..8e29b6f7b 100644 --- a/apps/sim/app/w/[id]/workflow.tsx +++ b/apps/sim/app/w/[id]/workflow.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import ReactFlow, { Background, @@ -228,34 +228,35 @@ const WorkflowContent = React.memo(() => { [getNodes] ) + // Optimize spacing based on handle orientation + const orientationConfig = useMemo(() => { + if (Object.keys(blocks).length === 0) return null + + const detectedOrientation = detectHandleOrientation(blocks) + + return detectedOrientation === 'vertical' + ? { + // Vertical handles: optimize for top-to-bottom flow + horizontalSpacing: 400, + verticalSpacing: 300, + startX: 200, + startY: 200, + } + : { + // Horizontal handles: optimize for left-to-right flow + horizontalSpacing: 600, + verticalSpacing: 200, + startX: 150, + startY: 300, + } + }, [blocks]) + // Auto-layout handler const handleAutoLayout = useCallback(() => { - if (Object.keys(blocks).length === 0) return + if (!orientationConfig) return - // Detect the predominant handle orientation in the workflow const detectedOrientation = detectHandleOrientation(blocks) - // Optimize spacing based on handle orientation - const orientationConfig = useMemo( - () => - detectedOrientation === 'vertical' - ? { - // Vertical handles: optimize for top-to-bottom flow - horizontalSpacing: 400, - verticalSpacing: 300, - startX: 200, - startY: 200, - } - : { - // Horizontal handles: optimize for left-to-right flow - horizontalSpacing: 600, - verticalSpacing: 200, - startX: 150, - startY: 300, - }, - [detectedOrientation] - ) - applyAutoLayoutSmooth(blocks, edges, updateBlockPosition, fitView, resizeLoopNodesWrapper, { ...orientationConfig, alignByLayer: true, diff --git a/apps/sim/app/w/components/sidebar/components/settings-modal/settings-modal.tsx b/apps/sim/app/w/components/sidebar/components/settings-modal/settings-modal.tsx index 251a516c7..8edcec939 100644 --- a/apps/sim/app/w/components/sidebar/components/settings-modal/settings-modal.tsx +++ b/apps/sim/app/w/components/sidebar/components/settings-modal/settings-modal.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { X } from 'lucide-react' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' @@ -44,7 +44,7 @@ export function SettingsModal({ open, onOpenChange }: SettingsModalProps) { const [usageData, setUsageData] = useState(null) const [isLoading, setIsLoading] = useState(true) const loadSettings = useGeneralStore((state) => state.loadSettings) - const subscription = useMemo(() => useSubscription(), []) + const subscription = useSubscription() const hasLoadedInitialData = useRef(false) useEffect(() => { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 86e9f39a5..cdd13dcfb 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2949,7 +2949,7 @@ export const ResponseIcon = (props: SVGProps) => ( > ) From f63287d452d5768425e7c03f96fce26b49a6deb9 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 19:12:42 -0700 Subject: [PATCH 11/13] feat: ran migrations for pan --- .../db/migrations/0047_opposite_loners.sql | 1 + .../sim/db/migrations/meta/0047_snapshot.json | 3863 +++++++++++++++++ apps/sim/db/migrations/meta/_journal.json | 9 +- 3 files changed, 3872 insertions(+), 1 deletion(-) create mode 100644 apps/sim/db/migrations/0047_opposite_loners.sql create mode 100644 apps/sim/db/migrations/meta/0047_snapshot.json diff --git a/apps/sim/db/migrations/0047_opposite_loners.sql b/apps/sim/db/migrations/0047_opposite_loners.sql new file mode 100644 index 000000000..5e08f6229 --- /dev/null +++ b/apps/sim/db/migrations/0047_opposite_loners.sql @@ -0,0 +1 @@ +ALTER TABLE "settings" ADD COLUMN "auto_pan" boolean DEFAULT true NOT NULL; \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/0047_snapshot.json b/apps/sim/db/migrations/meta/0047_snapshot.json new file mode 100644 index 000000000..8db665c2c --- /dev/null +++ b/apps/sim/db/migrations/meta/0047_snapshot.json @@ -0,0 +1,3863 @@ +{ + "id": "1a2c8ee6-cdb7-4672-a420-3bc0f7d9931f", + "prevId": "cc643ea0-33d4-410a-9c53-82faa9d2c352", + "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.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.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 + }, + "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": {} + } + }, + "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 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "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_metadata_gin_idx": { + "name": "emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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'" + }, + "debug_mode": { + "name": "debug_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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 + }, + "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": {}, + "foreignKeys": {}, + "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_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'" + }, + "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 + }, + "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_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 + }, + "public.workspace_member": { + "name": "workspace_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_workspace_idx": { + "name": "user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_member_user_id_user_id_fk": { + "name": "workspace_member_user_id_user_id_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": {} + } +} \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json index 1f03e0039..47a2c38cf 100644 --- a/apps/sim/db/migrations/meta/_journal.json +++ b/apps/sim/db/migrations/meta/_journal.json @@ -323,6 +323,13 @@ "when": 1750527995274, "tag": "0046_loose_blizzard", "breakpoints": true + }, + { + "idx": 47, + "version": "7", + "when": 1750731142525, + "tag": "0047_opposite_loners", + "breakpoints": true } ] -} +} \ No newline at end of file From 0be7631de163b06b3e8f917843cc4de67c8b156c Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 19:19:09 -0700 Subject: [PATCH 12/13] fix: linter --- .../sim/db/migrations/meta/0047_snapshot.json | 372 +++++------------- apps/sim/db/migrations/meta/_journal.json | 2 +- 2 files changed, 94 insertions(+), 280 deletions(-) diff --git a/apps/sim/db/migrations/meta/0047_snapshot.json b/apps/sim/db/migrations/meta/0047_snapshot.json index 8db665c2c..3763b05f3 100644 --- a/apps/sim/db/migrations/meta/0047_snapshot.json +++ b/apps/sim/db/migrations/meta/0047_snapshot.json @@ -93,12 +93,8 @@ "name": "account_user_id_user_id_fk", "tableFrom": "account", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -170,12 +166,8 @@ "name": "api_key_user_id_user_id_fk", "tableFrom": "api_key", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -185,9 +177,7 @@ "api_key_key_unique": { "name": "api_key_key_unique", "nullsNotDistinct": false, - "columns": [ - "key" - ] + "columns": ["key"] } }, "policies": {}, @@ -312,12 +302,8 @@ "name": "chat_workflow_id_workflow_id_fk", "tableFrom": "chat", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -325,12 +311,8 @@ "name": "chat_user_id_user_id_fk", "tableFrom": "chat", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -396,12 +378,8 @@ "name": "custom_tools_user_id_user_id_fk", "tableFrom": "custom_tools", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -598,12 +576,8 @@ "name": "document_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "document", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -900,12 +874,8 @@ "name": "embedding_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "embedding", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -913,12 +883,8 @@ "name": "embedding_document_id_document_id_fk", "tableFrom": "embedding", "tableTo": "document", - "columnsFrom": [ - "document_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["document_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -970,12 +936,8 @@ "name": "environment_user_id_user_id_fk", "tableFrom": "environment", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -985,9 +947,7 @@ "environment_user_id_unique": { "name": "environment_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -1054,12 +1014,8 @@ "name": "invitation_inviter_id_user_id_fk", "tableFrom": "invitation", "tableTo": "user", - "columnsFrom": [ - "inviter_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1067,12 +1023,8 @@ "name": "invitation_organization_id_organization_id_fk", "tableFrom": "invitation", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1239,12 +1191,8 @@ "name": "knowledge_base_user_id_user_id_fk", "tableFrom": "knowledge_base", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1252,12 +1200,8 @@ "name": "knowledge_base_workspace_id_workspace_id_fk", "tableFrom": "knowledge_base", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1348,12 +1292,8 @@ "name": "marketplace_workflow_id_workflow_id_fk", "tableFrom": "marketplace", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1361,12 +1301,8 @@ "name": "marketplace_author_id_user_id_fk", "tableFrom": "marketplace", "tableTo": "user", - "columnsFrom": [ - "author_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["author_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1419,12 +1355,8 @@ "name": "member_user_id_user_id_fk", "tableFrom": "member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1432,12 +1364,8 @@ "name": "member_organization_id_organization_id_fk", "tableFrom": "member", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1561,12 +1489,8 @@ "name": "memory_workflow_id_workflow_id_fk", "tableFrom": "memory", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1829,12 +1753,8 @@ "name": "permissions_user_id_user_id_fk", "tableFrom": "permissions", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1910,12 +1830,8 @@ "name": "session_user_id_user_id_fk", "tableFrom": "session", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1923,12 +1839,8 @@ "name": "session_active_organization_id_organization_id_fk", "tableFrom": "session", "tableTo": "organization", - "columnsFrom": [ - "active_organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1938,9 +1850,7 @@ "session_token_unique": { "name": "session_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -2040,12 +1950,8 @@ "name": "settings_user_id_user_id_fk", "tableFrom": "settings", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2055,9 +1961,7 @@ "settings_user_id_unique": { "name": "settings_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -2215,9 +2119,7 @@ "user_email_unique": { "name": "user_email_unique", "nullsNotDistinct": false, - "columns": [ - "email" - ] + "columns": ["email"] } }, "policies": {}, @@ -2303,12 +2205,8 @@ "name": "user_stats_user_id_user_id_fk", "tableFrom": "user_stats", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2318,9 +2216,7 @@ "user_stats_user_id_unique": { "name": "user_stats_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -2421,9 +2317,7 @@ "waitlist_email_unique": { "name": "waitlist_email_unique", "nullsNotDistinct": false, - "columns": [ - "email" - ] + "columns": ["email"] } }, "policies": {}, @@ -2508,12 +2402,8 @@ "name": "webhook_workflow_id_workflow_id_fk", "tableFrom": "webhook", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2661,12 +2551,8 @@ "name": "workflow_user_id_user_id_fk", "tableFrom": "workflow", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2674,12 +2560,8 @@ "name": "workflow_workspace_id_workspace_id_fk", "tableFrom": "workflow", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2687,12 +2569,8 @@ "name": "workflow_folder_id_workflow_folder_id_fk", "tableFrom": "workflow", "tableTo": "workflow_folder", - "columnsFrom": [ - "folder_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -2898,12 +2776,8 @@ "name": "workflow_blocks_workflow_id_workflow_id_fk", "tableFrom": "workflow_blocks", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3056,12 +2930,8 @@ "name": "workflow_edges_workflow_id_workflow_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3069,12 +2939,8 @@ "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow_blocks", - "columnsFrom": [ - "source_block_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3082,12 +2948,8 @@ "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow_blocks", - "columnsFrom": [ - "target_block_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3232,12 +3094,8 @@ "name": "workflow_folder_user_id_user_id_fk", "tableFrom": "workflow_folder", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3245,12 +3103,8 @@ "name": "workflow_folder_workspace_id_workspace_id_fk", "tableFrom": "workflow_folder", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3327,12 +3181,8 @@ "name": "workflow_logs_workflow_id_workflow_id_fk", "tableFrom": "workflow_logs", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3431,12 +3281,8 @@ "name": "workflow_schedule_workflow_id_workflow_id_fk", "tableFrom": "workflow_schedule", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3446,9 +3292,7 @@ "workflow_schedule_workflow_id_unique": { "name": "workflow_schedule_workflow_id_unique", "nullsNotDistinct": false, - "columns": [ - "workflow_id" - ] + "columns": ["workflow_id"] } }, "policies": {}, @@ -3542,12 +3386,8 @@ "name": "workflow_subflows_workflow_id_workflow_id_fk", "tableFrom": "workflow_subflows", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3601,12 +3441,8 @@ "name": "workspace_owner_id_user_id_fk", "tableFrom": "workspace", "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3700,12 +3536,8 @@ "name": "workspace_invitation_workspace_id_workspace_id_fk", "tableFrom": "workspace_invitation", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3713,12 +3545,8 @@ "name": "workspace_invitation_inviter_id_user_id_fk", "tableFrom": "workspace_invitation", "tableTo": "user", - "columnsFrom": [ - "inviter_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3728,9 +3556,7 @@ "workspace_invitation_token_unique": { "name": "workspace_invitation_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -3809,12 +3635,8 @@ "name": "workspace_member_workspace_id_workspace_id_fk", "tableFrom": "workspace_member", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3822,12 +3644,8 @@ "name": "workspace_member_user_id_user_id_fk", "tableFrom": "workspace_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3843,11 +3661,7 @@ "public.permission_type": { "name": "permission_type", "schema": "public", - "values": [ - "admin", - "write", - "read" - ] + "values": ["admin", "write", "read"] } }, "schemas": {}, @@ -3860,4 +3674,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json index 47a2c38cf..2ce54b008 100644 --- a/apps/sim/db/migrations/meta/_journal.json +++ b/apps/sim/db/migrations/meta/_journal.json @@ -332,4 +332,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From a77645e0475d11f5645aa31cc81f769a650af42d Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Mon, 23 Jun 2025 19:22:20 -0700 Subject: [PATCH 13/13] fix: removed old response structure from response block --- apps/sim/blocks/blocks/response.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/sim/blocks/blocks/response.ts b/apps/sim/blocks/blocks/response.ts index 4720c6cb2..3e6ba92d8 100644 --- a/apps/sim/blocks/blocks/response.ts +++ b/apps/sim/blocks/blocks/response.ts @@ -92,12 +92,8 @@ export const ResponseBlock: BlockConfig = { }, }, outputs: { - response: { - type: { - data: 'json', - status: 'number', - headers: 'json', - }, - }, + data: 'json', + status: 'number', + headers: 'json', }, }