@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.
@@ -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
- console.log(`🔄 Starting workflow: ${this.config.id} (${executionId})`);
61
- // 🔍 Trace: Workflow Execution
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
- console.log(` ⏭️ Step skipped: ${step.id} (condition not met)`);
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
- console.log(` 📍 Executing step: ${step.id} (${step.type})`);
85
- // 🔍 Trace: Workflow Step (child of workflow_execution)
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); // ✅ Child of workflow_execution
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
- if (step.retryConfig) {
99
- stepResult = await this.executeStepWithRetry(step, currentInput, context);
100
- }
101
- else {
102
- stepResult = await this.executeStep(step, currentInput, context);
103
- }
104
- // Armazenar resultado
105
- context.stepResults.set(step.id, stepResult);
106
- // 🔍 Finalizar trace do step
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
- console.log(`Step completed: ${step.id}`);
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
- // 🔍 Trace de erro do step
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 workflowOutput = {
147
- ...currentInput,
148
- _workflowMetadata: {
149
- workflowId: this.config.id,
150
- executionId,
151
- stepsExecuted: context.metadata.stepCount,
152
- totalDuration: Date.now() - context.metadata.startTime.getTime(),
153
- stepResults: Object.fromEntries(context.stepResults),
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
- // 🔍 Finalizar trace do workflow
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
- console.log(`🎉 Workflow completed: ${this.config.id}`);
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
- console.error(`❌ Workflow failed: ${this.config.id}`, error);
180
- // 🔍 Trace de erro do workflow
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
- console.log(` 🔄 Retry ${attempt}/${retryConfig.maxAttempts} in ${delay}ms: ${step.id}`);
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: results.reduce((acc, result, index) => {
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)