Files
sim/apps/sim/executor/handlers/api/api-handler.ts
Vikhyath Mondreti 5b0c2156e0 improvement(files): pass user file objects around consistently (#3119)
* improvement(collab): do not refetch active workflow id

* progress on files

* more integrations

* separate server and client logic

* consolidate more code

* fix integrations

* fix types

* consolidate more code

* fix tests

* fix more bugbot comments

* fix type check

* fix circular impport

* address more bugbot comments

* fix ocr integrations

* fix typing

* remove leftover type

* address bugbot comment

* fix file block adv mode

* fix

* normalize file input

* fix v2 blocmks for ocr

* fix for v2 versions

* fix more v2 blocks

* update single file blocks

* make interface simpler

* cleanup fireflies

* remove file only annotation

* accept all types

* added wand to ssh block

* user files should be passed through

* improve docs

* fix slack to include successful execs

* fix dropbox upload file

* fix sendgrid

* fix dropbox

* fix

* fix

* update skills

* fix uploaded file

---------

Co-authored-by: waleed <walif6@gmail.com>
2026-02-03 19:50:23 -08:00

165 lines
5.7 KiB
TypeScript

import { createLogger } from '@sim/logger'
import { validateUrlWithDNS } from '@/lib/core/security/input-validation.server'
import { BlockType, HTTP } from '@/executor/constants'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'
import { executeTool } from '@/tools'
import { getTool } from '@/tools/utils'
const logger = createLogger('ApiBlockHandler')
/**
* Handler for API blocks that make external HTTP requests.
*/
export class ApiBlockHandler implements BlockHandler {
canHandle(block: SerializedBlock): boolean {
return block.metadata?.id === BlockType.API
}
async execute(
ctx: ExecutionContext,
block: SerializedBlock,
inputs: Record<string, any>
): Promise<any> {
const tool = getTool(block.config.tool)
if (!tool) {
throw new Error(`Tool not found: ${block.config.tool}`)
}
if (tool.name?.includes('HTTP') && (!inputs.url || inputs.url.trim() === '')) {
return { data: null, status: HTTP.STATUS.OK, headers: {} }
}
if (tool.name?.includes('HTTP') && inputs.url) {
let urlToValidate = inputs.url
if (typeof urlToValidate === 'string') {
if (
(urlToValidate.startsWith('"') && urlToValidate.endsWith('"')) ||
(urlToValidate.startsWith("'") && urlToValidate.endsWith("'"))
) {
urlToValidate = urlToValidate.slice(1, -1)
inputs.url = urlToValidate
}
}
const urlValidation = await validateUrlWithDNS(urlToValidate, 'url')
if (!urlValidation.isValid) {
throw new Error(urlValidation.error)
}
}
try {
const processedInputs = { ...inputs }
if (processedInputs.body !== undefined) {
if (typeof processedInputs.body === 'string') {
try {
const trimmedBody = processedInputs.body.trim()
if (trimmedBody.startsWith('{') || trimmedBody.startsWith('[')) {
processedInputs.body = JSON.parse(trimmedBody)
}
} catch (e) {}
} else if (processedInputs.body === null) {
processedInputs.body = undefined
}
}
const result = await executeTool(
block.config.tool,
{
...processedInputs,
_context: {
workflowId: ctx.workflowId,
workspaceId: ctx.workspaceId,
executionId: ctx.executionId,
isDeployedContext: ctx.isDeployedContext,
},
},
false,
ctx
)
if (!result.success) {
const errorDetails = []
if (inputs.url) errorDetails.push(`URL: ${inputs.url}`)
if (inputs.method) errorDetails.push(`Method: ${inputs.method}`)
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}`)
let suggestion = ''
if (result.output?.status === HTTP.STATUS.FORBIDDEN) {
suggestion = ' - This may be due to CORS restrictions or authorization issues'
} else if (result.output?.status === HTTP.STATUS.NOT_FOUND) {
suggestion = ' - The requested resource was not found'
} else if (result.output?.status === HTTP.STATUS.TOO_MANY_REQUESTS) {
suggestion = ' - Too many requests, you may need to implement rate limiting'
} else if (result.output?.status >= HTTP.STATUS.SERVER_ERROR) {
suggestion = ' - Server error, the target server is experiencing issues'
} else if (result.error?.includes('CORS')) {
suggestion =
' - CORS policy prevented the request, try using a proxy or server-side request'
} else if (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`
const error = new Error(errorMessage)
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 result.output
} catch (error: any) {
if (!error.message || error.message === 'undefined (undefined)') {
let errorMessage = `API request to ${tool.name || block.config.tool} failed`
if (inputs.url) errorMessage += `: ${inputs.url}`
if (error.status) errorMessage += ` (Status: ${error.status})`
if (error.statusText) errorMessage += ` - ${error.statusText}`
if (errorMessage === `API request to ${tool.name || block.config.tool} failed`) {
errorMessage += ` - ${block.metadata?.name || 'Unknown error'}`
}
error.message = errorMessage
}
if (typeof error === 'object' && error !== null) {
if (!error.toolId) error.toolId = block.config.tool
if (!error.blockName) error.blockName = block.metadata?.name || 'Unnamed Block'
if (inputs && !error.request) {
error.request = {
url: inputs.url,
method: inputs.method || 'GET',
}
}
}
throw error
}
}
}