@outputai/core 0.10.1-dev.b7b2fbe.0 → 0.10.1-next.1070949.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.
- package/package.json +8 -7
- package/src/bus.js +58 -20
- package/src/bus.spec.js +135 -77
- package/src/consts.js +5 -2
- package/src/errors.d.ts +12 -2
- package/src/errors.js +17 -3
- package/src/errors.spec.js +19 -0
- package/src/helpers/activity_options.js +21 -0
- package/src/helpers/activity_options.spec.js +55 -0
- package/src/helpers/error_serializer.js +236 -0
- package/src/helpers/error_serializer.spec.js +413 -0
- package/src/helpers/errors.js +23 -14
- package/src/helpers/errors.spec.js +31 -75
- package/src/helpers/fetch.js +1 -38
- package/src/helpers/fetch.spec.js +1 -39
- package/src/helpers/function.js +13 -0
- package/src/helpers/function.spec.js +23 -1
- package/src/helpers/object.js +5 -1
- package/src/helpers/object.spec.js +5 -0
- package/src/helpers/redact.js +51 -0
- package/src/helpers/redact.spec.js +66 -0
- package/src/helpers/string.js +10 -0
- package/src/helpers/string.spec.js +36 -1
- package/src/hooks/index.d.ts +76 -78
- package/src/hooks/index.js +57 -49
- package/src/hooks/index.spec.js +104 -105
- package/src/hooks/pending_hooks.js +24 -0
- package/src/hooks/pending_hooks.spec.js +62 -0
- package/src/interface/evaluator.d.ts +10 -9
- package/src/interface/evaluator.js +1 -6
- package/src/interface/evaluator.spec.js +10 -10
- package/src/interface/index.js +2 -1
- package/src/interface/step.d.ts +16 -15
- package/src/interface/step.js +1 -6
- package/src/interface/step.spec.js +10 -10
- package/src/interface/validations/index.js +33 -53
- package/src/interface/validations/index.spec.js +25 -17
- package/src/interface/webhook.js +1 -2
- package/src/interface/webhook.spec.js +25 -19
- package/src/interface/workflow.d.ts +25 -18
- package/src/interface/workflow.js +24 -52
- package/src/interface/workflow.spec.js +109 -108
- package/src/interface/workflow_activity_options.js +2 -5
- package/src/interface/workflow_utils.d.ts +15 -0
- package/src/interface/workflow_utils.js +19 -0
- package/src/interface/workflow_utils.spec.js +82 -1
- package/src/sdk/runtime/events.d.ts +4 -6
- package/src/sdk/runtime/events.js +2 -15
- package/src/sdk/runtime/events.spec.js +14 -92
- package/src/tracing/trace_engine.js +9 -11
- package/src/tracing/trace_engine.spec.js +67 -28
- package/src/worker/catalog_workflow/catalog_job.js +3 -3
- package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
- package/src/worker/configs.js +0 -3
- package/src/worker/global_functions.js +2 -2
- package/src/worker/global_functions.spec.js +3 -3
- package/src/worker/index.js +20 -11
- package/src/worker/index.spec.js +29 -16
- package/src/worker/interceptors/activity.js +34 -21
- package/src/worker/interceptors/activity.spec.js +77 -172
- package/src/worker/interceptors/workflow.js +53 -11
- package/src/worker/interceptors/workflow.spec.js +176 -49
- package/src/worker/loader/workflows.js +16 -3
- package/src/worker/loader/workflows.spec.js +37 -5
- package/src/worker/log_hooks.js +22 -19
- package/src/worker/log_hooks.spec.js +14 -6
- package/src/worker/sinks.js +8 -8
- package/src/worker/sinks.spec.js +9 -9
- package/src/worker/telemetry.js +2 -1
- package/src/worker/temporal_logger.js +33 -0
- package/src/worker/temporal_logger.spec.js +90 -0
- package/src/helpers/aggregations.js +0 -44
- package/src/helpers/aggregations.spec.js +0 -92
- package/src/tracing/tools/utils.js +0 -42
- package/src/tracing/tools/utils.spec.js +0 -83
- package/src/worker/proxy.js +0 -20
- package/src/worker/proxy.spec.js +0 -51
package/src/hooks/index.spec.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { BusEventType
|
|
2
|
+
import { BusEventType } from '#consts';
|
|
3
3
|
|
|
4
4
|
const logErrorMock = vi.hoisted( () => vi.fn() );
|
|
5
5
|
const createChildLoggerMock = vi.hoisted( () =>
|
|
6
6
|
vi.fn( () => ( { error: logErrorMock } ) )
|
|
7
7
|
);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const serializeErrorMock = vi.hoisted( () => vi.fn( error => ( {
|
|
9
|
+
name: error.name,
|
|
10
|
+
message: error.message,
|
|
11
|
+
stack: error.stack
|
|
12
|
+
} ) ) );
|
|
13
|
+
|
|
14
|
+
const mainOnHandlers = vi.hoisted( () => ( {} ) );
|
|
15
|
+
const stepOnHandlers = vi.hoisted( () => ( {} ) );
|
|
16
|
+
const mainEventBusMock = vi.hoisted( () => ( {
|
|
17
|
+
on: vi.fn( ( eventType, handler ) => {
|
|
18
|
+
mainOnHandlers[eventType] = handler;
|
|
19
|
+
} )
|
|
20
|
+
} ) );
|
|
21
|
+
const stepEventBusMock = vi.hoisted( () => ( {
|
|
22
|
+
emit: vi.fn( () => true ),
|
|
11
23
|
on: vi.fn( ( eventType, handler ) => {
|
|
12
|
-
|
|
24
|
+
stepOnHandlers[eventType] = handler;
|
|
13
25
|
} )
|
|
14
26
|
} ) );
|
|
15
27
|
|
|
16
28
|
vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
|
|
17
|
-
vi.mock( '#
|
|
29
|
+
vi.mock( '#helpers/error_serializer', () => ( { serializeError: serializeErrorMock } ) );
|
|
30
|
+
vi.mock( '#bus', () => ( {
|
|
31
|
+
mainEventBus: mainEventBusMock,
|
|
32
|
+
stepEventBus: stepEventBusMock
|
|
33
|
+
} ) );
|
|
18
34
|
|
|
19
35
|
import {
|
|
36
|
+
emit,
|
|
20
37
|
on,
|
|
21
38
|
onActivityEnd,
|
|
22
39
|
onActivityError,
|
|
@@ -27,6 +44,7 @@ import {
|
|
|
27
44
|
onWorkflowError,
|
|
28
45
|
onWorkflowStart
|
|
29
46
|
} from './index.js';
|
|
47
|
+
import { pendingHooks } from './pending_hooks.js';
|
|
30
48
|
|
|
31
49
|
const workflowDetails = {
|
|
32
50
|
workflowId: 'wf-1',
|
|
@@ -38,11 +56,6 @@ const workflowDetails = {
|
|
|
38
56
|
attempt: 1
|
|
39
57
|
};
|
|
40
58
|
|
|
41
|
-
const catalogWorkflowDetails = {
|
|
42
|
-
...workflowDetails,
|
|
43
|
-
workflowType: WORKFLOW_CATALOG
|
|
44
|
-
};
|
|
45
|
-
|
|
46
59
|
const activityInfo = {
|
|
47
60
|
activityId: 'act-1',
|
|
48
61
|
activityType: 'wf#step',
|
|
@@ -52,17 +65,15 @@ const activityInfo = {
|
|
|
52
65
|
|
|
53
66
|
const eventDate = 1710000001234;
|
|
54
67
|
|
|
55
|
-
const aggregations = {
|
|
56
|
-
cost: { total: 0 },
|
|
57
|
-
tokens: { total: 0 },
|
|
58
|
-
httpRequests: { total: 1 }
|
|
59
|
-
};
|
|
60
|
-
|
|
61
68
|
describe( 'hooks/index', () => {
|
|
62
69
|
beforeEach( () => {
|
|
63
70
|
vi.clearAllMocks();
|
|
64
|
-
|
|
65
|
-
|
|
71
|
+
pendingHooks.clear();
|
|
72
|
+
Object.keys( mainOnHandlers ).forEach( k => {
|
|
73
|
+
delete mainOnHandlers[k];
|
|
74
|
+
} );
|
|
75
|
+
Object.keys( stepOnHandlers ).forEach( k => {
|
|
76
|
+
delete stepOnHandlers[k];
|
|
66
77
|
} );
|
|
67
78
|
} );
|
|
68
79
|
|
|
@@ -71,9 +82,9 @@ describe( 'hooks/index', () => {
|
|
|
71
82
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
72
83
|
onError( handler );
|
|
73
84
|
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
76
|
-
expect(
|
|
85
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, expect.any( Function ) );
|
|
86
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, expect.any( Function ) );
|
|
87
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, expect.any( Function ) );
|
|
77
88
|
} );
|
|
78
89
|
|
|
79
90
|
it( 'invokes handler with activity-shaped payload, forwarding bus fields', async () => {
|
|
@@ -81,7 +92,7 @@ describe( 'hooks/index', () => {
|
|
|
81
92
|
onError( handler );
|
|
82
93
|
|
|
83
94
|
const err = new Error( 'act-fail' );
|
|
84
|
-
await
|
|
95
|
+
await mainOnHandlers[BusEventType.ACTIVITY_ERROR]( {
|
|
85
96
|
eventId: 'evt-act-1',
|
|
86
97
|
eventDate,
|
|
87
98
|
activityInfo,
|
|
@@ -108,7 +119,7 @@ describe( 'hooks/index', () => {
|
|
|
108
119
|
onError( handler );
|
|
109
120
|
|
|
110
121
|
const err = new Error( 'wf-fail' );
|
|
111
|
-
await
|
|
122
|
+
await mainOnHandlers[BusEventType.WORKFLOW_ERROR]( {
|
|
112
123
|
eventId: 'evt-wf-1',
|
|
113
124
|
eventDate,
|
|
114
125
|
workflowDetails,
|
|
@@ -131,9 +142,17 @@ describe( 'hooks/index', () => {
|
|
|
131
142
|
onError( handler );
|
|
132
143
|
|
|
133
144
|
const error = new Error( 'rt' );
|
|
134
|
-
await
|
|
145
|
+
await mainOnHandlers[BusEventType.RUNTIME_ERROR]( { eventId: 'evt-rt-1', eventDate, error } );
|
|
135
146
|
|
|
136
147
|
expect( handler ).toHaveBeenCalledWith( { eventId: 'evt-rt-1', eventDate, source: 'runtime', error } );
|
|
148
|
+
expect( logErrorMock ).toHaveBeenCalledWith( 'onError hook error', {
|
|
149
|
+
error: {
|
|
150
|
+
name: 'Error',
|
|
151
|
+
message: 'boom',
|
|
152
|
+
stack: expect.any( String )
|
|
153
|
+
}
|
|
154
|
+
} );
|
|
155
|
+
expect( serializeErrorMock ).toHaveBeenCalledWith( expect.objectContaining( { message: 'boom' } ) );
|
|
137
156
|
} );
|
|
138
157
|
} );
|
|
139
158
|
|
|
@@ -142,125 +161,105 @@ describe( 'hooks/index', () => {
|
|
|
142
161
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
143
162
|
onBeforeWorkerStart( handler );
|
|
144
163
|
|
|
145
|
-
expect(
|
|
146
|
-
await
|
|
164
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKER_BEFORE_START, expect.any( Function ) );
|
|
165
|
+
await mainOnHandlers[BusEventType.WORKER_BEFORE_START]();
|
|
147
166
|
|
|
148
167
|
expect( handler ).toHaveBeenCalledWith( undefined );
|
|
149
168
|
} );
|
|
150
169
|
} );
|
|
151
170
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await Promise.resolve( onHandlers[BusEventType.WORKFLOW_START]( {
|
|
158
|
-
eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails
|
|
159
|
-
} ) );
|
|
160
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
161
|
-
|
|
162
|
-
await Promise.resolve( onHandlers[BusEventType.WORKFLOW_START]( {
|
|
163
|
-
eventId: 'evt-start-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
164
|
-
} ) );
|
|
165
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
166
|
-
eventId: 'evt-start-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
167
|
-
} );
|
|
171
|
+
it( 'tracks a hook callback until it settles', async () => {
|
|
172
|
+
const deferred = { resolve: null };
|
|
173
|
+
const handlerPromise = new Promise( resolve => {
|
|
174
|
+
deferred.resolve = resolve;
|
|
168
175
|
} );
|
|
169
|
-
|
|
176
|
+
const handler = vi.fn( () => handlerPromise );
|
|
177
|
+
onActivityEnd( handler );
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
it( 'skips catalog workflow and forwards bus fields for real workflows', async () => {
|
|
173
|
-
const handler = vi.fn().mockResolvedValue( undefined );
|
|
174
|
-
onWorkflowEnd( handler );
|
|
179
|
+
const callbackPromise = mainOnHandlers[BusEventType.ACTIVITY_END]( { activityInfo } );
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails
|
|
178
|
-
} ) );
|
|
179
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
181
|
+
expect( pendingHooks.size ).toBe( 1 );
|
|
180
182
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
185
|
-
eventId: 'evt-end-1', eventDate, workflowDetails, extra: 'passthrough'
|
|
186
|
-
} );
|
|
187
|
-
} );
|
|
183
|
+
deferred.resolve();
|
|
184
|
+
await callbackPromise;
|
|
185
|
+
expect( pendingHooks.size ).toBe( 0 );
|
|
188
186
|
} );
|
|
189
187
|
|
|
190
|
-
describe( '
|
|
191
|
-
|
|
188
|
+
describe( 'workflow lifecycle hooks', () => {
|
|
189
|
+
const cases = [
|
|
190
|
+
[ 'onWorkflowStart', onWorkflowStart, BusEventType.WORKFLOW_START, {} ],
|
|
191
|
+
[ 'onWorkflowEnd', onWorkflowEnd, BusEventType.WORKFLOW_END, {} ],
|
|
192
|
+
[ 'onWorkflowError', onWorkflowError, BusEventType.WORKFLOW_ERROR, { error: new Error( 'workflow failed' ) } ]
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
it.each( cases )( '%s forwards bus fields', async ( _name, registerHook, eventType, extraFields ) => {
|
|
192
196
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
193
|
-
const
|
|
194
|
-
|
|
197
|
+
const payload = {
|
|
198
|
+
eventId: 'evt-workflow-1',
|
|
199
|
+
eventDate,
|
|
200
|
+
workflowDetails,
|
|
201
|
+
extra: 'passthrough',
|
|
202
|
+
...extraFields
|
|
203
|
+
};
|
|
204
|
+
registerHook( handler );
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
} ) );
|
|
199
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
206
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
207
|
+
await mainOnHandlers[eventType]( payload );
|
|
200
208
|
|
|
201
|
-
|
|
202
|
-
eventId: 'evt-err-1', eventDate, workflowDetails, error: err, extra: 'passthrough'
|
|
203
|
-
} ) );
|
|
204
|
-
expect( handler ).toHaveBeenCalledWith( {
|
|
205
|
-
eventId: 'evt-err-1', eventDate, workflowDetails, error: err, extra: 'passthrough'
|
|
206
|
-
} );
|
|
209
|
+
expect( handler ).toHaveBeenCalledWith( payload );
|
|
207
210
|
} );
|
|
208
211
|
} );
|
|
209
212
|
|
|
210
213
|
describe( 'activity lifecycle hooks', () => {
|
|
211
214
|
const cases = [
|
|
212
215
|
[ 'onActivityStart', onActivityStart, BusEventType.ACTIVITY_START, undefined ],
|
|
213
|
-
[ 'onActivityEnd', onActivityEnd, BusEventType.ACTIVITY_END
|
|
216
|
+
[ 'onActivityEnd', onActivityEnd, BusEventType.ACTIVITY_END ],
|
|
214
217
|
[ 'onActivityError', onActivityError, BusEventType.ACTIVITY_ERROR, { error: new Error( 'activity failed' ) } ]
|
|
215
218
|
];
|
|
216
219
|
|
|
217
|
-
it.each( cases )( '%s
|
|
220
|
+
it.each( cases )( '%s forwards internal activity bus fields', async ( _name, registerHook, eventType, extraFields = {} ) => {
|
|
218
221
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
expect( messageBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
222
|
-
|
|
223
|
-
await Promise.resolve( onHandlers[eventType]( {
|
|
224
|
-
eventId: 'evt-ignored',
|
|
225
|
-
eventDate,
|
|
226
|
-
activityInfo,
|
|
227
|
-
workflowDetails,
|
|
228
|
-
outputActivityKind: ComponentType.INTERNAL_STEP,
|
|
229
|
-
...extraFields
|
|
230
|
-
} ) );
|
|
231
|
-
expect( handler ).not.toHaveBeenCalled();
|
|
232
|
-
|
|
233
|
-
await Promise.resolve( onHandlers[eventType]( {
|
|
222
|
+
const payload = {
|
|
234
223
|
eventId: 'evt-activity-1',
|
|
235
224
|
eventDate,
|
|
236
225
|
activityInfo,
|
|
237
226
|
workflowDetails,
|
|
238
|
-
outputActivityKind:
|
|
227
|
+
outputActivityKind: 'internal_step',
|
|
239
228
|
extra: 'passthrough',
|
|
240
229
|
...extraFields
|
|
241
|
-
}
|
|
230
|
+
};
|
|
231
|
+
registerHook( handler );
|
|
242
232
|
|
|
243
|
-
expect(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
workflowDetails,
|
|
248
|
-
outputActivityKind: ComponentType.STEP,
|
|
249
|
-
extra: 'passthrough',
|
|
250
|
-
...extraFields
|
|
251
|
-
} );
|
|
233
|
+
expect( mainEventBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
|
|
234
|
+
await mainOnHandlers[eventType]( payload );
|
|
235
|
+
|
|
236
|
+
expect( handler ).toHaveBeenCalledWith( payload );
|
|
252
237
|
} );
|
|
253
238
|
} );
|
|
254
239
|
|
|
255
240
|
describe( 'on', () => {
|
|
256
|
-
it( 'subscribes to
|
|
241
|
+
it( 'subscribes to SDK and user event channels and forwards payloads', async () => {
|
|
257
242
|
const handler = vi.fn().mockResolvedValue( undefined );
|
|
258
243
|
on( 'myEvent', handler );
|
|
259
244
|
|
|
260
|
-
expect(
|
|
261
|
-
|
|
245
|
+
expect( stepEventBusMock.on ).toHaveBeenCalledWith( 'sdk:myEvent', expect.any( Function ) );
|
|
246
|
+
expect( stepEventBusMock.on ).toHaveBeenCalledWith( 'usr:myEvent', expect.any( Function ) );
|
|
247
|
+
await stepOnHandlers['sdk:myEvent']( { payload: { source: 'sdk' } } );
|
|
248
|
+
await stepOnHandlers['usr:myEvent']( { payload: { source: 'user' } } );
|
|
249
|
+
|
|
250
|
+
expect( handler ).toHaveBeenNthCalledWith( 1, { payload: { source: 'sdk' } } );
|
|
251
|
+
expect( handler ).toHaveBeenNthCalledWith( 2, { payload: { source: 'user' } } );
|
|
252
|
+
} );
|
|
253
|
+
} );
|
|
254
|
+
|
|
255
|
+
describe( 'emit', () => {
|
|
256
|
+
it( 'emits payloads on the user event channel', () => {
|
|
257
|
+
const payload = { foo: 1 };
|
|
258
|
+
|
|
259
|
+
const emitted = emit( 'myEvent', payload );
|
|
262
260
|
|
|
263
|
-
expect(
|
|
261
|
+
expect( stepEventBusMock.emit ).toHaveBeenCalledWith( 'usr:myEvent', payload );
|
|
262
|
+
expect( emitted ).toBe( true );
|
|
264
263
|
} );
|
|
265
264
|
} );
|
|
266
265
|
} );
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createChildLogger } from '#logger';
|
|
2
|
+
|
|
3
|
+
export const pendingHooks = new Set();
|
|
4
|
+
|
|
5
|
+
const log = createChildLogger( 'Hooks' );
|
|
6
|
+
const flushTimeoutMs = 30_000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Await all pending hooks to flush for a certain time
|
|
10
|
+
*/
|
|
11
|
+
export const flushPendingHooks = async () => {
|
|
12
|
+
const state = { timeout: null };
|
|
13
|
+
try {
|
|
14
|
+
const flushed = await Promise.race( [
|
|
15
|
+
Promise.allSettled( [ ...pendingHooks ] ).then( _ => true ),
|
|
16
|
+
new Promise( r => state.timeout = setTimeout( () => r( false ), flushTimeoutMs ) )
|
|
17
|
+
] );
|
|
18
|
+
if ( !flushed ) {
|
|
19
|
+
log.warn( 'Some hook callbacks exceeded the timeout and will not be awaited', { count: pendingHooks.size } );
|
|
20
|
+
}
|
|
21
|
+
} finally {
|
|
22
|
+
clearTimeout( state.timeout );
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const logMock = vi.hoisted( () => ( { warn: vi.fn() } ) );
|
|
4
|
+
|
|
5
|
+
vi.mock( '#logger', () => ( { createChildLogger: () => logMock } ) );
|
|
6
|
+
|
|
7
|
+
import { flushPendingHooks, pendingHooks } from './pending_hooks.js';
|
|
8
|
+
|
|
9
|
+
describe( 'pending hooks', () => {
|
|
10
|
+
beforeEach( () => {
|
|
11
|
+
pendingHooks.clear();
|
|
12
|
+
vi.clearAllMocks();
|
|
13
|
+
} );
|
|
14
|
+
|
|
15
|
+
afterEach( () => {
|
|
16
|
+
pendingHooks.clear();
|
|
17
|
+
vi.useRealTimers();
|
|
18
|
+
} );
|
|
19
|
+
|
|
20
|
+
it( 'waits for pending hooks to settle', async () => {
|
|
21
|
+
const deferred = { resolve: null };
|
|
22
|
+
const hookPromise = new Promise( resolve => {
|
|
23
|
+
deferred.resolve = resolve;
|
|
24
|
+
} );
|
|
25
|
+
pendingHooks.add( hookPromise );
|
|
26
|
+
|
|
27
|
+
const flushPromise = flushPendingHooks();
|
|
28
|
+
const state = { flushed: false };
|
|
29
|
+
flushPromise.then( () => {
|
|
30
|
+
state.flushed = true;
|
|
31
|
+
} );
|
|
32
|
+
|
|
33
|
+
await Promise.resolve();
|
|
34
|
+
expect( state.flushed ).toBe( false );
|
|
35
|
+
|
|
36
|
+
deferred.resolve();
|
|
37
|
+
await flushPromise;
|
|
38
|
+
expect( state.flushed ).toBe( true );
|
|
39
|
+
} );
|
|
40
|
+
|
|
41
|
+
it( 'stops waiting after the timeout', async () => {
|
|
42
|
+
vi.useFakeTimers();
|
|
43
|
+
pendingHooks.add( new Promise( () => {} ) );
|
|
44
|
+
|
|
45
|
+
const flushPromise = flushPendingHooks();
|
|
46
|
+
await vi.advanceTimersByTimeAsync( 30_000 );
|
|
47
|
+
|
|
48
|
+
await expect( flushPromise ).resolves.toBeUndefined();
|
|
49
|
+
} );
|
|
50
|
+
|
|
51
|
+
it( 'logs the number of hooks that exceeded the timeout', async () => {
|
|
52
|
+
vi.useFakeTimers();
|
|
53
|
+
pendingHooks.add( new Promise( () => {} ) );
|
|
54
|
+
pendingHooks.add( new Promise( () => {} ) );
|
|
55
|
+
|
|
56
|
+
const flushPromise = flushPendingHooks();
|
|
57
|
+
await vi.advanceTimersByTimeAsync( 30_000 );
|
|
58
|
+
await flushPromise;
|
|
59
|
+
|
|
60
|
+
expect( logMock.warn ).toHaveBeenCalledWith( expect.any( String ), { count: 2 } );
|
|
61
|
+
} );
|
|
62
|
+
} );
|
|
@@ -16,7 +16,7 @@ export type EvaluatorOptions = {
|
|
|
16
16
|
/**
|
|
17
17
|
* The handler function of an evaluator.
|
|
18
18
|
*
|
|
19
|
-
* @param input -
|
|
19
|
+
* @param input - Parsed evaluator input (`z.infer<inputSchema>`).
|
|
20
20
|
*
|
|
21
21
|
* @returns The result of the evaluation.
|
|
22
22
|
*/
|
|
@@ -30,14 +30,15 @@ export type EvaluatorFunction<
|
|
|
30
30
|
/**
|
|
31
31
|
* A wrapper around the user defined `fn` handler function.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* It adds input validation based on the `inputSchema`.
|
|
33
|
+
* Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input
|
|
34
|
+
* and invokes `fn`.
|
|
36
35
|
*/
|
|
37
|
-
export type EvaluatorFunctionWrapper<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
export type EvaluatorFunctionWrapper<
|
|
37
|
+
InputSchema extends AnyZodSchema | undefined = undefined,
|
|
38
|
+
Result extends EvaluationResult = EvaluationResult
|
|
39
|
+
> = InputSchema extends AnyZodSchema ?
|
|
40
|
+
( input: z.input<InputSchema> ) => Promise<Result> :
|
|
41
|
+
() => Promise<Result>;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Creates an evaluation function. It is similar to a step, but must return an EvaluationResult.
|
|
@@ -67,4 +68,4 @@ export declare function evaluator<
|
|
|
67
68
|
inputSchema: InputSchema;
|
|
68
69
|
fn: EvaluatorFunction<InputSchema, Result>;
|
|
69
70
|
options?: EvaluatorOptions;
|
|
70
|
-
} ): EvaluatorFunctionWrapper<
|
|
71
|
+
} ): EvaluatorFunctionWrapper<InputSchema, Result>;
|
|
@@ -13,11 +13,6 @@ export function evaluator( { name, description, inputSchema, fn, options } ) {
|
|
|
13
13
|
description,
|
|
14
14
|
inputSchema,
|
|
15
15
|
options,
|
|
16
|
-
handler: async input =>
|
|
17
|
-
validator.validateInput( input );
|
|
18
|
-
const output = await fn( input );
|
|
19
|
-
validator.validateOutput( output );
|
|
20
|
-
return output;
|
|
21
|
-
}
|
|
16
|
+
handler: async input => validator.parseOutput( await fn( validator.parseInput( input ) ) )
|
|
22
17
|
} );
|
|
23
18
|
}
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
import { ValidationError } from '#errors';
|
|
10
10
|
|
|
11
11
|
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
|
|
13
|
+
const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
|
|
14
14
|
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
15
15
|
const createEvaluatorMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
16
16
|
|
|
@@ -22,8 +22,8 @@ vi.mock( './validations/index.js', () => {
|
|
|
22
22
|
|
|
23
23
|
constructor( ...args ) {
|
|
24
24
|
validatorConstructorMock( ...args );
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
25
|
+
this.parseInput = parseInputMock;
|
|
26
|
+
this.parseOutput = parseOutputMock;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -87,15 +87,15 @@ describe( 'evaluator()', () => {
|
|
|
87
87
|
|
|
88
88
|
await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
|
|
89
89
|
|
|
90
|
-
expect(
|
|
90
|
+
expect( parseInputMock ).toHaveBeenCalledWith( { value: 'input' } );
|
|
91
91
|
expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
|
|
92
|
-
expect(
|
|
92
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
93
93
|
} );
|
|
94
94
|
|
|
95
95
|
it( 'does not call the evaluator function when input validation throws', async () => {
|
|
96
96
|
const { evaluator } = await import( './evaluator.js' );
|
|
97
97
|
const error = new ValidationError( 'invalid input' );
|
|
98
|
-
|
|
98
|
+
parseInputMock.mockImplementationOnce( () => {
|
|
99
99
|
throw error;
|
|
100
100
|
} );
|
|
101
101
|
const fn = vi.fn();
|
|
@@ -106,13 +106,13 @@ describe( 'evaluator()', () => {
|
|
|
106
106
|
|
|
107
107
|
await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
|
|
108
108
|
expect( fn ).not.toHaveBeenCalled();
|
|
109
|
-
expect(
|
|
109
|
+
expect( parseOutputMock ).not.toHaveBeenCalled();
|
|
110
110
|
} );
|
|
111
111
|
|
|
112
112
|
it( 'propagates output validation errors after the evaluator function runs', async () => {
|
|
113
113
|
const { evaluator } = await import( './evaluator.js' );
|
|
114
114
|
const error = new ValidationError( 'invalid output' );
|
|
115
|
-
|
|
115
|
+
parseOutputMock.mockImplementationOnce( () => {
|
|
116
116
|
throw error;
|
|
117
117
|
} );
|
|
118
118
|
const output = { value: 'not-an-evaluation-result' };
|
|
@@ -124,7 +124,7 @@ describe( 'evaluator()', () => {
|
|
|
124
124
|
|
|
125
125
|
await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
|
|
126
126
|
expect( fn ).toHaveBeenCalledOnce();
|
|
127
|
-
expect(
|
|
127
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
128
128
|
} );
|
|
129
129
|
} );
|
|
130
130
|
|
package/src/interface/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { evaluator } from './evaluator.js';
|
|
9
9
|
import { step } from './step.js';
|
|
10
10
|
import { workflow } from './workflow.js';
|
|
11
|
-
import { executeInParallel } from './workflow_utils.js';
|
|
11
|
+
import { executeInParallel, hasErrorType } from './workflow_utils.js';
|
|
12
12
|
import { sendHttpRequest, sendPostRequestAndAwaitWebhook } from './webhook.js';
|
|
13
13
|
import { Logger } from './logger.js';
|
|
14
14
|
|
|
@@ -23,6 +23,7 @@ export {
|
|
|
23
23
|
EvaluationVerdictResult,
|
|
24
24
|
EvaluationFeedback,
|
|
25
25
|
executeInParallel,
|
|
26
|
+
hasErrorType,
|
|
26
27
|
sendHttpRequest,
|
|
27
28
|
sendPostRequestAndAwaitWebhook
|
|
28
29
|
};
|
package/src/interface/step.d.ts
CHANGED
|
@@ -15,31 +15,32 @@ export type StepOptions = {
|
|
|
15
15
|
/**
|
|
16
16
|
* The handler function of a step.
|
|
17
17
|
*
|
|
18
|
-
* @param input -
|
|
18
|
+
* @param input - Parsed step input (`z.infer<inputSchema>`).
|
|
19
19
|
*
|
|
20
|
-
* @returns A value
|
|
20
|
+
* @returns A value accepted by `outputSchema` before parse (`z.input<outputSchema>`).
|
|
21
21
|
*/
|
|
22
22
|
export type StepFunction<
|
|
23
23
|
InputSchema extends AnyZodSchema | undefined = undefined,
|
|
24
24
|
OutputSchema extends AnyZodSchema | undefined = undefined
|
|
25
25
|
> = InputSchema extends AnyZodSchema ?
|
|
26
|
-
( input: z.infer<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.
|
|
27
|
-
() => Promise<OutputSchema extends AnyZodSchema ? z.
|
|
26
|
+
( input: z.infer<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void> :
|
|
27
|
+
() => Promise<OutputSchema extends AnyZodSchema ? z.input<OutputSchema> : void>;
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A wrapper around the user defined `fn` handler function.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* It adds input and output validation based on the `inputSchema`, `outputSchema`.
|
|
32
|
+
* Callers pass values accepted by `inputSchema` (`z.input`). The wrapper parses input,
|
|
33
|
+
* invokes `fn`, parses output, and returns `z.infer<outputSchema>`.
|
|
35
34
|
*
|
|
36
|
-
* @param input - The
|
|
37
|
-
* @returns
|
|
35
|
+
* @param input - The step input before `inputSchema` parse.
|
|
36
|
+
* @returns The step output after `outputSchema` parse.
|
|
38
37
|
*/
|
|
39
|
-
export type StepFunctionWrapper<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
export type StepFunctionWrapper<
|
|
39
|
+
InputSchema extends AnyZodSchema | undefined = undefined,
|
|
40
|
+
OutputSchema extends AnyZodSchema | undefined = undefined
|
|
41
|
+
> = InputSchema extends AnyZodSchema ?
|
|
42
|
+
( input: z.input<InputSchema> ) => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
|
|
43
|
+
() => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Creates a step.
|
|
@@ -123,7 +124,7 @@ export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> =
|
|
|
123
124
|
* @param params.outputSchema - Zod schema for the `fn` output
|
|
124
125
|
* @param params.fn - A handler function containing the step code
|
|
125
126
|
* @param params.options - Optional step options.
|
|
126
|
-
* @returns
|
|
127
|
+
* @returns A wrapper that parses input/output around `fn`
|
|
127
128
|
*/
|
|
128
129
|
export declare function step<
|
|
129
130
|
InputSchema extends AnyZodSchema | undefined = undefined,
|
|
@@ -135,4 +136,4 @@ export declare function step<
|
|
|
135
136
|
outputSchema?: OutputSchema;
|
|
136
137
|
fn: StepFunction<InputSchema, OutputSchema>;
|
|
137
138
|
options?: StepOptions;
|
|
138
|
-
} ): StepFunctionWrapper<
|
|
139
|
+
} ): StepFunctionWrapper<InputSchema, OutputSchema>;
|
package/src/interface/step.js
CHANGED
|
@@ -14,11 +14,6 @@ export function step( { name, description, inputSchema, outputSchema, fn, option
|
|
|
14
14
|
inputSchema,
|
|
15
15
|
outputSchema,
|
|
16
16
|
options,
|
|
17
|
-
handler: async input =>
|
|
18
|
-
validator.validateInput( input );
|
|
19
|
-
const output = await fn( input );
|
|
20
|
-
validator.validateOutput( output );
|
|
21
|
-
return output;
|
|
22
|
-
}
|
|
17
|
+
handler: async input => validator.parseOutput( await fn( validator.parseInput( input ) ) )
|
|
23
18
|
} );
|
|
24
19
|
}
|