mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
Fixed error handling in reverse proxy, use executor as centralized place to handle errors. Error bubbles up to proxy -> executor -> block log -> custom console
This commit is contained in:
@@ -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'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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<BlockLog> = {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user