diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 9022368c5a..485e7b07c6 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -19,7 +19,9 @@ const logger = createLogger('FunctionExecuteAPI') function resolveCodeVariables( code: string, params: Record, - envVars: Record = {} + envVars: Record = {}, + blockData: Record = {}, + blockNameMapping: Record = {} ): { resolvedCode: string; contextVariables: Record } { let resolvedCode = code const contextVariables: Record = {} @@ -41,11 +43,54 @@ function resolveCodeVariables( // Resolve tags with syntax (including nested paths like ) const tagMatches = resolvedCode.match(/<([a-zA-Z_][a-zA-Z0-9_.]*[a-zA-Z0-9_])>/g) || [] + + for (const match of tagMatches) { const tagName = match.slice(1, -1).trim() - // Handle nested paths like "getrecord.response.data" - const tagValue = getNestedValue(params, tagName) || '' + + // Handle nested paths like "getrecord.response.data" or "function1.response.result" + // First try params, then blockData directly, then try with block name mapping + let tagValue = getNestedValue(params, tagName) || getNestedValue(blockData, tagName) || '' + + // If not found and the path starts with a block name, try mapping the block name to ID + if (!tagValue && tagName.includes('.')) { + const pathParts = tagName.split('.') + const normalizedBlockName = pathParts[0] // This should already be normalized like "function1" + + // Find the block ID by looking for a block name that normalizes to this value + let blockId = null + let matchedBlockName = null + + for (const [blockName, id] of Object.entries(blockNameMapping)) { + // Apply the same normalization logic as the UI: remove spaces and lowercase + const normalizedName = blockName.replace(/\s+/g, '').toLowerCase() + if (normalizedName === normalizedBlockName) { + blockId = id + matchedBlockName = blockName + break + } + } + + if (blockId) { + const remainingPath = pathParts.slice(1).join('.') + const fullPath = `${blockId}.${remainingPath}` + tagValue = getNestedValue(blockData, fullPath) || '' + + } + } + + + + // If the value is a stringified JSON, parse it back to object + if (typeof tagValue === 'string' && tagValue.length > 100 && + (tagValue.startsWith('{') || tagValue.startsWith('['))) { + try { + tagValue = JSON.parse(tagValue) + } catch (e) { + // Keep as string if parsing fails + } + } // Instead of injecting large JSON directly, create a variable reference const safeVarName = `__tag_${tagName.replace(/[^a-zA-Z0-9_]/g, '_')}` @@ -89,6 +134,8 @@ export async function POST(req: NextRequest) { params = {}, timeout = 5000, envVars = {}, + blockData = {}, + blockNameMapping = {}, workflowId, isCustomTool = false, } = body @@ -106,7 +153,14 @@ export async function POST(req: NextRequest) { }) // Resolve variables in the code with workflow environment variables - const { resolvedCode, contextVariables } = resolveCodeVariables(code, executionParams, envVars) + logger.info(`[${requestId}] Original code:`, code.substring(0, 200)) + logger.info(`[${requestId}] Execution params keys:`, Object.keys(executionParams)) + + + const { resolvedCode, contextVariables } = resolveCodeVariables(code, executionParams, envVars, blockData, blockNameMapping) + + logger.info(`[${requestId}] Resolved code:`, resolvedCode.substring(0, 200)) + logger.info(`[${requestId}] Context variables keys:`, Object.keys(contextVariables)) const executionMethod = 'vm' // Default execution method diff --git a/apps/sim/executor/handlers/function/function-handler.ts b/apps/sim/executor/handlers/function/function-handler.ts index c63c80d2fb..3a56a1c128 100644 --- a/apps/sim/executor/handlers/function/function-handler.ts +++ b/apps/sim/executor/handlers/function/function-handler.ts @@ -23,12 +23,29 @@ export class FunctionBlockHandler implements BlockHandler { ? inputs.code.map((c: { content: string }) => c.content).join('\n') : inputs.code + // Extract block data for variable resolution + const blockData: Record = {} + const blockNameMapping: Record = {} + + for (const [blockId, blockState] of context.blockStates.entries()) { + if (blockState.output) { + blockData[blockId] = blockState.output + + // Try to find the block name from the workflow + const workflowBlock = context.workflow?.blocks?.find(b => b.id === blockId) + if (workflowBlock?.metadata?.name) { + blockNameMapping[workflowBlock.metadata.name] = blockId + } + } + } + // Directly use the function_execute tool which calls the API route - logger.info(`Executing function block via API route: ${block.id}`) const result = await executeTool('function_execute', { code: codeContent, timeout: inputs.timeout || 5000, envVars: context.environmentVariables || {}, + blockData: blockData, // Pass block data for variable resolution + blockNameMapping: blockNameMapping, // Pass block name to ID mapping _context: { workflowId: context.workflowId }, }) diff --git a/apps/sim/executor/resolver.ts b/apps/sim/executor/resolver.ts index b3fd7ab5dd..33f269845c 100644 --- a/apps/sim/executor/resolver.ts +++ b/apps/sim/executor/resolver.ts @@ -593,6 +593,7 @@ export class InputResolver { isInTemplateLiteral ) } else { + // The function execution API will handle variable resolution within code strings formattedValue = typeof replacementValue === 'object' ? JSON.stringify(replacementValue) diff --git a/apps/sim/tools/function/execute.ts b/apps/sim/tools/function/execute.ts index e98c00e675..eb121ecbc6 100644 --- a/apps/sim/tools/function/execute.ts +++ b/apps/sim/tools/function/execute.ts @@ -45,6 +45,8 @@ export const functionExecuteTool: ToolConfig