@output.ai/core 0.2.2 → 0.3.0-dev.pr263-a59dd0e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import traverseModule from '@babel/traverse';
2
2
  import { dirname } from 'node:path';
3
- import { parse, toAbsolutePath, getFileKind } from '../tools.js';
4
- import { ComponentFile, CoreModule, EXTRANEOUS_FILE, ExtraneousFileList } from '../consts.js';
3
+ import { parse, toAbsolutePath, getFileKind, isAnyStepsPath, isEvaluatorsPath, isWorkflowPath } from '../tools.js';
4
+ import { ComponentFile, CoreModule } from '../consts.js';
5
5
  import {
6
6
  isCallExpression,
7
7
  isFunctionExpression,
@@ -18,36 +18,92 @@ import {
18
18
  const traverse = traverseModule.default ?? traverseModule;
19
19
 
20
20
  /**
21
- * Check if workflow dependencies
21
+ * Determine the file kind label for error messages.
22
+ * Handles both flat files (steps.js) and folder-based files (steps/fetch_data.js).
23
+ * @param {string} filename - The file path
24
+ * @returns {string} Human-readable file kind for error messages
25
+ */
26
+ const getFileKindLabel = filename => {
27
+ if ( isAnyStepsPath( filename ) ) {
28
+ return 'steps.js';
29
+ }
30
+ if ( isEvaluatorsPath( filename ) ) {
31
+ return 'evaluators.js';
32
+ }
33
+ if ( /workflow\.js$/.test( filename ) ) {
34
+ return 'workflow.js';
35
+ }
36
+ return filename;
37
+ };
38
+
39
+ /**
40
+ * Check if workflow dependencies are valid.
41
+ * Workflows can import:
42
+ * - Components (steps, evaluators, workflow)
43
+ * - Core modules (@output.ai/core, local_core)
44
+ * - ANY file that is NOT a component file (flexible utility imports)
22
45
  */
23
46
  const validateWorkflowImports = ( { specifier, filename } ) => {
24
47
  const isCore = Object.values( CoreModule ).includes( specifier );
25
- const isComponent = Object.values( ComponentFile ).includes( getFileKind( specifier ) );
26
- const isAllowedExtraneous = getFileKind( specifier ) === EXTRANEOUS_FILE;
27
- if ( !isCore && !isComponent && !isAllowedExtraneous ) {
48
+ const fileKind = getFileKind( specifier );
49
+ const isComponent = Object.values( ComponentFile ).includes( fileKind );
50
+ const isNonComponentFile = fileKind === null;
51
+
52
+ if ( !isCore && !isComponent && !isNonComponentFile ) {
28
53
  throw new Error( `Invalid dependency in workflow.js: '${specifier}'. \
29
- Only components (${Object.values( ComponentFile ) } ), @output.ai/core, or whitelisted (${ExtraneousFileList}) imports are allowed in ${filename}` );
54
+ Only components (${Object.values( ComponentFile ) } ), @output.ai/core, or non-component files are allowed in ${filename}` );
30
55
  }
31
56
  };
32
57
 
33
58
  /**
34
- * Check if evaluators, steps or shared_steps import invalid dependencies
59
+ * Check if evaluators or steps import invalid dependencies.
60
+ * Steps and evaluators CANNOT import:
61
+ * - Other steps (local or shared) - activity isolation
62
+ * - Other evaluators (local or shared) - activity isolation
63
+ * - Workflows
64
+ *
65
+ * Steps and evaluators CAN import:
66
+ * - ANY file that is NOT a component file (flexible utility imports)
35
67
  */
36
- const validateStepEvaluatorImports = ( { fileKind, specifier, filename } ) => {
37
- if ( Object.values( ComponentFile ).includes( getFileKind( specifier ) ) ) {
38
- throw new Error( `Invalid dependency in ${fileKind}.js: '${specifier}'. \
39
- Steps, shared_steps, evaluators or workflows are not allowed dependencies in ${filename}` );
68
+ const validateStepEvaluatorImports = ( { specifier, filename } ) => {
69
+ const importedFileKind = getFileKind( specifier );
70
+
71
+ // Activity isolation: steps/evaluators cannot import other steps, evaluators, or workflows
72
+ if ( Object.values( ComponentFile ).includes( importedFileKind ) ) {
73
+ const fileLabel = getFileKindLabel( filename );
74
+ throw new Error( `Invalid dependency in ${fileLabel}: '${specifier}'. \
75
+ Steps, evaluators or workflows are not allowed dependencies in ${filename}` );
40
76
  }
41
77
  };
42
78
 
43
79
  /**
44
- * Validate import for evaluators, steps, shared_steps, workflow
80
+ * Validate import for evaluators, steps, workflow
45
81
  */
46
82
  const executeImportValidations = ( { fileKind, specifier, filename } ) => {
47
83
  if ( fileKind === ComponentFile.WORKFLOW ) {
48
- validateWorkflowImports( { fileKind, specifier, filename } );
84
+ validateWorkflowImports( { specifier, filename } );
49
85
  } else if ( Object.values( ComponentFile ).includes( fileKind ) ) {
50
- validateStepEvaluatorImports( { fileKind, specifier, filename } );
86
+ validateStepEvaluatorImports( { specifier, filename } );
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Validate that component instantiation calls occur in the correct file locations.
92
+ * - step() must be called in a file whose path contains 'steps'
93
+ * - evaluator() must be called in a file whose path contains 'evaluators'
94
+ * - workflow() must be called in a file whose path contains 'workflow'
95
+ * @param {string} calleeName - The factory function name (step, evaluator, workflow)
96
+ * @param {string} filename - The file path where the call occurs
97
+ */
98
+ const validateInstantiationLocation = ( calleeName, filename ) => {
99
+ if ( calleeName === 'step' && !isAnyStepsPath( filename ) ) {
100
+ throw new Error( `Invalid instantiation location: step() can only be called in files with 'steps' in the path. Found in: ${filename}` );
101
+ }
102
+ if ( calleeName === 'evaluator' && !isEvaluatorsPath( filename ) ) {
103
+ throw new Error( `Invalid instantiation location: evaluator() can only be called in files with 'evaluators' in the path. Found in: ${filename}` );
104
+ }
105
+ if ( calleeName === 'workflow' && !isWorkflowPath( filename ) ) {
106
+ throw new Error( `Invalid instantiation location: workflow() can only be called in files with 'workflow' in the path. Found in: ${filename}` );
51
107
  }
52
108
  };
53
109
 
@@ -56,13 +112,13 @@ const executeImportValidations = ( { fileKind, specifier, filename } ) => {
56
112
  * Returns the source unchanged unless a validation error is found.
57
113
  *
58
114
  * Rules enforced:
59
- * - evaluators.js `fn`: at each evaluator().fn body: calling any evaluator, step, shared_step or workflow is forbidden
60
- * - evaluators.js: may not import evaluators.js, steps.js/shared_steps.js, workflow.js
61
- * - shared_steps.js `fn`: at each step().fn body: calling any evaluator, step, shared_step or workflow is forbidden
62
- * - shared_steps.js: may not import evaluators.js, steps.js, shared_steps.js, workflow.js
63
- * - steps.js: at each step().fn body: calling any evaluator, step, shared_step or workflow is forbidden
64
- * - steps.js: may not import evaluators.js, steps.js, shared_steps.js, workflow.js
65
- * - workflow.js: may only import components: evaluators.js, steps.js, shared_steps.js, workflow.js; and files: types.js or `@output.ai/core`
115
+ * - Instantiation location: step() must be in steps path, evaluator() in evaluators path, workflow() in workflow path
116
+ * - evaluators.js `fn`: at each evaluator().fn body: calling any evaluator, step, or workflow is forbidden
117
+ * - evaluators.js: may not import evaluators.js, steps.js, workflow.js, or any shared steps/evaluators
118
+ * - steps.js: at each step().fn body: calling any evaluator, step, or workflow is forbidden
119
+ * - steps.js: may not import evaluators.js, steps.js, workflow.js, or any shared steps/evaluators
120
+ * - workflow.js: may import components (evaluators.js, steps.js, workflow.js including shared);
121
+ * and any non-component file, or `@output.ai/core`
66
122
  *
67
123
  * @param {string|Buffer} source
68
124
  * @param {any} inputMap
@@ -82,7 +138,7 @@ export default function workflowValidatorLoader( source, inputMap ) {
82
138
  // Collect local declarations and imported identifiers by type
83
139
  const localStepIds = new Set();
84
140
  const localEvaluatorIds = new Set();
85
- const importedStepIds = new Set(); // includes shared_steps
141
+ const importedStepIds = new Set();
86
142
  const importedEvaluatorIds = new Set();
87
143
  const importedWorkflowIds = new Set();
88
144
 
@@ -94,12 +150,12 @@ export default function workflowValidatorLoader( source, inputMap ) {
94
150
  executeImportValidations( { fileKind, specifier, filename } );
95
151
 
96
152
  // Collect imported identifiers for later call checks
153
+ const importedKind = getFileKind( specifier );
97
154
  const accumulator = ( {
98
155
  [ComponentFile.STEPS]: importedStepIds,
99
- [ComponentFile.SHARED_STEPS]: importedStepIds,
100
156
  [ComponentFile.EVALUATORS]: importedEvaluatorIds,
101
157
  [ComponentFile.WORKFLOW]: importedWorkflowIds
102
- } )[fileKind];
158
+ } )[importedKind];
103
159
  if ( accumulator ) {
104
160
  for ( const s of path.node.specifiers ) {
105
161
  if ( isImportSpecifier( s ) || isImportDefaultSpecifier( s ) ) {
@@ -114,12 +170,21 @@ export default function workflowValidatorLoader( source, inputMap ) {
114
170
  return;
115
171
  }
116
172
 
117
- // Collect local step/evaluator declarations: const X = step({...}) / evaluator({...})
118
- if ( isIdentifier( init.callee, { name: 'step' } ) && isIdentifier( path.node.id ) ) {
119
- localStepIds.add( path.node.id.name );
173
+ // Validate instantiation location for step/evaluator/workflow calls
174
+ if ( isIdentifier( init.callee, { name: 'step' } ) ) {
175
+ validateInstantiationLocation( 'step', filename );
176
+ if ( isIdentifier( path.node.id ) ) {
177
+ localStepIds.add( path.node.id.name );
178
+ }
179
+ }
180
+ if ( isIdentifier( init.callee, { name: 'evaluator' } ) ) {
181
+ validateInstantiationLocation( 'evaluator', filename );
182
+ if ( isIdentifier( path.node.id ) ) {
183
+ localEvaluatorIds.add( path.node.id.name );
184
+ }
120
185
  }
121
- if ( isIdentifier( init.callee, { name: 'evaluator' } ) && isIdentifier( path.node.id ) ) {
122
- localEvaluatorIds.add( path.node.id.name );
186
+ if ( isIdentifier( init.callee, { name: 'workflow' } ) ) {
187
+ validateInstantiationLocation( 'workflow', filename );
123
188
  }
124
189
 
125
190
  // CommonJS requires: validate source and collect identifiers
@@ -134,7 +199,7 @@ export default function workflowValidatorLoader( source, inputMap ) {
134
199
  // Collect imported identifiers from require patterns
135
200
  if ( isStringLiteral( firstArg ) ) {
136
201
  const reqType = getFileKind( toAbsolutePath( fileDir, req ) );
137
- if ( [ ComponentFile.STEPS, ComponentFile.SHARED_STEPS ].includes( reqType ) && isObjectPattern( path.node.id ) ) {
202
+ if ( reqType === ComponentFile.STEPS && isObjectPattern( path.node.id ) ) {
138
203
  for ( const prop of path.node.id.properties ) {
139
204
  if ( isObjectProperty( prop ) && isIdentifier( prop.value ) ) {
140
205
  importedStepIds.add( prop.value.name );
@@ -157,7 +222,7 @@ export default function workflowValidatorLoader( source, inputMap ) {
157
222
  } );
158
223
 
159
224
  // Function-body call validations for steps/evaluators files
160
- if ( [ ComponentFile.STEPS, ComponentFile.SHARED_STEPS, ComponentFile.EVALUATORS ].includes( fileKind ) ) {
225
+ if ( [ ComponentFile.STEPS, ComponentFile.EVALUATORS ].includes( fileKind ) ) {
161
226
  traverse( ast, {
162
227
  ObjectProperty: path => {
163
228
  if ( !isIdentifier( path.node.key, { name: 'fn' } ) ) {
@@ -173,6 +238,7 @@ export default function workflowValidatorLoader( source, inputMap ) {
173
238
  const callee = cPath.node.callee;
174
239
  if ( isIdentifier( callee ) ) {
175
240
  const { name } = callee;
241
+ const fileLabel = getFileKindLabel( filename );
176
242
  const violation = [
177
243
  [ 'step', localStepIds.has( name ) || importedStepIds.has( name ) ],
178
244
  [ 'evaluator', localEvaluatorIds.has( name ) || importedEvaluatorIds.has( name ) ],
@@ -180,7 +246,7 @@ export default function workflowValidatorLoader( source, inputMap ) {
180
246
  ].find( v => v[1] )?.[0];
181
247
 
182
248
  if ( violation ) {
183
- throw new Error( `Invalid call in ${fileKind}.js fn: calling a ${violation} ('${name}') is not allowed in ${filename}` );
249
+ throw new Error( `Invalid call in ${fileLabel} fn: calling a ${violation} ('${name}') is not allowed in ${filename}` );
184
250
  }
185
251
  }
186
252
  }