From b67494eeefd99bb51fb239f54f647eea16f73281 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 3 Feb 2025 12:11:03 -0800 Subject: [PATCH] Fixed error handling in reverse proxy, use executor as centralized place to handle errors. Error bubbles up to proxy -> executor -> block log -> custom console --- app/api/proxy/route.ts | 131 ++++++++++++++--------------------------- executor/index.ts | 42 +++++++++---- tools/index.ts | 10 +--- 3 files changed, 77 insertions(+), 106 deletions(-) diff --git a/app/api/proxy/route.ts b/app/api/proxy/route.ts index 517ea2d9ce..4277969250 100644 --- a/app/api/proxy/route.ts +++ b/app/api/proxy/route.ts @@ -3,108 +3,65 @@ import { getTool } from '@/tools' export async function POST(request: Request) { try { - // Expecting a tool identifier and the validated parameters const { toolId, params } = await request.json() - // Look up the tool config from the registry const tool = getTool(toolId) if (!tool) { - return NextResponse.json( - { - success: false, - error: `Tool not found: ${toolId}`, - details: { toolId } - }, - { status: 400 } - ) + throw new Error(`Tool not found: ${toolId}`) + } + + if (tool.params?.apiKey?.required && !params.apiKey) { + throw new Error(`API key is required for ${toolId}`) } - // Destructure the request configuration from the tool const { url: urlOrFn, method: defaultMethod, headers: headersFn, body: bodyFn } = tool.request - // Compute the external URL - const url = typeof urlOrFn === 'function' ? urlOrFn(params) : urlOrFn + try { + const url = typeof urlOrFn === 'function' ? urlOrFn(params) : urlOrFn + const method = params.method || defaultMethod || 'GET' + const headers = headersFn ? headersFn(params) : {} + const hasBody = method !== 'GET' && method !== 'HEAD' && !!bodyFn + const body = hasBody ? JSON.stringify(bodyFn!(params)) : undefined - // If the params override the method, allow it (or use the default) - const method = params.method || defaultMethod || 'GET' + const externalResponse = await fetch(url, { method, headers, body }) - // Compute headers using the tool's function - const headers = headersFn ? headersFn(params) : {} + if (!externalResponse.ok) { + const errorContent = await externalResponse.json().catch(() => ({ + message: externalResponse.statusText + })) + + // Use the tool's error transformer or a default message + const error = tool.transformError + ? tool.transformError(errorContent) + : errorContent.message || `${toolId} API error: ${externalResponse.statusText}` - // Build request body if needed - const hasBody = method !== 'GET' && method !== 'HEAD' && !!bodyFn - const body = hasBody ? JSON.stringify(bodyFn!(params)) : undefined + throw new Error(error) + } - // Execute external fetch from the server side - const externalResponse = await fetch(url, { method, headers, body }) + const transformResponse = + tool.transformResponse || + (async (resp: Response) => ({ + success: true, + output: await resp.json(), + })) + const result = await transformResponse(externalResponse) - // If the response is not OK, transform the error - if (!externalResponse.ok) { - const errorContent = await externalResponse.json().catch(() => ({ - message: externalResponse.statusText - })) - - // Pass the complete error response to transformError - const error = tool.transformError - ? tool.transformError({ - ...errorContent, - status: externalResponse.status, - statusText: externalResponse.statusText, - // Include raw headers as they were sent - headers: { - authorization: externalResponse.headers.get('authorization'), - 'content-type': externalResponse.headers.get('content-type') - } - }) - : errorContent.message || 'External API error' + if (!result.success) { + throw new Error( + tool.transformError + ? tool.transformError(result) + : 'Tool returned an error' + ) + } - // Return error in a format that matches the ToolResponse type - return NextResponse.json({ - success: false, - output: {}, - error: error - }, { - status: externalResponse.status - }) + return NextResponse.json(result) + } catch (error: any) { + throw error } - - // Transform the response if needed - const transformResponse = - tool.transformResponse || - (async (resp: Response) => ({ - success: true, - output: await resp.json(), - })) - const result = await transformResponse(externalResponse) - - if (!result.success) { - const error = tool.transformError - ? tool.transformError(result) - : 'Tool returned an error' - - return NextResponse.json({ - success: false, - output: {}, - error: error - }, { - status: 400 - }) - } - - return NextResponse.json(result) } catch (error: any) { - console.error('Proxy route error:', error) - - return NextResponse.json( - { - success: false, - error: error.message || 'Internal server error', - details: { - name: error.name, - stack: process.env.NODE_ENV === 'development' ? error.stack : undefined - } - }, - { status: 500 } - ) + return NextResponse.json({ + success: false, + error: error.message || 'Unknown error' + }) } } \ No newline at end of file diff --git a/executor/index.ts b/executor/index.ts index ad2f63933e..ad42fa0cf2 100644 --- a/executor/index.ts +++ b/executor/index.ts @@ -67,11 +67,12 @@ export class Executor { }, logs: context.blockLogs, } - } catch (error) { + } catch (error: any) { + // Ensure we return a meaningful error message return { success: false, output: { response: {} }, - error: error instanceof Error ? error.message : 'Unknown error', + error: error.message || 'Workflow execution failed', logs: context.blockLogs, } } @@ -219,21 +220,40 @@ export class Executor { throw new Error(`Tool not found: ${toolId}`) } - // Merge block's static params with dynamic inputs and validate them const validatedParams = this.validateToolParams(tool, { ...block.config.params, ...inputs, }) - // Call centralized reverse proxy endpoint via executeTool(). - const result = await executeTool(toolId, validatedParams) - - if (!result.success) { - const transformError = tool.transformError ?? (() => 'Tool returned an error') - throw new Error(transformError(result)) - } + try { + const result = await executeTool(toolId, validatedParams) + + if (!result.success) { + // Ensure we have a meaningful error message + const errorMessage = result.error || `Tool ${toolId} failed with no error message` + throw new Error(errorMessage) + } - return { response: result.output } + return { response: result.output } + } catch (error: any) { + // Update block log with error + const blockLog: Partial = { + blockId: block.id, + blockTitle: block.metadata?.title, + blockType: block.metadata?.type, + success: false, + error: error.message || `Tool ${toolId} failed`, + startedAt: new Date().toISOString(), + endedAt: new Date().toISOString(), + durationMs: 0 + } + + // Add the log entry + context.blockLogs.push(blockLog as BlockLog) + + // Re-throw the error + throw error + } } /** diff --git a/tools/index.ts b/tools/index.ts index 0e08585142..c70e57719a 100644 --- a/tools/index.ts +++ b/tools/index.ts @@ -60,25 +60,19 @@ export async function executeTool( const result = await response.json() if (!result.success) { - // Format error message to include details if available - const errorMessage = result.details - ? `${result.error} (${JSON.stringify(result.details)})` - : result.error - return { success: false, output: {}, - error: errorMessage + error: result.error } } return result } catch (error: any) { - console.error('Tool execution error:', error) return { success: false, output: {}, - error: `Error executing tool: ${error.message || 'Unknown error'}` + error: error.message || 'Unknown error' } } } \ No newline at end of file