@output.ai/core 0.1.0 → 0.1.2

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 (53) hide show
  1. package/README.md +114 -30
  2. package/package.json +9 -6
  3. package/src/consts.js +3 -1
  4. package/src/index.d.ts +36 -4
  5. package/src/interface/evaluator.js +12 -8
  6. package/src/interface/step.js +4 -4
  7. package/src/interface/validations/static.js +16 -2
  8. package/src/interface/validations/static.spec.js +20 -0
  9. package/src/interface/workflow.js +28 -25
  10. package/src/interface/zod_integration.spec.js +6 -6
  11. package/src/internal_activities/index.js +1 -33
  12. package/src/tracing/index.d.ts +4 -4
  13. package/src/tracing/index.js +12 -121
  14. package/src/tracing/internal_interface.js +66 -0
  15. package/src/tracing/processors/local/index.js +50 -0
  16. package/src/tracing/processors/local/index.spec.js +67 -0
  17. package/src/tracing/processors/s3/index.js +51 -0
  18. package/src/tracing/processors/s3/index.spec.js +64 -0
  19. package/src/tracing/processors/s3/redis_client.js +19 -0
  20. package/src/tracing/processors/s3/redis_client.spec.js +50 -0
  21. package/src/tracing/processors/s3/s3_client.js +33 -0
  22. package/src/tracing/processors/s3/s3_client.spec.js +67 -0
  23. package/src/tracing/{tracer_tree.js → tools/build_trace_tree.js} +4 -11
  24. package/src/tracing/{tracer_tree.spec.js → tools/build_trace_tree.spec.js} +4 -20
  25. package/src/tracing/{utils.js → tools/utils.js} +7 -0
  26. package/src/tracing/trace_engine.js +63 -0
  27. package/src/tracing/trace_engine.spec.js +91 -0
  28. package/src/utils.js +37 -0
  29. package/src/utils.spec.js +60 -0
  30. package/src/worker/catalog_workflow/index.js +2 -1
  31. package/src/worker/catalog_workflow/index.spec.js +6 -10
  32. package/src/worker/configs.js +24 -0
  33. package/src/worker/index.js +7 -4
  34. package/src/worker/interceptors/activity.js +7 -14
  35. package/src/worker/interceptors/workflow.js +11 -3
  36. package/src/worker/loader.js +65 -29
  37. package/src/worker/loader.spec.js +32 -25
  38. package/src/worker/loader_tools.js +63 -0
  39. package/src/worker/loader_tools.spec.js +85 -0
  40. package/src/worker/sinks.js +8 -4
  41. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +38 -20
  42. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -4
  43. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +48 -0
  44. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +16 -20
  45. package/src/worker/webpack_loaders/workflow_rewriter/tools.js +23 -0
  46. package/src/configs.js +0 -31
  47. package/src/configs.spec.js +0 -331
  48. package/src/interface/metadata.js +0 -4
  49. package/src/tracing/index.private.spec.js +0 -84
  50. package/src/tracing/index.public.spec.js +0 -86
  51. package/src/worker/internal_utils.js +0 -60
  52. package/src/worker/internal_utils.spec.js +0 -134
  53. /package/src/tracing/{utils.spec.js → tools/utils.spec.js} +0 -0
package/README.md CHANGED
@@ -4,45 +4,43 @@ Provides tools to develop and run a workflow, which is a well defined logical un
4
4
 
5
5
  ## Structure
6
6
 
7
- Workflows are defined using core functions "workflow" and "step", separate files:
7
+ Workflows are defined using core functions ("workflow", "step", "evaluator"), these are defined in separate files, and must be placed within the same folder:
8
8
 
9
9
  ```
10
10
  └ workflows
11
11
  └ example
12
12
  ├ workflow.ts|js <- workflow entry point
13
- ├ steps.ts|js <- file with each step of this workflow
13
+ ├ steps.ts|js <- file containing steps used by the workflow
14
+ ├ evaluators.ts|js <- file containing evaluating functions
14
15
  └ prompt.prompt <- a prompt file
15
16
  └ other-example
16
17
 
17
18
  ```
18
19
 
19
- Think that workflows is the orchestrator and steps are executors. So the workflow only call the steps and the steps call the IO operations, like APIs, DBs, LLMs, etc.
20
+ Workflows are the orchestrator and steps are executors. So the workflow only call the steps and the steps call the IO operations, like APIs, DBs, LLMs, etc. Evaluators are just another different flavor for steps, they work the same, but must return an `EvaluationResult` object.
20
21
 
21
- ## Workflow code
22
+ ## Components
22
23
 
23
- ### workflow.js
24
+ ### Workflow
24
25
 
26
+ The main code, must contain only deterministic orchestration code.
27
+
28
+ File: `workflow.js`
29
+
30
+ Example:
25
31
  ```js
26
- import { workflow } from '@output.ai/workflow';
32
+ import { workflow, z } from '@output.ai/workflow';
27
33
  import { guessByName } from './steps.js';
28
34
 
29
35
  export default workflow( {
30
36
  name: 'guessMyProfession',
31
37
  description: 'Guess a person profession by its name',
32
- inputSchema: {
33
- type: 'object',
34
- required: [ 'name' ],
35
- properties: {
36
- name: { type: 'string'}
37
- }
38
- },
39
- outputSchema: {
40
- type: 'object',
41
- required: [ 'profession' ],
42
- properties: {
43
- profession: { type: 'string'}
44
- }
45
- },
38
+ inputSchema: z.object( {
39
+ name: z.string()
40
+ } ),
41
+ outputSchema: z.object( {
42
+ profession: z.string()
43
+ } ),
46
44
  fn: async input => {
47
45
  const profession = await guessByName( input.name );
48
46
  return { profession };
@@ -50,19 +48,20 @@ export default workflow( {
50
48
  })
51
49
  ```
52
50
 
53
- ### steps.js
51
+ ### Step
54
52
 
53
+ Re-usable units of work that can contain IO, used by the workflow.
54
+
55
+ File: `steps.js`
56
+
57
+ Example:
55
58
  ```js
56
59
  import { api } from './api.js'
57
60
 
58
61
  export const guessByName = step( {
59
62
  name: 'guessByName',
60
- inputSchema: {
61
- type: 'string'
62
- },
63
- outputSchema: {
64
- type: 'string'
65
- },
63
+ inputSchema: z.string(),
64
+ outputSchema: z.string(),
66
65
  fn: async name => {
67
66
  const res = await api.consumer( name );
68
67
  return res.body;
@@ -70,9 +69,73 @@ export const guessByName = step( {
70
69
  } )
71
70
  ```
72
71
 
73
- ## webhooks
72
+ ### Shared Steps
73
+
74
+ By default, steps are exclusive to the workflow, so it is not passible to use these steps from elsewhere. In order to have shared steps and make them accessible in different workflows, create a shared steps file. This file can be relatively imported anywhere.
75
+
76
+ File: `shared_steps.js`
77
+
78
+ Example:
79
+ ```js
80
+ export const mySharedStep = step( {
81
+ name: 'mySharedStep',
82
+ ...
83
+ } )
84
+ ```
85
+
86
+ And the usage is the same as any step:
87
+ `workflow.js`
88
+ ```js
89
+ import { mySharedStep } from '../../tools/shared_steps.js'
90
+ ```
74
91
 
75
- workflows can call webhooks that will stop their execution until an answer is given back.
92
+ ### Evaluators
93
+
94
+ Steps that analyze LLM response, or take other measurements are contained in evaluators.
95
+
96
+ File: `evaluators.js`
97
+
98
+ Example:
99
+ ```js
100
+ import { evaluator, EvaluationStringResult } from './api.js'
101
+
102
+ export const judgeResult = evaluator( {
103
+ name: 'judgeResult',
104
+ inputSchema: z.string(),
105
+ fn: async name => {
106
+ ...
107
+ return new EvaluationStringResult({
108
+ value: 'good',
109
+ confidence: .95
110
+ });
111
+ }
112
+ } )
113
+ ```
114
+
115
+ Its usage is the same as steps:
116
+ `workflow.js`
117
+ ```js
118
+ import { workflow, z } from '@output.ai/workflow';
119
+ import { judgeResult } from './evaluators.js';
120
+
121
+ export default workflow( {
122
+ name: 'guessMyProfession',
123
+ inputSchema: z.object( {
124
+ name: z.string()
125
+ } ),
126
+ outputSchema: z.object( {
127
+ result: z.string()
128
+ } ),
129
+ fn: async input => {
130
+ const judgment = await judgeResult( input.name );
131
+ return { result: judgement.value };
132
+ }
133
+ })
134
+ ```
135
+
136
+ ## Webhooks
137
+
138
+ Workflows can call webhooks that will stop their execution until an answer is given back.
76
139
 
77
140
  ```js
78
141
  import { workflow, createWebhook } from '@output.ai/workflow';
@@ -119,6 +182,21 @@ POST http://locahost:3001/workflow/feedback
119
182
  }
120
183
  ```
121
184
 
185
+ ## Options
186
+
187
+ All core interface functions: workflow, step, evaluator have similar signature, with the following options:
188
+ - name: The function name, used to call it internally and identify it in the trace files, must be a code friendly string;
189
+ - description: Human description of the workflow/step, used for the catalog;
190
+ - inputSchema: a zod object indicating the type of the argument received by the `fn` function. It is validated. Omit if it doesn't have input arguments;
191
+ - outputSchema: a zod object indicating the type of that the `fn` function returns. It is validated. Omit if it is void. Evaluators do not have this option, since they must always return an EvaluationResult object;
192
+ - fn: The actual implementation of the workflow/step, including all its logic.
193
+ - options: Advanced options that will overwrite Temporal's ActivityOptions when calling activities.
194
+
195
+ If used on `workflow()` it will apply for all activities. If used on `step()` or `evaluator()` it will apply only to that underlying activity. If changed in both places, the end value will be a merge between the initial values, workflow values and the step values.
196
+
197
+ Order of precedence
198
+ `step options > workflow options > default options`
199
+
122
200
  ## Developing
123
201
 
124
202
  To develop workflows you need the code, which will be called the worker, the API and the engine (Temporal).
@@ -138,4 +216,10 @@ Necessary env variables to run the worker locally:
138
216
  - `TEMPORAL_API_KEY`: The API key to access remote temporal. If using local temporal, leave it blank;
139
217
  - `CATALOG_ID`: The name of the local catalog, always set this. Use your email;
140
218
  - `API_AUTH_KEY`: The API key to access the Framework API. Local can be blank, remote use the proper API Key;
141
- - `TRACING_ENABLED`: A "stringbool" value indicating if traces should be generated or not;
219
+ - `TRACE_LOCAL_ON`: A "stringbool" value indicating if traces should be saved locally, needs REDIS_URL;
220
+ - `TRACE_REMOTE_ON`: A "stringbool" value indicating if traces should be saved remotely, needs REDIS_URL and AWS_* secrets;
221
+ - `REDIS_URL`: The redis address to connect. Only necessary when any type of trace is enabled;
222
+ - `TRACE_REMOTE_S3_BUCKET`: The AWS S3 bucket to send the traces. Only necessary when remote trace is enabled;
223
+ - `AWS_REGION`: AWS region to connect to send the traces, must match the bucket region. Only necessary when remote trace is enabled;
224
+ - `AWS_ACCESS_KEY_ID`: AWS key id. Only necessary when remote trace is enabled;
225
+ - `AWS_SECRET_ACCESS_KEY`: AWS secrete. Only necessary when remote trace is enabled;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@output.ai/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The core module of the output framework",
5
5
  "type": "module",
6
6
  "exports": {
@@ -25,21 +25,24 @@
25
25
  "worker": "node ./src/worker/index.js"
26
26
  },
27
27
  "dependencies": {
28
+ "@aws-sdk/client-s3": "3.913.0",
28
29
  "@babel/generator": "7.28.3",
29
30
  "@babel/parser": "7.28.4",
30
31
  "@babel/traverse": "7.28.4",
31
32
  "@babel/types": "7.28.4",
32
- "@temporalio/worker": "1.13.0",
33
- "@temporalio/workflow": "1.13.0",
34
- "zod": "4.1.9"
33
+ "@temporalio/worker": "1.13.1",
34
+ "@temporalio/workflow": "1.13.1",
35
+ "redis": "5.8.3",
36
+ "zod": "4.1.12"
35
37
  },
36
38
  "license": "UNLICENSED",
37
39
  "imports": {
38
40
  "#consts": "./src/consts.js",
39
- "#configs": "./src/configs.js",
40
41
  "#errors": "./src/errors.js",
41
- "#tracing": "./src/tracing/index.js",
42
+ "#utils": "./src/utils.js",
43
+ "#tracing": "./src/tracing/internal_interface.js",
42
44
  "#async_storage": "./src/async_storage.js",
45
+ "#temporal_options": "./src/temporal_options.js",
43
46
  "#internal_activities": "./src/internal_activities/index.js"
44
47
  }
45
48
  }
package/src/consts.js CHANGED
@@ -1,7 +1,9 @@
1
1
  export const ACTIVITY_SEND_WEBHOOK = '__internal#sendWebhook';
2
- export const ACTIVITY_READ_TRACE_FILE = '__internal#readTraceFile';
3
2
  export const METADATA_ACCESS_SYMBOL = Symbol( '__metadata' );
3
+ export const SHARED_STEP_PREFIX = '__shared#';
4
4
  export const WORKFLOWS_INDEX_FILENAME = '__workflows_entrypoint.js';
5
+ export const ACTIVITY_OPTIONS_FILENAME = '__activity_options.js';
6
+ export const WORKFLOW_CATALOG = '$catalog';
5
7
  export const ComponentType = {
6
8
  EVALUATOR: 'evaluator',
7
9
  INTERNAL_STEP: 'internal_step',
package/src/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Import Zod types for dual schema support
2
2
  import type { z } from 'zod';
3
+ import type { ActivityOptions } from '@temporalio/workflow';
3
4
 
4
5
  // Re-export Zod for consumers to use
5
6
  export { z } from 'zod';
@@ -19,6 +20,17 @@ export { z } from 'zod';
19
20
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
21
  type AnyZodSchema = z.ZodType<any, any, any>;
21
22
 
23
+ /**
24
+ * Activity retry options accepted by step/evaluator/workflow definitions.
25
+ * Uses Temporal's ActivityOptions['retry'] type.
26
+ */
27
+ export type Options = { retry?: ActivityOptions['retry'] };
28
+
29
+ /**
30
+ * @typedef {object} Options
31
+ * @property {import('@temporalio/workflow').ActivityOptions['retry']} [retry]
32
+ */
33
+
22
34
  /*
23
35
  ╭─────────╮
24
36
  │ S T E P │╮
@@ -43,6 +55,7 @@ type AnyZodSchema = z.ZodType<any, any, any>;
43
55
  * @param {z.ZodType} options.inputSchema - Zod schema for the fn input
44
56
  * @param {z.ZodType} options.outputSchema - Zod schema for the fn output
45
57
  * @param {function} options.fn - The function logic: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
58
+ * @param {Options} [options.options] - Activity retry options
46
59
  * @returns {function} Function with signature: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
47
60
  */
48
61
  export async function step<
@@ -54,6 +67,7 @@ export async function step<
54
67
  inputSchema: InputSchema;
55
68
  outputSchema: OutputSchema;
56
69
  fn: ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>;
70
+ options?: Options;
57
71
  } ): ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>;
58
72
 
59
73
  /**
@@ -64,6 +78,7 @@ export async function step<
64
78
  * @param {string} [options.description] - Description of the step
65
79
  * @param {z.ZodType} options.inputSchema - Zod schema for the fn input
66
80
  * @param {function} options.fn - The function logic: `(input: z.infer<InputSchema>) => Promise<void>`
81
+ * @param {Options} [options.options] - Activity retry options
67
82
  * @returns {function} Function with signature: `(input: z.infer<InputSchema>) => Promise<void>`
68
83
  */
69
84
  export async function step<
@@ -73,6 +88,7 @@ export async function step<
73
88
  description?: string;
74
89
  inputSchema: InputSchema;
75
90
  fn: ( input: z.infer<InputSchema> ) => Promise<void>;
91
+ options?: Options;
76
92
  } ): ( input: z.infer<InputSchema> ) => Promise<void>;
77
93
 
78
94
  /**
@@ -83,6 +99,7 @@ export async function step<
83
99
  * @param {string} [options.description] - Description of the step
84
100
  * @param {z.ZodType} options.outputSchema - Zod schema for the fn output
85
101
  * @param {function} options.fn - The function logic: `() => Promise<z.infer<OutputSchema>>`
102
+ * @param {Options} [options.options] - Activity retry options
86
103
  * @returns {function} Function with signature: `() => Promise<z.infer<OutputSchema>>`
87
104
  */
88
105
  export async function step<
@@ -92,6 +109,7 @@ export async function step<
92
109
  description?: string;
93
110
  outputSchema: OutputSchema;
94
111
  fn: () => Promise<z.infer<OutputSchema>>;
112
+ options?: Options;
95
113
  } ): () => Promise<z.infer<OutputSchema>>;
96
114
 
97
115
  /**
@@ -101,12 +119,14 @@ export async function step<
101
119
  * @param {string} options.name - Human-readable step name (only letters, numbers and "_")
102
120
  * @param {string} [options.description] - Description of the step
103
121
  * @param {function} options.fn - The function logic: `() => Promise<void>`
122
+ * @param {Options} [options.options] - Activity retry options
104
123
  * @returns {function} Function with signature: `() => Promise<void>`
105
124
  */
106
125
  export async function step( options: {
107
126
  name: string;
108
127
  description?: string;
109
128
  fn: () => Promise<void>;
129
+ options?: Options;
110
130
  } ): () => Promise<void>;
111
131
 
112
132
  /*
@@ -132,6 +152,7 @@ export async function step( options: {
132
152
  * @param {z.ZodType} options.inputSchema - Zod schema for workflow input
133
153
  * @param {z.ZodType} options.outputSchema - Zod schema for workflow output
134
154
  * @param {function} options.fn - Workflow logic: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
155
+ * @param {Options} [options.options] - Activity retry options
135
156
  * @returns {function} Callable workflow function: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
136
157
  */
137
158
  export function workflow<
@@ -142,7 +163,8 @@ export function workflow<
142
163
  description?: string,
143
164
  inputSchema: InputSchema,
144
165
  outputSchema: OutputSchema,
145
- fn: ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>
166
+ fn: ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>,
167
+ options?: Options
146
168
  } ): ( input: z.infer<InputSchema> ) => Promise<z.infer<OutputSchema>>;
147
169
 
148
170
  /**
@@ -153,6 +175,7 @@ export function workflow<
153
175
  * @param {string} [options.description] - Description of the workflow
154
176
  * @param {z.ZodType} options.inputSchema - Zod schema for workflow input
155
177
  * @param {function} options.fn - Workflow logic: `(input: z.infer<InputSchema>) => Promise<void>`
178
+ * @param {Options} [options.options] - Activity retry options
156
179
  * @returns {function} Callable workflow function: `(input: z.infer<InputSchema>) => Promise<void>`
157
180
  */
158
181
  export function workflow<
@@ -161,7 +184,8 @@ export function workflow<
161
184
  name: string,
162
185
  description?: string,
163
186
  inputSchema: InputSchema,
164
- fn: ( input: z.infer<InputSchema> ) => Promise<void>
187
+ fn: ( input: z.infer<InputSchema> ) => Promise<void>,
188
+ options?: Options
165
189
  } ): ( input: z.infer<InputSchema> ) => Promise<void>;
166
190
 
167
191
  /**
@@ -172,6 +196,7 @@ export function workflow<
172
196
  * @param {string} [options.description] - Description of the workflow
173
197
  * @param {z.ZodType} options.outputSchema - Zod schema for workflow output
174
198
  * @param {function} options.fn - Workflow logic: `() => Promise<z.infer<OutputSchema>>`
199
+ * @param {Options} [options.options] - Activity retry options
175
200
  * @returns {function} Callable workflow function: `() => Promise<z.infer<OutputSchema>>`
176
201
  */
177
202
  export function workflow<
@@ -180,7 +205,8 @@ export function workflow<
180
205
  name: string,
181
206
  description?: string,
182
207
  outputSchema: OutputSchema,
183
- fn: () => Promise<z.infer<OutputSchema>>
208
+ fn: () => Promise<z.infer<OutputSchema>>,
209
+ options?: Options
184
210
  } ): () => Promise<z.infer<OutputSchema>>;
185
211
 
186
212
  /**
@@ -190,12 +216,14 @@ export function workflow<
190
216
  * @param {string} options.name - Unique workflow name
191
217
  * @param {string} [options.description] - Description of the workflow
192
218
  * @param {function} options.fn - Workflow logic: `() => Promise<void>`
219
+ * @param {Options} [options.options] - Activity retry options
193
220
  * @returns {function} Callable workflow function: `() => Promise<void>`
194
221
  */
195
222
  export function workflow( options : {
196
223
  name: string,
197
224
  description?: string,
198
- fn: () => Promise<void>
225
+ fn: () => Promise<void>,
226
+ options?: Options
199
227
  } ): () => Promise<void>;
200
228
 
201
229
  /*
@@ -309,6 +337,7 @@ export class EvaluationBooleanResult extends EvaluationResult {
309
337
  * @param {string} [options.description] - Description of the evaluator
310
338
  * @param {z.ZodType} options.inputSchema - Zod schema for the fn input
311
339
  * @param {function} options.fn - The function logic: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
340
+ * @param {Options} [options.options] - Activity retry options
312
341
  * @returns {function} Function with signature: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
313
342
  */
314
343
  export async function evaluator<
@@ -319,6 +348,7 @@ export async function evaluator<
319
348
  description?: string;
320
349
  inputSchema: InputSchema;
321
350
  fn: ( input: z.infer<InputSchema> ) => Promise<Result>;
351
+ options?: Options;
322
352
  } ): ( input: z.infer<InputSchema> ) => Promise<Result>;
323
353
 
324
354
  /**
@@ -329,6 +359,7 @@ export async function evaluator<
329
359
  * @param {string} [options.description] - Description of the evaluator
330
360
  * @param {z.ZodType} options.inputSchema - Zod schema for the fn input
331
361
  * @param {function} options.fn - The function logic: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
362
+ * @param {Options} [options.options] - Activity retry options
332
363
  * @returns {function} Function with signature: `(input: z.infer<InputSchema>) => Promise<z.infer<OutputSchema>>`
333
364
  */
334
365
  export async function evaluator<
@@ -338,6 +369,7 @@ export async function evaluator<
338
369
  name: string;
339
370
  description?: string;
340
371
  fn: ( input: z.infer<InputSchema> ) => Promise<Result>;
372
+ options?: Options;
341
373
  } ): ( input: z.infer<InputSchema> ) => Promise<Result>;
342
374
 
343
375
  /*
@@ -1,6 +1,6 @@
1
- import { setMetadata } from './metadata.js';
2
1
  import { validateEvaluator } from './validations/static.js';
3
2
  import { validateWithSchema } from './validations/runtime.js';
3
+ import { setMetadata } from '#utils';
4
4
  import { ValidationError } from '#errors';
5
5
  import { ComponentType } from '#consts';
6
6
  import * as z from 'zod';
@@ -48,7 +48,8 @@ export class EvaluationResult {
48
48
  * @param {string} [args.reasoning] - The reasoning behind the result
49
49
  */
50
50
  constructor( args ) {
51
- if ( !EvaluationResult.#schema.safeParse( args ) ) {
51
+ const result = EvaluationResult.#schema.safeParse( args );
52
+ if ( result.error ) {
52
53
  throw new EvaluationResultValidationError( z.prettifyError( result.error ) );
53
54
  }
54
55
  this.value = args.value;
@@ -73,7 +74,8 @@ export class EvaluationStringResult extends EvaluationResult {
73
74
  * @param {string} [args.reasoning] - The reasoning behind the result
74
75
  */
75
76
  constructor( args ) {
76
- if ( !EvaluationStringResult.#valueSchema.safeParse( args.value ) ) {
77
+ const result = EvaluationStringResult.#valueSchema.safeParse( args.value );
78
+ if ( result.error ) {
77
79
  throw new EvaluationResultValidationError( z.prettifyError( result.error ) );
78
80
  }
79
81
  super( args );
@@ -96,7 +98,8 @@ export class EvaluationBooleanResult extends EvaluationResult {
96
98
  * @param {string} [args.reasoning] - The reasoning behind the result
97
99
  */
98
100
  constructor( args ) {
99
- if ( !EvaluationBooleanResult.#valueSchema.safeParse( args.value ) ) {
101
+ const result = EvaluationBooleanResult.#valueSchema.safeParse( args.value );
102
+ if ( result.error ) {
100
103
  throw new EvaluationResultValidationError( z.prettifyError( result.error ) );
101
104
  }
102
105
  super( args );
@@ -119,15 +122,16 @@ export class EvaluationNumberResult extends EvaluationResult {
119
122
  * @param {string} [args.reasoning] - The reasoning behind the result
120
123
  */
121
124
  constructor( args ) {
122
- if ( !EvaluationNumberResult.#valueSchema.safeParse( args.value ) ) {
125
+ const result = EvaluationNumberResult.#valueSchema.safeParse( args.value );
126
+ if ( result.error ) {
123
127
  throw new EvaluationResultValidationError( z.prettifyError( result.error ) );
124
128
  }
125
129
  super( args );
126
130
  }
127
131
  };
128
132
 
129
- export function evaluator( { name, description, inputSchema, fn } ) {
130
- validateEvaluator( { name, description, inputSchema, fn } );
133
+ export function evaluator( { name, description, inputSchema, fn, options } ) {
134
+ validateEvaluator( { name, description, inputSchema, fn, options } );
131
135
 
132
136
  const wrapper = async input => {
133
137
  validateWithSchema( inputSchema, input, `Evaluator ${name} input` );
@@ -141,6 +145,6 @@ export function evaluator( { name, description, inputSchema, fn } ) {
141
145
  return output;
142
146
  };
143
147
 
144
- setMetadata( wrapper, { name, description, inputSchema, type: ComponentType.EVALUATOR } );
148
+ setMetadata( wrapper, { name, description, inputSchema, type: ComponentType.EVALUATOR, options } );
145
149
  return wrapper;
146
150
  };
@@ -1,10 +1,10 @@
1
- import { setMetadata } from './metadata.js';
1
+ import { setMetadata } from '#utils';
2
2
  import { validateStep } from './validations/static.js';
3
3
  import { validateWithSchema } from './validations/runtime.js';
4
4
  import { ComponentType } from '#consts';
5
5
 
6
- export function step( { name, description, inputSchema, outputSchema, fn } ) {
7
- validateStep( { name, description, inputSchema, outputSchema, fn } );
6
+ export function step( { name, description, inputSchema, outputSchema, fn, options } ) {
7
+ validateStep( { name, description, inputSchema, outputSchema, fn, options } );
8
8
 
9
9
  const wrapper = async input => {
10
10
  validateWithSchema( inputSchema, input, `Step ${name} input` );
@@ -16,6 +16,6 @@ export function step( { name, description, inputSchema, outputSchema, fn } ) {
16
16
  return output;
17
17
  };
18
18
 
19
- setMetadata( wrapper, { name, description, inputSchema, outputSchema, type: ComponentType.STEP } );
19
+ setMetadata( wrapper, { name, description, inputSchema, outputSchema, type: ComponentType.STEP, options } );
20
20
  return wrapper;
21
21
  };
@@ -17,12 +17,26 @@ const refineSchema = ( value, ctx ) => {
17
17
  } );
18
18
  };
19
19
 
20
- const stepAndWorkflowSchema = z.object( {
20
+ export const durationStringSchema = z.string().regex(
21
+ /^(\d+)(ms|s|m|h|d)$/,
22
+ 'Expected duration like "500ms", "10s", "5m", "2h", or "1d"'
23
+ );
24
+
25
+ const stepAndWorkflowSchema = z.strictObject( {
21
26
  name: z.string().regex( /^[a-z_][a-z0-9_]*$/i ),
22
27
  description: z.string().optional(),
23
28
  inputSchema: z.any().optional().superRefine( refineSchema ),
24
29
  outputSchema: z.any().optional().superRefine( refineSchema ),
25
- fn: z.function()
30
+ fn: z.function(),
31
+ options: z.strictObject( {
32
+ retry: z.strictObject( {
33
+ initialInterval: durationStringSchema.optional(),
34
+ backoffCoefficient: z.number().gte( 1 ).optional(),
35
+ maximumInterval: durationStringSchema.optional(),
36
+ maximumAttempts: z.number().gte( 1 ).int().optional(),
37
+ nonRetryableErrorTypes: z.array( z.string() ).optional()
38
+ } ).optional()
39
+ } ).optional()
26
40
  } );
27
41
 
28
42
  const evaluatorSchema = stepAndWorkflowSchema.omit( { outputSchema: true } );
@@ -65,6 +65,26 @@ describe( 'interface/validator', () => {
65
65
  const error = new StaticValidationError( '✖ Invalid input: expected function, received string\n → at fn' );
66
66
  expect( () => validateStep( { ...validArgs, fn: 'not-fn' } ) ).toThrow( error );
67
67
  } );
68
+
69
+ it( 'passes with options.retry (second-level options)', () => {
70
+ const args = {
71
+ ...validArgs,
72
+ options: {
73
+ retry: {
74
+ initialInterval: '1s',
75
+ backoffCoefficient: 2,
76
+ maximumInterval: '10s',
77
+ maximumAttempts: 3,
78
+ nonRetryableErrorTypes: [ 'SomeError' ]
79
+ }
80
+ }
81
+ };
82
+ expect( () => validateStep( args ) ).not.toThrow();
83
+ } );
84
+
85
+ it( 'rejects unknown top-level keys due to strictObject', () => {
86
+ expect( () => validateStep( { ...validArgs, extra: 123 } ) ).toThrow( StaticValidationError );
87
+ } );
68
88
  } );
69
89
 
70
90
  describe( 'validateWorkflow', () => {
@@ -1,28 +1,29 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
2
  import { proxyActivities, inWorkflowContext, executeChild, workflowInfo } from '@temporalio/workflow';
3
3
  import { getInvocationDir } from './utils.js';
4
- import { setMetadata } from './metadata.js';
5
- import { FatalError, ValidationError } from '#errors';
6
4
  import { validateWorkflow } from './validations/static.js';
7
5
  import { validateWithSchema } from './validations/runtime.js';
8
- import { ACTIVITY_READ_TRACE_FILE } from '#consts';
6
+ import { SHARED_STEP_PREFIX } from '#consts';
7
+ import { mergeActivityOptions, setMetadata } from '#utils';
8
+ import { FatalError, ValidationError } from '#errors';
9
9
 
10
- const temporalActivityConfigs = {
11
- startToCloseTimeout: '20 minute',
10
+ const defaultActivityOptions = {
11
+ startToCloseTimeout: '20m',
12
12
  retry: {
13
13
  initialInterval: '10s',
14
14
  backoffCoefficient: 2.0,
15
- maximumInterval: '2 minutes',
15
+ maximumInterval: '2m',
16
16
  maximumAttempts: 3,
17
17
  nonRetryableErrorTypes: [ ValidationError.name, FatalError.name ]
18
18
  }
19
19
  };
20
20
 
21
- export function workflow( { name, description, inputSchema, outputSchema, fn } ) {
22
- validateWorkflow( { name, description, inputSchema, outputSchema, fn } );
21
+ export function workflow( { name, description, inputSchema, outputSchema, fn, options } ) {
22
+ validateWorkflow( { name, description, inputSchema, outputSchema, fn, options } );
23
23
  const workflowPath = getInvocationDir();
24
24
 
25
- const steps = proxyActivities( temporalActivityConfigs );
25
+ const activityOptions = mergeActivityOptions( defaultActivityOptions, options );
26
+ const steps = proxyActivities( activityOptions );
26
27
 
27
28
  const wrapper = async input => {
28
29
  validateWithSchema( inputSchema, input, `Workflow ${name} input` );
@@ -34,33 +35,35 @@ export function workflow( { name, description, inputSchema, outputSchema, fn } )
34
35
  return output;
35
36
  }
36
37
 
37
- const { workflowId, memo } = workflowInfo();
38
+ const { workflowId, memo, startTime } = workflowInfo();
38
39
 
39
- // keep trace id and helm from memo if present (child workflow)
40
- const traceId = memo.traceId ?? workflowId;
41
- const traceHelm = memo.traceHelm ?? name;
40
+ // Create the execution context object or preserve if it already exists:
41
+ // It will always contains the information about the root workflow
42
+ // It will be used to as context for tracing (connecting events)
43
+ const executionContext = memo.executionContext ?? {
44
+ workflowId,
45
+ workflowName: name,
46
+ startTime: startTime.getTime()
47
+ };
42
48
 
43
- Object.assign( memo, { traceId, traceHelm } );
49
+ Object.assign( memo, {
50
+ executionContext,
51
+ activityOptions: memo.activityOptions ?? activityOptions // Also preserve the original activity options
52
+ } );
44
53
 
45
- // binds the methods called in the code that Webpack loader will add, they will exposed via "this"
54
+ // binds the methods called in the code that Webpack loader will add, they will exposed via "this"
46
55
  const output = await fn.call( {
47
- invokeStep: async ( stepName, input ) => steps[`${workflowPath}#${stepName}`]( input ),
48
- invokeEvaluator: async ( evaluatorName, input ) => steps[`${workflowPath}#${evaluatorName}`]( input ),
56
+ invokeStep: async ( stepName, input, options ) => steps[`${workflowPath}#${stepName}`]( input, options ),
57
+ invokeSharedStep: async ( stepName, input, options ) => steps[`${SHARED_STEP_PREFIX}#${stepName}`]( input, options ),
58
+ invokeEvaluator: async ( evaluatorName, input, options ) => steps[`${workflowPath}#${evaluatorName}`]( input, options ),
49
59
 
50
60
  startWorkflow: async ( childName, input ) => {
51
- return executeChild( childName, { args: input ? [ input ] : [], memo: { traceId, traceHelm, parentId: workflowId } } );
61
+ return executeChild( childName, { args: input ? [ input ] : [], memo: { executionContext, parentId: workflowId } } );
52
62
  }
53
63
  }, input );
54
64
 
55
65
  validateWithSchema( outputSchema, output, `Workflow ${name} output` );
56
66
 
57
- // Adds trace file content to the output if it is the root workflow
58
- // @TODO this will be replaced in favor of a persistent storage elsewhere
59
- if ( !memo.parentId ) {
60
- const trace = await steps[ACTIVITY_READ_TRACE_FILE]( { traceId, traceHelm } );
61
- return { output, trace };
62
- }
63
-
64
67
  return output;
65
68
  };
66
69