@outputai/core 0.8.2-next.edf06bb.0 → 0.9.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 +3 -2
- package/src/hooks/index.d.ts +102 -30
- package/src/hooks/index.js +16 -1
- package/src/hooks/index.spec.js +55 -1
- package/src/index.d.ts +2 -2
- package/src/interface/evaluator.d.ts +2 -2
- package/src/interface/index.d.ts +1 -1
- package/src/interface/index.js +1 -1
- package/src/interface/logger.d.ts +52 -44
- package/src/interface/logger.js +16 -11
- package/src/interface/logger.spec.js +35 -1
- package/src/interface/step.d.ts +1 -1
- package/src/interface/workflow.d.ts +2 -2
- package/src/worker/interceptors/activity.js +6 -9
- package/src/worker/interceptors/activity.spec.js +25 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The core module of the output framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"#internal_activities": "./src/internal_activities/index.js"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
|
-
"worker": "node ./src/worker/index.js"
|
|
76
|
+
"worker": "node ./src/worker/index.js",
|
|
77
|
+
"build": "pnpm exec tsc -p ./tsconfig.typecheck.json"
|
|
77
78
|
}
|
|
78
79
|
}
|
package/src/hooks/index.d.ts
CHANGED
|
@@ -79,6 +79,47 @@ 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
|
+
/**
|
|
102
|
+
* Common lifecycle hook payload fields for events associated with a workflow.
|
|
103
|
+
*/
|
|
104
|
+
export interface HookPayloadBase {
|
|
105
|
+
/** UUID v4 stamped per emit. Stable per-emit idempotency key. */
|
|
106
|
+
eventId: string;
|
|
107
|
+
/** Timestamp of the event */
|
|
108
|
+
eventDate: number;
|
|
109
|
+
/** Information about the current workflow execution */
|
|
110
|
+
workflowDetails: WorkflowDetails;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Common hook payload fields for events associated with an activity.
|
|
115
|
+
*/
|
|
116
|
+
export interface ActivityPayloadBase extends HookPayloadBase {
|
|
117
|
+
/** Temporal's activityInfo(). */
|
|
118
|
+
activityInfo: Info;
|
|
119
|
+
/** Output component kind for the activity, e.g. step or evaluator. */
|
|
120
|
+
outputActivityKind: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
82
123
|
/**
|
|
83
124
|
* Payload passed to the onError() handler when a workflow, activity or runtime error occurs.
|
|
84
125
|
*/
|
|
@@ -95,6 +136,8 @@ export interface ErrorHookPayload {
|
|
|
95
136
|
activityInfo?: Info;
|
|
96
137
|
/** Output component kind for the activity, e.g. step, evaluator, or internal_step. */
|
|
97
138
|
outputActivityKind?: string;
|
|
139
|
+
/** Attribute totals collected during the activity execution. */
|
|
140
|
+
aggregations?: Aggregations | null;
|
|
98
141
|
/** The error thrown. */
|
|
99
142
|
error: Error;
|
|
100
143
|
}
|
|
@@ -102,37 +145,45 @@ export interface ErrorHookPayload {
|
|
|
102
145
|
/**
|
|
103
146
|
* Payload passed to the onWorkflowStart() handler when a workflow run begins.
|
|
104
147
|
*/
|
|
105
|
-
export
|
|
106
|
-
/** UUID v4 stamped per emit. Stable per-emit idempotency key. */
|
|
107
|
-
eventId: string;
|
|
108
|
-
/** Timestamp of the event */
|
|
109
|
-
eventDate: number;
|
|
110
|
-
/** Information about the current workflow execution */
|
|
111
|
-
workflowDetails: WorkflowDetails;
|
|
112
|
-
}
|
|
148
|
+
export type WorkflowStartHookPayload = HookPayloadBase;
|
|
113
149
|
|
|
114
150
|
/**
|
|
115
151
|
* Payload passed to the onWorkflowEnd() handler when a workflow run completes successfully.
|
|
116
152
|
*/
|
|
117
|
-
export
|
|
118
|
-
/** UUID v4 stamped per emit. Stable per-emit idempotency key. */
|
|
119
|
-
eventId: string;
|
|
120
|
-
/** Timestamp of the event */
|
|
121
|
-
eventDate: number;
|
|
122
|
-
/** Information about the current workflow execution */
|
|
123
|
-
workflowDetails: WorkflowDetails;
|
|
124
|
-
}
|
|
153
|
+
export type WorkflowEndHookPayload = HookPayloadBase;
|
|
125
154
|
|
|
126
155
|
/**
|
|
127
156
|
* Payload passed to the onWorkflowError() handler when a workflow run fails.
|
|
128
157
|
*/
|
|
129
|
-
export interface WorkflowErrorHookPayload {
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
158
|
+
export interface WorkflowErrorHookPayload extends HookPayloadBase {
|
|
159
|
+
/** The error thrown. */
|
|
160
|
+
error: Error;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Common activity lifecycle hook payload fields.
|
|
165
|
+
*/
|
|
166
|
+
export type ActivityHookPayload = ActivityPayloadBase;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Payload passed to the onActivityStart() handler when an activity starts.
|
|
170
|
+
*/
|
|
171
|
+
export type ActivityStartHookPayload = ActivityHookPayload;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Payload passed to the onActivityEnd() handler when an activity completes successfully.
|
|
175
|
+
*/
|
|
176
|
+
export interface ActivityEndHookPayload extends ActivityHookPayload {
|
|
177
|
+
/** Attribute totals collected during the activity execution. */
|
|
178
|
+
aggregations: Aggregations | null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Payload passed to the onActivityError() handler when an activity fails.
|
|
183
|
+
*/
|
|
184
|
+
export interface ActivityErrorHookPayload extends ActivityHookPayload {
|
|
185
|
+
/** Attribute totals collected during the activity execution. */
|
|
186
|
+
aggregations: Aggregations | null;
|
|
136
187
|
/** The error thrown. */
|
|
137
188
|
error: Error;
|
|
138
189
|
}
|
|
@@ -179,16 +230,37 @@ export declare function onWorkflowEnd( handler: ( payload: WorkflowEndHookPayloa
|
|
|
179
230
|
*/
|
|
180
231
|
export declare function onWorkflowError( handler: ( payload: WorkflowErrorHookPayload ) => void ): void;
|
|
181
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Register a handler to be invoked when an activity starts.
|
|
235
|
+
*
|
|
236
|
+
* Excludes internal activities.
|
|
237
|
+
*
|
|
238
|
+
* @param handler - Function called with the activity start payload.
|
|
239
|
+
*/
|
|
240
|
+
export declare function onActivityStart( handler: ( payload: ActivityStartHookPayload ) => void ): void;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Register a handler to be invoked when an activity completes successfully.
|
|
244
|
+
*
|
|
245
|
+
* Excludes internal activities.
|
|
246
|
+
*
|
|
247
|
+
* @param handler - Function called with the activity end payload.
|
|
248
|
+
*/
|
|
249
|
+
export declare function onActivityEnd( handler: ( payload: ActivityEndHookPayload ) => void ): void;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Register a handler to be invoked when an activity fails.
|
|
253
|
+
*
|
|
254
|
+
* Excludes internal activities.
|
|
255
|
+
*
|
|
256
|
+
* @param handler - Function called with the activity error payload.
|
|
257
|
+
*/
|
|
258
|
+
export declare function onActivityError( handler: ( payload: ActivityErrorHookPayload ) => void ): void;
|
|
259
|
+
|
|
182
260
|
/**
|
|
183
261
|
* Framework-managed envelope added to payloads passed to on() handlers.
|
|
184
262
|
*/
|
|
185
|
-
export interface OnHookEnvelope {
|
|
186
|
-
/** UUID v4 stamped per emit. Stable per-emit idempotency key. */
|
|
187
|
-
eventId: string;
|
|
188
|
-
/** Timestamp of the event */
|
|
189
|
-
eventDate: number;
|
|
190
|
-
/** Information about the current workflow execution */
|
|
191
|
-
workflowDetails: WorkflowDetails;
|
|
263
|
+
export interface OnHookEnvelope extends HookPayloadBase {
|
|
192
264
|
/** Temporal's activityInfo(). */
|
|
193
265
|
activityInfo: Info;
|
|
194
266
|
/** Output component kind for the activity, e.g. step, evaluator, or internal_step. */
|
package/src/hooks/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { messageBus } from '#bus';
|
|
2
|
-
import { BusEventType, WORKFLOW_CATALOG } from '#consts';
|
|
2
|
+
import { BusEventType, ComponentType, WORKFLOW_CATALOG } from '#consts';
|
|
3
3
|
import { createChildLogger } from '#logger';
|
|
4
4
|
|
|
5
5
|
const log = createChildLogger( 'Hooks' );
|
|
@@ -48,6 +48,21 @@ export const onWorkflowEnd = handler => messageBus.on( BusEventType.WORKFLOW_END
|
|
|
48
48
|
export const onWorkflowError = handler => messageBus.on( BusEventType.WORKFLOW_ERROR, ( { workflowDetails, ...eventFields } ) =>
|
|
49
49
|
shouldEmitWorkflowEvent( workflowDetails ) ? safeInvoke( handler, { workflowDetails, ...eventFields }, 'onWorkflowError' ) : null );
|
|
50
50
|
|
|
51
|
+
/** Internal activities do not trigger hooks */
|
|
52
|
+
const shouldEmitActivityEvent = outputActivityKind => outputActivityKind !== ComponentType.INTERNAL_STEP;
|
|
53
|
+
|
|
54
|
+
/** Listen to workflow start events, excludes catalog workflow */
|
|
55
|
+
export const onActivityStart = handler => messageBus.on( BusEventType.ACTIVITY_START, ( { outputActivityKind, ...eventFields } ) =>
|
|
56
|
+
shouldEmitActivityEvent( outputActivityKind ) ? safeInvoke( handler, { outputActivityKind, ...eventFields }, 'onActivityStart' ) : null );
|
|
57
|
+
|
|
58
|
+
/** Listen to workflow end events, excludes catalog workflow */
|
|
59
|
+
export const onActivityEnd = handler => messageBus.on( BusEventType.ACTIVITY_END, ( { outputActivityKind, ...eventFields } ) =>
|
|
60
|
+
shouldEmitActivityEvent( outputActivityKind ) ? safeInvoke( handler, { outputActivityKind, ...eventFields }, 'onActivityEnd' ) : null );
|
|
61
|
+
|
|
62
|
+
/** Listen to workflow error events, excludes catalog workflow */
|
|
63
|
+
export const onActivityError = handler => messageBus.on( BusEventType.ACTIVITY_ERROR, ( { outputActivityKind, ...eventFields } ) =>
|
|
64
|
+
shouldEmitActivityEvent( outputActivityKind ) ? safeInvoke( handler, { outputActivityKind, ...eventFields }, 'onActivityError' ) : null );
|
|
65
|
+
|
|
51
66
|
/** Generic listener for events emitted elsewhere (outside core) */
|
|
52
67
|
export const on = ( eventName, handler ) => messageBus.on( `external:${eventName}`, payload =>
|
|
53
68
|
safeInvoke( handler, payload, eventName ) );
|
package/src/hooks/index.spec.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { BusEventType, WORKFLOW_CATALOG } from '#consts';
|
|
2
|
+
import { BusEventType, ComponentType, WORKFLOW_CATALOG } from '#consts';
|
|
3
3
|
|
|
4
4
|
const logErrorMock = vi.hoisted( () => vi.fn() );
|
|
5
5
|
const createChildLoggerMock = vi.hoisted( () =>
|
|
@@ -18,6 +18,9 @@ vi.mock( '#bus', () => ( { messageBus: messageBusMock } ) );
|
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
on,
|
|
21
|
+
onActivityEnd,
|
|
22
|
+
onActivityError,
|
|
23
|
+
onActivityStart,
|
|
21
24
|
onBeforeWorkerStart,
|
|
22
25
|
onError,
|
|
23
26
|
onWorkflowEnd,
|
|
@@ -49,6 +52,12 @@ const activityInfo = {
|
|
|
49
52
|
|
|
50
53
|
const eventDate = 1710000001234;
|
|
51
54
|
|
|
55
|
+
const aggregations = {
|
|
56
|
+
cost: { total: 0 },
|
|
57
|
+
tokens: { total: 0 },
|
|
58
|
+
httpRequests: { total: 1 }
|
|
59
|
+
};
|
|
60
|
+
|
|
52
61
|
describe( 'hooks/index', () => {
|
|
53
62
|
beforeEach( () => {
|
|
54
63
|
vi.clearAllMocks();
|
|
@@ -198,6 +207,51 @@ describe( 'hooks/index', () => {
|
|
|
198
207
|
} );
|
|
199
208
|
} );
|
|
200
209
|
|
|
210
|
+
describe( 'activity lifecycle hooks', () => {
|
|
211
|
+
const cases = [
|
|
212
|
+
[ 'onActivityStart', onActivityStart, BusEventType.ACTIVITY_START, undefined ],
|
|
213
|
+
[ 'onActivityEnd', onActivityEnd, BusEventType.ACTIVITY_END, { aggregations } ],
|
|
214
|
+
[ 'onActivityError', onActivityError, BusEventType.ACTIVITY_ERROR, { error: new Error( 'activity failed' ) } ]
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
it.each( cases )( '%s skips internal activities and forwards bus fields', async ( _name, registerHook, eventType, extraFields = {} ) => {
|
|
218
|
+
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]( {
|
|
234
|
+
eventId: 'evt-activity-1',
|
|
235
|
+
eventDate,
|
|
236
|
+
activityInfo,
|
|
237
|
+
workflowDetails,
|
|
238
|
+
outputActivityKind: ComponentType.STEP,
|
|
239
|
+
extra: 'passthrough',
|
|
240
|
+
...extraFields
|
|
241
|
+
} ) );
|
|
242
|
+
|
|
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
|
+
} );
|
|
252
|
+
} );
|
|
253
|
+
} );
|
|
254
|
+
|
|
201
255
|
describe( 'on', () => {
|
|
202
256
|
it( 'subscribes to external event channel and forwards payload', async () => {
|
|
203
257
|
const handler = vi.fn().mockResolvedValue( undefined );
|
package/src/index.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Export all types from the interface
|
|
3
3
|
*
|
|
4
4
|
*/
|
|
5
|
-
export * from './interface/index.
|
|
5
|
+
export * from './interface/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Exports all errors
|
|
9
9
|
*/
|
|
10
|
-
export * from './errors.
|
|
10
|
+
export * from './errors.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Expose z from Zod as a convenience.
|
|
@@ -22,7 +22,7 @@ export type EvaluatorOptions = {
|
|
|
22
22
|
*/
|
|
23
23
|
export type EvaluatorFunction<
|
|
24
24
|
InputSchema extends AnyZodSchema | undefined = undefined,
|
|
25
|
-
Result extends EvaluationResult
|
|
25
|
+
Result extends EvaluationResult = EvaluationResult
|
|
26
26
|
> = InputSchema extends AnyZodSchema ?
|
|
27
27
|
( input: z.infer<InputSchema> ) => Promise<Result> :
|
|
28
28
|
() => Promise<Result>;
|
|
@@ -34,7 +34,7 @@ export type EvaluatorFunction<
|
|
|
34
34
|
*
|
|
35
35
|
* It adds input validation based on the `inputSchema`.
|
|
36
36
|
*/
|
|
37
|
-
export type EvaluatorFunctionWrapper<EvaluatorFunction> =
|
|
37
|
+
export type EvaluatorFunctionWrapper<EvaluatorFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
38
38
|
Parameters<EvaluatorFunction> extends [infer Input] ?
|
|
39
39
|
( input: Input ) => ReturnType<EvaluatorFunction> :
|
|
40
40
|
() => ReturnType<EvaluatorFunction>;
|
package/src/interface/index.d.ts
CHANGED
package/src/interface/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { step } from './step.js';
|
|
|
10
10
|
import { workflow } from './workflow.js';
|
|
11
11
|
import { executeInParallel } from './workflow_utils.js';
|
|
12
12
|
import { sendHttpRequest, sendPostRequestAndAwaitWebhook } from './webhook.js';
|
|
13
|
-
import
|
|
13
|
+
import { Logger } from './logger.js';
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
16
|
evaluator,
|
|
@@ -1,53 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
type Logger = {
|
|
2
|
+
/**
|
|
3
|
+
* Log an error (level 0)
|
|
4
|
+
* @param message Log message
|
|
5
|
+
* @param metadata Additional information to be displayed
|
|
6
|
+
*/
|
|
7
|
+
error( message: string, metadata?: Record<string, unknown> ) : void,
|
|
5
8
|
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Log a warn (level 1)
|
|
11
|
+
* @param message Log message
|
|
12
|
+
* @param metadata Additional information to be displayed
|
|
13
|
+
*/
|
|
14
|
+
warn( message: string, metadata?: Record<string, unknown> ) : void,
|
|
12
15
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Log an info (level 2)
|
|
18
|
+
* @param message Log message
|
|
19
|
+
* @param metadata Additional information to be displayed
|
|
20
|
+
*/
|
|
21
|
+
info( message: string, metadata?: Record<string, unknown> ) : void,
|
|
19
22
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Log http (level 3)
|
|
25
|
+
* @param message Log message
|
|
26
|
+
* @param metadata Additional information to be displayed
|
|
27
|
+
*/
|
|
28
|
+
http( message: string, metadata?: Record<string, unknown> ) : void,
|
|
26
29
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Log verbose (level 4)
|
|
32
|
+
* @param message Log message
|
|
33
|
+
* @param metadata Additional information to be displayed
|
|
34
|
+
*/
|
|
35
|
+
verbose( message: string, metadata?: Record<string, unknown> ) : void,
|
|
33
36
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Log debug (level 5)
|
|
39
|
+
* @param message Log message
|
|
40
|
+
* @param metadata Additional information to be displayed
|
|
41
|
+
*/
|
|
42
|
+
debug( message: string, metadata?: Record<string, unknown> ) : void,
|
|
40
43
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Log silly (level 6)
|
|
46
|
+
* @param message Log message
|
|
47
|
+
* @param metadata Additional information to be displayed
|
|
48
|
+
*/
|
|
49
|
+
silly( message: string, metadata?: Record<string, unknown> ) : void,
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new Logger with a namespace value preset for all emitted logs.
|
|
53
|
+
* @param namespace
|
|
54
|
+
*/
|
|
55
|
+
createLogger( namespace: string ) : Logger
|
|
56
|
+
};
|
|
47
57
|
|
|
48
58
|
/**
|
|
49
|
-
*
|
|
50
|
-
* @param message Log message
|
|
51
|
-
* @param metadata Additional information to be displayed
|
|
59
|
+
* Logger tool. Can be used in activities or workflows. Logs together with the framework's own logs.
|
|
52
60
|
*/
|
|
53
|
-
export declare
|
|
61
|
+
export declare const Logger : Logger;
|
package/src/interface/logger.js
CHANGED
|
@@ -9,7 +9,6 @@ const reservedMetadataFields = new Set( [
|
|
|
9
9
|
'level',
|
|
10
10
|
'message',
|
|
11
11
|
'metadata',
|
|
12
|
-
'namespace',
|
|
13
12
|
'splat',
|
|
14
13
|
'stack',
|
|
15
14
|
'timestamp',
|
|
@@ -26,6 +25,7 @@ const reservedMetadataFields = new Set( [
|
|
|
26
25
|
// This is inoffensive and can be used outside workflow sandbox
|
|
27
26
|
const sinks = proxySinks();
|
|
28
27
|
|
|
28
|
+
// Winston uses npm levels by default: https://github.com/winstonjs/winston#logging-levels
|
|
29
29
|
// Convert npm log levels to console levels
|
|
30
30
|
const levelToConsole = {
|
|
31
31
|
error: 'error',
|
|
@@ -36,14 +36,17 @@ const levelToConsole = {
|
|
|
36
36
|
debug: 'debug',
|
|
37
37
|
silly: 'log'
|
|
38
38
|
};
|
|
39
|
+
const levels = Object.keys( levelToConsole );
|
|
39
40
|
|
|
40
41
|
/** Drops reserved keys from object */
|
|
41
42
|
const removeReservedFields = obj => Object.fromEntries( Object.entries( obj ).filter( ( [ k ] ) => !reservedMetadataFields.has( k ) ) );
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
/** Dispatch the log message downstream */
|
|
45
|
+
const log = ( level, namespace, message, metadata ) => {
|
|
46
|
+
const baseMetadata = namespace ? { namespace } : {};
|
|
44
47
|
const sanitized = {
|
|
45
48
|
message: String( message ),
|
|
46
|
-
...
|
|
49
|
+
metadata: { ...baseMetadata, ...isPlainObject( metadata ) ? removeReservedFields( metadata ) : {} }
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
// When inside workflow, use sinks to send logs out
|
|
@@ -58,11 +61,13 @@ const log = ( level, message, metadata ) => {
|
|
|
58
61
|
}
|
|
59
62
|
};
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
/** Creates a new logger. It uses the npm levels, and for each level, bind the log function */
|
|
65
|
+
const createLogger = namespace => ( {
|
|
66
|
+
...Object.fromEntries( levels.map( l => [ l, log.bind( null, l, namespace ) ] ) ),
|
|
67
|
+
createLogger
|
|
68
|
+
} );
|
|
69
|
+
|
|
70
|
+
/** This is the default logger instance */
|
|
71
|
+
const logger = createLogger( null );
|
|
72
|
+
|
|
73
|
+
export { logger as Logger };
|
|
@@ -34,7 +34,7 @@ const consoleMethodsByLevel = {
|
|
|
34
34
|
silly: 'log'
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const loadLogger = async () => import( './logger.js' );
|
|
37
|
+
const loadLogger = async () => ( await import( './logger.js' ) ).Logger;
|
|
38
38
|
|
|
39
39
|
describe( 'interface/logger', () => {
|
|
40
40
|
beforeEach( () => {
|
|
@@ -135,4 +135,38 @@ describe( 'interface/logger', () => {
|
|
|
135
135
|
expect( consoleMocks.log ).toHaveBeenCalledTimes( 3 );
|
|
136
136
|
expect( workflowLogMock ).not.toHaveBeenCalled();
|
|
137
137
|
} );
|
|
138
|
+
|
|
139
|
+
it( 'creates a logger with a default namespace for every level', async () => {
|
|
140
|
+
const logger = await loadLogger();
|
|
141
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
142
|
+
|
|
143
|
+
const namespacedLogger = logger.createLogger( 'Namespace' );
|
|
144
|
+
logLevels.forEach( level => {
|
|
145
|
+
namespacedLogger[level]( `${level} message`, { requestId: level } );
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
logLevels.forEach( ( level, index ) => {
|
|
149
|
+
expect( workflowLogMock ).toHaveBeenNthCalledWith( index + 1, {
|
|
150
|
+
level,
|
|
151
|
+
message: `${level} message`,
|
|
152
|
+
metadata: {
|
|
153
|
+
namespace: 'Namespace',
|
|
154
|
+
requestId: level
|
|
155
|
+
}
|
|
156
|
+
} );
|
|
157
|
+
} );
|
|
158
|
+
} );
|
|
159
|
+
|
|
160
|
+
it( 'lets log metadata override the default namespace', async () => {
|
|
161
|
+
const logger = await loadLogger();
|
|
162
|
+
inWorkflowContextMock.mockReturnValue( true );
|
|
163
|
+
|
|
164
|
+
logger.createLogger( 'Default namespace' ).info( 'message', { namespace: 'Inline namespace' } );
|
|
165
|
+
|
|
166
|
+
expect( workflowLogMock ).toHaveBeenCalledWith( {
|
|
167
|
+
level: 'info',
|
|
168
|
+
message: 'message',
|
|
169
|
+
metadata: { namespace: 'Inline namespace' }
|
|
170
|
+
} );
|
|
171
|
+
} );
|
|
138
172
|
} );
|
package/src/interface/step.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export type StepFunction<
|
|
|
36
36
|
* @param input - The Step input; it matches the schema defined by `inputSchema`.
|
|
37
37
|
* @returns A value matching the schema defined by `outputSchema`.
|
|
38
38
|
*/
|
|
39
|
-
export type StepFunctionWrapper<StepFunction> =
|
|
39
|
+
export type StepFunctionWrapper<StepFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
40
40
|
Parameters<StepFunction> extends [infer Input] ?
|
|
41
41
|
( input: Input ) => ReturnType<StepFunction> :
|
|
42
42
|
() => ReturnType<StepFunction>;
|
|
@@ -133,7 +133,7 @@ export type WorkflowFunction<
|
|
|
133
133
|
> = InputSchema extends AnyZodSchema ?
|
|
134
134
|
( input: z.infer<InputSchema>, context: WorkflowContext<InputSchema, OutputSchema> ) =>
|
|
135
135
|
Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void> :
|
|
136
|
-
( input
|
|
136
|
+
( input: undefined | null, context: WorkflowContext<InputSchema, OutputSchema> ) =>
|
|
137
137
|
Promise<OutputSchema extends AnyZodSchema ? z.infer<OutputSchema> : void>;
|
|
138
138
|
|
|
139
139
|
/**
|
|
@@ -149,7 +149,7 @@ export type WorkflowFunction<
|
|
|
149
149
|
* @param options - Additional options for the invocation.
|
|
150
150
|
* @returns A value matching the schema defined by `outputSchema`.
|
|
151
151
|
*/
|
|
152
|
-
export type WorkflowFunctionWrapper<WorkflowFunction> =
|
|
152
|
+
export type WorkflowFunctionWrapper<WorkflowFunction extends ( ...args: any ) => any> = // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
153
153
|
[Parameters<WorkflowFunction>[0]] extends [undefined | null] ?
|
|
154
154
|
( input?: undefined | null, options?: WorkflowInvocationOptions ) =>
|
|
155
155
|
ReturnType<WorkflowFunction> :
|
|
@@ -77,20 +77,17 @@ export class ActivityExecutionInterceptor {
|
|
|
77
77
|
|
|
78
78
|
const output = await Storage.runWithContext( async _ => next( input ), storageContext );
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
|
|
81
|
+
|
|
82
|
+
messageBus.emit( BusEventType.ACTIVITY_END, { activityInfo, aggregations, workflowDetails, outputActivityKind } );
|
|
81
83
|
Tracing.addEventEnd( { id: activityId, details: output, traceInfo } );
|
|
82
84
|
|
|
83
|
-
return {
|
|
84
|
-
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1,
|
|
85
|
-
output,
|
|
86
|
-
aggregations: state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null
|
|
87
|
-
};
|
|
85
|
+
return { [ACTIVITY_WRAPPER_VERSION_FIELD]: 1, output, aggregations };
|
|
88
86
|
|
|
89
87
|
} catch ( error ) {
|
|
90
|
-
messageBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, workflowDetails, outputActivityKind, error } );
|
|
91
|
-
Tracing.addEventError( { id: activityId, details: error, traceInfo } );
|
|
92
|
-
|
|
93
88
|
const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
|
|
89
|
+
messageBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, aggregations, workflowDetails, outputActivityKind, error } );
|
|
90
|
+
Tracing.addEventError( { id: activityId, details: error, traceInfo } );
|
|
94
91
|
|
|
95
92
|
throw aggregations ? buildApplicationFailureWithDetails( error, { aggregations } ) : error;
|
|
96
93
|
} finally {
|
|
@@ -100,6 +100,12 @@ const httpRequestAttribute = {
|
|
|
100
100
|
requestId: 'req-1'
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
const httpRequestAggregations = {
|
|
104
|
+
cost: { total: 0 },
|
|
105
|
+
tokens: { total: 0 },
|
|
106
|
+
httpRequests: { total: 1 }
|
|
107
|
+
};
|
|
108
|
+
|
|
103
109
|
describe( 'ActivityExecutionInterceptor', () => {
|
|
104
110
|
beforeEach( () => {
|
|
105
111
|
vi.clearAllMocks();
|
|
@@ -136,7 +142,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
136
142
|
);
|
|
137
143
|
expect( messageBusEmitMock ).toHaveBeenCalledWith(
|
|
138
144
|
BusEventType.ACTIVITY_END,
|
|
139
|
-
{ activityInfo: activityInfoMock, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
145
|
+
{ activityInfo: activityInfoMock, aggregations: null, workflowDetails: workflowDetailsMock, outputActivityKind: 'step' }
|
|
140
146
|
);
|
|
141
147
|
expect( addEventStartMock ).toHaveBeenCalledWith( {
|
|
142
148
|
id: 'act-1',
|
|
@@ -189,14 +195,16 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
189
195
|
|
|
190
196
|
await expect( interceptor.execute( makeInput(), next ) ).resolves.toEqual( {
|
|
191
197
|
output: { result: 'ok' },
|
|
192
|
-
aggregations:
|
|
193
|
-
cost: { total: 0 },
|
|
194
|
-
tokens: { total: 0 },
|
|
195
|
-
httpRequests: { total: 1 }
|
|
196
|
-
},
|
|
198
|
+
aggregations: httpRequestAggregations,
|
|
197
199
|
[ACTIVITY_WRAPPER_VERSION_FIELD]: 1
|
|
198
200
|
} );
|
|
199
201
|
|
|
202
|
+
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_END, {
|
|
203
|
+
activityInfo: activityInfoMock,
|
|
204
|
+
aggregations: httpRequestAggregations,
|
|
205
|
+
workflowDetails: workflowDetailsMock,
|
|
206
|
+
outputActivityKind: 'step'
|
|
207
|
+
} );
|
|
200
208
|
} );
|
|
201
209
|
|
|
202
210
|
it( 'stores collected aggregations in ApplicationFailure details after failed execution', async () => {
|
|
@@ -215,15 +223,17 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
215
223
|
message: 'step failed',
|
|
216
224
|
type: 'Error',
|
|
217
225
|
details: [ {
|
|
218
|
-
aggregations:
|
|
219
|
-
cost: { total: 0 },
|
|
220
|
-
tokens: { total: 0 },
|
|
221
|
-
httpRequests: { total: 1 }
|
|
222
|
-
}
|
|
226
|
+
aggregations: httpRequestAggregations
|
|
223
227
|
} ],
|
|
224
228
|
cause: error
|
|
225
229
|
} );
|
|
226
|
-
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR,
|
|
230
|
+
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
231
|
+
activityInfo: activityInfoMock,
|
|
232
|
+
aggregations: httpRequestAggregations,
|
|
233
|
+
workflowDetails: workflowDetailsMock,
|
|
234
|
+
outputActivityKind: 'step',
|
|
235
|
+
error
|
|
236
|
+
} );
|
|
227
237
|
expect( addEventErrorMock ).toHaveBeenCalledOnce();
|
|
228
238
|
expect( addEventEndMock ).not.toHaveBeenCalled();
|
|
229
239
|
} );
|
|
@@ -243,13 +253,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
243
253
|
|
|
244
254
|
expect( thrown.details ).toEqual( [
|
|
245
255
|
{ domain: { reason: 'bad-input' } },
|
|
246
|
-
{
|
|
247
|
-
aggregations: {
|
|
248
|
-
cost: { total: 0 },
|
|
249
|
-
tokens: { total: 0 },
|
|
250
|
-
httpRequests: { total: 1 }
|
|
251
|
-
}
|
|
252
|
-
}
|
|
256
|
+
{ aggregations: httpRequestAggregations }
|
|
253
257
|
] );
|
|
254
258
|
} );
|
|
255
259
|
|
|
@@ -300,13 +304,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
300
304
|
nonRetryable: true,
|
|
301
305
|
details: [
|
|
302
306
|
{ domain: { reason: 'bad-input' } },
|
|
303
|
-
{
|
|
304
|
-
aggregations: {
|
|
305
|
-
cost: { total: 0 },
|
|
306
|
-
tokens: { total: 0 },
|
|
307
|
-
httpRequests: { total: 1 }
|
|
308
|
-
}
|
|
309
|
-
}
|
|
307
|
+
{ aggregations: httpRequestAggregations }
|
|
310
308
|
]
|
|
311
309
|
} );
|
|
312
310
|
} );
|
|
@@ -324,6 +322,7 @@ describe( 'ActivityExecutionInterceptor', () => {
|
|
|
324
322
|
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_START, expect.any( Object ) );
|
|
325
323
|
expect( messageBusEmitMock ).toHaveBeenCalledWith( BusEventType.ACTIVITY_ERROR, {
|
|
326
324
|
activityInfo: activityInfoMock,
|
|
325
|
+
aggregations: null,
|
|
327
326
|
workflowDetails: workflowDetailsMock,
|
|
328
327
|
outputActivityKind: 'step',
|
|
329
328
|
error
|