@outputai/core 0.10.1-next.f6a7c1a.0 → 0.11.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 +13 -7
- 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 +55 -13
- package/src/helpers/errors.spec.js +166 -66
- 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/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 +35 -44
- package/src/hooks/index.js +10 -7
- package/src/hooks/index.spec.js +34 -7
- 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/temporal/client.js +47 -0
- package/src/temporal/client.spec.js +124 -0
- package/src/temporal/index.d.ts +32 -0
- package/src/temporal/index.js +1 -0
- 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/index.js +21 -8
- package/src/worker/index.spec.js +30 -10
- package/src/worker/interceptors/activity.js +32 -19
- package/src/worker/interceptors/activity.spec.js +71 -166
- 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 +13 -10
- package/src/worker/log_hooks.spec.js +12 -4
- package/src/worker/sinks.js +3 -1
- package/src/worker/sinks.spec.js +21 -4
- 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.d.ts
CHANGED
|
@@ -79,25 +79,6 @@ export interface WorkflowDetails {
|
|
|
79
79
|
attempt: number;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/**
|
|
83
|
-
* Attribute totals collected while an activity executes.
|
|
84
|
-
*/
|
|
85
|
-
export interface Aggregations {
|
|
86
|
-
/** Cost totals collected from HTTP request cost and LLM usage attributes. */
|
|
87
|
-
cost: {
|
|
88
|
-
total: number;
|
|
89
|
-
};
|
|
90
|
-
/** Token totals collected from LLM usage attributes. */
|
|
91
|
-
tokens: {
|
|
92
|
-
[tokenType: string]: number | undefined;
|
|
93
|
-
total: number;
|
|
94
|
-
};
|
|
95
|
-
/** HTTP request count totals. */
|
|
96
|
-
httpRequests: {
|
|
97
|
-
total: number;
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
82
|
/**
|
|
102
83
|
* Common hook payload fields
|
|
103
84
|
*/
|
|
@@ -108,24 +89,6 @@ export interface HookPayloadBase {
|
|
|
108
89
|
eventDate: number;
|
|
109
90
|
}
|
|
110
91
|
|
|
111
|
-
/**
|
|
112
|
-
* Payload passed to the onError() handler when a workflow, activity or runtime error occurs.
|
|
113
|
-
*/
|
|
114
|
-
export interface ErrorHookPayload extends HookPayloadBase {
|
|
115
|
-
/** Origin of the error: workflow execution, activity execution, or runtime. */
|
|
116
|
-
source: 'workflow' | 'activity' | 'runtime';
|
|
117
|
-
/** Information about the current workflow execution */
|
|
118
|
-
workflowDetails?: WorkflowDetails;
|
|
119
|
-
/** Temporal's activityInfo(). If source is activity */
|
|
120
|
-
activityInfo?: Info;
|
|
121
|
-
/** Output component kind for the activity, e.g. step, evaluator, or internal_step. */
|
|
122
|
-
outputActivityKind: string;
|
|
123
|
-
/** Attribute totals collected during the activity execution. */
|
|
124
|
-
aggregations?: Aggregations | null;
|
|
125
|
-
/** The error thrown. */
|
|
126
|
-
error: Error;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
92
|
/**
|
|
130
93
|
* Common hook payload fields for events associated with an workflow.
|
|
131
94
|
*/
|
|
@@ -148,7 +111,13 @@ export type WorkflowEndHookPayload = WorkflowPayloadBase;
|
|
|
148
111
|
* Payload passed to the onWorkflowError() handler when a workflow run fails.
|
|
149
112
|
*/
|
|
150
113
|
export interface WorkflowErrorHookPayload extends WorkflowPayloadBase {
|
|
151
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Workflow error rehydrated after crossing the Temporal sandbox boundary.
|
|
116
|
+
* Preserves `name`, `message`, `cause`, and other enumerable properties from
|
|
117
|
+
* the serialized form; it is a reconstructed `Error`, not the original class.
|
|
118
|
+
* Extra fields are present at runtime but not on the `Error` type — narrow or
|
|
119
|
+
* cast in TypeScript when you need them.
|
|
120
|
+
*/
|
|
152
121
|
error: Error;
|
|
153
122
|
}
|
|
154
123
|
|
|
@@ -172,21 +141,43 @@ export type ActivityStartHookPayload = ActivityPayloadBase;
|
|
|
172
141
|
/**
|
|
173
142
|
* Payload passed to the onActivityEnd() handler when an activity completes successfully.
|
|
174
143
|
*/
|
|
175
|
-
export
|
|
176
|
-
/** Attribute totals collected during the activity execution. */
|
|
177
|
-
aggregations: Aggregations | null;
|
|
178
|
-
}
|
|
144
|
+
export type ActivityEndHookPayload = ActivityPayloadBase;
|
|
179
145
|
|
|
180
146
|
/**
|
|
181
147
|
* Payload passed to the onActivityError() handler when an activity fails.
|
|
182
148
|
*/
|
|
183
149
|
export interface ActivityErrorHookPayload extends ActivityPayloadBase {
|
|
184
|
-
/** Attribute totals collected during the activity execution. */
|
|
185
|
-
aggregations: Aggregations | null;
|
|
186
150
|
/** The error thrown. */
|
|
187
151
|
error: Error;
|
|
188
152
|
}
|
|
189
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Payload passed to the onError() handler when a workflow, activity or runtime error occurs.
|
|
156
|
+
*/
|
|
157
|
+
export type ErrorHookPayload =
|
|
158
|
+
( WorkflowPayloadBase & {
|
|
159
|
+
/** Workflow error origin. */
|
|
160
|
+
source: 'workflow';
|
|
161
|
+
/**
|
|
162
|
+
* Workflow error rehydrated after crossing the Temporal sandbox boundary.
|
|
163
|
+
* Preserves `name`, `message`, `cause`, and other enumerable properties from
|
|
164
|
+
* the serialized form; it is a reconstructed `Error`, not the original class.
|
|
165
|
+
* Extra fields are present at runtime but not on the `Error` type — narrow or
|
|
166
|
+
* cast in TypeScript when you need them.
|
|
167
|
+
*/
|
|
168
|
+
error: Error;
|
|
169
|
+
} ) |
|
|
170
|
+
( ActivityErrorHookPayload & {
|
|
171
|
+
/** Activity error origin. */
|
|
172
|
+
source: 'activity';
|
|
173
|
+
} ) |
|
|
174
|
+
( HookPayloadBase & {
|
|
175
|
+
/** Worker runtime error origin. */
|
|
176
|
+
source: 'runtime';
|
|
177
|
+
/** The original error instance. */
|
|
178
|
+
error: Error;
|
|
179
|
+
} );
|
|
180
|
+
|
|
190
181
|
/**
|
|
191
182
|
* Register a handler to be invoked on workflow, activity or runtime errors.
|
|
192
183
|
*
|
package/src/hooks/index.js
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { mainEventBus, stepEventBus } from '#bus';
|
|
2
2
|
import { BusEventType } from '#consts';
|
|
3
|
+
import { serializeError } from '#helpers/error_serializer';
|
|
3
4
|
import { createChildLogger } from '#logger';
|
|
5
|
+
import { pendingHooks } from './pending_hooks.js';
|
|
4
6
|
|
|
5
7
|
const log = createChildLogger( 'Hooks' );
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* Invokes a function within a try catch and log the error
|
|
10
|
+
* Invokes a function within a try catch and log the error.
|
|
9
11
|
*
|
|
10
12
|
* @param {Function} fn
|
|
11
13
|
* @param {any} args - Args to invoke the function with
|
|
12
14
|
* @param {string} hookName - hookName to identify this hook function in the logs
|
|
13
15
|
*/
|
|
14
|
-
const callHookCb =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const callHookCb = ( fn, args, hookName ) => {
|
|
17
|
+
const promise = Promise.resolve()
|
|
18
|
+
.then( () => fn( args ) )
|
|
19
|
+
.catch( e => log.error( `${hookName} hook error`, { error: serializeError( e ) } ) )
|
|
20
|
+
.finally( () => pendingHooks.delete( promise ) );
|
|
21
|
+
pendingHooks.add( promise );
|
|
22
|
+
return promise;
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
// General Life-cycle
|
package/src/hooks/index.spec.js
CHANGED
|
@@ -5,6 +5,11 @@ const logErrorMock = vi.hoisted( () => vi.fn() );
|
|
|
5
5
|
const createChildLoggerMock = vi.hoisted( () =>
|
|
6
6
|
vi.fn( () => ( { error: logErrorMock } ) )
|
|
7
7
|
);
|
|
8
|
+
const serializeErrorMock = vi.hoisted( () => vi.fn( error => ( {
|
|
9
|
+
name: error.name,
|
|
10
|
+
message: error.message,
|
|
11
|
+
stack: error.stack
|
|
12
|
+
} ) ) );
|
|
8
13
|
|
|
9
14
|
const mainOnHandlers = vi.hoisted( () => ( {} ) );
|
|
10
15
|
const stepOnHandlers = vi.hoisted( () => ( {} ) );
|
|
@@ -21,6 +26,7 @@ const stepEventBusMock = vi.hoisted( () => ( {
|
|
|
21
26
|
} ) );
|
|
22
27
|
|
|
23
28
|
vi.mock( '#logger', () => ( { createChildLogger: createChildLoggerMock } ) );
|
|
29
|
+
vi.mock( '#helpers/error_serializer', () => ( { serializeError: serializeErrorMock } ) );
|
|
24
30
|
vi.mock( '#bus', () => ( {
|
|
25
31
|
mainEventBus: mainEventBusMock,
|
|
26
32
|
stepEventBus: stepEventBusMock
|
|
@@ -38,6 +44,7 @@ import {
|
|
|
38
44
|
onWorkflowError,
|
|
39
45
|
onWorkflowStart
|
|
40
46
|
} from './index.js';
|
|
47
|
+
import { pendingHooks } from './pending_hooks.js';
|
|
41
48
|
|
|
42
49
|
const workflowDetails = {
|
|
43
50
|
workflowId: 'wf-1',
|
|
@@ -58,15 +65,10 @@ const activityInfo = {
|
|
|
58
65
|
|
|
59
66
|
const eventDate = 1710000001234;
|
|
60
67
|
|
|
61
|
-
const aggregations = {
|
|
62
|
-
cost: { total: 0 },
|
|
63
|
-
tokens: { total: 0 },
|
|
64
|
-
httpRequests: { total: 1 }
|
|
65
|
-
};
|
|
66
|
-
|
|
67
68
|
describe( 'hooks/index', () => {
|
|
68
69
|
beforeEach( () => {
|
|
69
70
|
vi.clearAllMocks();
|
|
71
|
+
pendingHooks.clear();
|
|
70
72
|
Object.keys( mainOnHandlers ).forEach( k => {
|
|
71
73
|
delete mainOnHandlers[k];
|
|
72
74
|
} );
|
|
@@ -143,6 +145,14 @@ describe( 'hooks/index', () => {
|
|
|
143
145
|
await mainOnHandlers[BusEventType.RUNTIME_ERROR]( { eventId: 'evt-rt-1', eventDate, error } );
|
|
144
146
|
|
|
145
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' } ) );
|
|
146
156
|
} );
|
|
147
157
|
} );
|
|
148
158
|
|
|
@@ -158,6 +168,23 @@ describe( 'hooks/index', () => {
|
|
|
158
168
|
} );
|
|
159
169
|
} );
|
|
160
170
|
|
|
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;
|
|
175
|
+
} );
|
|
176
|
+
const handler = vi.fn( () => handlerPromise );
|
|
177
|
+
onActivityEnd( handler );
|
|
178
|
+
|
|
179
|
+
const callbackPromise = mainOnHandlers[BusEventType.ACTIVITY_END]( { activityInfo } );
|
|
180
|
+
|
|
181
|
+
expect( pendingHooks.size ).toBe( 1 );
|
|
182
|
+
|
|
183
|
+
deferred.resolve();
|
|
184
|
+
await callbackPromise;
|
|
185
|
+
expect( pendingHooks.size ).toBe( 0 );
|
|
186
|
+
} );
|
|
187
|
+
|
|
161
188
|
describe( 'workflow lifecycle hooks', () => {
|
|
162
189
|
const cases = [
|
|
163
190
|
[ 'onWorkflowStart', onWorkflowStart, BusEventType.WORKFLOW_START, {} ],
|
|
@@ -186,7 +213,7 @@ describe( 'hooks/index', () => {
|
|
|
186
213
|
describe( 'activity lifecycle hooks', () => {
|
|
187
214
|
const cases = [
|
|
188
215
|
[ 'onActivityStart', onActivityStart, BusEventType.ACTIVITY_START, undefined ],
|
|
189
|
-
[ 'onActivityEnd', onActivityEnd, BusEventType.ACTIVITY_END
|
|
216
|
+
[ 'onActivityEnd', onActivityEnd, BusEventType.ACTIVITY_END ],
|
|
190
217
|
[ 'onActivityError', onActivityError, BusEventType.ACTIVITY_ERROR, { error: new Error( 'activity failed' ) } ]
|
|
191
218
|
];
|
|
192
219
|
|
|
@@ -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
|
}
|
|
@@ -2,8 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { ValidationError } from '#errors';
|
|
3
3
|
|
|
4
4
|
const validateDefinitionMock = vi.hoisted( () => vi.fn() );
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const parseInputMock = vi.hoisted( () => vi.fn( input => input ) );
|
|
6
|
+
const parseOutputMock = vi.hoisted( () => vi.fn( output => output ) );
|
|
7
7
|
const validatorConstructorMock = vi.hoisted( () => vi.fn() );
|
|
8
8
|
const createStepMock = vi.hoisted( () => vi.fn( ( { handler } ) => handler ) );
|
|
9
9
|
|
|
@@ -15,8 +15,8 @@ vi.mock( './validations/index.js', () => {
|
|
|
15
15
|
|
|
16
16
|
constructor( ...args ) {
|
|
17
17
|
validatorConstructorMock( ...args );
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
18
|
+
this.parseInput = parseInputMock;
|
|
19
|
+
this.parseOutput = parseOutputMock;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -86,15 +86,15 @@ describe( 'step()', () => {
|
|
|
86
86
|
|
|
87
87
|
await expect( wrapper( { value: 'input' } ) ).resolves.toBe( output );
|
|
88
88
|
|
|
89
|
-
expect(
|
|
89
|
+
expect( parseInputMock ).toHaveBeenCalledWith( { value: 'input' } );
|
|
90
90
|
expect( fn ).toHaveBeenCalledWith( { value: 'input' } );
|
|
91
|
-
expect(
|
|
91
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
92
92
|
} );
|
|
93
93
|
|
|
94
94
|
it( 'does not call the step function when input validation throws', async () => {
|
|
95
95
|
const { step } = await import( './step.js' );
|
|
96
96
|
const error = new ValidationError( 'invalid input' );
|
|
97
|
-
|
|
97
|
+
parseInputMock.mockImplementationOnce( () => {
|
|
98
98
|
throw error;
|
|
99
99
|
} );
|
|
100
100
|
const fn = vi.fn();
|
|
@@ -105,13 +105,13 @@ describe( 'step()', () => {
|
|
|
105
105
|
|
|
106
106
|
await expect( wrapper( { value: 'bad' } ) ).rejects.toBe( error );
|
|
107
107
|
expect( fn ).not.toHaveBeenCalled();
|
|
108
|
-
expect(
|
|
108
|
+
expect( parseOutputMock ).not.toHaveBeenCalled();
|
|
109
109
|
} );
|
|
110
110
|
|
|
111
111
|
it( 'propagates output validation errors after the step function runs', async () => {
|
|
112
112
|
const { step } = await import( './step.js' );
|
|
113
113
|
const error = new ValidationError( 'invalid output' );
|
|
114
|
-
|
|
114
|
+
parseOutputMock.mockImplementationOnce( () => {
|
|
115
115
|
throw error;
|
|
116
116
|
} );
|
|
117
117
|
const output = { ok: false };
|
|
@@ -123,6 +123,6 @@ describe( 'step()', () => {
|
|
|
123
123
|
|
|
124
124
|
await expect( wrapper( { value: 'input' } ) ).rejects.toBe( error );
|
|
125
125
|
expect( fn ).toHaveBeenCalledOnce();
|
|
126
|
-
expect(
|
|
126
|
+
expect( parseOutputMock ).toHaveBeenCalledWith( output );
|
|
127
127
|
} );
|
|
128
128
|
} );
|