@output.ai/core 0.0.16 → 0.1.0

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,102 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
3
- import { tmpdir, EOL } from 'node:os';
4
- import { join } from 'path';
5
- import { THIS_LIB_NAME } from '#consts';
6
-
7
- const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-' ) );
8
-
9
- // Single mocks (configured per-test by mutating the backing objects)
10
- const mockConfig = { tracing: { enabled: false } };
11
- vi.mock( '#configs', () => mockConfig );
12
-
13
- const mockStorageData = {
14
- activityId: 's1',
15
- activityType: 'Step 1',
16
- workflowId: 'wf1',
17
- workflowType: 'prompt',
18
- workflowPath: '/workflows/prompt.js',
19
- parentWorkflowId: undefined,
20
- rootWorkflowId: undefined,
21
- rootWorkflowType: undefined
22
- };
23
- vi.mock( '../async_storage.js', () => ( {
24
- Storage: { load: () => mockStorageData }
25
- } ) );
26
-
27
- vi.mock( './tracer_tree.js', () => ( { buildLogTree: vi.fn() } ) );
28
-
29
- describe( 'tracer/index', () => {
30
- beforeEach( () => {
31
- vi.resetModules();
32
- vi.clearAllMocks();
33
- vi.useFakeTimers();
34
- vi.setSystemTime( new Date( '2020-01-01T00:00:00.000Z' ) );
35
- } );
36
-
37
- afterEach( () => {
38
- vi.useRealTimers();
39
- } );
40
-
41
- it( 'writes a raw log entry and calls buildLogTree (mocked)', async () => {
42
- const originalArgv2 = process.argv[2];
43
- const tmp = createTempDir();
44
- process.argv[2] = tmp;
45
-
46
- mockConfig.tracing.enabled = true;
47
-
48
- const { buildLogTree } = await import( './tracer_tree.js' );
49
- const { trace } = await import( './index.js' );
50
-
51
- const input = { foo: 1 };
52
- trace( { lib: THIS_LIB_NAME, event: 'workflow_start', input, output: null } );
53
-
54
- expect( buildLogTree ).toHaveBeenCalledTimes( 1 );
55
- const logPath = buildLogTree.mock.calls[0][0];
56
-
57
- const raw = readFileSync( logPath, 'utf-8' );
58
- const [ firstLine ] = raw.split( EOL );
59
- const entry = JSON.parse( firstLine );
60
-
61
- expect( entry ).toMatchObject( {
62
- lib: THIS_LIB_NAME,
63
- event: 'workflow_start',
64
- input,
65
- output: null,
66
- stepId: 's1',
67
- stepName: 'Step 1',
68
- workflowId: 'wf1',
69
- workflowType: 'prompt',
70
- workflowPath: '/workflows/prompt.js'
71
- } );
72
- expect( typeof entry.timestamp ).toBe( 'number' );
73
-
74
- rmSync( tmp, { recursive: true, force: true } );
75
- process.argv[2] = originalArgv2;
76
- } );
77
-
78
- it( 'does nothing when tracing is disabled', async () => {
79
- const originalArgv2 = process.argv[2];
80
- const tmp = createTempDir();
81
- process.argv[2] = tmp;
82
-
83
- mockConfig.tracing.enabled = false;
84
- const { buildLogTree } = await import( './tracer_tree.js' );
85
- const { trace } = await import( './index.js' );
86
-
87
- trace( { lib: THIS_LIB_NAME, event: 'workflow_start', input: {}, output: null } );
88
-
89
- expect( buildLogTree ).not.toHaveBeenCalled();
90
-
91
- rmSync( tmp, { recursive: true, force: true } );
92
- process.argv[2] = originalArgv2;
93
- } );
94
-
95
- it( 'setupGlobalTracer installs global symbol', async () => {
96
- mockConfig.tracing.enabled = false;
97
- const { setupGlobalTracer } = await import( './index.js' );
98
- setupGlobalTracer();
99
- const sym = Symbol.for( '__trace' );
100
- expect( typeof globalThis[sym] ).toBe( 'function' );
101
- } );
102
- } );
@@ -1,85 +0,0 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
2
- import { EOL } from 'os';
3
- import { THIS_LIB_NAME, TraceEvent } from '#consts';
4
-
5
- /**
6
- * Sorting function that compares two objects and ASC sort them by either .startedAt or, if not present, .timestamp
7
- *
8
- * @param {object} a
9
- * @param {object} b
10
- * @returns {number} The sorting result [1,-1]
11
- */
12
- const timestampAscSort = ( a, b ) => {
13
- if ( a.startedAt ) {
14
- return a.startedAt > b.startedAt ? 1 : 1;
15
- }
16
- return a.timestamp > b.timestamp ? 1 : -1;
17
- };
18
-
19
- /**
20
- * Add a member to an array an sort it. It is a mutating method.
21
- *
22
- * @param {array} arr - The arr to be changed
23
- * @param {any} entry - The entry to be added
24
- * @param {Function} sorter - The sort function to be used (within .filter)
25
- */
26
- const pushSort = ( arr, entry, sorter ) => {
27
- arr.push( entry );
28
- arr.sort( sorter );
29
- };
30
-
31
- /**
32
- * Transform the trace file into a tree of events, where nested events are represented as children of parent events.
33
- * And the events STEP_START/STEP_END and WORKFLOW_START/WORKFLOW_END are combined into single events with start and end timestamps.
34
- *
35
- * @param {string} src - The trace src filename
36
- */
37
- export const buildLogTree = src => {
38
- const content = readFileSync( src, 'utf-8' );
39
- const entries = content.split( EOL ).slice( 0, -1 ).map( c => JSON.parse( c ) );
40
-
41
- const stepsMap = new Map();
42
- const workflowsMap = new Map();
43
-
44
- // close steps/workflows
45
- for ( const entry of entries.filter( e => e.lib === THIS_LIB_NAME ) ) {
46
- const { event, workflowId, workflowType, workflowPath, parentWorkflowId, stepId, stepName, input, output, timestamp } = entry;
47
-
48
- const baseEntry = { children: [], startedAt: timestamp, workflowId };
49
- if ( [ TraceEvent.EVALUATOR_START, TraceEvent.STEP_START ].includes( event ) ) {
50
- const eventName = event === TraceEvent.EVALUATOR_START ? 'evaluator' : 'step';
51
- stepsMap.set( `${workflowId}:${stepId}`, { event: eventName, input, stepId, stepName, ...baseEntry } );
52
- }
53
-
54
- if ( [ TraceEvent.EVALUATOR_END, TraceEvent.STEP_END ].includes( event ) ) {
55
- Object.assign( stepsMap.get( `${workflowId}:${stepId}` ) ?? {}, { output, endedAt: timestamp } );
56
- }
57
-
58
- if ( event === TraceEvent.WORKFLOW_START ) {
59
- workflowsMap.set( workflowId, { event: 'workflow', input, parentWorkflowId, workflowPath, workflowType, ...baseEntry } );
60
- }
61
-
62
- if ( event === TraceEvent.WORKFLOW_END ) {
63
- Object.assign( workflowsMap.get( workflowId ) ?? {}, { output, endedAt: timestamp } );
64
- }
65
- }
66
-
67
- // insert operations inside steps
68
- for ( const entry of entries.filter( e => e.lib !== THIS_LIB_NAME ) ) {
69
- pushSort( stepsMap.get( `${entry.workflowId}:${entry.stepId}` ).children, entry, timestampAscSort );
70
- }
71
-
72
- // insert steps into workflows
73
- for ( const step of stepsMap.values() ) {
74
- pushSort( workflowsMap.get( step.workflowId ).children, step, timestampAscSort );
75
- }
76
-
77
- // insert children workflows
78
- for ( const workflow of [ ...workflowsMap.values() ].filter( w => w.parentWorkflowId ) ) {
79
- pushSort( workflowsMap.get( workflow.parentWorkflowId ).children, workflow, timestampAscSort );
80
- }
81
-
82
- const rootWorkflow = [ ...workflowsMap.values() ].find( w => !w.parentWorkflowId );
83
-
84
- writeFileSync( src.replace( /\.raw$/, '.json' ), JSON.stringify( rootWorkflow, undefined, 2 ), 'utf-8' );
85
- };
@@ -1,115 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { writeFileSync, readFileSync, rmSync } from 'node:fs';
3
- import { mkdtempSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'path';
6
- import { EOL } from 'os';
7
- import { buildLogTree } from './tracer_tree.js';
8
- import { THIS_LIB_NAME, TraceEvent } from '#consts';
9
-
10
- const createTempDir = () => mkdtempSync( join( tmpdir(), 'output-sdk-trace-tree-' ) );
11
-
12
- describe( 'tracer/tracer_tree', () => {
13
- it( 'builds a tree JSON from a raw log file', () => {
14
- const tmp = createTempDir();
15
- const rawPath = join( tmp, 'run-123.raw' );
16
-
17
- const entries = [
18
- // root workflow start
19
- {
20
- lib: THIS_LIB_NAME,
21
- event: TraceEvent.WORKFLOW_START,
22
- input: { a: 1 },
23
- output: null,
24
- timestamp: 1000,
25
- stepId: undefined,
26
- stepName: undefined,
27
- workflowId: 'wf1',
28
- workflowType: 'prompt',
29
- workflowPath: '/workflows/prompt.js',
30
- parentWorkflowId: undefined
31
- },
32
- // step start
33
- {
34
- lib: THIS_LIB_NAME,
35
- event: TraceEvent.STEP_START,
36
- input: { x: 1 },
37
- output: null,
38
- timestamp: 2000,
39
- stepId: 's1',
40
- stepName: 'Step 1',
41
- workflowId: 'wf1',
42
- workflowType: 'prompt',
43
- workflowPath: '/workflows/prompt.js',
44
- parentWorkflowId: undefined
45
- },
46
- // non-core operation within step
47
- {
48
- lib: 'tool',
49
- event: 'call',
50
- input: { y: 2 },
51
- output: { y: 3 },
52
- timestamp: 3000,
53
- stepId: 's1',
54
- stepName: 'Step 1',
55
- workflowId: 'wf1'
56
- },
57
- // step end
58
- {
59
- lib: THIS_LIB_NAME,
60
- event: TraceEvent.STEP_END,
61
- input: null,
62
- output: { done: true },
63
- timestamp: 4000,
64
- stepId: 's1',
65
- stepName: 'Step 1',
66
- workflowId: 'wf1',
67
- workflowType: 'prompt',
68
- workflowPath: '/workflows/prompt.js',
69
- parentWorkflowId: undefined
70
- },
71
- // workflow end
72
- {
73
- lib: THIS_LIB_NAME,
74
- event: TraceEvent.WORKFLOW_END,
75
- input: null,
76
- output: { ok: true },
77
- timestamp: 5000,
78
- stepId: undefined,
79
- stepName: undefined,
80
- workflowId: 'wf1',
81
- workflowType: 'prompt',
82
- workflowPath: '/workflows/prompt.js',
83
- parentWorkflowId: undefined
84
- }
85
- ];
86
-
87
- writeFileSync( rawPath, entries.map( e => JSON.stringify( e ) ).join( EOL ) + EOL, 'utf-8' );
88
-
89
- buildLogTree( rawPath );
90
-
91
- const tree = JSON.parse( readFileSync( rawPath.replace( /.raw$/, '.json' ), 'utf-8' ) );
92
-
93
- expect( tree.event ).toBe( 'workflow' );
94
- expect( tree.workflowId ).toBe( 'wf1' );
95
- expect( tree.workflowType ).toBe( 'prompt' );
96
- expect( tree.startedAt ).toBe( 1000 );
97
- expect( tree.endedAt ).toBe( 5000 );
98
- expect( tree.output ).toEqual( { ok: true } );
99
- expect( Array.isArray( tree.children ) ).toBe( true );
100
- expect( tree.children.length ).toBe( 1 );
101
-
102
- const step = tree.children[0];
103
- expect( step.event ).toBe( 'step' );
104
- expect( step.stepId ).toBe( 's1' );
105
- expect( step.startedAt ).toBe( 2000 );
106
- expect( step.endedAt ).toBe( 4000 );
107
- expect( step.output ).toEqual( { done: true } );
108
- expect( step.children.length ).toBe( 1 );
109
- expect( step.children[0].lib ).toBe( 'tool' );
110
- expect( step.children[0].timestamp ).toBe( 3000 );
111
-
112
- rmSync( tmp, { recursive: true, force: true } );
113
- } );
114
- } );
115
-