@hexabot-ai/agentic 3.0.1-alpha.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/LICENSE.md +120 -0
- package/README.md +231 -0
- package/dist/cjs/action/abstract-action.js +141 -0
- package/dist/cjs/action/action.js +47 -0
- package/dist/cjs/action/action.types.js +7 -0
- package/dist/cjs/bindings/base-binding.js +278 -0
- package/dist/cjs/context.js +79 -0
- package/dist/cjs/dsl.types.js +318 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/runner-runtime-control.js +343 -0
- package/dist/cjs/step-executors/conditional-executor.js +63 -0
- package/dist/cjs/step-executors/loop-executor.js +187 -0
- package/dist/cjs/step-executors/parallel-executor.js +51 -0
- package/dist/cjs/step-executors/skip-helpers.js +29 -0
- package/dist/cjs/step-executors/task-executor.js +155 -0
- package/dist/cjs/step-executors/types.js +7 -0
- package/dist/cjs/suspension-rebuilder.js +301 -0
- package/dist/cjs/utils/deferred.js +17 -0
- package/dist/cjs/utils/naming.js +48 -0
- package/dist/cjs/utils/timeout.js +42 -0
- package/dist/cjs/utils/workflow-definition.js +102 -0
- package/dist/cjs/workflow-compiler.js +212 -0
- package/dist/cjs/workflow-event-emitter.js +40 -0
- package/dist/cjs/workflow-runner.js +448 -0
- package/dist/cjs/workflow-types.js +7 -0
- package/dist/cjs/workflow-values.js +99 -0
- package/dist/cjs/workflow.js +243 -0
- package/dist/esm/action/abstract-action.js +137 -0
- package/dist/esm/action/action.js +44 -0
- package/dist/esm/action/action.types.js +6 -0
- package/dist/esm/bindings/base-binding.js +273 -0
- package/dist/esm/context.js +75 -0
- package/dist/esm/dsl.types.js +309 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/runner-runtime-control.js +338 -0
- package/dist/esm/step-executors/conditional-executor.js +60 -0
- package/dist/esm/step-executors/loop-executor.js +182 -0
- package/dist/esm/step-executors/parallel-executor.js +48 -0
- package/dist/esm/step-executors/skip-helpers.js +25 -0
- package/dist/esm/step-executors/task-executor.js +152 -0
- package/dist/esm/step-executors/types.js +6 -0
- package/dist/esm/suspension-rebuilder.js +296 -0
- package/dist/esm/utils/deferred.js +14 -0
- package/dist/esm/utils/naming.js +42 -0
- package/dist/esm/utils/timeout.js +37 -0
- package/dist/esm/utils/workflow-definition.js +98 -0
- package/dist/esm/workflow-compiler.js +208 -0
- package/dist/esm/workflow-event-emitter.js +36 -0
- package/dist/esm/workflow-runner.js +444 -0
- package/dist/esm/workflow-types.js +6 -0
- package/dist/esm/workflow-values.js +89 -0
- package/dist/esm/workflow.js +236 -0
- package/dist/types/action/abstract-action.d.ts +54 -0
- package/dist/types/action/abstract-action.d.ts.map +1 -0
- package/dist/types/action/action.d.ts +23 -0
- package/dist/types/action/action.d.ts.map +1 -0
- package/dist/types/action/action.types.d.ts +55 -0
- package/dist/types/action/action.types.d.ts.map +1 -0
- package/dist/types/bindings/base-binding.d.ts +52 -0
- package/dist/types/bindings/base-binding.d.ts.map +1 -0
- package/dist/types/context.d.ts +105 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/dsl.types.d.ts +220 -0
- package/dist/types/dsl.types.d.ts.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/runner-runtime-control.d.ts +64 -0
- package/dist/types/runner-runtime-control.d.ts.map +1 -0
- package/dist/types/step-executors/conditional-executor.d.ts +13 -0
- package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
- package/dist/types/step-executors/loop-executor.d.ts +33 -0
- package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
- package/dist/types/step-executors/parallel-executor.d.ts +14 -0
- package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
- package/dist/types/step-executors/skip-helpers.d.ts +12 -0
- package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
- package/dist/types/step-executors/task-executor.d.ts +13 -0
- package/dist/types/step-executors/task-executor.d.ts.map +1 -0
- package/dist/types/step-executors/types.d.ts +24 -0
- package/dist/types/step-executors/types.d.ts.map +1 -0
- package/dist/types/suspension-rebuilder.d.ts +67 -0
- package/dist/types/suspension-rebuilder.d.ts.map +1 -0
- package/dist/types/utils/deferred.d.ts +7 -0
- package/dist/types/utils/deferred.d.ts.map +1 -0
- package/dist/types/utils/naming.d.ts +24 -0
- package/dist/types/utils/naming.d.ts.map +1 -0
- package/dist/types/utils/timeout.d.ts +17 -0
- package/dist/types/utils/timeout.d.ts.map +1 -0
- package/dist/types/utils/workflow-definition.d.ts +3 -0
- package/dist/types/utils/workflow-definition.d.ts.map +1 -0
- package/dist/types/workflow-compiler.d.ts +12 -0
- package/dist/types/workflow-compiler.d.ts.map +1 -0
- package/dist/types/workflow-event-emitter.d.ts +70 -0
- package/dist/types/workflow-event-emitter.d.ts.map +1 -0
- package/dist/types/workflow-runner.d.ts +179 -0
- package/dist/types/workflow-runner.d.ts.map +1 -0
- package/dist/types/workflow-types.d.ts +195 -0
- package/dist/types/workflow-types.d.ts.map +1 -0
- package/dist/types/workflow-values.d.ts +34 -0
- package/dist/types/workflow-values.d.ts.map +1 -0
- package/dist/types/workflow.d.ts +75 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
- package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
- package/examples/defs-bindings-agent/actions/index.ts +15 -0
- package/examples/defs-bindings-agent/bindings.ts +20 -0
- package/examples/defs-bindings-agent/context.ts +32 -0
- package/examples/defs-bindings-agent/workflow.ts +67 -0
- package/examples/defs-bindings-agent/workflow.yml +28 -0
- package/examples/full/actions/await-user-input.ts +46 -0
- package/examples/full/actions/call-llm.ts +133 -0
- package/examples/full/actions/create-ticket.ts +46 -0
- package/examples/full/actions/decision-router.ts +83 -0
- package/examples/full/actions/get-calendar-events.ts +55 -0
- package/examples/full/actions/get-user-profile.ts +72 -0
- package/examples/full/actions/index.ts +33 -0
- package/examples/full/actions/query-memory.ts +45 -0
- package/examples/full/actions/search-web.ts +53 -0
- package/examples/full/actions/send-email.ts +50 -0
- package/examples/full/context.ts +41 -0
- package/examples/full/workflow.ts +75 -0
- package/examples/full/workflow.yml +359 -0
- package/examples/loop/actions/await-reply.ts +47 -0
- package/examples/loop/actions/index.ts +19 -0
- package/examples/loop/actions/send-text-message.ts +40 -0
- package/examples/loop/context.ts +32 -0
- package/examples/loop/workflow.ts +66 -0
- package/examples/loop/workflow.yml +66 -0
- package/examples/suspend-resume/actions/format-reply.ts +43 -0
- package/examples/suspend-resume/actions/index.ts +13 -0
- package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
- package/examples/suspend-resume/context.ts +32 -0
- package/examples/suspend-resume/workflow.ts +79 -0
- package/examples/suspend-resume/workflow.yml +29 -0
- package/package.json +60 -0
- package/src/__tests__/parser.test.ts +95 -0
- package/src/__tests__/suspension-rebuilder.test.ts +361 -0
- package/src/__tests__/test-helpers.ts +36 -0
- package/src/__tests__/validation.test.ts +526 -0
- package/src/__tests__/workflow-compiler.test.ts +715 -0
- package/src/__tests__/workflow-definition-path.test.ts +381 -0
- package/src/__tests__/workflow-event-emitter.test.ts +50 -0
- package/src/__tests__/workflow-runner.test.ts +1397 -0
- package/src/__tests__/workflow-values.test.ts +132 -0
- package/src/__tests__/workflow.test.ts +320 -0
- package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
- package/src/action/__tests__/action.test.ts +473 -0
- package/src/action/abstract-action.ts +215 -0
- package/src/action/action.ts +83 -0
- package/src/action/action.types.ts +93 -0
- package/src/bindings/base-binding.ts +508 -0
- package/src/context.ts +179 -0
- package/src/dsl.types.ts +473 -0
- package/src/index.ts +114 -0
- package/src/runner-runtime-control.ts +544 -0
- package/src/step-executors/conditional-executor.test.ts +192 -0
- package/src/step-executors/conditional-executor.ts +83 -0
- package/src/step-executors/loop-executor.test.ts +303 -0
- package/src/step-executors/loop-executor.ts +299 -0
- package/src/step-executors/parallel-executor.test.ts +182 -0
- package/src/step-executors/parallel-executor.ts +77 -0
- package/src/step-executors/skip-helpers.ts +68 -0
- package/src/step-executors/task-executor.test.ts +257 -0
- package/src/step-executors/task-executor.ts +248 -0
- package/src/step-executors/types.ts +70 -0
- package/src/suspension-rebuilder.ts +504 -0
- package/src/utils/deferred.ts +23 -0
- package/src/utils/naming.ts +55 -0
- package/src/utils/timeout.ts +48 -0
- package/src/utils/workflow-definition.ts +173 -0
- package/src/workflow-compiler.ts +317 -0
- package/src/workflow-event-emitter.ts +86 -0
- package/src/workflow-runner.ts +603 -0
- package/src/workflow-types.ts +207 -0
- package/src/workflow-values.ts +159 -0
- package/src/workflow.ts +387 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
4
|
+
* Copyright (c) 2025 Hexastack.
|
|
5
|
+
* Full terms: see LICENSE.md.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.Workflow = exports.WorkflowRunner = exports.WorkflowEventEmitter = exports.compileWorkflow = void 0;
|
|
9
|
+
const yaml_1 = require("yaml");
|
|
10
|
+
const dsl_types_1 = require("./dsl.types");
|
|
11
|
+
const workflow_definition_1 = require("./utils/workflow-definition");
|
|
12
|
+
const workflow_compiler_1 = require("./workflow-compiler");
|
|
13
|
+
const workflow_runner_1 = require("./workflow-runner");
|
|
14
|
+
var workflow_compiler_2 = require("./workflow-compiler");
|
|
15
|
+
Object.defineProperty(exports, "compileWorkflow", { enumerable: true, get: function () { return workflow_compiler_2.compileWorkflow; } });
|
|
16
|
+
var workflow_event_emitter_1 = require("./workflow-event-emitter");
|
|
17
|
+
Object.defineProperty(exports, "WorkflowEventEmitter", { enumerable: true, get: function () { return workflow_event_emitter_1.WorkflowEventEmitter; } });
|
|
18
|
+
var workflow_runner_2 = require("./workflow-runner");
|
|
19
|
+
Object.defineProperty(exports, "WorkflowRunner", { enumerable: true, get: function () { return workflow_runner_2.WorkflowRunner; } });
|
|
20
|
+
class WorkflowRunSuspendedError extends Error {
|
|
21
|
+
constructor(stepId, options) {
|
|
22
|
+
super(`Workflow run suspended at step ${stepId}${options?.reason ? `: ${options.reason}` : ''}`);
|
|
23
|
+
this.name = 'WorkflowRunSuspendedError';
|
|
24
|
+
this.stepId = stepId;
|
|
25
|
+
this.reason = options?.reason;
|
|
26
|
+
this.data = options?.data;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const getTaskNameFromStep = (step) => {
|
|
30
|
+
if (!step || typeof step !== 'object') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const taskName = step.do;
|
|
34
|
+
return typeof taskName === 'string' ? taskName : null;
|
|
35
|
+
};
|
|
36
|
+
const hasTaskReference = (steps, taskName) => {
|
|
37
|
+
return steps.some((step) => {
|
|
38
|
+
if ('do' in step) {
|
|
39
|
+
return step.do === taskName;
|
|
40
|
+
}
|
|
41
|
+
if ('parallel' in step) {
|
|
42
|
+
return hasTaskReference(step.parallel.steps, taskName);
|
|
43
|
+
}
|
|
44
|
+
if ('conditional' in step) {
|
|
45
|
+
return step.conditional.when.some((branch) => hasTaskReference(branch.steps, taskName));
|
|
46
|
+
}
|
|
47
|
+
if ('loop' in step) {
|
|
48
|
+
return hasTaskReference(step.loop.steps, taskName);
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Entry point for preparing and executing workflows from YAML or object definitions.
|
|
55
|
+
* Instances are thin wrappers around a compiled workflow graph.
|
|
56
|
+
*/
|
|
57
|
+
class Workflow {
|
|
58
|
+
constructor(compiled) {
|
|
59
|
+
this.compiled = compiled;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a workflow from an already parsed definition.
|
|
63
|
+
* The definition is validated before compilation to catch schema issues early.
|
|
64
|
+
*/
|
|
65
|
+
static fromDefinition(definition, options) {
|
|
66
|
+
const validation = (0, dsl_types_1.validateWorkflow)(definition, {
|
|
67
|
+
bindingKinds: options.bindingKinds,
|
|
68
|
+
actions: options.actions,
|
|
69
|
+
});
|
|
70
|
+
if (!validation.success) {
|
|
71
|
+
throw new Error(`Workflow validation failed: ${validation.errors.join('; ')}`);
|
|
72
|
+
}
|
|
73
|
+
const compiled = (0, workflow_compiler_1.compileWorkflow)(validation.data, options);
|
|
74
|
+
return new Workflow(compiled);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a workflow from YAML source.
|
|
78
|
+
* YAML is validated and compiled before being wrapped in a {@link Workflow} instance.
|
|
79
|
+
*/
|
|
80
|
+
static fromYaml(yaml, options) {
|
|
81
|
+
const validation = (0, dsl_types_1.validateWorkflow)(yaml, {
|
|
82
|
+
bindingKinds: options.bindingKinds,
|
|
83
|
+
actions: options.actions,
|
|
84
|
+
});
|
|
85
|
+
if (!validation.success) {
|
|
86
|
+
throw new Error(`Workflow validation failed: ${validation.errors.join('; ')}`);
|
|
87
|
+
}
|
|
88
|
+
const compiled = (0, workflow_compiler_1.compileWorkflow)(validation.data, options);
|
|
89
|
+
return new Workflow(compiled);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convert a workflow definition to YAML.
|
|
93
|
+
* The definition is validated before serialization.
|
|
94
|
+
*/
|
|
95
|
+
static stringifyDefinition(definition) {
|
|
96
|
+
const parsed = dsl_types_1.WorkflowDefinitionSchema.parse(definition);
|
|
97
|
+
return (0, yaml_1.stringify)(parsed);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a nested value from a workflow definition by path.
|
|
101
|
+
*/
|
|
102
|
+
static getValueAtPath(value, path) {
|
|
103
|
+
return path.reduce((acc, key) => {
|
|
104
|
+
if (acc === null || acc === undefined) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(acc)) {
|
|
108
|
+
return typeof key === 'number' ? acc[key] : undefined;
|
|
109
|
+
}
|
|
110
|
+
if (typeof acc === 'object') {
|
|
111
|
+
return acc[String(key)];
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}, value);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a new value with a nested path updated immutably.
|
|
118
|
+
*/
|
|
119
|
+
static setValueAtPath(value, path, nextValue) {
|
|
120
|
+
if (path.length === 0) {
|
|
121
|
+
return nextValue;
|
|
122
|
+
}
|
|
123
|
+
const [key, ...rest] = path;
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
if (typeof key !== 'number') {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
const nextArray = [...value];
|
|
129
|
+
nextArray[key] = Workflow.setValueAtPath(value[key], rest, nextValue);
|
|
130
|
+
return nextArray;
|
|
131
|
+
}
|
|
132
|
+
if (value && typeof value === 'object') {
|
|
133
|
+
return {
|
|
134
|
+
...value,
|
|
135
|
+
[String(key)]: Workflow.setValueAtPath(value[String(key)], rest, nextValue),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Remove a flow step from the definition at the given path, if valid.
|
|
142
|
+
*/
|
|
143
|
+
static removeStepAtPath(definition, stepPath) {
|
|
144
|
+
if (!stepPath.length) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const removeIndex = stepPath[stepPath.length - 1];
|
|
148
|
+
if (typeof removeIndex !== 'number') {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const stepsPath = stepPath.slice(0, -1);
|
|
152
|
+
const steps = Workflow.getValueAtPath(definition, stepsPath);
|
|
153
|
+
if (!Array.isArray(steps)) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (removeIndex < 0 || removeIndex >= steps.length) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const removedTaskName = getTaskNameFromStep(steps[removeIndex]);
|
|
160
|
+
const nextSteps = [...steps];
|
|
161
|
+
nextSteps.splice(removeIndex, 1);
|
|
162
|
+
const nextDefinition = Workflow.setValueAtPath(definition, stepsPath, nextSteps);
|
|
163
|
+
if (!removedTaskName ||
|
|
164
|
+
!Object.prototype.hasOwnProperty.call(nextDefinition.defs, removedTaskName)) {
|
|
165
|
+
return nextDefinition;
|
|
166
|
+
}
|
|
167
|
+
if (nextDefinition.defs[removedTaskName]?.kind !== dsl_types_1.TASK_KIND) {
|
|
168
|
+
return nextDefinition;
|
|
169
|
+
}
|
|
170
|
+
if (hasTaskReference(nextDefinition.flow, removedTaskName)) {
|
|
171
|
+
return nextDefinition;
|
|
172
|
+
}
|
|
173
|
+
const { [removedTaskName]: _removedTask, ...remainingDefs } = nextDefinition.defs;
|
|
174
|
+
return {
|
|
175
|
+
...nextDefinition,
|
|
176
|
+
defs: remainingDefs,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Insert a flow step into the definition at the given path, if valid.
|
|
181
|
+
*/
|
|
182
|
+
static insertStepAtPath(definition, insertPath, step) {
|
|
183
|
+
if (!insertPath.length) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const insertIndex = insertPath[insertPath.length - 1];
|
|
187
|
+
if (typeof insertIndex !== 'number') {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const stepsPath = insertPath.slice(0, -1);
|
|
191
|
+
const steps = Workflow.getValueAtPath(definition, stepsPath);
|
|
192
|
+
if (!Array.isArray(steps)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const nextSteps = [...steps];
|
|
196
|
+
const safeIndex = Math.min(Math.max(insertIndex, 0), nextSteps.length);
|
|
197
|
+
nextSteps.splice(safeIndex, 0, step);
|
|
198
|
+
return Workflow.setValueAtPath(definition, stepsPath, nextSteps);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Rename a task key and update references in flow steps and output expressions.
|
|
202
|
+
*/
|
|
203
|
+
static safeRenameTaskInDefinition(definition, currentTaskName, nextTaskName) {
|
|
204
|
+
return (0, workflow_definition_1.safeRenameTaskInDefinition)(definition, currentTaskName, nextTaskName);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Run the workflow until completion or suspension.
|
|
208
|
+
* Throws when a task suspends so callers can capture the state.
|
|
209
|
+
*/
|
|
210
|
+
async run(inputData, context, options) {
|
|
211
|
+
const runner = new workflow_runner_1.WorkflowRunner(this.compiled, options);
|
|
212
|
+
const result = await runner.start({
|
|
213
|
+
inputData,
|
|
214
|
+
context,
|
|
215
|
+
});
|
|
216
|
+
if (result.status === 'finished') {
|
|
217
|
+
return result.output;
|
|
218
|
+
}
|
|
219
|
+
if (result.status === 'failed') {
|
|
220
|
+
throw result.error instanceof Error
|
|
221
|
+
? result.error
|
|
222
|
+
: new Error(String(result.error));
|
|
223
|
+
}
|
|
224
|
+
context.attachWorkflowRuntime(undefined);
|
|
225
|
+
throw new WorkflowRunSuspendedError(result.step.id, {
|
|
226
|
+
reason: result.reason,
|
|
227
|
+
data: result.data,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Construct a runner without executing, allowing hosts to manage start/resume manually.
|
|
232
|
+
*/
|
|
233
|
+
async buildAsyncRunner(options) {
|
|
234
|
+
return new workflow_runner_1.WorkflowRunner(this.compiled, options);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Rebuild a runner from persisted state and snapshot, useful after restarts.
|
|
238
|
+
*/
|
|
239
|
+
async buildRunnerFromState(options) {
|
|
240
|
+
return workflow_runner_1.WorkflowRunner.fromPersistedState(this.compiled, options);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
exports.Workflow = Workflow;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { BaseSettingsSchema } from '../dsl.types';
|
|
8
|
+
import { assertSnakeCaseName } from '../utils/naming';
|
|
9
|
+
import { sleep, withTimeout } from '../utils/timeout';
|
|
10
|
+
const BASE_SETTINGS_KEYS = Object.keys(BaseSettingsSchema.shape);
|
|
11
|
+
/**
|
|
12
|
+
* Base implementation that enforces schema-validated input/output.
|
|
13
|
+
*/
|
|
14
|
+
export class AbstractAction {
|
|
15
|
+
/**
|
|
16
|
+
* Sets up core metadata and schemas for the action.
|
|
17
|
+
*
|
|
18
|
+
* @param metadata - Describes the action name, description, and schemas.
|
|
19
|
+
* @param options - Optional configuration or definition override.
|
|
20
|
+
*/
|
|
21
|
+
constructor(metadata) {
|
|
22
|
+
if (metadata.settingsSchema instanceof z.ZodObject) {
|
|
23
|
+
const actionSettingsKeys = Object.keys(metadata.settingsSchema.shape);
|
|
24
|
+
const overlappingKeys = actionSettingsKeys.filter((key) => BASE_SETTINGS_KEYS.includes(key));
|
|
25
|
+
if (overlappingKeys.length > 0) {
|
|
26
|
+
throw new Error(`settingsSchema cannot redefine base settings keys: ${overlappingKeys.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
assertSnakeCaseName(metadata.name, 'action');
|
|
30
|
+
this.name = metadata.name;
|
|
31
|
+
this.description = metadata.description;
|
|
32
|
+
this.inputSchema = metadata.inputSchema;
|
|
33
|
+
this.outputSchema = metadata.outputSchema;
|
|
34
|
+
this.settingSchema = metadata.settingsSchema;
|
|
35
|
+
this.supportedBindings = metadata.supportedBindings ?? [];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parses incoming payloads using the input schema.
|
|
39
|
+
*
|
|
40
|
+
* @param payload - Raw input received by the action.
|
|
41
|
+
* @returns Validated input typed as `I`.
|
|
42
|
+
*/
|
|
43
|
+
parseInput(payload) {
|
|
44
|
+
return this.inputSchema.parse(payload);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates and returns the raw action output.
|
|
48
|
+
*
|
|
49
|
+
* @param payload - Raw output produced by {@link execute}.
|
|
50
|
+
* @returns Output typed as `O` after schema validation.
|
|
51
|
+
*/
|
|
52
|
+
parseOutput(payload) {
|
|
53
|
+
return this.outputSchema.parse(payload);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes the action with retry, timeout, and schema safety.
|
|
57
|
+
*
|
|
58
|
+
* @param payload - Raw input being provided to the action.
|
|
59
|
+
* @param context - Workflow context used during execution.
|
|
60
|
+
* @returns Validated output produced by the action.
|
|
61
|
+
* @throws Error when retries are exhausted or validation fails.
|
|
62
|
+
*/
|
|
63
|
+
parseSettings(payload) {
|
|
64
|
+
const settings = z
|
|
65
|
+
.intersection(BaseSettingsSchema, this.settingSchema)
|
|
66
|
+
.parse(payload ?? {});
|
|
67
|
+
return (settings ?? {});
|
|
68
|
+
}
|
|
69
|
+
async run(payload, context, settings, bindings) {
|
|
70
|
+
const input = this.parseInput(payload);
|
|
71
|
+
const parsedSettings = this.parseSettings(settings);
|
|
72
|
+
const parsedBindings = (bindings ?? {});
|
|
73
|
+
this.assertSupportedBindings(parsedBindings);
|
|
74
|
+
const timeoutMs = parsedSettings.timeout_ms ?? 0;
|
|
75
|
+
const retrySettings = parsedSettings.retries ?? {
|
|
76
|
+
enabled: false,
|
|
77
|
+
max_attempts: 1,
|
|
78
|
+
backoff_ms: 0,
|
|
79
|
+
max_delay_ms: 0,
|
|
80
|
+
jitter: 0,
|
|
81
|
+
multiplier: 1,
|
|
82
|
+
};
|
|
83
|
+
const retriesEnabled = retrySettings.enabled ?? true;
|
|
84
|
+
const maxAttempts = retriesEnabled ? retrySettings.max_attempts : 1;
|
|
85
|
+
let attempt = 0;
|
|
86
|
+
let currentDelay = retrySettings.backoff_ms ?? 0;
|
|
87
|
+
const maxDelayMs = retrySettings.max_delay_ms;
|
|
88
|
+
const jitter = retrySettings.jitter;
|
|
89
|
+
const multiplier = retrySettings.multiplier;
|
|
90
|
+
while (attempt < maxAttempts) {
|
|
91
|
+
try {
|
|
92
|
+
const result = await withTimeout(this.execute({
|
|
93
|
+
input,
|
|
94
|
+
context,
|
|
95
|
+
settings: parsedSettings,
|
|
96
|
+
bindings: parsedBindings,
|
|
97
|
+
}), timeoutMs);
|
|
98
|
+
return this.parseOutput(result);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
attempt += 1;
|
|
102
|
+
if (attempt >= maxAttempts) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
let delay = currentDelay;
|
|
106
|
+
if (maxDelayMs > 0) {
|
|
107
|
+
delay = Math.min(delay, maxDelayMs);
|
|
108
|
+
}
|
|
109
|
+
if (delay > 0) {
|
|
110
|
+
const jitterFactor = jitter > 0 ? 1 + (Math.random() * 2 - 1) * jitter : 1;
|
|
111
|
+
const jitteredDelay = Math.max(0, Math.round(delay * jitterFactor));
|
|
112
|
+
if (jitteredDelay > 0) {
|
|
113
|
+
await sleep(jitteredDelay);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const nextDelay = currentDelay * multiplier;
|
|
117
|
+
currentDelay =
|
|
118
|
+
maxDelayMs > 0 ? Math.min(nextDelay, maxDelayMs) : nextDelay;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw new Error('Action failed after exhausting retry attempts.');
|
|
122
|
+
}
|
|
123
|
+
assertSupportedBindings(bindings) {
|
|
124
|
+
const bindingKinds = Object.keys((bindings ?? {}));
|
|
125
|
+
if (bindingKinds.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const unsupportedKinds = bindingKinds.filter((bindingKind) => !this.supportedBindings.includes(bindingKind));
|
|
129
|
+
if (unsupportedKinds.length === 0) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const supported = this.supportedBindings.length > 0
|
|
133
|
+
? this.supportedBindings.join(', ')
|
|
134
|
+
: '<none>';
|
|
135
|
+
throw new Error(`Action "${this.name}" does not support binding kind(s): ${unsupportedKinds.join(', ')}. Supported binding kinds: ${supported}.`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { AbstractAction } from './abstract-action';
|
|
8
|
+
/**
|
|
9
|
+
* Builds an {@link AbstractAction} subclass from simple configuration.
|
|
10
|
+
*
|
|
11
|
+
* @param params - Action definition containing metadata and runtime logic.
|
|
12
|
+
* @returns Instantiated action ready to be used by a workflow.
|
|
13
|
+
* @typeParam I - Action input type.
|
|
14
|
+
* @typeParam O - Action output type.
|
|
15
|
+
* @typeParam Ctx - Workflow context type.
|
|
16
|
+
*/
|
|
17
|
+
export function defineAction(params) {
|
|
18
|
+
const defaultSettingsSchema = z.any();
|
|
19
|
+
const defaultInputSchema = z.any();
|
|
20
|
+
const defaultOutputSchema = z.any();
|
|
21
|
+
class FnAction extends AbstractAction {
|
|
22
|
+
constructor() {
|
|
23
|
+
const metadata = {
|
|
24
|
+
name: params.name,
|
|
25
|
+
description: params.description ?? '',
|
|
26
|
+
inputSchema: params.inputSchema ?? defaultInputSchema,
|
|
27
|
+
outputSchema: params.outputSchema ?? defaultOutputSchema,
|
|
28
|
+
settingsSchema: params.settingSchema ?? defaultSettingsSchema,
|
|
29
|
+
supportedBindings: params.supportedBindings,
|
|
30
|
+
};
|
|
31
|
+
super(metadata);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Delegates to the user supplied execute callback.
|
|
35
|
+
*
|
|
36
|
+
* @param args - Action execution arguments supplied by the runner.
|
|
37
|
+
* @returns Result of the user callback as a promise.
|
|
38
|
+
*/
|
|
39
|
+
async execute(args) {
|
|
40
|
+
return await Promise.resolve(params.execute(args));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return new FnAction();
|
|
44
|
+
}
|