diff --git a/apps/viewer/src/app/api/integrations/openai/streamer/route.ts b/apps/viewer/src/app/api/integrations/openai/streamer/route.ts index d8571b549..a8bfe137c 100644 --- a/apps/viewer/src/app/api/integrations/openai/streamer/route.ts +++ b/apps/viewer/src/app/api/integrations/openai/streamer/route.ts @@ -17,7 +17,6 @@ import { getChatCompletionStream } from '@typebot.io/bot-engine/blocks/integrati import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/schema' import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers' -export const runtime = 'edge' export const preferredRegion = 'lhr1' export const dynamic = 'force-dynamic' diff --git a/packages/bot-engine/blocks/integrations/webhook/resumeWebhookExecution.ts b/packages/bot-engine/blocks/integrations/webhook/resumeWebhookExecution.ts index b850eba84..ca7b8ec5f 100644 --- a/packages/bot-engine/blocks/integrations/webhook/resumeWebhookExecution.ts +++ b/packages/bot-engine/blocks/integrations/webhook/resumeWebhookExecution.ts @@ -11,6 +11,7 @@ import { SessionState } from '@typebot.io/schemas/features/chat/sessionState' import { ExecuteIntegrationResponse } from '../../../types' import { parseVariables } from '@typebot.io/variables/parseVariables' import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession' +import vm from 'vm' type Props = { state: SessionState @@ -55,12 +56,14 @@ export const resumeWebhookExecution = ({ if (!varMapping?.bodyPath || !varMapping.variableId) return newVariables const existingVariable = typebot.variables.find(byId(varMapping.variableId)) if (!existingVariable) return newVariables - const func = Function( - 'data', - `return data.${parseVariables(typebot.variables)(varMapping?.bodyPath)}` - ) + const sandbox = vm.createContext({ + data: response.data, + }) try { - const value: unknown = func(response) + const value: unknown = vm.runInContext( + parseVariables(typebot.variables)(varMapping?.bodyPath), + sandbox + ) return [...newVariables, { ...existingVariable, value }] } catch (err) { return newVariables diff --git a/packages/bot-engine/blocks/logic/setVariable/executeSetVariable.ts b/packages/bot-engine/blocks/logic/setVariable/executeSetVariable.ts index 183567e0b..ea45b1cf6 100644 --- a/packages/bot-engine/blocks/logic/setVariable/executeSetVariable.ts +++ b/packages/bot-engine/blocks/logic/setVariable/executeSetVariable.ts @@ -7,6 +7,7 @@ import { parseVariables } from '@typebot.io/variables/parseVariables' import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession' import { createId } from '@paralleldrive/cuid2' import { utcToZonedTime, format as tzFormat } from 'date-fns-tz' +import vm from 'vm' export const executeSetVariable = ( state: SessionState, @@ -67,11 +68,16 @@ const evaluateSetVariableExpression = // To avoid octal number evaluation if (!isNaN(str as unknown as number) && /0[^.].+/.test(str)) return str const evaluating = parseVariables(variables, { fieldToParse: 'id' })( - str.includes('return ') ? str : `return ${str}` + `(function() {${str.includes('return ') ? str : 'return ' + str}})()` ) try { - const func = Function(...variables.map((v) => v.id), evaluating) - return func(...variables.map((v) => parseGuessedValueType(v.value))) + const sandbox = vm.createContext({ + ...Object.fromEntries( + variables.map((v) => [v.id, parseGuessedValueType(v.value)]) + ), + fetch, + }) + return vm.runInContext(evaluating, sandbox) } catch (err) { return parseVariables(variables)(str) } diff --git a/packages/forge/blocks/openai/shared/runChatCompletionStream.ts b/packages/forge/blocks/openai/shared/runChatCompletionStream.ts index 4777d82c2..71a74e75b 100644 --- a/packages/forge/blocks/openai/shared/runChatCompletionStream.ts +++ b/packages/forge/blocks/openai/shared/runChatCompletionStream.ts @@ -75,7 +75,7 @@ export const runChatCompletionStream = async ({ continue } - const { output } = await executeFunction({ + const { output, newVariables } = await executeFunction({ variables: variables.list(), args: typeof toolCall.func.arguments === 'string' @@ -84,8 +84,7 @@ export const runChatCompletionStream = async ({ body: toolDefinition.code, }) - // TO-DO: enable once we're out of edge runtime. - // newVariables?.forEach((v) => variables.set(v.id, v.value)) + newVariables?.forEach((v) => variables.set(v.id, v.value)) const newMessages = appendToolCallMessage({ tool_call_id: toolCall.id, diff --git a/packages/variables/executeFunction.ts b/packages/variables/executeFunction.ts index e4f738257..21b513dc7 100644 --- a/packages/variables/executeFunction.ts +++ b/packages/variables/executeFunction.ts @@ -4,6 +4,7 @@ import { parseGuessedValueType } from './parseGuessedValueType' import { isDefined } from '@typebot.io/lib' import { safeStringify } from '@typebot.io/lib/safeStringify' import { Variable } from './types' +import vm from 'vm' const defaultTimeout = 10 @@ -18,7 +19,9 @@ export const executeFunction = async ({ body, args: initialArgs, }: Props) => { - const parsedBody = parseVariables(variables, { fieldToParse: 'id' })(body) + const parsedBody = `(async function() {${parseVariables(variables, { + fieldToParse: 'id', + })(body)}})()` const args = ( extractVariablesFromText(variables)(body).map((variable) => ({ @@ -30,22 +33,25 @@ export const executeFunction = async ({ ? Object.entries(initialArgs).map(([id, value]) => ({ id, value })) : [] ) - const func = AsyncFunction( - ...args.map(({ id }) => id), - 'setVariable', - parsedBody - ) let updatedVariables: Record = {} const setVariable = (key: string, value: any) => { updatedVariables[key] = value } + + const context = vm.createContext({ + ...Object.fromEntries(args.map(({ id, value }) => [id, value])), + setVariable, + fetch, + setTimeout, + }) + const timeout = new Timeout() try { const output: unknown = await timeout.wrap( - func(...args.map(({ value }) => value), setVariable), + await vm.runInContext(parsedBody, context), defaultTimeout * 1000 ) timeout.clear() @@ -81,8 +87,6 @@ export const executeFunction = async ({ } } -const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor - class Timeout { private ids: NodeJS.Timeout[] diff --git a/packages/variables/parseVariables.ts b/packages/variables/parseVariables.ts index 72fbfe42c..2b37023ed 100644 --- a/packages/variables/parseVariables.ts +++ b/packages/variables/parseVariables.ts @@ -2,6 +2,7 @@ import { safeStringify } from '@typebot.io/lib/safeStringify' import { isDefined, isNotDefined } from '@typebot.io/lib/utils' import { parseGuessedValueType } from './parseGuessedValueType' import { Variable, VariableWithValue } from './types' +import vm from 'vm' export type ParseVariablesOptions = { fieldToParse?: 'value' | 'id' @@ -73,11 +74,17 @@ const evaluateInlineCode = ( { variables }: { variables: Variable[] } ) => { const evaluating = parseVariables(variables, { fieldToParse: 'id' })( - code.includes('return ') ? code : `return ${code}` + `(async function() {${ + code.includes('return ') ? code : 'return ' + code + }})()` ) try { - const func = Function(...variables.map((v) => v.id), evaluating) - return func(...variables.map((v) => parseGuessedValueType(v.value))) + const sandbox = vm.createContext({ + ...Object.fromEntries( + variables.map((v) => [v.id, parseGuessedValueType(v.value)]) + ), + }) + return vm.runInContext(evaluating, sandbox) } catch (err) { return parseVariables(variables)(code) }