@@ -8,7 +8,6 @@ import { sha256Hex } from '@sim/security/hash'
88import { getErrorMessage } from '@sim/utils/errors'
99import { generateShortId } from '@sim/utils/id'
1010import { toRecord } from '@sim/utils/object'
11- import { escapeRegExp } from '@sim/utils/string'
1211import { NextResponse } from 'next/server'
1312import type { ParsedFunctionExecuteBody } from '@/lib/api/contracts'
1413import { isMothershipSandboxEnabled , isRemoteSandboxEnabled } from '@/lib/core/config/env-flags'
@@ -87,6 +86,7 @@ import {
8786} from '@/lib/execution/remote-sandbox/sandbox-paths'
8887import type { SandboxCollectedFile , SandboxFile } from '@/lib/execution/remote-sandbox/types'
8988import { isExecutionResourceLimitError } from '@/lib/execution/resource-errors'
89+ import { MAX_FUNCTION_REFERENCES } from '@/lib/function-execution/limits'
9090import type { SandboxExportedFile } from '@/lib/function-execution/output'
9191import { planUserFileMounts , resolveUserFileMounts } from '@/lib/function-execution/sandbox-mounts'
9292import {
@@ -750,38 +750,32 @@ function scrubInternalIdentifiers(message: string, identifiers: readonly string[
750750
751751function resolveWorkflowVariables (
752752 code : string ,
753- workflowVariables : Record < string , any > ,
754- contextVariables : Record < string , any >
753+ workflowVariables : Record < string , unknown > ,
754+ contextVariables : Record < string , unknown >
755755) : string {
756- let resolvedCode = code
757-
758- const regex = createWorkflowVariablePattern ( )
759- let match : RegExpExecArray | null
760- const replacements : Array < {
761- match : string
762- index : number
763- variableName : string
764- variableValue : unknown
765- } > = [ ]
766-
767- while ( ( match = regex . exec ( code ) ) !== null ) {
768- const variableName = match [ 1 ] . trim ( )
769-
770- const foundVariable = Object . entries ( workflowVariables ) . find (
771- ( [ _ , variable ] ) => normalizeName ( variable . name || '' ) === variableName
772- )
773-
774- if ( ! foundVariable ) {
775- const availableVars = Object . values ( workflowVariables )
776- . map ( ( v ) => v . name )
777- . filter ( Boolean )
756+ const variablesByName = new Map < string , Record < string , unknown > > ( )
757+ for ( const value of Object . values ( workflowVariables ) ) {
758+ const variable = toRecord ( value )
759+ if ( typeof variable . name !== 'string' ) continue
760+ const name = normalizeName ( variable . name )
761+ if ( ! variablesByName . has ( name ) ) variablesByName . set ( name , variable )
762+ }
763+ const replacements = new Map < string , string > ( )
764+
765+ return code . replace ( createWorkflowVariablePattern ( ) , ( _match , name : string ) => {
766+ const variableName = name . trim ( )
767+ const cached = replacements . get ( variableName )
768+ if ( cached !== undefined ) return cached
769+
770+ const variable = variablesByName . get ( variableName )
771+ if ( ! variable ) {
772+ const availableVars = [ ...variablesByName . values ( ) ] . map ( ( value ) => value . name ) . filter ( Boolean )
778773 throw new Error (
779774 `Variable "${ variableName } " doesn't exist.` +
780775 ( availableVars . length > 0 ? ` Available: ${ availableVars . join ( ', ' ) } ` : '' )
781776 )
782777 }
783778
784- const variable = foundVariable [ 1 ]
785779 let variableValue : unknown = variable . value
786780
787781 if ( variable . value !== undefined && variable . value !== null ) {
@@ -805,24 +799,11 @@ function resolveWorkflowVariables(
805799 }
806800 }
807801
808- replacements . push ( {
809- match : match [ 0 ] ,
810- index : match . index ,
811- variableName,
812- variableValue,
813- } )
814- }
815-
816- for ( let i = replacements . length - 1 ; i >= 0 ; i -- ) {
817- const { match : matchStr , index, variableName, variableValue } = replacements [ i ]
818-
819802 const safeVarName = `__variable_${ variableName . replace ( / [ ^ a - z A - Z 0 - 9 _ ] / g, '_' ) } `
820803 contextVariables [ safeVarName ] = variableValue
821- resolvedCode =
822- resolvedCode . slice ( 0 , index ) + safeVarName + resolvedCode . slice ( index + matchStr . length )
823- }
824-
825- return resolvedCode
804+ replacements . set ( variableName , safeVarName )
805+ return safeVarName
806+ } )
826807}
827808
828809/**
@@ -869,13 +850,12 @@ function resolveTagVariables(
869850 contextVariables : Record < string , unknown > ,
870851 language = 'javascript'
871852) : string {
872- let resolvedCode = code
873853 const undefinedLiteral = language === 'python' ? 'None' : 'undefined'
854+ const replacements = new Map < string , string | undefined > ( )
874855
875- const tagMatches = resolvedCode . match ( TAG_PATTERN ) || [ ]
876-
877- for ( const match of tagMatches ) {
856+ return code . replace ( TAG_PATTERN , ( match ) => {
878857 const tagName = match . slice ( REFERENCE . START . length , - REFERENCE . END . length ) . trim ( )
858+ if ( replacements . has ( tagName ) ) return replacements . get ( tagName ) ?? match
879859 const pathParts = tagName . split ( REFERENCE . PATH_DELIMITER )
880860 const blockName = pathParts [ 0 ]
881861 const fieldPath = pathParts . slice ( 1 )
@@ -887,14 +867,15 @@ function resolveTagVariables(
887867 } )
888868
889869 if ( ! result ) {
890- continue
870+ replacements . set ( tagName , undefined )
871+ return match
891872 }
892873
893874 let tagValue = result . value
894875
895876 if ( tagValue === undefined ) {
896- resolvedCode = resolvedCode . replace ( new RegExp ( escapeRegExp ( match ) , 'g' ) , undefinedLiteral )
897- continue
877+ replacements . set ( tagName , undefinedLiteral )
878+ return undefinedLiteral
898879 }
899880
900881 if ( typeof tagValue === 'string' ) {
@@ -910,10 +891,9 @@ function resolveTagVariables(
910891
911892 const safeVarName = `__tag_${ tagName . replace ( / _ / g, '_1' ) . replace ( / \. / g, '_0' ) } `
912893 contextVariables [ safeVarName ] = tagValue
913- resolvedCode = resolvedCode . replace ( new RegExp ( escapeRegExp ( match ) , 'g' ) , safeVarName )
914- }
915-
916- return resolvedCode
894+ replacements . set ( tagName , safeVarName )
895+ return safeVarName
896+ } )
917897}
918898
919899/**
@@ -2281,6 +2261,23 @@ export async function executeFunctionRequest(
22812261 )
22822262 includePrivateResolvedSecretNames = privateResolvedSecretNamesMetadataType !== undefined
22832263
2264+ let referenceCount = 0
2265+ for ( const _match of body . code . matchAll ( TAG_PATTERN ) ) {
2266+ if ( ++ referenceCount > MAX_FUNCTION_REFERENCES ) {
2267+ return appendPrivateResolvedSecretNames (
2268+ NextResponse . json (
2269+ {
2270+ success : false ,
2271+ error : `Function code exceeds the maximum of ${ MAX_FUNCTION_REFERENCES } references` ,
2272+ } ,
2273+ { status : 400 }
2274+ ) ,
2275+ includePrivateResolvedSecretNames ? [ ] : null ,
2276+ privateResolvedSecretNamesMetadataType
2277+ )
2278+ }
2279+ }
2280+
22842281 const mountedWorkspaceFileProvenance = inspectMountedWorkspaceFileProvenance ( req . headers , body )
22852282 if ( mountedWorkspaceFileProvenance . status === 'invalid' ) {
22862283 return appendPrivateResolvedSecretNames (
0 commit comments