diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 0518445cf..5ca4eeb36 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -32,6 +32,14 @@ describe('Function Execute API Route', () => { createLogger: vi.fn().mockReturnValue(mockLogger), })) + vi.doMock('@/lib/execution/e2b', () => ({ + executeInE2B: vi.fn().mockResolvedValue({ + result: 'e2b success', + stdout: 'e2b output', + sandboxId: 'test-sandbox-id', + }), + })) + mockRunInContext.mockResolvedValue('vm success') mockCreateContext.mockReturnValue({}) }) @@ -45,6 +53,7 @@ describe('Function Execute API Route', () => { const req = createMockRequest('POST', { code: 'return "Hello World"', timeout: 5000, + useLocalVM: true, }) const { POST } = await import('@/app/api/function/execute/route') @@ -74,6 +83,7 @@ describe('Function Execute API Route', () => { it('should use default timeout when not provided', async () => { const req = createMockRequest('POST', { code: 'return "test"', + useLocalVM: true, }) const { POST } = await import('@/app/api/function/execute/route') @@ -93,6 +103,7 @@ describe('Function Execute API Route', () => { it('should resolve environment variables with {{var_name}} syntax', async () => { const req = createMockRequest('POST', { code: 'return {{API_KEY}}', + useLocalVM: true, envVars: { API_KEY: 'secret-key-123', }, @@ -108,6 +119,7 @@ describe('Function Execute API Route', () => { it('should resolve tag variables with syntax', async () => { const req = createMockRequest('POST', { code: 'return ', + useLocalVM: true, params: { email: { id: '123', subject: 'Test Email' }, }, @@ -123,6 +135,7 @@ describe('Function Execute API Route', () => { it('should NOT treat email addresses as template variables', async () => { const req = createMockRequest('POST', { code: 'return "Email sent to user"', + useLocalVM: true, params: { email: { from: 'Waleed Latif ', @@ -141,6 +154,7 @@ describe('Function Execute API Route', () => { it('should only match valid variable names in angle brackets', async () => { const req = createMockRequest('POST', { code: 'return + "" + ', + useLocalVM: true, params: { validVar: 'hello', another_valid: 'world', @@ -178,6 +192,7 @@ describe('Function Execute API Route', () => { const req = createMockRequest('POST', { code: 'return ', + useLocalVM: true, params: gmailData, }) @@ -200,6 +215,7 @@ describe('Function Execute API Route', () => { const req = createMockRequest('POST', { code: 'return ', + useLocalVM: true, params: complexEmailData, }) @@ -214,6 +230,7 @@ describe('Function Execute API Route', () => { it('should handle custom tool execution with direct parameter access', async () => { const req = createMockRequest('POST', { code: 'return location + " weather is sunny"', + useLocalVM: true, params: { location: 'San Francisco', }, @@ -245,6 +262,7 @@ describe('Function Execute API Route', () => { it('should handle timeout parameter', async () => { const req = createMockRequest('POST', { code: 'return "test"', + useLocalVM: true, timeout: 10000, }) @@ -262,6 +280,7 @@ describe('Function Execute API Route', () => { it('should handle empty parameters object', async () => { const req = createMockRequest('POST', { code: 'return "no params"', + useLocalVM: true, params: {}, }) @@ -295,6 +314,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'const obj = {\n name: "test",\n description: "This has a missing closing quote\n};\nreturn obj;', + useLocalVM: true, timeout: 5000, }) @@ -338,6 +358,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'const obj = null;\nreturn obj.someMethod();', + useLocalVM: true, timeout: 5000, }) @@ -379,6 +400,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'const x = 42;\nreturn undefinedVariable + x;', + useLocalVM: true, timeout: 5000, }) @@ -409,6 +431,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'return "test";', + useLocalVM: true, timeout: 5000, }) @@ -445,6 +468,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = 4;\nreturn a + b + c + d;', + useLocalVM: true, timeout: 5000, }) @@ -476,6 +500,7 @@ SyntaxError: Invalid or unexpected token const req = createMockRequest('POST', { code: 'const obj = {\n name: "test"\n// Missing closing brace', + useLocalVM: true, timeout: 5000, }) @@ -496,6 +521,7 @@ SyntaxError: Invalid or unexpected token // This tests the escapeRegExp function indirectly const req = createMockRequest('POST', { code: 'return {{special.chars+*?}}', + useLocalVM: true, envVars: { 'special.chars+*?': 'escaped-value', }, @@ -512,6 +538,7 @@ SyntaxError: Invalid or unexpected token // Test with complex but not circular data first const req = createMockRequest('POST', { code: 'return ', + useLocalVM: true, params: { complexData: { special: 'chars"with\'quotes', diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 08dfae068..f8cc71eb9 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -1,5 +1,7 @@ import { createContext, Script } from 'vm' import { type NextRequest, NextResponse } from 'next/server' +import { executeInE2B } from '@/lib/execution/e2b' +import { CodeLanguage, DEFAULT_CODE_LANGUAGE, isValidCodeLanguage } from '@/lib/execution/languages' import { createLogger } from '@/lib/logs/console/logger' export const dynamic = 'force-dynamic' @@ -8,6 +10,10 @@ export const maxDuration = 60 const logger = createLogger('FunctionExecuteAPI') +// Constants for E2B code wrapping line counts +const E2B_JS_WRAPPER_LINES = 3 // Lines before user code: ';(async () => {', ' try {', ' const __sim_result = await (async () => {' +const E2B_PYTHON_WRAPPER_LINES = 1 // Lines before user code: 'def __sim_main__():' + /** * Enhanced error information interface */ @@ -124,6 +130,103 @@ function extractEnhancedError( return enhanced } +/** + * Parse and format E2B error message + * Removes E2B-specific line references and adds correct user line numbers + */ +function formatE2BError( + errorMessage: string, + errorOutput: string, + language: CodeLanguage, + userCode: string, + prologueLineCount: number +): { formattedError: string; cleanedOutput: string } { + // Calculate line offset based on language and prologue + const wrapperLines = + language === CodeLanguage.Python ? E2B_PYTHON_WRAPPER_LINES : E2B_JS_WRAPPER_LINES + const totalOffset = prologueLineCount + wrapperLines + + let userLine: number | undefined + let cleanErrorType = '' + let cleanErrorMsg = '' + + if (language === CodeLanguage.Python) { + // Python error format: "Cell In[X], line Y" followed by error details + // Extract line number from the Cell reference + const cellMatch = errorOutput.match(/Cell In\[\d+\], line (\d+)/) + if (cellMatch) { + const originalLine = Number.parseInt(cellMatch[1], 10) + userLine = originalLine - totalOffset + } + + // Extract clean error message from the error string + // Remove file references like "(detected at line X) (file.py, line Y)" + cleanErrorMsg = errorMessage + .replace(/\s*\(detected at line \d+\)/g, '') + .replace(/\s*\([^)]+\.py, line \d+\)/g, '') + .trim() + } else if (language === CodeLanguage.JavaScript) { + // JavaScript error format from E2B: "SyntaxError: /path/file.ts: Message. (line:col)\n\n 9 | ..." + // First, extract the error type and message from the first line + const firstLineEnd = errorMessage.indexOf('\n') + const firstLine = firstLineEnd > 0 ? errorMessage.substring(0, firstLineEnd) : errorMessage + + // Parse: "SyntaxError: /home/user/index.ts: Missing semicolon. (11:9)" + const jsErrorMatch = firstLine.match(/^(\w+Error):\s*[^:]+:\s*([^(]+)\.\s*\((\d+):(\d+)\)/) + if (jsErrorMatch) { + cleanErrorType = jsErrorMatch[1] + cleanErrorMsg = jsErrorMatch[2].trim() + const originalLine = Number.parseInt(jsErrorMatch[3], 10) + userLine = originalLine - totalOffset + } else { + // Fallback: look for line number in the arrow pointer line (> 11 |) + const arrowMatch = errorMessage.match(/^>\s*(\d+)\s*\|/m) + if (arrowMatch) { + const originalLine = Number.parseInt(arrowMatch[1], 10) + userLine = originalLine - totalOffset + } + // Try to extract error type and message + const errorMatch = firstLine.match(/^(\w+Error):\s*(.+)/) + if (errorMatch) { + cleanErrorType = errorMatch[1] + cleanErrorMsg = errorMatch[2] + .replace(/^[^:]+:\s*/, '') // Remove file path + .replace(/\s*\(\d+:\d+\)\s*$/, '') // Remove line:col at end + .trim() + } else { + cleanErrorMsg = firstLine + } + } + } + + // Build the final clean error message + const finalErrorMsg = + cleanErrorType && cleanErrorMsg + ? `${cleanErrorType}: ${cleanErrorMsg}` + : cleanErrorMsg || errorMessage + + // Format with line number if available + let formattedError = finalErrorMsg + if (userLine && userLine > 0) { + const codeLines = userCode.split('\n') + // Clamp userLine to the actual user code range + const actualUserLine = Math.min(userLine, codeLines.length) + if (actualUserLine > 0 && actualUserLine <= codeLines.length) { + const lineContent = codeLines[actualUserLine - 1]?.trim() + if (lineContent) { + formattedError = `Line ${actualUserLine}: \`${lineContent}\` - ${finalErrorMsg}` + } else { + formattedError = `Line ${actualUserLine} - ${finalErrorMsg}` + } + } + } + + // For stdout, just return the clean error message without the full traceback + const cleanedOutput = finalErrorMsg + + return { formattedError, cleanedOutput } +} + /** * Create a detailed error message for users */ @@ -442,6 +545,8 @@ export async function POST(req: NextRequest) { code, params = {}, timeout = 5000, + language = DEFAULT_CODE_LANGUAGE, + useLocalVM = false, envVars = {}, blockData = {}, blockNameMapping = {}, @@ -474,19 +579,163 @@ export async function POST(req: NextRequest) { resolvedCode = codeResolution.resolvedCode const contextVariables = codeResolution.contextVariables - const executionMethod = 'vm' // Default execution method + const e2bEnabled = process.env.E2B_ENABLED === 'true' + const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE + const useE2B = + e2bEnabled && + !useLocalVM && + (lang === CodeLanguage.JavaScript || lang === CodeLanguage.Python) - logger.info(`[${requestId}] Using VM for code execution`, { - hasEnvVars: Object.keys(envVars).length > 0, - hasWorkflowVariables: Object.keys(workflowVariables).length > 0, - }) + if (useE2B) { + logger.info(`[${requestId}] E2B status`, { + enabled: e2bEnabled, + hasApiKey: Boolean(process.env.E2B_API_KEY), + language: lang, + }) + let prologue = '' + const epilogue = '' - // Create a secure context with console logging + if (lang === CodeLanguage.JavaScript) { + // Track prologue lines for error adjustment + let prologueLineCount = 0 + prologue += `const params = JSON.parse(${JSON.stringify(JSON.stringify(executionParams))});\n` + prologueLineCount++ + prologue += `const environmentVariables = JSON.parse(${JSON.stringify(JSON.stringify(envVars))});\n` + prologueLineCount++ + for (const [k, v] of Object.entries(contextVariables)) { + prologue += `const ${k} = JSON.parse(${JSON.stringify(JSON.stringify(v))});\n` + prologueLineCount++ + } + const wrapped = [ + ';(async () => {', + ' try {', + ' const __sim_result = await (async () => {', + ` ${resolvedCode.split('\n').join('\n ')}`, + ' })();', + " console.log('__SIM_RESULT__=' + JSON.stringify(__sim_result));", + ' } catch (error) {', + ' console.log(String((error && (error.stack || error.message)) || error));', + ' throw error;', + ' }', + '})();', + ].join('\n') + const codeForE2B = prologue + wrapped + epilogue + + const execStart = Date.now() + const { + result: e2bResult, + stdout: e2bStdout, + sandboxId, + error: e2bError, + } = await executeInE2B({ + code: codeForE2B, + language: CodeLanguage.JavaScript, + timeoutMs: timeout, + }) + const executionTime = Date.now() - execStart + stdout += e2bStdout + + logger.info(`[${requestId}] E2B JS sandbox`, { + sandboxId, + stdoutPreview: e2bStdout?.slice(0, 200), + error: e2bError, + }) + + // If there was an execution error, format it properly + if (e2bError) { + const { formattedError, cleanedOutput } = formatE2BError( + e2bError, + e2bStdout, + lang, + resolvedCode, + prologueLineCount + ) + return NextResponse.json( + { + success: false, + error: formattedError, + output: { result: null, stdout: cleanedOutput, executionTime }, + }, + { status: 500 } + ) + } + + return NextResponse.json({ + success: true, + output: { result: e2bResult ?? null, stdout, executionTime }, + }) + } + // Track prologue lines for error adjustment + let prologueLineCount = 0 + prologue += 'import json\n' + prologueLineCount++ + prologue += `params = json.loads(${JSON.stringify(JSON.stringify(executionParams))})\n` + prologueLineCount++ + prologue += `environmentVariables = json.loads(${JSON.stringify(JSON.stringify(envVars))})\n` + prologueLineCount++ + for (const [k, v] of Object.entries(contextVariables)) { + prologue += `${k} = json.loads(${JSON.stringify(JSON.stringify(v))})\n` + prologueLineCount++ + } + const wrapped = [ + 'def __sim_main__():', + ...resolvedCode.split('\n').map((l) => ` ${l}`), + '__sim_result__ = __sim_main__()', + "print('__SIM_RESULT__=' + json.dumps(__sim_result__))", + ].join('\n') + const codeForE2B = prologue + wrapped + epilogue + + const execStart = Date.now() + const { + result: e2bResult, + stdout: e2bStdout, + sandboxId, + error: e2bError, + } = await executeInE2B({ + code: codeForE2B, + language: CodeLanguage.Python, + timeoutMs: timeout, + }) + const executionTime = Date.now() - execStart + stdout += e2bStdout + + logger.info(`[${requestId}] E2B Py sandbox`, { + sandboxId, + stdoutPreview: e2bStdout?.slice(0, 200), + error: e2bError, + }) + + // If there was an execution error, format it properly + if (e2bError) { + const { formattedError, cleanedOutput } = formatE2BError( + e2bError, + e2bStdout, + lang, + resolvedCode, + prologueLineCount + ) + return NextResponse.json( + { + success: false, + error: formattedError, + output: { result: null, stdout: cleanedOutput, executionTime }, + }, + { status: 500 } + ) + } + + return NextResponse.json({ + success: true, + output: { result: e2bResult ?? null, stdout, executionTime }, + }) + } + + const executionMethod = 'vm' const context = createContext({ params: executionParams, environmentVariables: envVars, - ...contextVariables, // Add resolved variables directly to context - fetch: globalThis.fetch || require('node-fetch').default, + ...contextVariables, + fetch: (globalThis as any).fetch || require('node-fetch').default, console: { log: (...args: any[]) => { const logMessage = `${args @@ -504,23 +753,17 @@ export async function POST(req: NextRequest) { }, }) - // Calculate line offset for user code to provide accurate error reporting const wrapperLines = ['(async () => {', ' try {'] - - // Add custom tool parameter declarations if needed if (isCustomTool) { wrapperLines.push(' // For custom tools, make parameters directly accessible') Object.keys(executionParams).forEach((key) => { wrapperLines.push(` const ${key} = params.${key};`) }) } - - userCodeStartLine = wrapperLines.length + 1 // +1 because user code starts on next line - - // Build the complete script with proper formatting for line numbers + userCodeStartLine = wrapperLines.length + 1 const fullScript = [ ...wrapperLines, - ` ${resolvedCode.split('\n').join('\n ')}`, // Indent user code + ` ${resolvedCode.split('\n').join('\n ')}`, ' } catch (error) {', ' console.error(error);', ' throw error;', @@ -529,33 +772,26 @@ export async function POST(req: NextRequest) { ].join('\n') const script = new Script(fullScript, { - filename: 'user-function.js', // This filename will appear in stack traces - lineOffset: 0, // Start line numbering from 0 - columnOffset: 0, // Start column numbering from 0 + filename: 'user-function.js', + lineOffset: 0, + columnOffset: 0, }) const result = await script.runInContext(context, { timeout, displayErrors: true, - breakOnSigint: true, // Allow breaking on SIGINT for better debugging + breakOnSigint: true, }) - // } const executionTime = Date.now() - startTime logger.info(`[${requestId}] Function executed successfully using ${executionMethod}`, { executionTime, }) - const response = { + return NextResponse.json({ success: true, - output: { - result, - stdout, - executionTime, - }, - } - - return NextResponse.json(response) + output: { result, stdout, executionTime }, + }) } catch (error: any) { const executionTime = Date.now() - startTime logger.error(`[${requestId}] Function execution failed`, { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx index 0ee02f0f7..e940272ec 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/code.tsx @@ -5,10 +5,12 @@ import { useParams } from 'next/navigation' import { highlight, languages } from 'prismjs' import 'prismjs/components/prism-javascript' import 'prismjs/themes/prism.css' +import 'prismjs/components/prism-python' import Editor from 'react-simple-code-editor' import { Button } from '@/components/ui/button' import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown' import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown' +import { CodeLanguage } from '@/lib/execution/languages' import { createLogger } from '@/lib/logs/console/logger' import { cn } from '@/lib/utils' import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar' @@ -26,7 +28,7 @@ interface CodeProps { subBlockId: string isConnecting: boolean placeholder?: string - language?: 'javascript' | 'json' + language?: 'javascript' | 'json' | 'python' generationType?: GenerationType value?: string isPreview?: boolean @@ -125,35 +127,73 @@ export function Code({ if (onValidationChange && subBlockId === 'responseFormat') { const timeoutId = setTimeout(() => { onValidationChange(isValidJson) - }, 150) // Match debounce time from setStoreValue + }, 150) return () => clearTimeout(timeoutId) } }, [isValidJson, onValidationChange, subBlockId]) const editorRef = useRef(null) - // Function to toggle collapsed state const toggleCollapsed = () => { setCollapsedValue(blockId, collapsedStateKey, !isCollapsed) } - // Create refs to hold the handlers const handleStreamStartRef = useRef<() => void>(() => {}) const handleGeneratedContentRef = useRef<(generatedCode: string) => void>(() => {}) const handleStreamChunkRef = useRef<(chunk: string) => void>(() => {}) - // AI Code Generation Hook - use new wand system - const wandHook = wandConfig?.enabled + const [languageValue] = useSubBlockValue(blockId, 'language') + const [remoteExecution] = useSubBlockValue(blockId, 'remoteExecution') + + const effectiveLanguage = (languageValue as 'javascript' | 'python' | 'json') || language + + const dynamicPlaceholder = useMemo(() => { + if (remoteExecution && languageValue === CodeLanguage.Python) { + return 'Write Python...' + } + return placeholder + }, [remoteExecution, languageValue, placeholder]) + + const dynamicWandConfig = useMemo(() => { + if (remoteExecution && languageValue === CodeLanguage.Python) { + return { + ...wandConfig, + prompt: `You are an expert Python programmer. +Generate ONLY the raw body of a Python function based on the user's request. +The code should be executable within a Python function body context. +- 'params' (object): Contains input parameters derived from the JSON schema. Access these directly using the parameter name wrapped in angle brackets, e.g., ''. Do NOT use 'params.paramName'. +- 'environmentVariables' (object): Contains environment variables. Reference these using the double curly brace syntax: '{{ENV_VAR_NAME}}'. Do NOT use os.environ or env. + +Current code context: {context} + +IMPORTANT FORMATTING RULES: +1. Reference Environment Variables: Use the exact syntax {{VARIABLE_NAME}}. Do NOT wrap it in quotes. +2. Reference Input Parameters/Workflow Variables: Use the exact syntax . Do NOT wrap it in quotes. +3. Function Body ONLY: Do NOT include the function signature (e.g., 'def my_func(...)') or surrounding braces. Return the final value with 'return'. +4. Imports: You may add imports as needed (standard library or pip-installed packages) without comments. +5. No Markdown: Do NOT include backticks, code fences, or any markdown. +6. Clarity: Write clean, readable Python code.`, + placeholder: 'Describe the Python function you want to create...', + } + } + return wandConfig + }, [wandConfig, remoteExecution, languageValue]) + + const wandHook = dynamicWandConfig?.enabled ? useWand({ - wandConfig, + wandConfig: dynamicWandConfig, currentValue: code, onStreamStart: () => handleStreamStartRef.current?.(), - onStreamChunk: (chunk: string) => handleStreamChunkRef.current?.(chunk), - onGeneratedContent: (content: string) => handleGeneratedContentRef.current?.(content), + onStreamChunk: (chunk: string) => { + setCode((prev) => prev + chunk) + handleStreamChunkRef.current?.(chunk) + }, + onGeneratedContent: (content: string) => { + handleGeneratedContentRef.current?.(content) + }, }) : null - // Extract values from wand hook const isAiLoading = wandHook?.isLoading || false const isAiStreaming = wandHook?.isStreaming || false const generateCodeStream = wandHook?.generateStream || (() => {}) @@ -164,7 +204,6 @@ export function Code({ const updatePromptValue = wandHook?.updatePromptValue || (() => {}) const cancelGeneration = wandHook?.cancelGeneration || (() => {}) - // State management - useSubBlockValue with explicit streaming control const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId, false, { isStreaming: isAiStreaming, onStreamingEnd: () => { @@ -174,43 +213,28 @@ export function Code({ const emitTagSelection = useTagSelection(blockId, subBlockId) - // Use preview value when in preview mode, otherwise use store value or prop value const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue - // Define the handlers in useEffect to avoid setState during render useEffect(() => { handleStreamStartRef.current = () => { setCode('') - // Streaming state is now controlled by isAiStreaming } handleGeneratedContentRef.current = (generatedCode: string) => { setCode(generatedCode) if (!isPreview && !disabled) { setStoreValue(generatedCode) - // Final value will be persisted when isAiStreaming becomes false } } - - handleStreamChunkRef.current = (chunk: string) => { - setCode((currentCode) => { - const newCode = currentCode + chunk - if (!isPreview && !disabled) { - // Update the value - it won't be persisted until streaming ends - setStoreValue(newCode) - } - return newCode - }) - } }, [isPreview, disabled, setStoreValue]) - // Effects useEffect(() => { + if (isAiStreaming) return const valueString = value?.toString() ?? '' if (valueString !== code) { setCode(valueString) } - }, [value]) + }, [value, code, isAiStreaming]) useEffect(() => { if (!editorRef.current) return @@ -279,7 +303,6 @@ export function Code({ } }, [code]) - // Handlers const handleDrop = (e: React.DragEvent) => { if (isPreview) return e.preventDefault() @@ -338,12 +361,11 @@ export function Code({ }, 0) } - // Render helpers - const renderLineNumbers = () => { + const renderLineNumbers = (): ReactElement[] => { const numbers: ReactElement[] = [] let lineNumber = 1 - visualLineHeights.forEach((height, index) => { + visualLineHeights.forEach((height) => { numbers.push(
{lineNumber} @@ -364,7 +386,7 @@ export function Code({ if (numbers.length === 0) { numbers.push( -
+
1
) @@ -383,7 +405,7 @@ export function Code({ onSubmit={(prompt: string) => generateCodeStream({ prompt })} onCancel={isAiStreaming ? cancelGeneration : hidePromptInline} onChange={updatePromptValue} - placeholder={wandConfig?.placeholder || aiPromptPlaceholder} + placeholder={dynamicWandConfig?.placeholder || aiPromptPlaceholder} />
{code.length === 0 && !isCollapsed && (
- {placeholder} + {dynamicPlaceholder}
)} @@ -478,7 +500,11 @@ export function Code({ } }} highlight={(codeToHighlight) => - highlight(codeToHighlight, languages[language], language) + highlight( + codeToHighlight, + languages[effectiveLanguage === 'python' ? 'python' : 'javascript'], + effectiveLanguage === 'python' ? 'python' : 'javascript' + ) } padding={12} style={{ diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/e2b-switch.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/e2b-switch.tsx new file mode 100644 index 000000000..5352af6bc --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/e2b-switch.tsx @@ -0,0 +1,60 @@ +import { Info } from 'lucide-react' +import { Label, Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui' +import { Switch as UISwitch } from '@/components/ui/switch' +import { env } from '@/lib/env' +import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value' + +interface E2BSwitchProps { + blockId: string + subBlockId: string + title: string + value?: boolean + isPreview?: boolean + previewValue?: boolean | null + disabled?: boolean +} + +export function E2BSwitch({ + blockId, + subBlockId, + title, + value: propValue, + isPreview = false, + previewValue, + disabled = false, +}: E2BSwitchProps) { + const e2bEnabled = env.NEXT_PUBLIC_E2B_ENABLED === 'true' + if (!e2bEnabled) return null + + const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId) + const value = isPreview ? previewValue : propValue !== undefined ? propValue : storeValue + + const handleChange = (checked: boolean) => { + if (!isPreview && !disabled) setStoreValue(checked) + } + + return ( +
+ + + + + + + + Python/Javascript code run in a sandbox environment. Can have slower execution times. + + +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx index b26bce81b..3ed5a49be 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/sub-block.tsx @@ -34,6 +34,7 @@ import { } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components' import type { SubBlockConfig } from '@/blocks/types' import { DocumentTagEntry } from './components/document-tag-entry/document-tag-entry' +import { E2BSwitch } from './components/e2b-switch' import { KnowledgeTagFilters } from './components/knowledge-tag-filters/knowledge-tag-filters' interface SubBlockProps { @@ -203,6 +204,18 @@ export function SubBlock({ /> ) case 'switch': + if (config.id === 'remoteExecution') { + return ( + + ) + } return ( ) { const isTriggerMode = useWorkflowStore.getState().blocks[blockId]?.triggerMode ?? false const effectiveAdvanced = currentWorkflow.isDiffMode ? displayAdvancedMode : isAdvancedMode const effectiveTrigger = currentWorkflow.isDiffMode ? displayTriggerMode : isTriggerMode + const e2bClientEnabled = env.NEXT_PUBLIC_E2B_ENABLED === 'true' // Filter visible blocks and those that meet their conditions const visibleSubBlocks = subBlocks.filter((block) => { if (block.hidden) return false + // Filter out E2B-related blocks if E2B is not enabled on the client + if (!e2bClientEnabled && (block.id === 'remoteExecution' || block.id === 'language')) { + return false + } + // Special handling for trigger mode if (block.type === ('trigger-config' as SubBlockType)) { // Show trigger-config blocks when in trigger mode OR for pure trigger blocks diff --git a/apps/sim/blocks/blocks/function.ts b/apps/sim/blocks/blocks/function.ts index b2de04dae..f47b9178b 100644 --- a/apps/sim/blocks/blocks/function.ts +++ b/apps/sim/blocks/blocks/function.ts @@ -1,4 +1,5 @@ import { CodeIcon } from '@/components/icons' +import { CodeLanguage, getLanguageDisplayName } from '@/lib/execution/languages' import type { BlockConfig } from '@/blocks/types' import type { CodeExecutionOutput } from '@/tools/function/types' @@ -7,12 +8,34 @@ export const FunctionBlock: BlockConfig = { name: 'Function', description: 'Run custom logic', longDescription: - 'Execute custom JavaScript or TypeScript code within your workflow to transform data or implement complex logic. Create reusable functions to process inputs and generate outputs for other blocks.', + 'Execute custom JavaScript or Python code within your workflow. Use E2B for remote execution with imports or enable Fast Mode (bolt) to run JavaScript locally for lowest latency.', docsLink: 'https://docs.sim.ai/blocks/function', category: 'blocks', bgColor: '#FF402F', icon: CodeIcon, subBlocks: [ + { + id: 'remoteExecution', + type: 'switch', + layout: 'full', + title: 'Remote Code Execution', + description: 'Python/Javascript code run in a sandbox environment. Slower execution times.', + }, + { + id: 'language', + type: 'dropdown', + layout: 'full', + options: [ + { label: getLanguageDisplayName(CodeLanguage.JavaScript), id: CodeLanguage.JavaScript }, + { label: getLanguageDisplayName(CodeLanguage.Python), id: CodeLanguage.Python }, + ], + placeholder: 'Select language', + value: () => CodeLanguage.JavaScript, + condition: { + field: 'remoteExecution', + value: true, + }, + }, { id: 'code', type: 'code', @@ -76,7 +99,9 @@ try { access: ['function_execute'], }, inputs: { - code: { type: 'string', description: 'JavaScript/TypeScript code to execute' }, + code: { type: 'string', description: 'JavaScript or Python code to execute' }, + remoteExecution: { type: 'boolean', description: 'Use E2B remote execution' }, + language: { type: 'string', description: 'Language (javascript or python)' }, timeout: { type: 'number', description: 'Execution timeout' }, }, outputs: { diff --git a/apps/sim/executor/handlers/function/function-handler.test.ts b/apps/sim/executor/handlers/function/function-handler.test.ts index 91b912398..f1054743a 100644 --- a/apps/sim/executor/handlers/function/function-handler.test.ts +++ b/apps/sim/executor/handlers/function/function-handler.test.ts @@ -75,6 +75,8 @@ describe('FunctionBlockHandler', () => { } const expectedToolParams = { code: inputs.code, + language: 'javascript', + useLocalVM: true, timeout: inputs.timeout, envVars: {}, workflowVariables: {}, @@ -107,6 +109,8 @@ describe('FunctionBlockHandler', () => { const expectedCode = 'const x = 5;\nreturn x * 2;' const expectedToolParams = { code: expectedCode, + language: 'javascript', + useLocalVM: true, timeout: inputs.timeout, envVars: {}, workflowVariables: {}, @@ -132,6 +136,8 @@ describe('FunctionBlockHandler', () => { const inputs = { code: 'return true;' } const expectedToolParams = { code: inputs.code, + language: 'javascript', + useLocalVM: true, timeout: 5000, // Default timeout envVars: {}, workflowVariables: {}, diff --git a/apps/sim/executor/handlers/function/function-handler.ts b/apps/sim/executor/handlers/function/function-handler.ts index cb3e15936..78f15c93b 100644 --- a/apps/sim/executor/handlers/function/function-handler.ts +++ b/apps/sim/executor/handlers/function/function-handler.ts @@ -1,3 +1,4 @@ +import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages' import { createLogger } from '@/lib/logs/console/logger' import { BlockType } from '@/executor/consts' import type { BlockHandler, ExecutionContext } from '@/executor/types' @@ -58,6 +59,8 @@ export class FunctionBlockHandler implements BlockHandler { 'function_execute', { code: codeContent, + language: inputs.language || DEFAULT_CODE_LANGUAGE, + useLocalVM: !inputs.remoteExecution, timeout: inputs.timeout || 5000, envVars: context.environmentVariables || {}, workflowVariables: context.workflowVariables || {}, diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index d03cc674c..7e562707e 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -189,6 +189,10 @@ export const env = createEnv({ SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret + + // E2B Remote Code Execution + E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution + E2B_API_KEY: z.string().optional(), // E2B API key for sandbox creation }, client: { @@ -220,6 +224,8 @@ export const env = createEnv({ NEXT_PUBLIC_BRAND_FAVICON_URL: z.string().url().optional(), // Custom favicon URL NEXT_PUBLIC_CUSTOM_CSS_URL: z.string().url().optional(), // Custom CSS stylesheet URL NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email + + NEXT_PUBLIC_E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution (client-side) NEXT_PUBLIC_DOCUMENTATION_URL: z.string().url().optional(), // Custom documentation URL NEXT_PUBLIC_TERMS_URL: z.string().url().optional(), // Custom terms of service URL NEXT_PUBLIC_PRIVACY_URL: z.string().url().optional(), // Custom privacy policy URL @@ -268,6 +274,7 @@ export const env = createEnv({ NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR: process.env.NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR, NEXT_PUBLIC_BRAND_BACKGROUND_COLOR: process.env.NEXT_PUBLIC_BRAND_BACKGROUND_COLOR, NEXT_PUBLIC_TRIGGER_DEV_ENABLED: process.env.NEXT_PUBLIC_TRIGGER_DEV_ENABLED, + NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED, NODE_ENV: process.env.NODE_ENV, NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED, }, diff --git a/apps/sim/lib/execution/e2b.ts b/apps/sim/lib/execution/e2b.ts new file mode 100644 index 000000000..7ee85c4c5 --- /dev/null +++ b/apps/sim/lib/execution/e2b.ts @@ -0,0 +1,98 @@ +import { Sandbox } from '@e2b/code-interpreter' +import { createLogger } from '@/lib/logs/console/logger' +import { CodeLanguage } from './languages' + +export interface E2BExecutionRequest { + code: string + language: CodeLanguage + timeoutMs: number +} + +export interface E2BExecutionResult { + result: unknown + stdout: string + sandboxId?: string + error?: string +} + +const logger = createLogger('E2BExecution') + +export async function executeInE2B(req: E2BExecutionRequest): Promise { + const { code, language, timeoutMs } = req + + logger.info(`Executing code in E2B`, { + code, + language, + timeoutMs, + }) + + const apiKey = process.env.E2B_API_KEY + if (!apiKey) { + throw new Error('E2B_API_KEY is required when E2B is enabled') + } + + const sandbox = await Sandbox.create({ apiKey }) + const sandboxId = sandbox.sandboxId + + const stdoutChunks = [] + + try { + const execution = await sandbox.runCode(code, { + language: language === CodeLanguage.Python ? 'python' : 'javascript', + timeoutMs, + }) + + // Check for execution errors + if (execution.error) { + const errorMessage = `${execution.error.name}: ${execution.error.value}` + logger.error(`E2B execution error`, { + sandboxId, + error: execution.error, + errorMessage, + }) + + // Include error traceback in stdout if available + const errorOutput = execution.error.traceback || errorMessage + return { + result: null, + stdout: errorOutput, + error: errorMessage, + sandboxId, + } + } + + // Get output from execution + if (execution.text) { + stdoutChunks.push(execution.text) + } + if (execution.logs?.stdout) { + stdoutChunks.push(...execution.logs.stdout) + } + if (execution.logs?.stderr) { + stdoutChunks.push(...execution.logs.stderr) + } + + const stdout = stdoutChunks.join('\n') + + let result: unknown = null + const prefix = '__SIM_RESULT__=' + const lines = stdout.split('\n') + const marker = lines.find((l) => l.startsWith(prefix)) + let cleanedStdout = stdout + if (marker) { + const jsonPart = marker.slice(prefix.length) + try { + result = JSON.parse(jsonPart) + } catch { + result = jsonPart + } + cleanedStdout = lines.filter((l) => !l.startsWith(prefix)).join('\n') + } + + return { result, stdout: cleanedStdout, sandboxId } + } finally { + try { + await sandbox.kill() + } catch {} + } +} diff --git a/apps/sim/lib/execution/languages.ts b/apps/sim/lib/execution/languages.ts new file mode 100644 index 000000000..e25b991dd --- /dev/null +++ b/apps/sim/lib/execution/languages.ts @@ -0,0 +1,33 @@ +/** + * Supported code execution languages + */ +export enum CodeLanguage { + JavaScript = 'javascript', + Python = 'python', +} + +/** + * Type guard to check if a string is a valid CodeLanguage + */ +export function isValidCodeLanguage(value: string): value is CodeLanguage { + return Object.values(CodeLanguage).includes(value as CodeLanguage) +} + +/** + * Get language display name + */ +export function getLanguageDisplayName(language: CodeLanguage): string { + switch (language) { + case CodeLanguage.JavaScript: + return 'JavaScript' + case CodeLanguage.Python: + return 'Python' + default: + return language + } +} + +/** + * Default language for code execution + */ +export const DEFAULT_CODE_LANGUAGE = CodeLanguage.JavaScript diff --git a/apps/sim/package.json b/apps/sim/package.json index d1cf6f1b3..daa83498d 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -26,6 +26,7 @@ "test:billing:suite": "bun run scripts/test-billing-suite.ts" }, "dependencies": { + "@e2b/code-interpreter": "^2.0.0", "@anthropic-ai/sdk": "^0.39.0", "@aws-sdk/client-s3": "^3.779.0", "@aws-sdk/s3-request-presigner": "^3.779.0", diff --git a/apps/sim/tools/function/execute.test.ts b/apps/sim/tools/function/execute.test.ts index bc2d41ed0..eb47f760a 100644 --- a/apps/sim/tools/function/execute.test.ts +++ b/apps/sim/tools/function/execute.test.ts @@ -54,6 +54,8 @@ describe('Function Execute Tool', () => { blockData: {}, blockNameMapping: {}, isCustomTool: false, + language: 'javascript', + useLocalVM: false, timeout: 5000, workflowId: undefined, }) @@ -80,6 +82,8 @@ describe('Function Execute Tool', () => { blockData: {}, blockNameMapping: {}, isCustomTool: false, + language: 'javascript', + useLocalVM: false, workflowId: undefined, }) }) @@ -97,6 +101,8 @@ describe('Function Execute Tool', () => { blockData: {}, blockNameMapping: {}, isCustomTool: false, + language: 'javascript', + useLocalVM: false, workflowId: undefined, }) }) diff --git a/apps/sim/tools/function/execute.ts b/apps/sim/tools/function/execute.ts index 09735293f..e8c9ca6a3 100644 --- a/apps/sim/tools/function/execute.ts +++ b/apps/sim/tools/function/execute.ts @@ -1,3 +1,4 @@ +import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages' import type { CodeExecutionInput, CodeExecutionOutput } from '@/tools/function/types' import type { ToolConfig } from '@/tools/types' @@ -17,6 +18,21 @@ export const functionExecuteTool: ToolConfig | string + language?: CodeLanguage + useLocalVM?: boolean timeout?: number memoryLimit?: number envVars?: Record - workflowVariables?: Record - blockData?: Record + workflowVariables?: Record + blockData?: Record blockNameMapping?: Record _context?: { workflowId?: string diff --git a/bun.lock b/bun.lock index ab439321d..2d1473485 100644 --- a/bun.lock +++ b/bun.lock @@ -64,6 +64,7 @@ "@cerebras/cerebras_cloud_sdk": "^1.23.0", "@chatscope/chat-ui-kit-react": "2.1.1", "@chatscope/chat-ui-kit-styles": "1.4.0", + "@e2b/code-interpreter": "^2.0.0", "@hookform/resolvers": "^4.1.3", "@opentelemetry/api": "^1.9.0", "@opentelemetry/exporter-collector": "^0.25.0", @@ -463,6 +464,8 @@ "@browserbasehq/stagehand": ["@browserbasehq/stagehand@2.4.4", "", { "dependencies": { "@anthropic-ai/sdk": "0.39.0", "@browserbasehq/sdk": "^2.4.0", "@google/genai": "^0.8.0", "ai": "^4.3.9", "devtools-protocol": "^0.0.1464554", "fetch-cookie": "^3.1.0", "openai": "^4.87.1", "pino": "^9.6.0", "pino-pretty": "^13.0.0", "playwright": "^1.52.0", "ws": "^8.18.0", "zod-to-json-schema": "^3.23.5" }, "optionalDependencies": { "@ai-sdk/anthropic": "^1.2.6", "@ai-sdk/azure": "^1.3.19", "@ai-sdk/cerebras": "^0.2.6", "@ai-sdk/deepseek": "^0.2.13", "@ai-sdk/google": "^1.2.6", "@ai-sdk/groq": "^1.2.4", "@ai-sdk/mistral": "^1.2.7", "@ai-sdk/openai": "^1.0.14", "@ai-sdk/perplexity": "^1.1.7", "@ai-sdk/togetherai": "^0.2.6", "@ai-sdk/xai": "^1.2.15", "ollama-ai-provider": "^1.2.0" }, "peerDependencies": { "deepmerge": "^4.3.1", "dotenv": "^16.4.5", "zod": ">=3.25.0 <4.1.0" } }, "sha512-PSctDxqMrLLCwv+fg3piYfzUvhe9vWZjSkBWiDVfTL2/611+PrTMGPjnikz1rhyDrTo7IU9KGui/ZMntZHEnzQ=="], + "@bufbuild/protobuf": ["@bufbuild/protobuf@2.7.0", "", {}, "sha512-qn6tAIZEw5i/wiESBF4nQxZkl86aY4KoO0IkUa2Lh+rya64oTOdJQFlZuMwI1Qz9VBJQrQC4QlSA2DNek5gCOA=="], + "@bugsnag/cuid": ["@bugsnag/cuid@3.2.1", "", {}, "sha512-zpvN8xQ5rdRWakMd/BcVkdn2F8HKlDSbM3l7duueK590WmI1T0ObTLc1V/1e55r14WNjPd5AJTYX4yPEAFVi+Q=="], "@cerebras/cerebras_cloud_sdk": ["@cerebras/cerebras_cloud_sdk@1.46.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-5IFlD9jvZr5fRsHd4pL4sFkzDPSvHxzM0dmk3mJG2/biRBl+3EV6CdXCabf7J8kr8hyQyK67N0rQLnYy6NSh9w=="], @@ -471,6 +474,10 @@ "@chatscope/chat-ui-kit-styles": ["@chatscope/chat-ui-kit-styles@1.4.0", "", {}, "sha512-016mBJD3DESw7Nh+lkKcPd22xG92ghA0VpIXIbjQtmXhC7Ve6wRazTy8z1Ahut+Tbv179+JxrftuMngsj/yV8Q=="], + "@connectrpc/connect": ["@connectrpc/connect@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0" } }, "sha512-ARBt64yEyKbanyRETTjcjJuHr2YXorzQo0etyS5+P6oSeW8xEuzajA9g+zDnMcj1hlX2dQE93foIWQGfpru7gQ=="], + + "@connectrpc/connect-web": ["@connectrpc/connect-web@2.0.0-rc.3", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.2.0", "@connectrpc/connect": "2.0.0-rc.3" } }, "sha512-w88P8Lsn5CCsA7MFRl2e6oLY4J/5toiNtJns/YJrlyQaWOy3RO8pDgkz+iIkG98RPMhj2thuBvsd3Cn4DKKCkw=="], + "@csstools/color-helpers": ["@csstools/color-helpers@5.1.0", "", {}, "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA=="], "@csstools/css-calc": ["@csstools/css-calc@2.1.4", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4" } }, "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ=="], @@ -485,6 +492,8 @@ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@e2b/code-interpreter": ["@e2b/code-interpreter@2.0.0", "", { "dependencies": { "e2b": "^2.0.1" } }, "sha512-rCIW4dV544sUx2YQB/hhwDrK6sQzIb5lr5h/1CIVoOIRgU9q3NUxsFj+2OsgWd4rMG8l6b/oA7FVZJwP4sGX/A=="], + "@electric-sql/client": ["@electric-sql/client@1.0.0-beta.1", "", { "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-Ei9jN3pDoGzc+a/bGqnB5ajb52IvSv7/n2btuyzUlcOHIR2kM9fqtYTJXPwZYKLkGZlHWlpHgWyRtrinkP2nHg=="], "@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], @@ -1741,6 +1750,8 @@ "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], @@ -1905,6 +1916,8 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "e2b": ["e2b@2.0.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.6.2", "@connectrpc/connect": "2.0.0-rc.3", "@connectrpc/connect-web": "2.0.0-rc.3", "compare-versions": "^6.1.0", "openapi-fetch": "^0.9.7", "platform": "^1.3.6" } }, "sha512-wJgZTV1QFeh5WKQ23n6hWmMODPmyKiiWaQy+uxvV/5M9NH/zMY/ONzhh7j7ONYgeH8jdRZxOS2e+G345EodGeA=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], @@ -2601,6 +2614,10 @@ "openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="], + "openapi-fetch": ["openapi-fetch@0.9.8", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.8" } }, "sha512-zM6elH0EZStD/gSiNlcPrzXcVQ/pZo3BDvC6CDwRDUt1dDzxlshpmQnpD6cZaJ39THaSmwVCxxRrPKNM1hHrDg=="], + + "openapi-typescript-helpers": ["openapi-typescript-helpers@0.0.8", "", {}, "sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g=="], + "opentracing": ["opentracing@0.14.7", "", {}, "sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q=="], "option": ["option@0.2.4", "", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], @@ -2681,6 +2698,8 @@ "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "platform": ["platform@1.3.6", "", {}, "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="], + "playwright": ["playwright@1.55.0", "", { "dependencies": { "playwright-core": "1.55.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA=="], "playwright-core": ["playwright-core@1.55.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg=="],