Flowise/packages/components/nodes/agentflow/HTTP/HTTP.ts

410 lines
14 KiB
TypeScript

import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import axios, { AxiosRequestConfig, Method, ResponseType } from 'axios'
import FormData from 'form-data'
import * as querystring from 'querystring'
import { getCredentialData, getCredentialParam } from '../../../src/utils'
class HTTP_Agentflow implements INode {
label: string
name: string
version: number
description: string
type: string
icon: string
category: string
color: string
baseClasses: string[]
documentation?: string
credential: INodeParams
inputs: INodeParams[]
private sanitizeJsonString(jsonString: string): string {
// Remove common problematic escape sequences that are not valid JSON
let sanitized = jsonString
// Remove escaped square brackets (not valid JSON)
.replace(/\\(\[|\])/g, '$1')
// Fix unquoted string values in JSON (simple case)
.replace(/:\s*([a-zA-Z][a-zA-Z0-9]*)\s*([,}])/g, ': "$1"$2')
// Fix trailing commas
.replace(/,(\s*[}\]])/g, '$1')
return sanitized
}
private parseJsonBody(body: string): any {
try {
// First try to parse as-is
return JSON.parse(body)
} catch (error) {
try {
// If that fails, try to sanitize and parse
const sanitized = this.sanitizeJsonString(body)
return JSON.parse(sanitized)
} catch (sanitizeError) {
// If sanitization also fails, throw the original error with helpful message
throw new Error(
`Invalid JSON format in body. Original error: ${error.message}. Please ensure your JSON is properly formatted with quoted strings and valid escape sequences.`
)
}
}
}
constructor() {
this.label = 'HTTP'
this.name = 'httpAgentflow'
this.version = 1.1
this.type = 'HTTP'
this.category = 'Agent Flows'
this.description = 'Send a HTTP request'
this.baseClasses = [this.type]
this.color = '#FF7F7F'
this.credential = {
label: 'HTTP Credential',
name: 'credential',
type: 'credential',
credentialNames: ['httpBasicAuth', 'httpBearerToken', 'httpApiKey'],
optional: true
}
this.inputs = [
{
label: 'Method',
name: 'method',
type: 'options',
options: [
{
label: 'GET',
name: 'GET'
},
{
label: 'POST',
name: 'POST'
},
{
label: 'PUT',
name: 'PUT'
},
{
label: 'DELETE',
name: 'DELETE'
},
{
label: 'PATCH',
name: 'PATCH'
}
],
default: 'GET'
},
{
label: 'URL',
name: 'url',
type: 'string'
},
{
label: 'Headers',
name: 'headers',
type: 'array',
acceptVariable: true,
array: [
{
label: 'Key',
name: 'key',
type: 'string',
default: ''
},
{
label: 'Value',
name: 'value',
type: 'string',
default: '',
acceptVariable: true
}
],
optional: true
},
{
label: 'Query Params',
name: 'queryParams',
type: 'array',
acceptVariable: true,
array: [
{
label: 'Key',
name: 'key',
type: 'string',
default: ''
},
{
label: 'Value',
name: 'value',
type: 'string',
default: '',
acceptVariable: true
}
],
optional: true
},
{
label: 'Body Type',
name: 'bodyType',
type: 'options',
options: [
{
label: 'JSON',
name: 'json'
},
{
label: 'Raw',
name: 'raw'
},
{
label: 'Form Data',
name: 'formData'
},
{
label: 'x-www-form-urlencoded',
name: 'xWwwFormUrlencoded'
}
],
optional: true
},
{
label: 'Body',
name: 'body',
type: 'string',
acceptVariable: true,
rows: 4,
show: {
bodyType: ['raw', 'json']
},
optional: true
},
{
label: 'Body',
name: 'body',
type: 'array',
acceptVariable: true,
show: {
bodyType: ['xWwwFormUrlencoded', 'formData']
},
array: [
{
label: 'Key',
name: 'key',
type: 'string',
default: ''
},
{
label: 'Value',
name: 'value',
type: 'string',
default: '',
acceptVariable: true
}
],
optional: true
},
{
label: 'Response Type',
name: 'responseType',
type: 'options',
options: [
{
label: 'JSON',
name: 'json'
},
{
label: 'Text',
name: 'text'
},
{
label: 'Array Buffer',
name: 'arraybuffer'
},
{
label: 'Raw (Base64)',
name: 'base64'
}
],
optional: true
}
]
}
async run(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const method = nodeData.inputs?.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
const url = nodeData.inputs?.url as string
const headers = nodeData.inputs?.headers as ICommonObject
const queryParams = nodeData.inputs?.queryParams as ICommonObject
const bodyType = nodeData.inputs?.bodyType as 'json' | 'raw' | 'formData' | 'xWwwFormUrlencoded'
const body = nodeData.inputs?.body as ICommonObject | string | ICommonObject[]
const responseType = nodeData.inputs?.responseType as 'json' | 'text' | 'arraybuffer' | 'base64'
const state = options.agentflowRuntime?.state as ICommonObject
try {
// Prepare headers
const requestHeaders: Record<string, string> = {}
// Add headers from inputs
if (headers && Array.isArray(headers)) {
for (const header of headers) {
if (header.key && header.value) {
requestHeaders[header.key] = header.value
}
}
}
// Add credentials if provided
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
if (credentialData && Object.keys(credentialData).length !== 0) {
const basicAuthUsername = getCredentialParam('basicAuthUsername', credentialData, nodeData)
const basicAuthPassword = getCredentialParam('basicAuthPassword', credentialData, nodeData)
const bearerToken = getCredentialParam('token', credentialData, nodeData)
const apiKeyName = getCredentialParam('key', credentialData, nodeData)
const apiKeyValue = getCredentialParam('value', credentialData, nodeData)
// Determine which type of auth to use based on available credentials
if (basicAuthUsername || basicAuthPassword) {
// Basic Auth
const auth = Buffer.from(`${basicAuthUsername}:${basicAuthPassword}`).toString('base64')
requestHeaders['Authorization'] = `Basic ${auth}`
} else if (bearerToken) {
// Bearer Token
requestHeaders['Authorization'] = `Bearer ${bearerToken}`
} else if (apiKeyName && apiKeyValue) {
// API Key in header
requestHeaders[apiKeyName] = apiKeyValue
}
}
// Prepare query parameters
let queryString = ''
if (queryParams && Array.isArray(queryParams)) {
const params = new URLSearchParams()
for (const param of queryParams) {
if (param.key && param.value) {
params.append(param.key, param.value)
}
}
queryString = params.toString()
}
// Build final URL with query parameters
const finalUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url
// Prepare request config
const requestConfig: AxiosRequestConfig = {
method: method as Method,
url: finalUrl,
headers: requestHeaders,
responseType: (responseType || 'json') as ResponseType
}
// Handle request body based on body type
if (method !== 'GET' && body) {
switch (bodyType) {
case 'json': {
requestConfig.data = typeof body === 'string' ? this.parseJsonBody(body) : body
requestHeaders['Content-Type'] = 'application/json'
break
}
case 'raw':
requestConfig.data = body
break
case 'formData': {
const formData = new FormData()
if (Array.isArray(body) && body.length > 0) {
for (const item of body) {
formData.append(item.key, item.value)
}
}
requestConfig.data = formData
break
}
case 'xWwwFormUrlencoded':
requestConfig.data = querystring.stringify(typeof body === 'string' ? this.parseJsonBody(body) : body)
requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'
break
}
}
// Make the HTTP request
const response = await axios(requestConfig)
// Process response based on response type
let responseData
if (responseType === 'base64' && response.data) {
responseData = Buffer.from(response.data, 'binary').toString('base64')
} else {
responseData = response.data
}
const returnOutput = {
id: nodeData.id,
name: this.name,
input: {
http: {
method,
url,
headers,
queryParams,
bodyType,
body,
responseType
}
},
output: {
http: {
data: responseData,
status: response.status,
statusText: response.statusText,
headers: response.headers
}
},
state
}
return returnOutput
} catch (error) {
console.error('HTTP Request Error:', error)
const errorMessage =
error.response?.data?.message || error.response?.data?.error || error.message || 'An error occurred during the HTTP request'
// Format error response
const errorResponse: any = {
id: nodeData.id,
name: this.name,
input: {
http: {
method,
url,
headers,
queryParams,
bodyType,
body,
responseType
}
},
error: {
name: error.name || 'Error',
message: errorMessage
},
state
}
// Add more error details if available
if (error.response) {
errorResponse.error.status = error.response.status
errorResponse.error.statusText = error.response.statusText
errorResponse.error.data = error.response.data
errorResponse.error.headers = error.response.headers
}
throw new Error(errorMessage)
}
}
}
module.exports = { nodeClass: HTTP_Agentflow }