From a345b2ffab815749d78cf37fae00b53a73e352ae Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Sat, 1 Mar 2025 17:03:03 -0800 Subject: [PATCH] feat(code): added complex code execution for agents' custom tools, added envvar resolution/styling/dropdown for agent custom tool code modal --- app/api/execute/route.ts | 47 ++++- .../tool-input/components/code-editor.tsx | 39 +++- .../components/custom-tool-modal.tsx | 149 +++++++++++++- components/ui/env-var-dropdown.tsx | 58 +++--- components/ui/tag-dropdown.tsx | 58 +++--- executor/handlers.ts | 191 ++++++++++++------ tools/index.ts | 113 +++++++++++ tools/types.ts | 3 + 8 files changed, 526 insertions(+), 132 deletions(-) diff --git a/app/api/execute/route.ts b/app/api/execute/route.ts index e33593f52f..cddfe453b6 100644 --- a/app/api/execute/route.ts +++ b/app/api/execute/route.ts @@ -5,6 +5,40 @@ import { Script, createContext } from 'vm' export const dynamic = 'force-dynamic' // Disable static optimization export const runtime = 'nodejs' // Use Node.js runtime +/** + * 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 = {} +): string { + let resolvedCode = code + + // Resolve environment variables with {{var_name}} syntax + const envVarMatches = resolvedCode.match(/\{\{([^}]+)\}\}/g) || [] + for (const match of envVarMatches) { + const varName = match.slice(2, -2).trim() + // Priority: 1. Environment variables from workflow, 2. Params, 3. process.env + const varValue = envVars[varName] || params[varName] || process.env[varName] || '' + resolvedCode = resolvedCode.replace(match, varValue) + } + + // Resolve tags with syntax + const tagMatches = resolvedCode.match(/<([^>]+)>/g) || [] + for (const match of tagMatches) { + const tagName = match.slice(1, -1).trim() + const tagValue = params[tagName] || '' + resolvedCode = resolvedCode.replace(match, tagValue) + } + + return resolvedCode +} + export async function POST(req: NextRequest) { const startTime = Date.now() let stdout = '' @@ -12,18 +46,15 @@ export async function POST(req: NextRequest) { try { const body = await req.json() - const { code, params = {}, timeout = 3000 } = body + const { code, params = {}, timeout = 3000, envVars = {} } = body - // Check if code contains unresolved template variables - if (code.includes('<') && code.includes('>')) { - throw new Error( - 'Code contains unresolved template variables. Please ensure all variables are resolved before execution.' - ) - } + // Resolve variables in the code with workflow environment variables + const resolvedCode = resolveCodeVariables(code, params, envVars) // Create a secure context with console logging const context = createContext({ params, + environmentVariables: envVars, // Make environment variables available in the context console: { log: (...args: any[]) => { const logMessage = @@ -46,7 +77,7 @@ export async function POST(req: NextRequest) { const script = new Script(` (async () => { try { - ${code} + ${resolvedCode} } catch (error) { console.error(error); throw error; diff --git a/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor.tsx b/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor.tsx index d003f44c0d..4dc6c92654 100644 --- a/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor.tsx +++ b/app/w/[id]/components/workflow-block/components/sub-block/components/tool-input/components/code-editor.tsx @@ -14,6 +14,8 @@ interface CodeEditorProps { placeholder?: string className?: string minHeight?: string + highlightVariables?: boolean + onKeyDown?: (e: React.KeyboardEvent) => void } export function CodeEditor({ @@ -23,6 +25,8 @@ export function CodeEditor({ placeholder = '', className = '', minHeight = '360px', + highlightVariables = true, + onKeyDown, }: CodeEditorProps) { const [code, setCode] = useState(value) const [visualLineHeights, setVisualLineHeights] = useState([]) @@ -103,6 +107,38 @@ export function CodeEditor({ return numbers } + // Custom highlighter that highlights environment variables and tags + const customHighlight = (code: string) => { + if (!highlightVariables || language !== 'javascript') { + // Use default Prism highlighting for non-JS or when variable highlighting is off + return highlight(code, languages[language], language) + } + + // 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}}' + ) + } + + // 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('