diff --git a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts index 3dbb7c7d..4ab2a6f2 100644 --- a/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts +++ b/packages/components/nodes/utilities/CustomFunction/CustomFunction.ts @@ -52,11 +52,19 @@ class CustomFunction_Utilities implements INode { label: 'Output', name: 'output', baseClasses: ['string', 'number', 'boolean', 'json', 'array'] + }, + { + label: 'Ending Node', + name: 'EndingNode', + baseClasses: [this.type] } ] } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { + const isEndingNode = nodeData?.outputs?.output === 'EndingNode' + if (isEndingNode && !options.isRun) return // prevent running both init and run twice + const javascriptFunction = nodeData.inputs?.javascriptFunction as string const functionInputVariablesRaw = nodeData.inputs?.functionInputVariables const appDataSource = options.appDataSource as DataSource @@ -123,7 +131,8 @@ class CustomFunction_Utilities implements INode { const vm = new NodeVM(nodeVMOptions) try { const response = await vm.run(`module.exports = async function() {${javascriptFunction}}()`, __dirname) - if (typeof response === 'string') { + + if (typeof response === 'string' && !isEndingNode) { return handleEscapeCharacters(response, false) } return response @@ -131,6 +140,10 @@ class CustomFunction_Utilities implements INode { throw new Error(e) } } + + async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + return await this.init(nodeData, input, { ...options, isRun: true }) + } } module.exports = { nodeClass: CustomFunction_Utilities } diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0adad721..76e48e58 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -465,8 +465,12 @@ export class App { const endingNodeData = endingNode.data if (!endingNodeData) return res.status(500).send(`Ending node ${endingNode.id} data not found`) - if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { - return res.status(500).send(`Ending node must be either a Chain or Agent`) + const isEndingNode = endingNodeData?.outputs?.output === 'EndingNode' + + if (!isEndingNode) { + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } } isStreaming = isFlowValidForStream(nodes, endingNodeData) @@ -1665,20 +1669,20 @@ export class App { const endingNodeData = endingNode.data if (!endingNodeData) return res.status(500).send(`Ending node ${endingNode.id} data not found`) - if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { - return res.status(500).send(`Ending node must be either a Chain or Agent`) - } + const isEndingNode = endingNodeData?.outputs?.output === 'EndingNode' - if ( - endingNodeData.outputs && - Object.keys(endingNodeData.outputs).length && - !Object.values(endingNodeData.outputs).includes(endingNodeData.name) - ) { - return res - .status(500) - .send( - `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` - ) + if (!isEndingNode) { + if (endingNodeData && endingNodeData.category !== 'Chains' && endingNodeData.category !== 'Agents') { + return res.status(500).send(`Ending node must be either a Chain or Agent`) + } + + if (!Object.values(endingNodeData.outputs ?? {}).includes(endingNodeData.name)) { + return res + .status(500) + .send( + `Output of ${endingNodeData.label} (${endingNodeData.id}) must be ${endingNodeData.label}, can't be an Output Prediction` + ) + } } isStreamValid = isFlowValidForStream(nodes, endingNodeData)