@runflow-ai/sdk 1.0.85 → 1.0.87
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/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types/all-types.d.ts +132 -2
- package/dist/types/all-types.d.ts.map +1 -1
- package/dist/workflows/index.d.ts +2 -1
- package/dist/workflows/index.d.ts.map +1 -1
- package/dist/workflows/index.js +6 -2
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/workflow.d.ts +50 -3
- package/dist/workflows/workflow.d.ts.map +1 -1
- package/dist/workflows/workflow.js +528 -40
- package/dist/workflows/workflow.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkflowBuilder = exports.Workflow = void 0;
|
|
3
|
+
exports.FlowBuilder = exports.WorkflowBuilder = exports.Workflow = void 0;
|
|
4
4
|
exports.createWorkflow = createWorkflow;
|
|
5
5
|
exports.createStep = createStep;
|
|
6
6
|
exports.createAgentStep = createAgentStep;
|
|
7
7
|
exports.createFunctionStep = createFunctionStep;
|
|
8
8
|
exports.createConnectorStep = createConnectorStep;
|
|
9
|
+
exports.flow = flow;
|
|
10
|
+
const events_1 = require("events");
|
|
9
11
|
const agent_1 = require("../core/agent");
|
|
10
12
|
const api_client_1 = require("../core/api-client");
|
|
11
13
|
const trace_collector_1 = require("../observability/trace-collector");
|
|
14
|
+
const logger_1 = require("../core/logger");
|
|
15
|
+
const performance_logger_1 = require("../core/performance-logger");
|
|
16
|
+
const context_1 = require("../core/context");
|
|
12
17
|
// ============================================================================
|
|
13
18
|
// WORKFLOW CLASS
|
|
14
19
|
// ============================================================================
|
|
15
|
-
class Workflow {
|
|
20
|
+
class Workflow extends events_1.EventEmitter {
|
|
16
21
|
constructor(config) {
|
|
22
|
+
super();
|
|
17
23
|
this.config = config;
|
|
24
|
+
this.logger = (0, logger_1.createLogger)({ name: `workflow:${config.id}`, level: this.resolveLogLevel() });
|
|
18
25
|
// Auto-initialize API client when used directly as SDK (outside execution engine)
|
|
19
26
|
if (!this.apiClient) {
|
|
20
27
|
const apiClient = (0, api_client_1.createRunflowAPIClient)();
|
|
@@ -34,6 +41,20 @@ class Workflow {
|
|
|
34
41
|
flushInterval: 1000,
|
|
35
42
|
});
|
|
36
43
|
}
|
|
44
|
+
// Resolve log level from environment
|
|
45
|
+
resolveLogLevel() {
|
|
46
|
+
if (process.env.RUNFLOW_LOG_LEVEL) {
|
|
47
|
+
return process.env.RUNFLOW_LOG_LEVEL;
|
|
48
|
+
}
|
|
49
|
+
return process.env.NODE_ENV === 'production' ? 'info' : 'debug';
|
|
50
|
+
}
|
|
51
|
+
// Save result to both stepResults Map and results plain object
|
|
52
|
+
saveResult(context, stepId, result) {
|
|
53
|
+
context.stepResults.set(stepId, result);
|
|
54
|
+
if (context.results) {
|
|
55
|
+
context.results[stepId] = result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
37
58
|
// Executar workflow
|
|
38
59
|
async execute(input) {
|
|
39
60
|
if (!this.apiClient) {
|
|
@@ -41,13 +62,18 @@ class Workflow {
|
|
|
41
62
|
}
|
|
42
63
|
// Validar input
|
|
43
64
|
const validatedInput = this.config.inputSchema.parse(input);
|
|
65
|
+
// Resolve execution ID: prefer Runflow global state, fallback to generated
|
|
66
|
+
const globalExecId = context_1.Runflow.getState().executionId;
|
|
67
|
+
const executionId = globalExecId || `wf_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
68
|
+
// Plain object for V2 FlowContext results
|
|
69
|
+
const resultsObj = {};
|
|
44
70
|
// Criar contexto de execução
|
|
45
|
-
const executionId = `wf_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
46
71
|
const context = {
|
|
47
72
|
workflowId: this.config.id,
|
|
48
73
|
executionId,
|
|
49
74
|
input: validatedInput,
|
|
50
75
|
stepResults: new Map(),
|
|
76
|
+
results: resultsObj,
|
|
51
77
|
currentStep: '',
|
|
52
78
|
metadata: {
|
|
53
79
|
startTime: new Date(),
|
|
@@ -57,8 +83,9 @@ class Workflow {
|
|
|
57
83
|
},
|
|
58
84
|
runflowAPI: this.apiClient,
|
|
59
85
|
};
|
|
60
|
-
|
|
61
|
-
|
|
86
|
+
this.logger.info({ workflowId: this.config.id, executionId }, `Starting workflow: ${this.config.id} (${executionId})`);
|
|
87
|
+
this.emit('workflow:start', { workflowId: this.config.id, executionId, input: validatedInput });
|
|
88
|
+
// Trace: Workflow Execution
|
|
62
89
|
const workflowStartTime = Date.now();
|
|
63
90
|
const workflowSpan = this.traceCollector?.startSpan('workflow_execution', {
|
|
64
91
|
workflowId: this.config.id,
|
|
@@ -77,12 +104,13 @@ class Workflow {
|
|
|
77
104
|
context.metadata.currentTime = new Date();
|
|
78
105
|
// Verificar condição se existir (antes de contar como executado)
|
|
79
106
|
if (step.condition && !step.condition(context)) {
|
|
80
|
-
|
|
107
|
+
this.logger.debug({ stepId: step.id }, `Step skipped: ${step.id} (condition not met)`);
|
|
108
|
+
this.emit('step:skip', { stepId: step.id, reason: 'condition not met' });
|
|
81
109
|
continue;
|
|
82
110
|
}
|
|
83
111
|
context.metadata.stepCount++;
|
|
84
|
-
|
|
85
|
-
//
|
|
112
|
+
this.logger.debug({ stepId: step.id, stepType: step.type }, `Executing step: ${step.id} (${step.type})`);
|
|
113
|
+
// Trace: Workflow Step (child of workflow_execution)
|
|
86
114
|
const stepStartTime = Date.now();
|
|
87
115
|
const stepSpan = this.traceCollector?.startSpan('workflow_step', {
|
|
88
116
|
workflowId: this.config.id,
|
|
@@ -90,20 +118,23 @@ class Workflow {
|
|
|
90
118
|
stepType: step.type,
|
|
91
119
|
stepNumber: context.metadata.stepCount,
|
|
92
120
|
totalSteps: this.config.steps.length,
|
|
93
|
-
}, workflowSpanId);
|
|
121
|
+
}, workflowSpanId);
|
|
94
122
|
stepSpan?.setInput(currentInput);
|
|
123
|
+
this.emit('step:start', { stepId: step.id, stepType: step.type, input: currentInput });
|
|
95
124
|
// Executar step com retry se configurado
|
|
96
125
|
let stepResult;
|
|
97
126
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
127
|
+
stepResult = await performance_logger_1.performanceLogger.measure(`workflow:${this.config.id}:step:${step.id}`, async () => {
|
|
128
|
+
if (step.retryConfig) {
|
|
129
|
+
return this.executeStepWithRetry(step, currentInput, context);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return this.executeStep(step, currentInput, context);
|
|
133
|
+
}
|
|
134
|
+
}, { stepType: step.type });
|
|
135
|
+
// Armazenar resultado (both Map and plain object)
|
|
136
|
+
this.saveResult(context, step.id, stepResult);
|
|
137
|
+
// Finalizar trace do step
|
|
107
138
|
stepSpan?.setOutput(stepResult);
|
|
108
139
|
stepSpan?.setMetadata({
|
|
109
140
|
workflowId: this.config.id,
|
|
@@ -125,10 +156,11 @@ class Workflow {
|
|
|
125
156
|
else {
|
|
126
157
|
currentInput = stepResult;
|
|
127
158
|
}
|
|
128
|
-
|
|
159
|
+
this.logger.debug({ stepId: step.id }, `Step completed: ${step.id}`);
|
|
160
|
+
this.emit('step:complete', { stepId: step.id, output: stepResult, durationMs: Date.now() - stepStartTime });
|
|
129
161
|
}
|
|
130
162
|
catch (stepError) {
|
|
131
|
-
//
|
|
163
|
+
// Trace de erro do step
|
|
132
164
|
stepSpan?.setMetadata({
|
|
133
165
|
workflowId: this.config.id,
|
|
134
166
|
stepId: step.id,
|
|
@@ -139,23 +171,27 @@ class Workflow {
|
|
|
139
171
|
error: stepError instanceof Error ? stepError.message : 'Unknown error',
|
|
140
172
|
});
|
|
141
173
|
stepSpan?.finish();
|
|
174
|
+
this.emit('step:error', { stepId: step.id, error: stepError instanceof Error ? stepError.message : 'Unknown error' });
|
|
142
175
|
throw stepError;
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
// Preparar output final com todos os stepResults
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
const allStepResults = Object.fromEntries(context.stepResults);
|
|
180
|
+
const workflowOutput = this.config.outputTransform
|
|
181
|
+
? this.config.outputTransform(allStepResults, context.input)
|
|
182
|
+
: {
|
|
183
|
+
...currentInput,
|
|
184
|
+
_workflowMetadata: {
|
|
185
|
+
workflowId: this.config.id,
|
|
186
|
+
executionId,
|
|
187
|
+
stepsExecuted: context.metadata.stepCount,
|
|
188
|
+
totalDuration: Date.now() - context.metadata.startTime.getTime(),
|
|
189
|
+
stepResults: allStepResults,
|
|
190
|
+
}
|
|
191
|
+
};
|
|
156
192
|
// Validar output final
|
|
157
193
|
const finalOutput = this.config.outputSchema.parse(workflowOutput);
|
|
158
|
-
//
|
|
194
|
+
// Finalizar trace do workflow
|
|
159
195
|
workflowSpan?.setOutput({
|
|
160
196
|
stepsExecuted: context.metadata.stepCount,
|
|
161
197
|
totalSteps: this.config.steps.length,
|
|
@@ -172,12 +208,13 @@ class Workflow {
|
|
|
172
208
|
workflowSpan?.finish();
|
|
173
209
|
// Forçar flush dos traces
|
|
174
210
|
await this.traceCollector?.flush();
|
|
175
|
-
|
|
211
|
+
this.logger.info({ workflowId: this.config.id, executionId }, `Workflow completed: ${this.config.id}`);
|
|
212
|
+
this.emit('workflow:complete', { executionId, output: finalOutput, durationMs: Date.now() - workflowStartTime });
|
|
176
213
|
return finalOutput;
|
|
177
214
|
}
|
|
178
215
|
catch (error) {
|
|
179
|
-
|
|
180
|
-
//
|
|
216
|
+
this.logger.error({ workflowId: this.config.id, executionId, err: error }, `Workflow failed: ${this.config.id}`);
|
|
217
|
+
// Trace de erro do workflow
|
|
181
218
|
workflowSpan?.setMetadata({
|
|
182
219
|
workflowId: this.config.id,
|
|
183
220
|
workflowName: this.config.name || this.config.id,
|
|
@@ -190,6 +227,7 @@ class Workflow {
|
|
|
190
227
|
});
|
|
191
228
|
workflowSpan?.finish();
|
|
192
229
|
await this.traceCollector?.flush();
|
|
230
|
+
this.emit('workflow:error', { executionId, error: error instanceof Error ? error.message : 'Unknown error', durationMs: Date.now() - workflowStartTime });
|
|
193
231
|
throw error;
|
|
194
232
|
}
|
|
195
233
|
}
|
|
@@ -206,6 +244,10 @@ class Workflow {
|
|
|
206
244
|
return this.executeConditionStep(step.config, input, context);
|
|
207
245
|
case 'parallel':
|
|
208
246
|
return this.executeParallelStep(step.config, input, context);
|
|
247
|
+
case 'switch':
|
|
248
|
+
return this.executeSwitchStep(step.config, input, context);
|
|
249
|
+
case 'foreach':
|
|
250
|
+
return this.executeForeachStep(step.config, input, context);
|
|
209
251
|
default:
|
|
210
252
|
throw new Error(`Unknown step type: ${step.type}`);
|
|
211
253
|
}
|
|
@@ -227,7 +269,7 @@ class Workflow {
|
|
|
227
269
|
}
|
|
228
270
|
if (attempt < retryConfig.maxAttempts) {
|
|
229
271
|
const delay = this.calculateRetryDelay(retryConfig, attempt);
|
|
230
|
-
|
|
272
|
+
this.logger.info({ stepId: step.id, attempt, maxAttempts: retryConfig.maxAttempts, delay }, `Retry ${attempt}/${retryConfig.maxAttempts} in ${delay}ms: ${step.id}`);
|
|
231
273
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
232
274
|
}
|
|
233
275
|
}
|
|
@@ -265,6 +307,7 @@ class Workflow {
|
|
|
265
307
|
prompt = this.interpolateTemplate(config.promptTemplate, {
|
|
266
308
|
input,
|
|
267
309
|
context: Object.fromEntries(context.stepResults),
|
|
310
|
+
results: context.results || Object.fromEntries(context.stepResults),
|
|
268
311
|
workflow: { id: context.workflowId, step: context.currentStep }
|
|
269
312
|
});
|
|
270
313
|
}
|
|
@@ -319,6 +362,7 @@ class Workflow {
|
|
|
319
362
|
let currentInput = input;
|
|
320
363
|
for (const step of pathSteps) {
|
|
321
364
|
currentInput = await this.executeStep(step, currentInput, context);
|
|
365
|
+
this.saveResult(context, step.id, currentInput);
|
|
322
366
|
}
|
|
323
367
|
// Quando unwrapResult=true, retorna apenas o resultado interno (mais natural para downstream)
|
|
324
368
|
if (config.unwrapResult) {
|
|
@@ -336,12 +380,14 @@ class Workflow {
|
|
|
336
380
|
if (config.waitForAll !== false) {
|
|
337
381
|
// Aguardar todos completarem
|
|
338
382
|
const results = await Promise.all(promises);
|
|
383
|
+
const resultsMap = {};
|
|
384
|
+
config.steps.forEach((step, i) => {
|
|
385
|
+
resultsMap[step.id] = results[i];
|
|
386
|
+
this.saveResult(context, step.id, results[i]);
|
|
387
|
+
});
|
|
339
388
|
return {
|
|
340
389
|
type: 'parallel',
|
|
341
|
-
results:
|
|
342
|
-
acc[config.steps[index].id] = result;
|
|
343
|
-
return acc;
|
|
344
|
-
}, {}),
|
|
390
|
+
results: resultsMap,
|
|
345
391
|
};
|
|
346
392
|
}
|
|
347
393
|
else {
|
|
@@ -356,6 +402,60 @@ class Workflow {
|
|
|
356
402
|
};
|
|
357
403
|
}
|
|
358
404
|
}
|
|
405
|
+
// Executar switch step
|
|
406
|
+
async executeSwitchStep(config, input, context) {
|
|
407
|
+
const value = config.evaluate(context);
|
|
408
|
+
const handler = config.cases[value] ?? config.defaultCase;
|
|
409
|
+
if (!handler) {
|
|
410
|
+
throw new Error(`Switch step: no case matched for "${value}" and no default provided. Available cases: [${Object.keys(config.cases).join(', ')}]`);
|
|
411
|
+
}
|
|
412
|
+
this.logger.debug({ switchValue: value }, `Switch routing: "${value}"`);
|
|
413
|
+
let result;
|
|
414
|
+
if (Array.isArray(handler)) {
|
|
415
|
+
let currentInput = input;
|
|
416
|
+
for (const step of handler) {
|
|
417
|
+
currentInput = await this.executeStep(step, currentInput, context);
|
|
418
|
+
this.saveResult(context, step.id, currentInput);
|
|
419
|
+
}
|
|
420
|
+
result = currentInput;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
result = await handler(input, context);
|
|
424
|
+
}
|
|
425
|
+
if (config.unwrapResult ?? true) {
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
return { switchValue: value, caseTaken: config.cases[value] ? value : 'default', result };
|
|
429
|
+
}
|
|
430
|
+
// Executar foreach step
|
|
431
|
+
async executeForeachStep(config, input, context) {
|
|
432
|
+
const items = Array.isArray(input) ? input : [];
|
|
433
|
+
if (items.length === 0)
|
|
434
|
+
return [];
|
|
435
|
+
const concurrency = config.concurrency || 1;
|
|
436
|
+
if (concurrency === 1) {
|
|
437
|
+
const results = [];
|
|
438
|
+
for (const item of items) {
|
|
439
|
+
results.push(await config.handler(item, context));
|
|
440
|
+
}
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
443
|
+
// Concurrent with limit.
|
|
444
|
+
// The shared `index` variable is safe because Node.js is single-threaded:
|
|
445
|
+
// `index++` (read + increment) completes synchronously before the `await`,
|
|
446
|
+
// so no two workers can read the same index value.
|
|
447
|
+
const results = new Array(items.length);
|
|
448
|
+
let index = 0;
|
|
449
|
+
const runNext = async () => {
|
|
450
|
+
while (index < items.length) {
|
|
451
|
+
const currentIndex = index++;
|
|
452
|
+
results[currentIndex] = await config.handler(items[currentIndex], context);
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => runNext());
|
|
456
|
+
await Promise.all(workers);
|
|
457
|
+
return results;
|
|
458
|
+
}
|
|
359
459
|
// Interpolar template com variáveis
|
|
360
460
|
interpolateTemplate(template, variables) {
|
|
361
461
|
return template.replace(/\{\{([\w-]+(?:\.[\w-]+)*)\}\}/g, (match, path) => {
|
|
@@ -388,6 +488,231 @@ class Workflow {
|
|
|
388
488
|
getNestedValue(obj, path) {
|
|
389
489
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
390
490
|
}
|
|
491
|
+
// ============================================================================
|
|
492
|
+
// GRAPH GENERATION
|
|
493
|
+
// ============================================================================
|
|
494
|
+
toGraph() {
|
|
495
|
+
const nodes = [];
|
|
496
|
+
const edges = [];
|
|
497
|
+
let previousExitNodes = [];
|
|
498
|
+
for (const step of this.config.steps) {
|
|
499
|
+
const entryNodes = this.addStepToGraph(step, nodes, edges);
|
|
500
|
+
// Connect previous step's exit nodes to this step's entry nodes
|
|
501
|
+
for (const prev of previousExitNodes) {
|
|
502
|
+
for (const entry of entryNodes) {
|
|
503
|
+
edges.push({ source: prev, target: entry });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
previousExitNodes = this.getExitNodes(step);
|
|
507
|
+
}
|
|
508
|
+
return { id: this.config.id, name: this.config.name || this.config.id, nodes, edges };
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Adds a step (and its sub-steps) to the graph. Returns the entry node IDs.
|
|
512
|
+
*/
|
|
513
|
+
addStepToGraph(step, nodes, edges) {
|
|
514
|
+
const nodeType = this.stepTypeToNodeType(step.type);
|
|
515
|
+
if (step.type === 'condition') {
|
|
516
|
+
const config = step.config;
|
|
517
|
+
// Add the branch decision node
|
|
518
|
+
nodes.push({
|
|
519
|
+
id: step.id,
|
|
520
|
+
type: nodeType,
|
|
521
|
+
label: step.name || step.id,
|
|
522
|
+
...(step.description && { metadata: { description: step.description } }),
|
|
523
|
+
});
|
|
524
|
+
// True path sub-nodes
|
|
525
|
+
let prevIds = [step.id];
|
|
526
|
+
for (const sub of config.truePath) {
|
|
527
|
+
const subEntries = this.addStepToGraph(sub, nodes, edges);
|
|
528
|
+
for (const prev of prevIds) {
|
|
529
|
+
for (const entry of subEntries) {
|
|
530
|
+
edges.push({
|
|
531
|
+
source: prev,
|
|
532
|
+
target: entry,
|
|
533
|
+
label: prev === step.id ? 'true' : undefined,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
prevIds = this.getExitNodes(sub);
|
|
538
|
+
}
|
|
539
|
+
// False path sub-nodes
|
|
540
|
+
if (config.falsePath && config.falsePath.length > 0) {
|
|
541
|
+
let falsePrevIds = [step.id];
|
|
542
|
+
for (const sub of config.falsePath) {
|
|
543
|
+
const subEntries = this.addStepToGraph(sub, nodes, edges);
|
|
544
|
+
for (const prev of falsePrevIds) {
|
|
545
|
+
for (const entry of subEntries) {
|
|
546
|
+
edges.push({
|
|
547
|
+
source: prev,
|
|
548
|
+
target: entry,
|
|
549
|
+
label: prev === step.id ? 'false' : undefined,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
falsePrevIds = this.getExitNodes(sub);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return [step.id];
|
|
557
|
+
}
|
|
558
|
+
if (step.type === 'switch') {
|
|
559
|
+
const config = step.config;
|
|
560
|
+
nodes.push({
|
|
561
|
+
id: step.id,
|
|
562
|
+
type: nodeType,
|
|
563
|
+
label: step.name || step.id,
|
|
564
|
+
...(step.description && { metadata: { description: step.description } }),
|
|
565
|
+
});
|
|
566
|
+
// Add case nodes
|
|
567
|
+
for (const [caseName, handler] of Object.entries(config.cases)) {
|
|
568
|
+
if (Array.isArray(handler)) {
|
|
569
|
+
// handler is WorkflowStep[]
|
|
570
|
+
let prevIds = [step.id];
|
|
571
|
+
for (const sub of handler) {
|
|
572
|
+
const subEntries = this.addStepToGraph(sub, nodes, edges);
|
|
573
|
+
for (const prev of prevIds) {
|
|
574
|
+
for (const entry of subEntries) {
|
|
575
|
+
edges.push({
|
|
576
|
+
source: prev,
|
|
577
|
+
target: entry,
|
|
578
|
+
label: prev === step.id ? caseName : undefined,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
prevIds = this.getExitNodes(sub);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
// handler is a function — create virtual node
|
|
587
|
+
const virtualId = `${step.id}:${caseName}`;
|
|
588
|
+
nodes.push({ id: virtualId, type: 'function', label: caseName });
|
|
589
|
+
edges.push({ source: step.id, target: virtualId, label: caseName });
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Default case
|
|
593
|
+
if (config.defaultCase) {
|
|
594
|
+
if (Array.isArray(config.defaultCase)) {
|
|
595
|
+
let prevIds = [step.id];
|
|
596
|
+
for (const sub of config.defaultCase) {
|
|
597
|
+
const subEntries = this.addStepToGraph(sub, nodes, edges);
|
|
598
|
+
for (const prev of prevIds) {
|
|
599
|
+
for (const entry of subEntries) {
|
|
600
|
+
edges.push({
|
|
601
|
+
source: prev,
|
|
602
|
+
target: entry,
|
|
603
|
+
label: prev === step.id ? 'default' : undefined,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
prevIds = this.getExitNodes(sub);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const virtualId = `${step.id}:default`;
|
|
612
|
+
nodes.push({ id: virtualId, type: 'function', label: 'default' });
|
|
613
|
+
edges.push({ source: step.id, target: virtualId, label: 'default' });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return [step.id];
|
|
617
|
+
}
|
|
618
|
+
if (step.type === 'parallel') {
|
|
619
|
+
const config = step.config;
|
|
620
|
+
// Add container node
|
|
621
|
+
nodes.push({
|
|
622
|
+
id: step.id,
|
|
623
|
+
type: nodeType,
|
|
624
|
+
label: step.name || step.id,
|
|
625
|
+
...(step.description && { metadata: { description: step.description } }),
|
|
626
|
+
});
|
|
627
|
+
// Add child nodes
|
|
628
|
+
for (const sub of config.steps) {
|
|
629
|
+
const subEntries = this.addStepToGraph(sub, nodes, edges);
|
|
630
|
+
for (const entry of subEntries) {
|
|
631
|
+
edges.push({ source: step.id, target: entry });
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return [step.id];
|
|
635
|
+
}
|
|
636
|
+
// Simple step (function, agent, connector, foreach)
|
|
637
|
+
nodes.push({
|
|
638
|
+
id: step.id,
|
|
639
|
+
type: nodeType,
|
|
640
|
+
label: step.name || step.id,
|
|
641
|
+
...(step.description && { metadata: { description: step.description } }),
|
|
642
|
+
});
|
|
643
|
+
return [step.id];
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Maps step type to GraphNode type. 'function' becomes 'step' for display purposes,
|
|
647
|
+
* but we keep the original type for graph fidelity.
|
|
648
|
+
*/
|
|
649
|
+
stepTypeToNodeType(type) {
|
|
650
|
+
return type;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Returns the IDs of nodes that "exit" a complex step — i.e., the last nodes
|
|
654
|
+
* in each branch that need to connect to the next sequential step.
|
|
655
|
+
*/
|
|
656
|
+
getExitNodes(step) {
|
|
657
|
+
if (step.type === 'condition') {
|
|
658
|
+
const config = step.config;
|
|
659
|
+
const exits = [];
|
|
660
|
+
if (config.truePath.length > 0) {
|
|
661
|
+
const last = config.truePath[config.truePath.length - 1];
|
|
662
|
+
exits.push(...this.getExitNodes(last));
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
exits.push(step.id);
|
|
666
|
+
}
|
|
667
|
+
if (config.falsePath && config.falsePath.length > 0) {
|
|
668
|
+
const last = config.falsePath[config.falsePath.length - 1];
|
|
669
|
+
exits.push(...this.getExitNodes(last));
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
exits.push(step.id);
|
|
673
|
+
}
|
|
674
|
+
return exits;
|
|
675
|
+
}
|
|
676
|
+
if (step.type === 'switch') {
|
|
677
|
+
const config = step.config;
|
|
678
|
+
const exits = [];
|
|
679
|
+
for (const [caseName, handler] of Object.entries(config.cases)) {
|
|
680
|
+
if (Array.isArray(handler) && handler.length > 0) {
|
|
681
|
+
const last = handler[handler.length - 1];
|
|
682
|
+
exits.push(...this.getExitNodes(last));
|
|
683
|
+
}
|
|
684
|
+
else if (!Array.isArray(handler)) {
|
|
685
|
+
exits.push(`${step.id}:${caseName}`);
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
exits.push(step.id);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (config.defaultCase) {
|
|
692
|
+
if (Array.isArray(config.defaultCase) && config.defaultCase.length > 0) {
|
|
693
|
+
const last = config.defaultCase[config.defaultCase.length - 1];
|
|
694
|
+
exits.push(...this.getExitNodes(last));
|
|
695
|
+
}
|
|
696
|
+
else if (!Array.isArray(config.defaultCase)) {
|
|
697
|
+
exits.push(`${step.id}:default`);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
exits.push(step.id);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return exits;
|
|
704
|
+
}
|
|
705
|
+
if (step.type === 'parallel') {
|
|
706
|
+
const config = step.config;
|
|
707
|
+
const exits = [];
|
|
708
|
+
for (const sub of config.steps) {
|
|
709
|
+
exits.push(...this.getExitNodes(sub));
|
|
710
|
+
}
|
|
711
|
+
return exits.length > 0 ? exits : [step.id];
|
|
712
|
+
}
|
|
713
|
+
// Simple step
|
|
714
|
+
return [step.id];
|
|
715
|
+
}
|
|
391
716
|
// Getters
|
|
392
717
|
get id() {
|
|
393
718
|
return this.config.id;
|
|
@@ -485,7 +810,7 @@ class WorkflowBuilder {
|
|
|
485
810
|
return this.then(parallelStep);
|
|
486
811
|
}
|
|
487
812
|
// Adicionar conditional step
|
|
488
|
-
condition(id, condition, truePath, falsePath) {
|
|
813
|
+
condition(id, condition, truePath, falsePath, options) {
|
|
489
814
|
const conditionStep = {
|
|
490
815
|
id,
|
|
491
816
|
type: 'condition',
|
|
@@ -493,10 +818,16 @@ class WorkflowBuilder {
|
|
|
493
818
|
condition,
|
|
494
819
|
truePath,
|
|
495
820
|
falsePath,
|
|
821
|
+
unwrapResult: options?.unwrap,
|
|
496
822
|
},
|
|
497
823
|
};
|
|
498
824
|
return this.then(conditionStep);
|
|
499
825
|
}
|
|
826
|
+
// Definir transform do output final (monta o objeto final a partir dos step results)
|
|
827
|
+
output(transform) {
|
|
828
|
+
this.config = { ...this.config, outputTransform: transform };
|
|
829
|
+
return this;
|
|
830
|
+
}
|
|
500
831
|
// Finalizar e criar workflow
|
|
501
832
|
build() {
|
|
502
833
|
const workflowConfig = {
|
|
@@ -512,6 +843,159 @@ class WorkflowBuilder {
|
|
|
512
843
|
}
|
|
513
844
|
exports.WorkflowBuilder = WorkflowBuilder;
|
|
514
845
|
// ============================================================================
|
|
846
|
+
// FLOW BUILDER (V2 Fluent API)
|
|
847
|
+
// ============================================================================
|
|
848
|
+
class FlowBuilder {
|
|
849
|
+
constructor(config) {
|
|
850
|
+
this.steps = [];
|
|
851
|
+
this.stepCounter = 0;
|
|
852
|
+
this.config = config;
|
|
853
|
+
}
|
|
854
|
+
nextId(prefix) {
|
|
855
|
+
return `${prefix}_${++this.stepCounter}`;
|
|
856
|
+
}
|
|
857
|
+
step(id, handlerOrOpts) {
|
|
858
|
+
let handler;
|
|
859
|
+
let opts = {};
|
|
860
|
+
if (typeof handlerOrOpts === 'function') {
|
|
861
|
+
handler = handlerOrOpts;
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
handler = handlerOrOpts.handler;
|
|
865
|
+
opts = handlerOrOpts;
|
|
866
|
+
}
|
|
867
|
+
const step = {
|
|
868
|
+
id,
|
|
869
|
+
type: 'function',
|
|
870
|
+
config: {
|
|
871
|
+
execute: handler,
|
|
872
|
+
outputSchema: opts.outputSchema,
|
|
873
|
+
},
|
|
874
|
+
condition: opts.condition,
|
|
875
|
+
retryConfig: opts.retryConfig,
|
|
876
|
+
inputTransform: opts.inputTransform,
|
|
877
|
+
outputTransform: opts.outputTransform,
|
|
878
|
+
};
|
|
879
|
+
this.steps.push(step);
|
|
880
|
+
return this;
|
|
881
|
+
}
|
|
882
|
+
agent(id, agent, opts) {
|
|
883
|
+
const step = {
|
|
884
|
+
id,
|
|
885
|
+
type: 'agent',
|
|
886
|
+
config: {
|
|
887
|
+
agent,
|
|
888
|
+
prompt: opts?.prompt,
|
|
889
|
+
promptTemplate: opts?.promptTemplate,
|
|
890
|
+
},
|
|
891
|
+
};
|
|
892
|
+
this.steps.push(step);
|
|
893
|
+
return this;
|
|
894
|
+
}
|
|
895
|
+
branch(id, opts) {
|
|
896
|
+
const step = {
|
|
897
|
+
id,
|
|
898
|
+
type: 'condition',
|
|
899
|
+
config: {
|
|
900
|
+
condition: opts.condition,
|
|
901
|
+
truePath: this.toBranchPath(`${id}_true`, opts.onTrue),
|
|
902
|
+
falsePath: opts.onFalse ? this.toBranchPath(`${id}_false`, opts.onFalse) : undefined,
|
|
903
|
+
unwrapResult: opts.unwrap ?? true,
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
this.steps.push(step);
|
|
907
|
+
return this;
|
|
908
|
+
}
|
|
909
|
+
switch(id, opts) {
|
|
910
|
+
const step = {
|
|
911
|
+
id,
|
|
912
|
+
type: 'switch',
|
|
913
|
+
config: {
|
|
914
|
+
evaluate: opts.on,
|
|
915
|
+
cases: opts.cases,
|
|
916
|
+
defaultCase: opts.default,
|
|
917
|
+
unwrapResult: opts.unwrap ?? true,
|
|
918
|
+
},
|
|
919
|
+
};
|
|
920
|
+
this.steps.push(step);
|
|
921
|
+
return this;
|
|
922
|
+
}
|
|
923
|
+
map(transform) {
|
|
924
|
+
const id = this.nextId('map');
|
|
925
|
+
const step = {
|
|
926
|
+
id,
|
|
927
|
+
type: 'function',
|
|
928
|
+
config: {
|
|
929
|
+
execute: async (input) => transform(input),
|
|
930
|
+
},
|
|
931
|
+
};
|
|
932
|
+
this.steps.push(step);
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
foreach(id, opts) {
|
|
936
|
+
const step = {
|
|
937
|
+
id,
|
|
938
|
+
type: 'foreach',
|
|
939
|
+
config: {
|
|
940
|
+
handler: opts.handler,
|
|
941
|
+
concurrency: opts.concurrency,
|
|
942
|
+
},
|
|
943
|
+
};
|
|
944
|
+
this.steps.push(step);
|
|
945
|
+
return this;
|
|
946
|
+
}
|
|
947
|
+
connector(id, connector, resource, action, parameters) {
|
|
948
|
+
const step = {
|
|
949
|
+
id,
|
|
950
|
+
type: 'connector',
|
|
951
|
+
config: {
|
|
952
|
+
connector,
|
|
953
|
+
resource,
|
|
954
|
+
action,
|
|
955
|
+
parameters,
|
|
956
|
+
},
|
|
957
|
+
};
|
|
958
|
+
this.steps.push(step);
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
parallel(id, steps, opts) {
|
|
962
|
+
const step = {
|
|
963
|
+
id,
|
|
964
|
+
type: 'parallel',
|
|
965
|
+
config: {
|
|
966
|
+
steps,
|
|
967
|
+
waitForAll: opts?.waitForAll,
|
|
968
|
+
maxConcurrency: opts?.maxConcurrency,
|
|
969
|
+
},
|
|
970
|
+
};
|
|
971
|
+
this.steps.push(step);
|
|
972
|
+
return this;
|
|
973
|
+
}
|
|
974
|
+
output(transform) {
|
|
975
|
+
this.config = { ...this.config, outputTransform: transform };
|
|
976
|
+
return this;
|
|
977
|
+
}
|
|
978
|
+
build() {
|
|
979
|
+
return new Workflow({ ...this.config, steps: this.steps });
|
|
980
|
+
}
|
|
981
|
+
toBranchPath(defaultId, handlerOrSteps) {
|
|
982
|
+
if (Array.isArray(handlerOrSteps)) {
|
|
983
|
+
return handlerOrSteps;
|
|
984
|
+
}
|
|
985
|
+
return [{
|
|
986
|
+
id: defaultId,
|
|
987
|
+
type: 'function',
|
|
988
|
+
config: {
|
|
989
|
+
execute: handlerOrSteps,
|
|
990
|
+
},
|
|
991
|
+
}];
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
exports.FlowBuilder = FlowBuilder;
|
|
995
|
+
function flow(config) {
|
|
996
|
+
return new FlowBuilder(config);
|
|
997
|
+
}
|
|
998
|
+
// ============================================================================
|
|
515
999
|
// HELPER FUNCTIONS
|
|
516
1000
|
// ============================================================================
|
|
517
1001
|
function inferStepType(config) {
|
|
@@ -519,6 +1003,10 @@ function inferStepType(config) {
|
|
|
519
1003
|
return 'agent';
|
|
520
1004
|
if ('execute' in config)
|
|
521
1005
|
return 'function';
|
|
1006
|
+
if ('evaluate' in config)
|
|
1007
|
+
return 'switch';
|
|
1008
|
+
if ('handler' in config && !('execute' in config))
|
|
1009
|
+
return 'foreach';
|
|
522
1010
|
if ('connector' in config)
|
|
523
1011
|
return 'connector';
|
|
524
1012
|
if ('condition' in config)
|