@output.ai/core 0.1.6 → 0.1.8-dev.pr156.05c9aa2

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.
package/README.md CHANGED
@@ -48,6 +48,28 @@ export default workflow( {
48
48
  })
49
49
  ```
50
50
 
51
+ Workflows can only import the following files
52
+
53
+ #### Components
54
+ - `evaluators.js`
55
+ - `shared_steps.js`
56
+ - `steps.js`
57
+ - `workflow.js`
58
+
59
+ #### Core library
60
+ - `@output.ai.core`
61
+
62
+ #### Whitelisted files
63
+ - `types.js`
64
+ - `consts.js`
65
+ - `constants.js`
66
+ - `vars.js`
67
+ - `variables.js`
68
+ - `utils.js`
69
+ - `tools.js`
70
+ - `functions.js`
71
+ - `shared.js`
72
+
51
73
  ### Step
52
74
 
53
75
  Re-usable units of work that can contain IO, used by the workflow.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@output.ai/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.8-dev.pr156.05c9aa2",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.d.ts CHANGED
@@ -144,7 +144,7 @@ export async function step( options: {
144
144
  */
145
145
 
146
146
  /**
147
- * Creates a workflow orchestrator defined schema for both input and output.
147
+ * Creates a workflow orchestrator with defined schema for both input and output.
148
148
  *
149
149
  * @param {object} options - Workflow options
150
150
  * @param {string} options.name - Unique workflow name
@@ -27,7 +27,17 @@ export const init = () => {
27
27
  cleanupOldTempFiles();
28
28
  };
29
29
 
30
- const getOutputDir = workflowName => join( process.argv[2], 'logs', 'runs', workflowName );
30
+ /**
31
+ * Get the base path for trace file storage
32
+ * @returns {string} The base path where traces should be written
33
+ */
34
+ const getBasePath = () => {
35
+ return process.env.HOST_TRACE_PATH || join( process.cwd(), 'logs' );
36
+ };
37
+
38
+ const getOutputDir = workflowName => {
39
+ return join( getBasePath(), 'runs', workflowName );
40
+ };
31
41
 
32
42
  const buildOutputFileName = ( { startTime, workflowId } ) => {
33
43
  const timestamp = new Date( startTime ).toISOString().replace( /[:T.]/g, '-' );
@@ -56,12 +66,13 @@ export const exec = ( { entry, executionContext } ) => {
56
66
  };
57
67
 
58
68
  /**
59
- * Returns where the trace is saved
69
+ * Returns where the trace is saved as an absolute path
60
70
  * @param {object} args
61
71
  * @param {string} args.startTime - The start time of the workflow
62
72
  * @param {string} args.workflowId - The id of the workflow execution
63
73
  * @param {string} args.workflowName - The name of the workflow
64
- * @returns {string} The path where the trace will be saved
74
+ * @returns {string} The absolute path where the trace will be saved
65
75
  */
66
- export const getDestination = ( { startTime, workflowId, workflowName } ) =>
67
- join( getOutputDir( workflowName ), buildOutputFileName( { workflowId, startTime } ) );
76
+ export const getDestination = ( { startTime, workflowId, workflowName } ) => {
77
+ return join( getOutputDir( workflowName ), buildOutputFileName( { workflowId, startTime } ) );
78
+ };
@@ -60,8 +60,23 @@ describe( 'tracing/processors/local', () => {
60
60
 
61
61
  expect( writeFileSyncMock ).toHaveBeenCalledTimes( 3 );
62
62
  const [ writtenPath, content ] = writeFileSyncMock.mock.calls.at( -1 );
63
- expect( writtenPath ).toMatch( /\/logs\/runs\/WF\// );
63
+ // Changed: Now uses process.cwd() + '/logs' fallback when HOST_TRACE_PATH not set
64
+ expect( writtenPath ).toMatch( /\/runs\/WF\// );
64
65
  expect( JSON.parse( content.trim() ).count ).toBe( 3 );
65
66
  } );
67
+
68
+ it( 'getDestination(): returns absolute path', async () => {
69
+ const { getDestination } = await import( './index.js' );
70
+
71
+ const startTime = Date.parse( '2020-01-02T03:04:05.678Z' );
72
+ const workflowId = 'workflow-id-123';
73
+ const workflowName = 'test-workflow';
74
+
75
+ const destination = getDestination( { startTime, workflowId, workflowName } );
76
+
77
+ // Should return an absolute path
78
+ expect( destination ).toMatch( /^\/|^[A-Z]:\\/i ); // Starting with / or Windows drive letter
79
+ expect( destination ).toContain( '/logs/runs/test-workflow/2020-01-02-03-04-05-678Z_workflow-id-123.json' );
80
+ } );
66
81
  } );
67
82
 
@@ -3,15 +3,24 @@ export const NodeType = {
3
3
  };
4
4
 
5
5
  export const ComponentFile = {
6
+ EVALUATORS: 'evaluators',
6
7
  STEPS: 'steps',
7
8
  SHARED_STEPS: 'shared_steps',
8
- EVALUATORS: 'evaluators',
9
9
  WORKFLOW: 'workflow'
10
10
  };
11
11
 
12
- export const ExtraneousFile = {
13
- TYPES: 'types'
14
- };
12
+ export const EXTRANEOUS_FILE = 'extraneous';
13
+ export const ExtraneousFileList = [
14
+ 'types',
15
+ 'consts',
16
+ 'constants',
17
+ 'vars',
18
+ 'variables',
19
+ 'utils',
20
+ 'tools',
21
+ 'functions',
22
+ 'shared'
23
+ ];
15
24
 
16
25
  export const CoreModule = {
17
26
  LOCAL: 'local_core',
@@ -20,7 +20,7 @@ import {
20
20
  thisExpression,
21
21
  isExportDefaultDeclaration
22
22
  } from '@babel/types';
23
- import { ComponentFile, ExtraneousFile, NodeType } from './consts.js';
23
+ import { ComponentFile, EXTRANEOUS_FILE, ExtraneousFileList, NodeType } from './consts.js';
24
24
 
25
25
  /**
26
26
  * Resolve a relative module specifier against a base directory.
@@ -134,6 +134,8 @@ export const isWorkflowPath = value => /(^|\/)workflow\.js$/.test( value );
134
134
  */
135
135
  export const isTypesPath = value => /(^|\/)types\.js$/.test( value );
136
136
 
137
+ export const isExtraneousFile = value => ExtraneousFileList.map( t => new RegExp( `(^|\/)${t}\\.js$` ) ).find( rx => rx.test( value ) );
138
+
137
139
  /**
138
140
  * Determine file kind based on its path.
139
141
  * @param {string} filename
@@ -152,8 +154,8 @@ export const getFileKind = path => {
152
154
  if ( isWorkflowPath( path ) ) {
153
155
  return ComponentFile.WORKFLOW;
154
156
  }
155
- if ( isTypesPath( path ) ) {
156
- return ExtraneousFile.TYPES;
157
+ if ( isExtraneousFile( path ) ) {
158
+ return EXTRANEOUS_FILE;
157
159
  }
158
160
  return null;
159
161
  };
@@ -20,6 +20,7 @@ import {
20
20
  buildSharedStepsNameMap,
21
21
  buildWorkflowNameMap,
22
22
  buildEvaluatorsNameMap,
23
+ isExtraneousFile,
23
24
  getFileKind
24
25
  } from './tools.js';
25
26
 
@@ -169,6 +170,49 @@ describe( 'workflow_rewriter tools', () => {
169
170
  expect( isEvaluatorsPath( 'steps.js' ) ).toBe( false );
170
171
  } );
171
172
 
173
+ it( 'isExtraneousFile: returns truthy for extraneous files (types/consts/constants/vars/variables)', () => {
174
+ const ok = [
175
+ 'types.js',
176
+ './types.js',
177
+ '/a/b/types.js',
178
+ 'consts.js',
179
+ './consts.js',
180
+ '/a/b/consts.js',
181
+ 'constants.js',
182
+ './constants.js',
183
+ '/a/b/constants.js',
184
+ 'vars.js',
185
+ './vars.js',
186
+ '/a/b/vars.js',
187
+ 'variables.js',
188
+ './variables.js',
189
+ '/a/b/variables.js'
190
+ ];
191
+ for ( const p of ok ) {
192
+ expect( Boolean( isExtraneousFile( p ) ) ).toBe( true );
193
+ }
194
+ } );
195
+
196
+ it( 'isExtraneousFile: returns falsy for non-extraneous or non-.js files', () => {
197
+ const bad = [
198
+ 'types.ts',
199
+ '/a/b/types.ts',
200
+ 'types.mjs',
201
+ 'variables.jsx',
202
+ 'variables.mjs',
203
+ 'myconstants.js',
204
+ 'steps.js',
205
+ 'evaluators.js',
206
+ 'shared_steps.js',
207
+ 'workflow.js',
208
+ 'typess.js',
209
+ '/a/b/c/variables.json'
210
+ ];
211
+ for ( const p of bad ) {
212
+ expect( isExtraneousFile( p ) ).toBeFalsy();
213
+ }
214
+ } );
215
+
172
216
  it( 'createThisMethodCall: builds this.method(\'name\', ...args) call', () => {
173
217
  const call = createThisMethodCall( 'invoke', 'n', [ t.numericLiteral( 1 ), t.identifier( 'x' ) ] );
174
218
  expect( t.isCallExpression( call ) ).toBe( true );
@@ -1,7 +1,7 @@
1
1
  import traverseModule from '@babel/traverse';
2
2
  import { dirname } from 'node:path';
3
3
  import { parse, toAbsolutePath, getFileKind } from '../tools.js';
4
- import { ComponentFile, CoreModule, ExtraneousFile } from '../consts.js';
4
+ import { ComponentFile, CoreModule, EXTRANEOUS_FILE, ExtraneousFileList } from '../consts.js';
5
5
  import {
6
6
  isCallExpression,
7
7
  isFunctionExpression,
@@ -23,10 +23,10 @@ const traverse = traverseModule.default ?? traverseModule;
23
23
  const validateWorkflowImports = ( { specifier, filename } ) => {
24
24
  const isCore = Object.values( CoreModule ).includes( specifier );
25
25
  const isComponent = Object.values( ComponentFile ).includes( getFileKind( specifier ) );
26
- const isAllowedExtraneous = getFileKind( specifier ) === ExtraneousFile.TYPES;
26
+ const isAllowedExtraneous = getFileKind( specifier ) === EXTRANEOUS_FILE;
27
27
  if ( !isCore && !isComponent && !isAllowedExtraneous ) {
28
28
  throw new Error( `Invalid dependency in workflow.js: '${specifier}'. \
29
- Only evaluators, steps, shared_steps, types, workflows or @output.ai/* imports are allowed in ${filename}` );
29
+ Only components (${Object.values( ComponentFile ) } ), @output.ai/core, or whitelisted (${ExtraneousFileList}) imports are allowed in ${filename}` );
30
30
  }
31
31
  };
32
32
 
@@ -38,7 +38,7 @@ describe( 'workflow_validator loader', () => {
38
38
 
39
39
  it( 'workflow.js: rejects external dependencies', async () => {
40
40
  const dir = mkdtempSync( join( tmpdir(), 'wf-reject-' ) );
41
- const src = 'import x from "./utils.js";';
41
+ const src = 'import x from "./foo.js";';
42
42
  await expect( runLoader( join( dir, 'workflow.js' ), src ) ).rejects.toThrow( /Invalid (import|dependency) in workflow\.js/ );
43
43
  rmSync( dir, { recursive: true, force: true } );
44
44
  } );
@@ -218,6 +218,17 @@ describe( 'workflow_validator loader', () => {
218
218
  rmSync( dir, { recursive: true, force: true } );
219
219
  } );
220
220
 
221
+ it( 'workflow.js: allows importing extraneous files (consts/constants/vars/variables)', async () => {
222
+ const dir = mkdtempSync( join( tmpdir(), 'wf-extra-allow-' ) );
223
+ const bases = [ 'consts', 'constants', 'vars', 'variables' ];
224
+ for ( const base of bases ) {
225
+ writeFileSync( join( dir, `${base}.js` ), 'export const X = 1\n' );
226
+ const src = `import x from "./${base}.js";`;
227
+ await expect( runLoader( join( dir, 'workflow.js' ), src ) ).resolves.toBeTruthy();
228
+ }
229
+ rmSync( dir, { recursive: true, force: true } );
230
+ } );
231
+
221
232
  it( 'steps.js: rejects require of steps/shared_steps/evaluators/workflow; allows other require', async () => {
222
233
  const dir = mkdtempSync( join( tmpdir(), 'steps-require-' ) );
223
234
  await expect( runLoader( join( dir, 'steps.js' ), 'const { S } = require("./steps.js");' ) )