@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.
Files changed (77) hide show
  1. package/package.json +8 -7
  2. package/src/bus.js +58 -20
  3. package/src/bus.spec.js +135 -77
  4. package/src/consts.js +5 -2
  5. package/src/errors.d.ts +12 -2
  6. package/src/errors.js +17 -3
  7. package/src/errors.spec.js +19 -0
  8. package/src/helpers/activity_options.js +21 -0
  9. package/src/helpers/activity_options.spec.js +55 -0
  10. package/src/helpers/error_serializer.js +236 -0
  11. package/src/helpers/error_serializer.spec.js +413 -0
  12. package/src/helpers/errors.js +23 -14
  13. package/src/helpers/errors.spec.js +31 -75
  14. package/src/helpers/fetch.js +1 -38
  15. package/src/helpers/fetch.spec.js +1 -39
  16. package/src/helpers/function.js +13 -0
  17. package/src/helpers/function.spec.js +23 -1
  18. package/src/helpers/object.js +5 -1
  19. package/src/helpers/object.spec.js +5 -0
  20. package/src/helpers/redact.js +51 -0
  21. package/src/helpers/redact.spec.js +66 -0
  22. package/src/helpers/string.js +10 -0
  23. package/src/helpers/string.spec.js +36 -1
  24. package/src/hooks/index.d.ts +76 -78
  25. package/src/hooks/index.js +57 -49
  26. package/src/hooks/index.spec.js +104 -105
  27. package/src/hooks/pending_hooks.js +24 -0
  28. package/src/hooks/pending_hooks.spec.js +62 -0
  29. package/src/interface/evaluator.d.ts +10 -9
  30. package/src/interface/evaluator.js +1 -6
  31. package/src/interface/evaluator.spec.js +10 -10
  32. package/src/interface/index.js +2 -1
  33. package/src/interface/step.d.ts +16 -15
  34. package/src/interface/step.js +1 -6
  35. package/src/interface/step.spec.js +10 -10
  36. package/src/interface/validations/index.js +33 -53
  37. package/src/interface/validations/index.spec.js +25 -17
  38. package/src/interface/webhook.js +1 -2
  39. package/src/interface/webhook.spec.js +25 -19
  40. package/src/interface/workflow.d.ts +25 -18
  41. package/src/interface/workflow.js +24 -52
  42. package/src/interface/workflow.spec.js +109 -108
  43. package/src/interface/workflow_activity_options.js +2 -5
  44. package/src/interface/workflow_utils.d.ts +15 -0
  45. package/src/interface/workflow_utils.js +19 -0
  46. package/src/interface/workflow_utils.spec.js +82 -1
  47. package/src/sdk/runtime/events.d.ts +4 -6
  48. package/src/sdk/runtime/events.js +2 -15
  49. package/src/sdk/runtime/events.spec.js +14 -92
  50. package/src/tracing/trace_engine.js +9 -11
  51. package/src/tracing/trace_engine.spec.js +67 -28
  52. package/src/worker/catalog_workflow/catalog_job.js +3 -3
  53. package/src/worker/catalog_workflow/catalog_job.spec.js +1 -1
  54. package/src/worker/configs.js +0 -3
  55. package/src/worker/global_functions.js +2 -2
  56. package/src/worker/global_functions.spec.js +3 -3
  57. package/src/worker/index.js +20 -11
  58. package/src/worker/index.spec.js +29 -16
  59. package/src/worker/interceptors/activity.js +34 -21
  60. package/src/worker/interceptors/activity.spec.js +77 -172
  61. package/src/worker/interceptors/workflow.js +53 -11
  62. package/src/worker/interceptors/workflow.spec.js +176 -49
  63. package/src/worker/loader/workflows.js +16 -3
  64. package/src/worker/loader/workflows.spec.js +37 -5
  65. package/src/worker/log_hooks.js +22 -19
  66. package/src/worker/log_hooks.spec.js +14 -6
  67. package/src/worker/sinks.js +8 -8
  68. package/src/worker/sinks.spec.js +9 -9
  69. package/src/worker/telemetry.js +2 -1
  70. package/src/worker/temporal_logger.js +33 -0
  71. package/src/worker/temporal_logger.spec.js +90 -0
  72. package/src/helpers/aggregations.js +0 -44
  73. package/src/helpers/aggregations.spec.js +0 -92
  74. package/src/tracing/tools/utils.js +0 -42
  75. package/src/tracing/tools/utils.spec.js +0 -83
  76. package/src/worker/proxy.js +0 -20
  77. package/src/worker/proxy.spec.js +0 -51
@@ -1,22 +1,39 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { BusEventType, ComponentType, WORKFLOW_CATALOG } from '#consts';
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
- const onHandlers = vi.hoisted( () => ( {} ) );
10
- const messageBusMock = vi.hoisted( () => ( {
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
- onHandlers[eventType] = handler;
24
+ stepOnHandlers[eventType] = handler;
13
25
  } )
14
26
  } ) );
15
27
 
16
28
  vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
17
- vi.mock( '#bus', () => ( { messageBus: messageBusMock } ) );
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
- Object.keys( onHandlers ).forEach( k => {
65
- delete onHandlers[k];
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( messageBusMock.on ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, expect.any( Function ) );
75
- expect( messageBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKFLOW_ERROR, expect.any( Function ) );
76
- expect( messageBusMock.on ).toHaveBeenCalledWith( BusEventType.RUNTIME_ERROR, expect.any( Function ) );
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 onHandlers[BusEventType.ACTIVITY_ERROR]( {
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 onHandlers[BusEventType.WORKFLOW_ERROR]( {
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 onHandlers[BusEventType.RUNTIME_ERROR]( { eventId: 'evt-rt-1', eventDate, error } );
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( messageBusMock.on ).toHaveBeenCalledWith( BusEventType.WORKER_BEFORE_START, expect.any( Function ) );
146
- await onHandlers[BusEventType.WORKER_BEFORE_START]();
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
- describe( 'onWorkflowStart', () => {
153
- it( 'skips catalog workflow and forwards bus fields for real workflows', async () => {
154
- const handler = vi.fn().mockResolvedValue( undefined );
155
- onWorkflowStart( handler );
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
- describe( 'onWorkflowEnd', () => {
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
- await Promise.resolve( onHandlers[BusEventType.WORKFLOW_END]( {
177
- eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails
178
- } ) );
179
- expect( handler ).not.toHaveBeenCalled();
181
+ expect( pendingHooks.size ).toBe( 1 );
180
182
 
181
- await Promise.resolve( onHandlers[BusEventType.WORKFLOW_END]( {
182
- eventId: 'evt-end-1', eventDate, workflowDetails, extra: 'passthrough'
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( 'onWorkflowError', () => {
191
- it( 'skips catalog workflow and forwards bus fields for real workflows', async () => {
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 err = new Error( 'wf' );
194
- onWorkflowError( handler );
197
+ const payload = {
198
+ eventId: 'evt-workflow-1',
199
+ eventDate,
200
+ workflowDetails,
201
+ extra: 'passthrough',
202
+ ...extraFields
203
+ };
204
+ registerHook( handler );
195
205
 
196
- await Promise.resolve( onHandlers[BusEventType.WORKFLOW_ERROR]( {
197
- eventId: 'evt-ignored', eventDate, workflowDetails: catalogWorkflowDetails, error: err
198
- } ) );
199
- expect( handler ).not.toHaveBeenCalled();
206
+ expect( mainEventBusMock.on ).toHaveBeenCalledWith( eventType, expect.any( Function ) );
207
+ await mainOnHandlers[eventType]( payload );
200
208
 
201
- await Promise.resolve( onHandlers[BusEventType.WORKFLOW_ERROR]( {
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, { aggregations } ],
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 skips internal activities and forwards bus fields', async ( _name, registerHook, eventType, extraFields = {} ) => {
220
+ it.each( cases )( '%s forwards internal activity bus fields', async ( _name, registerHook, eventType, extraFields = {} ) => {
218
221
  const handler = vi.fn().mockResolvedValue( undefined );
219
- registerHook( handler );
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: ComponentType.STEP,
227
+ outputActivityKind: 'internal_step',
239
228
  extra: 'passthrough',
240
229
  ...extraFields
241
- } ) );
230
+ };
231
+ registerHook( handler );
242
232
 
243
- expect( handler ).toHaveBeenCalledWith( {
244
- eventId: 'evt-activity-1',
245
- eventDate,
246
- activityInfo,
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 external event channel and forwards payload', async () => {
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( messageBusMock.on ).toHaveBeenCalledWith( 'external:myEvent', expect.any( Function ) );
261
- await onHandlers['external:myEvent']( { foo: 1 } );
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( handler ).toHaveBeenCalledWith( { foo: 1 } );
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 - The evaluator input; it matches the schema defined by `inputSchema`.
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
- * It has the same signature and returns the same value, calling the user function inside.
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<EvaluatorFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
38
- Parameters<EvaluatorFunction> extends [infer Input] ?
39
- ( input: Input ) => ReturnType<EvaluatorFunction> :
40
- () => ReturnType<EvaluatorFunction>;
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<EvaluatorFunction<InputSchema, Result>>;
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 validateInputMock = vi.hoisted( () => vi.fn() );
13
- const validateOutputMock = vi.hoisted( () => vi.fn() );
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.validateInput = validateInputMock;
26
- this.validateOutput = validateOutputMock;
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( validateInputMock ).toHaveBeenCalledWith( { value: 'input' } );
90
+ expect( parseInputMock ).toHaveBeenCalledWith( { value: 'input' } );
91
91
  expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
92
- expect( validateOutputMock ).toHaveBeenCalledWith( output );
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
- validateInputMock.mockImplementationOnce( () => {
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( validateOutputMock ).not.toHaveBeenCalled();
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
- validateOutputMock.mockImplementationOnce( () => {
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( validateOutputMock ).toHaveBeenCalledWith( output );
127
+ expect( parseOutputMock ).toHaveBeenCalledWith( output );
128
128
  } );
129
129
  } );
130
130
 
@@ -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
  };
@@ -15,31 +15,32 @@ export type StepOptions = {
15
15
  /**
16
16
  * The handler function of a step.
17
17
  *
18
- * @param input - The step input; it matches the schema defined by `inputSchema`.
18
+ * @param input - Parsed step input (`z.infer<inputSchema>`).
19
19
  *
20
- * @returns A value matching the schema defined by `outputSchema`.
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.infer<OutputSchema> : void> :
27
- () => Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
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
- * It accepts the same input and returns the same value, calling the user function inside.
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 Step input; it matches the schema defined by `inputSchema`.
37
- * @returns A value matching the schema defined by `outputSchema`.
35
+ * @param input - The step input before `inputSchema` parse.
36
+ * @returns The step output after `outputSchema` parse.
38
37
  */
39
- export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
40
- Parameters<StepFunction> extends [infer Input] ?
41
- ( input: Input ) => ReturnType<StepFunction> :
42
- () => ReturnType<StepFunction>;
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 The same handler function set at `fn`
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<StepFunction<InputSchema, OutputSchema>>;
139
+ } ): StepFunctionWrapper<InputSchema, OutputSchema>;
@@ -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
  }