diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 96d1d6ed3..08dfae068 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -213,24 +213,81 @@ function createUserFriendlyErrorMessage( } /** - * Resolves environment variables and tags in code - * @param code - Code with variables - * @param params - Parameters that may contain variable values - * @param envVars - Environment variables from the workflow - * @returns Resolved code + * Resolves workflow variables with syntax */ +function resolveWorkflowVariables( + code: string, + workflowVariables: Record, + contextVariables: Record +): string { + let resolvedCode = code -function resolveCodeVariables( + const variableMatches = resolvedCode.match(/]+)>/g) || [] + for (const match of variableMatches) { + const variableName = match.slice(' (variable.name || '').replace(/\s+/g, '') === variableName + ) + + if (foundVariable) { + const variable = foundVariable[1] + // Get the typed value - handle different variable types + let variableValue = variable.value + + if (variable.value !== undefined && variable.value !== null) { + try { + // Handle 'string' type the same as 'plain' for backward compatibility + const type = variable.type === 'string' ? 'plain' : variable.type + + // For plain text, use exactly what's entered without modifications + if (type === 'plain' && typeof variableValue === 'string') { + // Use as-is for plain text + } else if (type === 'number') { + variableValue = Number(variableValue) + } else if (type === 'boolean') { + variableValue = variableValue === 'true' || variableValue === true + } else if (type === 'json') { + try { + variableValue = + typeof variableValue === 'string' ? JSON.parse(variableValue) : variableValue + } catch { + // Keep original value if JSON parsing fails + } + } + } catch (error) { + // Fallback to original value on error + variableValue = variable.value + } + } + + // Create a safe variable reference + const safeVarName = `__variable_${variableName.replace(/[^a-zA-Z0-9_]/g, '_')}` + contextVariables[safeVarName] = variableValue + + // Replace the variable reference with the safe variable name + resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName) + } else { + // Variable not found - replace with empty string to avoid syntax errors + resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), '') + } + } + + return resolvedCode +} + +/** + * Resolves environment variables with {{var_name}} syntax + */ +function resolveEnvironmentVariables( code: string, params: Record, - envVars: Record = {}, - blockData: Record = {}, - blockNameMapping: Record = {} -): { resolvedCode: string; contextVariables: Record } { + envVars: Record, + contextVariables: Record +): string { let resolvedCode = code - const contextVariables: Record = {} - // Resolve environment variables with {{var_name}} syntax const envVarMatches = resolvedCode.match(/\{\{([^}]+)\}\}/g) || [] for (const match of envVarMatches) { const varName = match.slice(2, -2).trim() @@ -245,7 +302,21 @@ function resolveCodeVariables( resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName) } - // Resolve tags with syntax (including nested paths like ) + return resolvedCode +} + +/** + * Resolves tags with syntax (including nested paths like ) + */ +function resolveTagVariables( + code: string, + params: Record, + blockData: Record, + blockNameMapping: Record, + contextVariables: Record +): string { + let resolvedCode = code + const tagMatches = resolvedCode.match(/<([a-zA-Z_][a-zA-Z0-9_.]*[a-zA-Z0-9_])>/g) || [] for (const match of tagMatches) { @@ -300,6 +371,42 @@ function resolveCodeVariables( resolvedCode = resolvedCode.replace(new RegExp(escapeRegExp(match), 'g'), safeVarName) } + return resolvedCode +} + +/** + * Resolves environment variables and tags in code + * @param code - Code with variables + * @param params - Parameters that may contain variable values + * @param envVars - Environment variables from the workflow + * @returns Resolved code + */ +function resolveCodeVariables( + code: string, + params: Record, + envVars: Record = {}, + blockData: Record = {}, + blockNameMapping: Record = {}, + workflowVariables: Record = {} +): { resolvedCode: string; contextVariables: Record } { + let resolvedCode = code + const contextVariables: Record = {} + + // Resolve workflow variables with syntax first + resolvedCode = resolveWorkflowVariables(resolvedCode, workflowVariables, contextVariables) + + // Resolve environment variables with {{var_name}} syntax + resolvedCode = resolveEnvironmentVariables(resolvedCode, params, envVars, contextVariables) + + // Resolve tags with syntax (including nested paths like ) + resolvedCode = resolveTagVariables( + resolvedCode, + params, + blockData, + blockNameMapping, + contextVariables + ) + return { resolvedCode, contextVariables } } @@ -338,6 +445,7 @@ export async function POST(req: NextRequest) { envVars = {}, blockData = {}, blockNameMapping = {}, + workflowVariables = {}, workflowId, isCustomTool = false, } = body @@ -360,7 +468,8 @@ export async function POST(req: NextRequest) { executionParams, envVars, blockData, - blockNameMapping + blockNameMapping, + workflowVariables ) resolvedCode = codeResolution.resolvedCode const contextVariables = codeResolution.contextVariables @@ -368,8 +477,8 @@ export async function POST(req: NextRequest) { const executionMethod = 'vm' // Default execution method logger.info(`[${requestId}] Using VM for code execution`, { - resolvedCode, hasEnvVars: Object.keys(envVars).length > 0, + hasWorkflowVariables: Object.keys(workflowVariables).length > 0, }) // Create a secure context with console logging diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index 17d37a4f3..8aa62f7e7 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -39,6 +39,9 @@ export async function POST(request: NextRequest) { stream, messages, environmentVariables, + workflowVariables, + blockData, + blockNameMapping, reasoningEffort, verbosity, } = body @@ -60,6 +63,7 @@ export async function POST(request: NextRequest) { messageCount: messages?.length || 0, hasEnvironmentVariables: !!environmentVariables && Object.keys(environmentVariables).length > 0, + hasWorkflowVariables: !!workflowVariables && Object.keys(workflowVariables).length > 0, reasoningEffort, verbosity, }) @@ -103,6 +107,9 @@ export async function POST(request: NextRequest) { stream, messages, environmentVariables, + workflowVariables, + blockData, + blockNameMapping, reasoningEffort, verbosity, }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor/code-editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor/code-editor.tsx index 9ab634e66..1ea5279d5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor/code-editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor/code-editor.tsx @@ -18,6 +18,7 @@ interface CodeEditorProps { highlightVariables?: boolean onKeyDown?: (e: React.KeyboardEvent) => void disabled?: boolean + schemaParameters?: Array<{ name: string; type: string; description: string; required: boolean }> } export function CodeEditor({ @@ -30,6 +31,7 @@ export function CodeEditor({ highlightVariables = true, onKeyDown, disabled = false, + schemaParameters = [], }: CodeEditorProps) { const [code, setCode] = useState(value) const [visualLineHeights, setVisualLineHeights] = useState([]) @@ -120,25 +122,80 @@ export function CodeEditor({ // First, get the default Prism highlighting let highlighted = highlight(code, languages[language], language) - // Then, highlight environment variables with {{var_name}} syntax in blue - if (highlighted.includes('{{')) { - highlighted = highlighted.replace( - /\{\{([^}]+)\}\}/g, - '{{$1}}' - ) + // Collect all syntax highlights to apply in a single pass + type SyntaxHighlight = { + start: number + end: number + replacement: string } + const highlights: SyntaxHighlight[] = [] - // Also highlight tags with syntax in blue - if (highlighted.includes('<') && !language.includes('html')) { - highlighted = highlighted.replace(/<([^>\s/]+)>/g, (match, group) => { - // Avoid replacing HTML tags in comments - if (match.startsWith('