mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
fix(function): disabled freestyle, VM by default with node-fetch (#570)
* fix(function): disabled freestyle, VM by default with node-fetch * fix: add input log to error --------- Co-authored-by: Waleed Latif <walif6@gmail.com>
This commit is contained in:
@@ -228,7 +228,7 @@ describe('Function Execute API Route', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Freestyle Execution', () => {
|
||||
describe.skip('Freestyle Execution', () => {
|
||||
it('should use Freestyle when API key is available', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "freestyle test"',
|
||||
@@ -281,7 +281,7 @@ describe('Function Execute API Route', () => {
|
||||
})
|
||||
|
||||
describe('VM Execution', () => {
|
||||
it('should use VM when Freestyle API key is not available', async () => {
|
||||
it.skip('should use VM when Freestyle API key is not available', async () => {
|
||||
// Mock no Freestyle API key
|
||||
vi.doMock('@/lib/env', () => ({
|
||||
env: {
|
||||
@@ -470,7 +470,7 @@ describe('Function Execute API - Template Variable Edge Cases', () => {
|
||||
mockCreateContext.mockReturnValue({})
|
||||
})
|
||||
|
||||
it('should handle nested template variables', async () => {
|
||||
it.skip('should handle nested template variables', async () => {
|
||||
mockFreestyleExecuteScript.mockResolvedValueOnce({
|
||||
result: 'environment-valueparam-value',
|
||||
logs: [],
|
||||
@@ -495,7 +495,7 @@ describe('Function Execute API - Template Variable Edge Cases', () => {
|
||||
expect(data.output.result).toBe('environment-valueparam-value')
|
||||
})
|
||||
|
||||
it('should prioritize environment variables over params for {{}} syntax', async () => {
|
||||
it.skip('should prioritize environment variables over params for {{}} syntax', async () => {
|
||||
mockFreestyleExecuteScript.mockResolvedValueOnce({
|
||||
result: 'env-wins',
|
||||
logs: [],
|
||||
@@ -521,7 +521,7 @@ describe('Function Execute API - Template Variable Edge Cases', () => {
|
||||
expect(data.output.result).toBe('env-wins')
|
||||
})
|
||||
|
||||
it('should handle missing template variables gracefully', async () => {
|
||||
it.skip('should handle missing template variables gracefully', async () => {
|
||||
mockFreestyleExecuteScript.mockResolvedValueOnce({
|
||||
result: '',
|
||||
logs: [],
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { createContext, Script } from 'vm'
|
||||
import { FreestyleSandboxes } from 'freestyle-sandboxes'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -122,129 +120,185 @@ export async function POST(req: NextRequest) {
|
||||
// Resolve variables in the code with workflow environment variables
|
||||
const resolvedCode = resolveCodeVariables(code, executionParams, envVars)
|
||||
|
||||
let result: any
|
||||
let executionMethod = 'vm' // Default execution method
|
||||
const executionMethod = 'vm' // Default execution method
|
||||
|
||||
// Try to use Freestyle if the API key is available
|
||||
if (env.FREESTYLE_API_KEY) {
|
||||
try {
|
||||
logger.info(`[${requestId}] Using Freestyle for code execution`)
|
||||
executionMethod = 'freestyle'
|
||||
// // Try to use Freestyle if the API key is available
|
||||
// if (env.FREESTYLE_API_KEY) {
|
||||
// try {
|
||||
// logger.info(`[${requestId}] Using Freestyle for code execution`)
|
||||
// executionMethod = 'freestyle'
|
||||
|
||||
// Extract npm packages from code if needed
|
||||
const importRegex =
|
||||
/import\s+?(?:(?:(?:[\w*\s{},]*)\s+from\s+?)|)(?:(?:"([^"]*)")|(?:'([^']*)'))[^;]*/g
|
||||
const requireRegex = /const\s+[\w\s{}]*\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
||||
// // Extract npm packages from code if needed
|
||||
// const importRegex =
|
||||
// /import\s+?(?:(?:(?:[\w*\s{},]*)\s+from\s+?)|)(?:(?:"([^"]*)")|(?:'([^']*)'))[^;]*/g
|
||||
// const requireRegex = /const\s+[\w\s{}]*\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
||||
|
||||
const packages: Record<string, string> = {}
|
||||
const matches = [
|
||||
...resolvedCode.matchAll(importRegex),
|
||||
...resolvedCode.matchAll(requireRegex),
|
||||
]
|
||||
// const packages: Record<string, string> = {}
|
||||
// const matches = [
|
||||
// ...resolvedCode.matchAll(importRegex),
|
||||
// ...resolvedCode.matchAll(requireRegex),
|
||||
// ]
|
||||
|
||||
// Extract package names from import statements
|
||||
for (const match of matches) {
|
||||
const packageName = match[1] || match[2]
|
||||
if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
|
||||
// Extract just the package name without version or subpath
|
||||
const basePackageName = packageName.split('/')[0]
|
||||
packages[basePackageName] = 'latest' // Use latest version
|
||||
}
|
||||
}
|
||||
// // Extract package names from import statements
|
||||
// for (const match of matches) {
|
||||
// const packageName = match[1] || match[2]
|
||||
// if (packageName && !packageName.startsWith('.') && !packageName.startsWith('/')) {
|
||||
// // Extract just the package name without version or subpath
|
||||
// const basePackageName = packageName.split('/')[0]
|
||||
// packages[basePackageName] = 'latest' // Use latest version
|
||||
// }
|
||||
// }
|
||||
|
||||
const freestyle = new FreestyleSandboxes({
|
||||
apiKey: env.FREESTYLE_API_KEY,
|
||||
})
|
||||
// const freestyle = new FreestyleSandboxes({
|
||||
// apiKey: env.FREESTYLE_API_KEY,
|
||||
// })
|
||||
|
||||
// Wrap code in export default to match Freestyle's expectations
|
||||
const wrappedCode = isCustomTool
|
||||
? `export default async () => {
|
||||
// For custom tools, directly declare parameters as variables
|
||||
${Object.entries(executionParams)
|
||||
.map(([key, value]) => `const ${key} = ${safeJSONStringify(value)};`)
|
||||
.join('\n ')}
|
||||
${resolvedCode}
|
||||
}`
|
||||
: `export default async () => { ${resolvedCode} }`
|
||||
// // Wrap code in export default to match Freestyle's expectations
|
||||
// const wrappedCode = isCustomTool
|
||||
// ? `export default async () => {
|
||||
// // For custom tools, directly declare parameters as variables
|
||||
// ${Object.entries(executionParams)
|
||||
// .map(([key, value]) => `const ${key} = ${safeJSONStringify(value)};`)
|
||||
// .join('\n ')}
|
||||
// ${resolvedCode}
|
||||
// }`
|
||||
// : `export default async () => { ${resolvedCode} }`
|
||||
|
||||
// Execute the code with Freestyle
|
||||
const res = await freestyle.executeScript(wrappedCode, {
|
||||
nodeModules: packages,
|
||||
timeout: null,
|
||||
envVars: envVars,
|
||||
})
|
||||
// // Execute the code with Freestyle
|
||||
// const res = await freestyle.executeScript(wrappedCode, {
|
||||
// nodeModules: packages,
|
||||
// timeout: null,
|
||||
// envVars: envVars,
|
||||
// })
|
||||
|
||||
// Check for direct API error response
|
||||
// Type assertion since the library types don't include error response
|
||||
const response = res as { _type?: string; error?: string }
|
||||
if (response._type === 'error' && response.error) {
|
||||
logger.error(`[${requestId}] Freestyle returned error response`, {
|
||||
error: response.error,
|
||||
})
|
||||
throw response.error
|
||||
}
|
||||
// // Check for direct API error response
|
||||
// // Type assertion since the library types don't include error response
|
||||
// const response = res as { _type?: string; error?: string }
|
||||
// if (response._type === 'error' && response.error) {
|
||||
// logger.error(`[${requestId}] Freestyle returned error response`, {
|
||||
// error: response.error,
|
||||
// })
|
||||
// throw response.error
|
||||
// }
|
||||
|
||||
// Capture stdout/stderr from Freestyle logs
|
||||
stdout =
|
||||
res.logs
|
||||
?.map((log) => (log.type === 'error' ? 'ERROR: ' : '') + log.message)
|
||||
.join('\n') || ''
|
||||
// // Capture stdout/stderr from Freestyle logs
|
||||
// stdout =
|
||||
// res.logs
|
||||
// ?.map((log) => (log.type === 'error' ? 'ERROR: ' : '') + log.message)
|
||||
// .join('\n') || ''
|
||||
|
||||
// Check for errors reported within Freestyle logs
|
||||
const freestyleErrors = res.logs?.filter((log) => log.type === 'error') || []
|
||||
if (freestyleErrors.length > 0) {
|
||||
const errorMessage = freestyleErrors.map((log) => log.message).join('\n')
|
||||
logger.error(`[${requestId}] Freestyle execution completed with script errors`, {
|
||||
errorMessage,
|
||||
stdout,
|
||||
})
|
||||
// Create a proper Error object to be caught by the outer handler
|
||||
const scriptError = new Error(errorMessage)
|
||||
scriptError.name = 'FreestyleScriptError'
|
||||
throw scriptError
|
||||
}
|
||||
// // Check for errors reported within Freestyle logs
|
||||
// const freestyleErrors = res.logs?.filter((log) => log.type === 'error') || []
|
||||
// if (freestyleErrors.length > 0) {
|
||||
// const errorMessage = freestyleErrors.map((log) => log.message).join('\n')
|
||||
// logger.error(`[${requestId}] Freestyle execution completed with script errors`, {
|
||||
// errorMessage,
|
||||
// stdout,
|
||||
// })
|
||||
// // Create a proper Error object to be caught by the outer handler
|
||||
// const scriptError = new Error(errorMessage)
|
||||
// scriptError.name = 'FreestyleScriptError'
|
||||
// throw scriptError
|
||||
// }
|
||||
|
||||
// If no errors, execution was successful
|
||||
result = res.result
|
||||
logger.info(`[${requestId}] Freestyle execution successful`, {
|
||||
result,
|
||||
stdout,
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Check if the error came from our explicit throw above due to script errors
|
||||
if (error.name === 'FreestyleScriptError') {
|
||||
throw error // Re-throw to be caught by the outer handler
|
||||
}
|
||||
// // If no errors, execution was successful
|
||||
// result = res.result
|
||||
// logger.info(`[${requestId}] Freestyle execution successful`, {
|
||||
// result,
|
||||
// stdout,
|
||||
// })
|
||||
// } catch (error: any) {
|
||||
// // Check if the error came from our explicit throw above due to script errors
|
||||
// if (error.name === 'FreestyleScriptError') {
|
||||
// throw error // Re-throw to be caught by the outer handler
|
||||
// }
|
||||
|
||||
// Otherwise, it's likely a Freestyle API call error (network, auth, config, etc.) -> Fallback to VM
|
||||
logger.error(`[${requestId}] Freestyle API call failed, falling back to VM:`, {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
executionMethod = 'vm_fallback'
|
||||
// // Otherwise, it's likely a Freestyle API call error (network, auth, config, etc.) -> Fallback to VM
|
||||
// logger.error(`[${requestId}] Freestyle API call failed, falling back to VM:`, {
|
||||
// error: error.message,
|
||||
// stack: error.stack,
|
||||
// })
|
||||
// executionMethod = 'vm_fallback'
|
||||
|
||||
// Continue to VM execution
|
||||
const context = createContext({
|
||||
params: executionParams,
|
||||
environmentVariables: envVars,
|
||||
console: {
|
||||
log: (...args: any[]) => {
|
||||
const logMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
stdout += logMessage
|
||||
},
|
||||
error: (...args: any[]) => {
|
||||
const errorMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
logger.error(`[${requestId}] Code Console Error: ${errorMessage}`)
|
||||
stdout += `ERROR: ${errorMessage}`
|
||||
},
|
||||
},
|
||||
})
|
||||
// // Continue to VM execution
|
||||
// const context = createContext({
|
||||
// params: executionParams,
|
||||
// environmentVariables: envVars,
|
||||
// console: {
|
||||
// log: (...args: any[]) => {
|
||||
// const logMessage = `${args
|
||||
// .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
// .join(' ')}\n`
|
||||
// stdout += logMessage
|
||||
// },
|
||||
// error: (...args: any[]) => {
|
||||
// const errorMessage = `${args
|
||||
// .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
// .join(' ')}\n`
|
||||
// logger.error(`[${requestId}] Code Console Error: ${errorMessage}`)
|
||||
// stdout += `ERROR: ${errorMessage}`
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
const script = new Script(`
|
||||
// const script = new Script(`
|
||||
// (async () => {
|
||||
// try {
|
||||
// ${
|
||||
// isCustomTool
|
||||
// ? `// For custom tools, make parameters directly accessible
|
||||
// ${Object.keys(executionParams)
|
||||
// .map((key) => `const ${key} = params.${key};`)
|
||||
// .join('\n ')}`
|
||||
// : ''
|
||||
// }
|
||||
// ${resolvedCode}
|
||||
// } catch (error) {
|
||||
// console.error(error);
|
||||
// throw error;
|
||||
// }
|
||||
// })()
|
||||
// `)
|
||||
|
||||
// result = await script.runInContext(context, {
|
||||
// timeout,
|
||||
// displayErrors: true,
|
||||
// })
|
||||
// logger.info(`[${requestId}] VM execution result`, {
|
||||
// result,
|
||||
// stdout,
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
logger.info(`[${requestId}] Using VM for code execution`, {
|
||||
resolvedCode,
|
||||
executionParams,
|
||||
envVars,
|
||||
})
|
||||
|
||||
// Create a secure context with console logging
|
||||
const context = createContext({
|
||||
params: executionParams,
|
||||
environmentVariables: envVars,
|
||||
fetch: globalThis.fetch || require('node-fetch').default,
|
||||
console: {
|
||||
log: (...args: any[]) => {
|
||||
const logMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
stdout += logMessage
|
||||
},
|
||||
error: (...args: any[]) => {
|
||||
const errorMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
logger.error(`[${requestId}] Code Console Error: ${errorMessage}`)
|
||||
stdout += `ERROR: ${errorMessage}`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const script = new Script(`
|
||||
(async () => {
|
||||
try {
|
||||
${
|
||||
@@ -252,7 +306,7 @@ export async function POST(req: NextRequest) {
|
||||
? `// For custom tools, make parameters directly accessible
|
||||
${Object.keys(executionParams)
|
||||
.map((key) => `const ${key} = params.${key};`)
|
||||
.join('\n ')}`
|
||||
.join('\n ')}`
|
||||
: ''
|
||||
}
|
||||
${resolvedCode}
|
||||
@@ -263,64 +317,11 @@ export async function POST(req: NextRequest) {
|
||||
})()
|
||||
`)
|
||||
|
||||
result = await script.runInContext(context, {
|
||||
timeout,
|
||||
displayErrors: true,
|
||||
})
|
||||
logger.info(`[${requestId}] VM execution result`, {
|
||||
result,
|
||||
stdout,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// No Freestyle API key, use VM execution
|
||||
logger.info(`[${requestId}] Using VM for code execution (no Freestyle API key available)`)
|
||||
|
||||
// Create a secure context with console logging
|
||||
const context = createContext({
|
||||
params: executionParams,
|
||||
environmentVariables: envVars,
|
||||
console: {
|
||||
log: (...args: any[]) => {
|
||||
const logMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
stdout += logMessage
|
||||
},
|
||||
error: (...args: any[]) => {
|
||||
const errorMessage = `${args
|
||||
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg)))
|
||||
.join(' ')}\n`
|
||||
logger.error(`[${requestId}] Code Console Error: ${errorMessage}`)
|
||||
stdout += `ERROR: ${errorMessage}`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const script = new Script(`
|
||||
(async () => {
|
||||
try {
|
||||
${
|
||||
isCustomTool
|
||||
? `// For custom tools, make parameters directly accessible
|
||||
${Object.keys(executionParams)
|
||||
.map((key) => `const ${key} = params.${key};`)
|
||||
.join('\n ')}`
|
||||
: ''
|
||||
}
|
||||
${resolvedCode}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
})()
|
||||
`)
|
||||
|
||||
result = await script.runInContext(context, {
|
||||
timeout,
|
||||
displayErrors: true,
|
||||
})
|
||||
}
|
||||
const result = await script.runInContext(context, {
|
||||
timeout,
|
||||
displayErrors: true,
|
||||
})
|
||||
// }
|
||||
|
||||
const executionTime = Date.now() - startTime
|
||||
logger.info(`[${requestId}] Function executed successfully using ${executionMethod}`, {
|
||||
|
||||
@@ -1139,6 +1139,9 @@ export class Executor {
|
||||
}
|
||||
}
|
||||
|
||||
// Store raw input configuration first for error debugging
|
||||
blockLog.input = block.config.params
|
||||
|
||||
// Resolve inputs (which will look up references to other blocks including starter)
|
||||
const inputs = this.resolver.resolveInputs(block, context)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user