diff --git a/sim/app/executor/handlers.ts b/sim/app/executor/handlers.ts index 862582b83c..ef766428c7 100644 --- a/sim/app/executor/handlers.ts +++ b/sim/app/executor/handlers.ts @@ -793,15 +793,123 @@ export class ApiBlockHandler implements BlockHandler { throw new Error(`Tool not found: ${block.config.tool}`) } - const result = await executeTool(block.config.tool, { - ...inputs, - _context: { workflowId: context.workflowId }, - }) - if (!result.success) { - throw new Error(result.error || `API request failed with no error message`) + // Early return with empty success response if URL is not provided or empty + if (tool.name && tool.name.includes('HTTP') && (!inputs.url || inputs.url.trim() === '')) { + return { response: { content: '', success: true } } } - return { response: result.output } + // Pre-validate common HTTP request issues to provide better error messages + if (tool.name && tool.name.includes('HTTP') && inputs.url) { + // Check for missing protocol + if (!inputs.url.match(/^https?:\/\//i)) { + throw new Error( + `Invalid URL: "${inputs.url}" - URL must include protocol (try "https://${inputs.url}")` + ); + } + + // Detect other common URL issues + try { + new URL(inputs.url); + } catch (e: any) { + throw new Error(`Invalid URL format: "${inputs.url}" - ${e.message}`); + } + } + + try { + const result = await executeTool(block.config.tool, { + ...inputs, + _context: { workflowId: context.workflowId }, + }) + + if (!result.success) { + const errorDetails = []; + + // Add request details to error message + if (inputs.url) errorDetails.push(`URL: ${inputs.url}`); + if (inputs.method) errorDetails.push(`Method: ${inputs.method}`); + + // Add response details + if (result.error) errorDetails.push(`Error: ${result.error}`); + if (result.output?.status) errorDetails.push(`Status: ${result.output.status}`); + if (result.output?.statusText) errorDetails.push(`Status text: ${result.output.statusText}`); + + // Add specific suggestions for common error codes + let suggestion = ''; + if (result.output?.status === 403) { + suggestion = ' - This may be due to CORS restrictions or authorization issues'; + } else if (result.output?.status === 404) { + suggestion = ' - The requested resource was not found'; + } else if (result.output?.status === 429) { + suggestion = ' - Too many requests, you may need to implement rate limiting'; + } else if (result.output?.status >= 500) { + suggestion = ' - Server error, the target server is experiencing issues'; + } else if (result.error && result.error.includes('CORS')) { + suggestion = ' - CORS policy prevented the request, try using a proxy or server-side request'; + } else if (result.error && result.error.includes('Failed to fetch')) { + suggestion = ' - Network error, check if the URL is accessible and if you have internet connectivity'; + } + + const errorMessage = errorDetails.length > 0 + ? `HTTP Request failed: ${errorDetails.join(' | ')}${suggestion}` + : `API request to ${tool.name || block.config.tool} failed with no error message`; + + // Create a detailed error object with formatted message + const error = new Error(errorMessage); + + // Add additional properties for debugging + Object.assign(error, { + toolId: block.config.tool, + toolName: tool.name || 'Unknown tool', + blockId: block.id, + blockName: block.metadata?.name || 'Unnamed Block', + output: result.output || {}, + status: result.output?.status || null, + request: { + url: inputs.url, + method: inputs.method || 'GET', + }, + timestamp: new Date().toISOString() + }); + + throw error; + } + + return { response: result.output } + } catch (error: any) { + // Ensure we have a meaningful error message + if (!error.message || error.message === "undefined (undefined)") { + // Construct a detailed error message with available information + let errorMessage = `API request to ${tool.name || block.config.tool} failed`; + + // Add details if available + if (inputs.url) errorMessage += `: ${inputs.url}`; + if (error.status) errorMessage += ` (Status: ${error.status})`; + if (error.statusText) errorMessage += ` - ${error.statusText}`; + + // If we still have no details, give a generic but helpful message + if (errorMessage === `API request to ${tool.name || block.config.tool} failed`) { + errorMessage += ` - ${block.metadata?.name || 'Unknown error'}`; + } + + error.message = errorMessage; + } + + // Add additional context to the error + if (typeof error === 'object' && error !== null) { + if (!error.toolId) error.toolId = block.config.tool; + if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block'; + + // Add request details if missing + if (inputs && !error.request) { + error.request = { + url: inputs.url, + method: inputs.method || 'GET' + }; + } + } + + throw error; + } } } @@ -860,14 +968,63 @@ export class GenericBlockHandler implements BlockHandler { throw new Error(`Tool not found: ${block.config.tool}`) } - const result = await executeTool(block.config.tool, { - ...inputs, - _context: { workflowId: context.workflowId }, - }) - if (!result.success) { - throw new Error(result.error || `Block execution failed with no error message`) - } + try { + const result = await executeTool(block.config.tool, { + ...inputs, + _context: { workflowId: context.workflowId }, + }) + + if (!result.success) { + const errorDetails = []; + if (result.error) errorDetails.push(result.error); + + const errorMessage = errorDetails.length > 0 + ? errorDetails.join(' - ') + : `Block execution of ${tool.name || block.config.tool} failed with no error message`; + + // Create a detailed error object with formatted message + const error = new Error(errorMessage); + + // Add additional properties for debugging + Object.assign(error, { + toolId: block.config.tool, + toolName: tool.name || 'Unknown tool', + blockId: block.id, + blockName: block.metadata?.name || 'Unnamed Block', + output: result.output || {}, + timestamp: new Date().toISOString() + }); + + throw error; + } - return { response: result.output } + return { response: result.output } + } catch (error: any) { + // Ensure we have a meaningful error message + if (!error.message || error.message === "undefined (undefined)") { + // Construct a detailed error message with available information + let errorMessage = `Block execution of ${tool.name || block.config.tool} failed`; + + // Add block name if available + if (block.metadata?.name) { + errorMessage += `: ${block.metadata.name}`; + } + + // Add status code if available + if (error.status) { + errorMessage += ` (Status: ${error.status})`; + } + + error.message = errorMessage; + } + + // Add additional context to the error + if (typeof error === 'object' && error !== null) { + if (!error.toolId) error.toolId = block.config.tool; + if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block'; + } + + throw error; + } } } diff --git a/sim/app/executor/index.ts b/sim/app/executor/index.ts index a715a9bc1c..9f9124e0b3 100644 --- a/sim/app/executor/index.ts +++ b/sim/app/executor/index.ts @@ -114,12 +114,12 @@ export class Executor { logs: context.blockLogs, } } catch (error: any) { - logger.error('Workflow execution failed:', { error }) + logger.error('Workflow execution failed:', this.sanitizeError(error)) return { success: false, output: finalOutput, - error: error.message || 'Workflow execution failed', + error: this.extractErrorMessage(error), logs: context.blockLogs, } } finally { @@ -442,7 +442,7 @@ export class Executor { return output } catch (error: any) { blockLog.success = false - blockLog.error = error.message + blockLog.error = error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}` blockLog.endedAt = new Date().toISOString() blockLog.durationMs = new Date(blockLog.endedAt).getTime() - new Date(blockLog.startedAt).getTime() @@ -450,7 +450,7 @@ export class Executor { context.blockLogs.push(blockLog) addConsole({ output: {}, - error: error.message, + error: error.message || `Error executing ${block.metadata?.id || 'unknown'} block: ${String(error)}`, durationMs: blockLog.durationMs, startedAt: blockLog.startedAt, endedAt: blockLog.endedAt, @@ -460,7 +460,22 @@ export class Executor { blockType: block.metadata?.id || 'unknown', }) - throw error + // Create a proper error message that is never undefined + let errorMessage = error.message; + + // Handle the specific "undefined (undefined)" case + if (!errorMessage || errorMessage === "undefined (undefined)") { + errorMessage = `Error executing ${block.metadata?.id || 'unknown'} block: ${block.metadata?.name || 'Unnamed Block'}`; + + // Try to get more details if possible + if (error && typeof error === 'object') { + if (error.code) errorMessage += ` (code: ${error.code})`; + if (error.status) errorMessage += ` (status: ${error.status})`; + if (error.type) errorMessage += ` (type: ${error.type})`; + } + } + + throw new Error(errorMessage); } } @@ -590,4 +605,118 @@ export class Executor { success: false, } } + + /** + * Extracts a meaningful error message from any error object structure. + * Handles nested error objects, undefined messages, and various error formats. + * + * @param error - The error object to extract a message from + * @returns A meaningful error message string + */ + private extractErrorMessage(error: any): string { + if (!error) return 'Unknown error occurred'; + + // Handle Error instances + if (error instanceof Error) { + return error.message || `Error: ${String(error)}`; + } + + // Handle string errors + if (typeof error === 'string') { + return error; + } + + // Handle object errors with nested structure + if (typeof error === 'object') { + // Case: { error: { message: "msg" } } + if (error.error && typeof error.error === 'object' && error.error.message) { + return error.error.message; + } + + // Case: { error: "msg" } + if (error.error && typeof error.error === 'string') { + return error.error; + } + + // Case: { message: "msg" } + if (error.message) { + return error.message; + } + + // Add specific handling for HTTP errors + if (error.status || error.request) { + let message = 'API request failed'; + + // Add URL information if available + if (error.request && error.request.url) { + message += `: ${error.request.url}`; + } + + // Add status code if available + if (error.status) { + message += ` (Status: ${error.status})`; + } + + return message; + } + + // Last resort: try to stringify the object + try { + return `Error details: ${JSON.stringify(error)}`; + } catch { + return 'Error occurred but details could not be displayed'; + } + } + + return 'Unknown error occurred'; + } + + /** + * Sanitizes an error object for logging purposes. + * Ensures the error is in a format that won't cause "undefined" to appear in logs. + * + * @param error - The error object to sanitize + * @returns A sanitized version of the error for logging + */ + private sanitizeError(error: any): any { + if (!error) return { message: 'No error details available' }; + + // Handle Error instances + if (error instanceof Error) { + return { + message: error.message || 'Error without message', + stack: error.stack + }; + } + + // Handle string errors + if (typeof error === 'string') { + return { message: error }; + } + + // Handle object errors with nested structure + if (typeof error === 'object') { + // If error has a nested error object with undefined message, fix it + if (error.error && typeof error.error === 'object') { + if (!error.error.message) { + error.error.message = 'No specific error message provided'; + } + } + + // If no message property exists at root level, add one + if (!error.message) { + if (error.error && typeof error.error === 'string') { + error.message = error.error; + } else if (error.status) { + error.message = `API request failed with status ${error.status}`; + } else { + error.message = 'Error occurred during workflow execution'; + } + } + + return error; + } + + return { message: `Unexpected error type: ${typeof error}` }; + } } diff --git a/sim/app/tools/index.ts b/sim/app/tools/index.ts index 5598811b3d..aa08ff25bc 100644 --- a/sim/app/tools/index.ts +++ b/sim/app/tools/index.ts @@ -452,14 +452,66 @@ export async function executeTool( logger.error(`Looking for custom tool with identifier: ${identifier}`) } + // Process the error to ensure we have a useful message + let errorMessage = 'Unknown error occurred'; + let errorDetails = {}; + + if (error instanceof Error) { + errorMessage = error.message || `Error executing tool ${toolId}`; + } else if (typeof error === 'string') { + errorMessage = error; + } else if (error && typeof error === 'object') { + // Handle API response errors + if (error.response) { + const response = error.response; + errorMessage = `API Error: ${response.statusText || response.status || 'Unknown status'}`; + + // Try to extract more details from the response + if (response.data) { + if (typeof response.data === 'string') { + errorMessage = `${errorMessage} - ${response.data}`; + } else if (response.data.message) { + errorMessage = `${errorMessage} - ${response.data.message}`; + } else if (response.data.error) { + errorMessage = `${errorMessage} - ${typeof response.data.error === 'string' ? + response.data.error : JSON.stringify(response.data.error)}`; + } + } + + // Include useful debugging information + errorDetails = { + status: response.status, + statusText: response.statusText, + data: response.data + }; + } + // Handle fetch or other network errors + else if (error.message) { + // Don't pass along "undefined (undefined)" messages + if (error.message === "undefined (undefined)") { + errorMessage = `Error executing tool ${toolId}`; + // Add status if available + if (error.status) { + errorMessage += ` (Status: ${error.status})`; + } + } else { + errorMessage = error.message; + } + + if (error.cause) { + errorMessage = `${errorMessage} (${error.cause})`; + } + } + } + // Add timing data even for errors const endTime = new Date() const endTimeISO = endTime.toISOString() const duration = endTime.getTime() - startTime.getTime() return { success: false, - output: {}, - error: error.message || 'Unknown error', + output: errorDetails, + error: errorMessage, timing: { startTime: startTimeISO, endTime: endTimeISO, @@ -700,21 +752,66 @@ async function handleProxyRequest( } const proxyUrl = new URL('/api/proxy', baseUrl).toString() - const response = await fetch(proxyUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ toolId, params }), - }) + try { + const response = await fetch(proxyUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ toolId, params }), + }); - const result = await response.json() + if (!response.ok) { + const errorText = await response.text(); + let errorMessage = `HTTP error ${response.status}: ${response.statusText}`; + let errorDetails = { status: response.status, statusText: response.statusText }; + + try { + // Try to parse as JSON for more details + const errorJson = JSON.parse(errorText); + if (errorJson.error) { + errorMessage = typeof errorJson.error === 'string' ? + errorJson.error : + `API Error: ${response.status} ${response.statusText}`; + } + errorDetails = { ...errorDetails, ...errorJson }; + } catch { + // If not JSON, use the raw text + if (errorText && errorText !== "undefined (undefined)") { + errorMessage = `${errorMessage} - ${errorText}`; + } + } + + return { + success: false, + output: errorDetails, + error: errorMessage, + }; + } - if (!result.success) { + const result = await response.json(); + + if (!result.success) { + return { + success: false, + output: result.output || {}, + error: result.error || `API request to ${toolId} failed with no error message`, + }; + } + + return result; + } catch (error: any) { + // Handle network or other fetch errors + logger.error(`Error in proxy request for tool ${toolId}:`, { error }); + + let errorMessage = error instanceof Error ? + error.message : + typeof error === 'string' ? + error : + `Unknown error in API request to ${toolId}`; + return { success: false, - output: {}, - error: result.error, - } + output: { originalError: error }, + error: errorMessage, + }; } - - return result } diff --git a/sim/app/w/[id]/hooks/use-workflow-execution.ts b/sim/app/w/[id]/hooks/use-workflow-execution.ts index c0ed0a7b22..4a0293478a 100644 --- a/sim/app/w/[id]/hooks/use-workflow-execution.ts +++ b/sim/app/w/[id]/hooks/use-workflow-execution.ts @@ -348,9 +348,42 @@ export function useWorkflowExecution() { // Send the entire execution result to our API to be processed server-side await persistLogs(executionId, result) } catch (error: any) { - logger.error('Workflow Execution Error:', { error }) + logger.error('Workflow Execution Error:', error) - const errorMessage = error instanceof Error ? error.message : 'Unknown error' + // Properly extract error message ensuring it's never undefined + let errorMessage = 'Unknown error' + + if (error instanceof Error) { + errorMessage = error.message || `Error: ${String(error)}` + } else if (typeof error === 'string') { + errorMessage = error + } else if (error && typeof error === 'object') { + // Fix the "undefined (undefined)" pattern specifically + if (error.message === 'undefined (undefined)' || + (error.error && typeof error.error === 'object' && error.error.message === 'undefined (undefined)')) { + errorMessage = 'API request failed - no specific error details available'; + } + // Try to extract error details from potential API or execution errors + else if (error.message) { + errorMessage = error.message + } else if (error.error && typeof error.error === 'string') { + errorMessage = error.error + } else if (error.error && typeof error.error === 'object' && error.error.message) { + errorMessage = error.error.message + } else { + // Last resort: stringify the whole object + try { + errorMessage = `Error details: ${JSON.stringify(error)}` + } catch { + errorMessage = 'Error occurred but details could not be displayed' + } + } + } + + // Ensure errorMessage is never "undefined (undefined)" + if (errorMessage === 'undefined (undefined)') { + errorMessage = 'API request failed - no specific error details available'; + } // Set error result and show notification immediately const errorResult = { @@ -361,7 +394,27 @@ export function useWorkflowExecution() { } setExecutionResult(errorResult) - addNotification('error', `Workflow execution failed: ${errorMessage}`, activeWorkflowId) + + // Create a more user-friendly notification message + let notificationMessage = `Workflow execution failed`; + + // Add URL for HTTP errors + if (error && error.request && error.request.url) { + // Don't show empty URL errors + if (error.request.url && error.request.url.trim() !== '') { + notificationMessage += `: Request to ${error.request.url} failed`; + + // Add status if available + if (error.status) { + notificationMessage += ` (Status: ${error.status})`; + } + } + } else { + // Regular errors + notificationMessage += `: ${errorMessage}`; + } + + addNotification('error', notificationMessage, activeWorkflowId) // Also send the error result to the API await persistLogs(executionId, errorResult)