@lov3kaizen/agentsea-debugger 0.5.1

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.
@@ -0,0 +1,789 @@
1
+ import { nanoid } from 'nanoid';
2
+
3
+ // src/utils/helpers.ts
4
+ function generateId(prefix = "") {
5
+ const id = nanoid(12);
6
+ return prefix ? `${prefix}_${id}` : id;
7
+ }
8
+
9
+ // src/visualization/DecisionTree.ts
10
+ var DEFAULT_BUILD_OPTIONS = {
11
+ includeToolCalls: true,
12
+ allSteps: false,
13
+ maxDepth: 50,
14
+ collapseSimilar: false
15
+ };
16
+ var DEFAULT_LAYOUT_OPTIONS = {
17
+ direction: "vertical",
18
+ nodeSpacing: 50,
19
+ levelSpacing: 100,
20
+ nodeWidth: 200,
21
+ nodeHeight: 80
22
+ };
23
+ var DecisionTreeBuilder = class {
24
+ nodes = /* @__PURE__ */ new Map();
25
+ rootId;
26
+ buildOptions;
27
+ layoutOptions;
28
+ constructor(buildOptions, layoutOptions) {
29
+ this.buildOptions = {
30
+ ...DEFAULT_BUILD_OPTIONS,
31
+ ...buildOptions
32
+ };
33
+ this.layoutOptions = {
34
+ ...DEFAULT_LAYOUT_OPTIONS,
35
+ ...layoutOptions
36
+ };
37
+ }
38
+ /**
39
+ * Build tree from a recording
40
+ */
41
+ build(recording) {
42
+ this.clear();
43
+ const rootNode = this.addNode({
44
+ label: "Start",
45
+ type: "action",
46
+ stepIndex: -1,
47
+ metadata: {
48
+ agentId: recording.agentId,
49
+ agentName: recording.agentName
50
+ }
51
+ });
52
+ this.rootId = rootNode.id;
53
+ let currentParentId = rootNode.id;
54
+ let depth = 0;
55
+ for (const step of recording.steps) {
56
+ if (depth >= this.buildOptions.maxDepth) {
57
+ break;
58
+ }
59
+ const node = this.processStep(step, currentParentId, depth);
60
+ if (node) {
61
+ if (step.type === "decision") {
62
+ currentParentId = node.id;
63
+ depth++;
64
+ } else if (this.buildOptions.allSteps) {
65
+ currentParentId = node.id;
66
+ }
67
+ }
68
+ }
69
+ this.addNode({
70
+ label: recording.status === "completed" ? "Complete" : "Failed",
71
+ type: "outcome",
72
+ parentId: currentParentId,
73
+ metadata: {
74
+ status: recording.status,
75
+ durationMs: recording.durationMs
76
+ }
77
+ });
78
+ return this.export();
79
+ }
80
+ /**
81
+ * Process a step into a node
82
+ */
83
+ processStep(step, parentId, depth) {
84
+ if (step.type === "decision" && step.decision) {
85
+ return this.addDecisionNode(step, parentId, depth);
86
+ }
87
+ if (this.buildOptions.includeToolCalls && step.type === "tool-call") {
88
+ return this.addNode({
89
+ label: `Tool: ${step.toolCall?.name ?? "unknown"}`,
90
+ type: "action",
91
+ stepIndex: step.index,
92
+ parentId,
93
+ metadata: {
94
+ toolName: step.toolCall?.name,
95
+ arguments: step.toolCall?.arguments
96
+ }
97
+ });
98
+ }
99
+ if (this.buildOptions.allSteps) {
100
+ return this.addNode({
101
+ label: this.getStepLabel(step),
102
+ type: "action",
103
+ stepIndex: step.index,
104
+ parentId
105
+ });
106
+ }
107
+ return null;
108
+ }
109
+ /**
110
+ * Add a decision node with branches
111
+ */
112
+ addDecisionNode(step, parentId, _depth) {
113
+ const decision = step.decision;
114
+ const decisionNode = this.addNode({
115
+ label: decision.reason ?? "Decision",
116
+ type: "decision",
117
+ decision,
118
+ stepIndex: step.index,
119
+ parentId,
120
+ metadata: {
121
+ confidence: decision.confidence,
122
+ optionsCount: decision.options.length
123
+ }
124
+ });
125
+ for (const option of decision.options) {
126
+ const isChosen = option.id === decision.chosen.id;
127
+ this.addNode({
128
+ label: option.description,
129
+ type: "branch",
130
+ parentId: decisionNode.id,
131
+ metadata: {
132
+ optionId: option.id,
133
+ chosen: isChosen,
134
+ score: option.score
135
+ }
136
+ });
137
+ }
138
+ return decisionNode;
139
+ }
140
+ /**
141
+ * Get label for a step
142
+ */
143
+ getStepLabel(step) {
144
+ switch (step.type) {
145
+ case "input":
146
+ return "User Input";
147
+ case "prompt":
148
+ return "Send Prompt";
149
+ case "response":
150
+ return "LLM Response";
151
+ case "tool-call":
152
+ return `Call: ${step.toolCall?.name}`;
153
+ case "tool-result":
154
+ return `Result: ${step.toolCall?.success ? "OK" : "Failed"}`;
155
+ case "decision":
156
+ return "Decision";
157
+ case "error":
158
+ return `Error: ${step.error?.name}`;
159
+ default:
160
+ return step.type;
161
+ }
162
+ }
163
+ /**
164
+ * Add a node
165
+ */
166
+ addNode(options) {
167
+ const id = generateId("node");
168
+ const node = {
169
+ id,
170
+ label: options.label,
171
+ type: options.type,
172
+ decision: options.decision,
173
+ stepIndex: options.stepIndex,
174
+ children: [],
175
+ metadata: options.metadata
176
+ };
177
+ this.nodes.set(id, node);
178
+ if (options.parentId) {
179
+ const parent = this.nodes.get(options.parentId);
180
+ if (parent) {
181
+ parent.children.push(id);
182
+ }
183
+ }
184
+ if (!options.parentId && !this.rootId) {
185
+ this.rootId = id;
186
+ }
187
+ return node;
188
+ }
189
+ /**
190
+ * Get a node by ID
191
+ */
192
+ getNode(id) {
193
+ return this.nodes.get(id);
194
+ }
195
+ /**
196
+ * Get root node
197
+ */
198
+ getRoot() {
199
+ return this.rootId ? this.nodes.get(this.rootId) : void 0;
200
+ }
201
+ /**
202
+ * Get all nodes
203
+ */
204
+ getNodes() {
205
+ return Array.from(this.nodes.values());
206
+ }
207
+ /**
208
+ * Get nodes at depth
209
+ */
210
+ getNodesAtDepth(depth) {
211
+ const result = [];
212
+ const traverse = (nodeId, currentDepth) => {
213
+ const node = this.nodes.get(nodeId);
214
+ if (!node) return;
215
+ if (currentDepth === depth) {
216
+ result.push(node);
217
+ } else {
218
+ for (const childId of node.children) {
219
+ traverse(childId, currentDepth + 1);
220
+ }
221
+ }
222
+ };
223
+ if (this.rootId) {
224
+ traverse(this.rootId, 0);
225
+ }
226
+ return result;
227
+ }
228
+ /**
229
+ * Get tree depth
230
+ */
231
+ getDepth() {
232
+ let maxDepth = 0;
233
+ const traverse = (nodeId, depth) => {
234
+ maxDepth = Math.max(maxDepth, depth);
235
+ const node = this.nodes.get(nodeId);
236
+ if (node) {
237
+ for (const childId of node.children) {
238
+ traverse(childId, depth + 1);
239
+ }
240
+ }
241
+ };
242
+ if (this.rootId) {
243
+ traverse(this.rootId, 0);
244
+ }
245
+ return maxDepth;
246
+ }
247
+ /**
248
+ * Find path to node
249
+ */
250
+ findPath(nodeId) {
251
+ const path = [];
252
+ const findParent = (targetId) => {
253
+ for (const [id, node] of this.nodes) {
254
+ if (node.children.includes(targetId)) {
255
+ return id;
256
+ }
257
+ }
258
+ return null;
259
+ };
260
+ let currentId = nodeId;
261
+ while (currentId) {
262
+ const node = this.nodes.get(currentId);
263
+ if (node) {
264
+ path.unshift(node);
265
+ }
266
+ currentId = findParent(currentId);
267
+ }
268
+ return path;
269
+ }
270
+ /**
271
+ * Find nodes by step index
272
+ */
273
+ findByStepIndex(stepIndex) {
274
+ return this.getNodes().filter((n) => n.stepIndex === stepIndex);
275
+ }
276
+ /**
277
+ * Find decision nodes
278
+ */
279
+ findDecisionNodes() {
280
+ return this.getNodes().filter((n) => n.type === "decision");
281
+ }
282
+ /**
283
+ * Clear the tree
284
+ */
285
+ clear() {
286
+ this.nodes.clear();
287
+ this.rootId = void 0;
288
+ }
289
+ /**
290
+ * Export tree structure
291
+ */
292
+ export() {
293
+ const root = this.getRoot();
294
+ return {
295
+ id: generateId("tree"),
296
+ root: root ?? {
297
+ id: "empty",
298
+ label: "Empty Tree",
299
+ type: "action",
300
+ children: []
301
+ },
302
+ nodes: this.getNodes(),
303
+ metadata: {
304
+ nodeCount: this.nodes.size,
305
+ depth: this.getDepth(),
306
+ decisionCount: this.findDecisionNodes().length
307
+ }
308
+ };
309
+ }
310
+ /**
311
+ * Export to Mermaid format
312
+ */
313
+ toMermaid() {
314
+ const lines = ["graph TD"];
315
+ const traverse = (nodeId) => {
316
+ const node = this.nodes.get(nodeId);
317
+ if (!node) return;
318
+ const shape = this.getMermaidShape(node);
319
+ lines.push(` ${nodeId}${shape}`);
320
+ for (const childId of node.children) {
321
+ const child = this.nodes.get(childId);
322
+ const edgeLabel = child?.metadata?.chosen ? " --> |chosen|" : " --> ";
323
+ lines.push(` ${nodeId}${edgeLabel}${childId}`);
324
+ traverse(childId);
325
+ }
326
+ };
327
+ if (this.rootId) {
328
+ traverse(this.rootId);
329
+ }
330
+ return lines.join("\n");
331
+ }
332
+ /**
333
+ * Get Mermaid shape for node
334
+ */
335
+ getMermaidShape(node) {
336
+ const label = node.label.replace(/"/g, "'");
337
+ switch (node.type) {
338
+ case "decision":
339
+ return `{${label}}`;
340
+ case "outcome":
341
+ return `((${label}))`;
342
+ case "branch":
343
+ return `[/${label}/]`;
344
+ default:
345
+ return `[${label}]`;
346
+ }
347
+ }
348
+ /**
349
+ * Calculate layout positions
350
+ */
351
+ calculateLayout() {
352
+ const positions = /* @__PURE__ */ new Map();
353
+ const levelWidths = [];
354
+ const levelPositions = [];
355
+ const calculateLevelWidths = (nodeId, level) => {
356
+ if (!levelWidths[level]) {
357
+ levelWidths[level] = 0;
358
+ }
359
+ levelWidths[level]++;
360
+ const node = this.nodes.get(nodeId);
361
+ if (node) {
362
+ for (const childId of node.children) {
363
+ calculateLevelWidths(childId, level + 1);
364
+ }
365
+ }
366
+ };
367
+ if (this.rootId) {
368
+ calculateLevelWidths(this.rootId, 0);
369
+ }
370
+ for (let i = 0; i < levelWidths.length; i++) {
371
+ levelPositions[i] = 0;
372
+ }
373
+ const assignPositions = (nodeId, level) => {
374
+ const node = this.nodes.get(nodeId);
375
+ if (!node) return;
376
+ const levelWidth = levelWidths[level] * this.layoutOptions.nodeSpacing;
377
+ const startX = -levelWidth / 2;
378
+ const x = startX + levelPositions[level] * this.layoutOptions.nodeSpacing;
379
+ const y = level * this.layoutOptions.levelSpacing;
380
+ positions.set(nodeId, { x, y });
381
+ levelPositions[level]++;
382
+ for (const childId of node.children) {
383
+ assignPositions(childId, level + 1);
384
+ }
385
+ };
386
+ if (this.rootId) {
387
+ assignPositions(this.rootId, 0);
388
+ }
389
+ return positions;
390
+ }
391
+ };
392
+ function createDecisionTreeBuilder(buildOptions, layoutOptions) {
393
+ return new DecisionTreeBuilder(buildOptions, layoutOptions);
394
+ }
395
+
396
+ // src/visualization/FlowGraph.ts
397
+ var DEFAULT_NODE_STYLES = {
398
+ input: { fill: "#e3f2fd", shape: "rectangle" },
399
+ prompt: { fill: "#f3e5f5", shape: "rectangle" },
400
+ response: { fill: "#e8f5e9", shape: "rectangle" },
401
+ output: { fill: "#c8e6c9", shape: "rectangle" },
402
+ "tool-call": { fill: "#fff3e0", shape: "hexagon" },
403
+ "tool-result": { fill: "#fff8e1", shape: "rectangle" },
404
+ decision: { fill: "#fce4ec", shape: "diamond" },
405
+ error: { fill: "#ffebee", shape: "rectangle" },
406
+ "memory-read": { fill: "#e0f7fa", shape: "ellipse" },
407
+ "memory-write": { fill: "#e0f2f1", shape: "ellipse" },
408
+ handoff: { fill: "#ede7f6", shape: "hexagon" },
409
+ delegation: { fill: "#e8eaf6", shape: "hexagon" },
410
+ custom: { fill: "#fafafa", shape: "rectangle" }
411
+ };
412
+ var DEFAULT_BUILD_OPTIONS2 = {
413
+ groupSimilar: false,
414
+ includeDurations: true,
415
+ includeToolDetails: true,
416
+ maxNodes: 200,
417
+ nodeStyles: {}
418
+ };
419
+ var FlowGraphBuilder = class {
420
+ nodes = /* @__PURE__ */ new Map();
421
+ edges = /* @__PURE__ */ new Map();
422
+ options;
423
+ nodeStyles;
424
+ constructor(options) {
425
+ this.options = {
426
+ ...DEFAULT_BUILD_OPTIONS2,
427
+ ...options
428
+ };
429
+ this.nodeStyles = {
430
+ ...DEFAULT_NODE_STYLES,
431
+ ...options?.nodeStyles
432
+ };
433
+ }
434
+ /**
435
+ * Build graph from a recording
436
+ */
437
+ build(recording) {
438
+ this.clear();
439
+ const startNode = this.addNode({
440
+ label: "Start",
441
+ type: "input",
442
+ metadata: {
443
+ agentId: recording.agentId,
444
+ agentName: recording.agentName
445
+ }
446
+ });
447
+ let previousNodeId = startNode.id;
448
+ let nodeCount = 1;
449
+ for (const step of recording.steps) {
450
+ if (nodeCount >= this.options.maxNodes) {
451
+ break;
452
+ }
453
+ if (this.options.groupSimilar && previousNodeId) {
454
+ const prevNode = this.nodes.get(previousNodeId);
455
+ if (prevNode && prevNode.type === step.type) {
456
+ this.updateNodeForGroup(prevNode, step);
457
+ continue;
458
+ }
459
+ }
460
+ const node = this.createNodeFromStep(step);
461
+ this.nodes.set(node.id, node);
462
+ if (previousNodeId) {
463
+ this.addEdge(previousNodeId, node.id, {
464
+ durationMs: step.durationMs
465
+ });
466
+ }
467
+ previousNodeId = node.id;
468
+ nodeCount++;
469
+ }
470
+ const endNode = this.addNode({
471
+ label: recording.status === "completed" ? "Complete" : "Failed",
472
+ type: recording.status === "completed" ? "response" : "error",
473
+ metadata: {
474
+ status: recording.status,
475
+ durationMs: recording.durationMs
476
+ }
477
+ });
478
+ if (previousNodeId) {
479
+ this.addEdge(previousNodeId, endNode.id);
480
+ }
481
+ return this.export();
482
+ }
483
+ /**
484
+ * Create a node from a step
485
+ */
486
+ createNodeFromStep(step) {
487
+ const style = this.nodeStyles[step.type] ?? DEFAULT_NODE_STYLES.custom;
488
+ const node = {
489
+ id: generateId("node"),
490
+ label: this.getStepLabel(step),
491
+ type: step.type,
492
+ stepIndex: step.index,
493
+ style,
494
+ metadata: this.getStepMetadata(step)
495
+ };
496
+ return node;
497
+ }
498
+ /**
499
+ * Get label for a step
500
+ */
501
+ getStepLabel(step) {
502
+ switch (step.type) {
503
+ case "input":
504
+ return "User Input";
505
+ case "prompt":
506
+ return "Prompt";
507
+ case "response":
508
+ return "Response";
509
+ case "tool-call":
510
+ if (this.options.includeToolDetails && step.toolCall) {
511
+ return `${step.toolCall.name}()`;
512
+ }
513
+ return "Tool Call";
514
+ case "tool-result":
515
+ return step.toolCall?.success ? "Success" : "Failed";
516
+ case "decision":
517
+ return step.decision?.reason ?? "Decision";
518
+ case "error":
519
+ return step.error?.name ?? "Error";
520
+ case "memory-read":
521
+ return "Read Memory";
522
+ case "memory-write":
523
+ return "Write Memory";
524
+ case "handoff":
525
+ case "delegation":
526
+ return "Hand Off";
527
+ default:
528
+ return step.type;
529
+ }
530
+ }
531
+ /**
532
+ * Get metadata for a step
533
+ */
534
+ getStepMetadata(step) {
535
+ const metadata = {
536
+ stepIndex: step.index,
537
+ timestamp: step.timestamp
538
+ };
539
+ if (this.options.includeDurations && step.durationMs) {
540
+ metadata.durationMs = step.durationMs;
541
+ }
542
+ if (step.toolCall) {
543
+ metadata.toolName = step.toolCall.name;
544
+ if (this.options.includeToolDetails) {
545
+ metadata.toolArguments = step.toolCall.arguments;
546
+ }
547
+ }
548
+ if (step.decision) {
549
+ metadata.confidence = step.decision.confidence;
550
+ metadata.chosenOption = step.decision.chosen.description;
551
+ }
552
+ if (step.error) {
553
+ metadata.errorMessage = step.error.message;
554
+ }
555
+ return metadata;
556
+ }
557
+ /**
558
+ * Update node when grouping similar steps
559
+ */
560
+ updateNodeForGroup(node, step) {
561
+ const count = node.metadata?.groupCount ?? 1;
562
+ node.metadata = {
563
+ ...node.metadata,
564
+ groupCount: count + 1,
565
+ lastStepIndex: step.index
566
+ };
567
+ node.label = `${node.label} (${count + 1})`;
568
+ }
569
+ /**
570
+ * Add a node
571
+ */
572
+ addNode(options) {
573
+ const id = generateId("node");
574
+ const style = options.style ?? this.nodeStyles[options.type] ?? DEFAULT_NODE_STYLES.custom;
575
+ const node = {
576
+ id,
577
+ label: options.label,
578
+ type: options.type,
579
+ stepIndex: options.stepIndex,
580
+ style,
581
+ metadata: options.metadata
582
+ };
583
+ this.nodes.set(id, node);
584
+ return node;
585
+ }
586
+ /**
587
+ * Add an edge
588
+ */
589
+ addEdge(sourceId, targetId, options) {
590
+ const id = generateId("edge");
591
+ const edge = {
592
+ id,
593
+ source: sourceId,
594
+ target: targetId,
595
+ label: options?.label,
596
+ style: options?.style,
597
+ metadata: options?.durationMs ? { durationMs: options.durationMs } : void 0
598
+ };
599
+ this.edges.set(id, edge);
600
+ return edge;
601
+ }
602
+ /**
603
+ * Get a node by ID
604
+ */
605
+ getNode(id) {
606
+ return this.nodes.get(id);
607
+ }
608
+ /**
609
+ * Get all nodes
610
+ */
611
+ getNodes() {
612
+ return Array.from(this.nodes.values());
613
+ }
614
+ /**
615
+ * Get nodes by type
616
+ */
617
+ getNodesByType(type) {
618
+ return this.getNodes().filter((n) => n.type === type);
619
+ }
620
+ /**
621
+ * Get an edge by ID
622
+ */
623
+ getEdge(id) {
624
+ return this.edges.get(id);
625
+ }
626
+ /**
627
+ * Get all edges
628
+ */
629
+ getEdges() {
630
+ return Array.from(this.edges.values());
631
+ }
632
+ /**
633
+ * Get edges from a node
634
+ */
635
+ getEdgesFrom(nodeId) {
636
+ return this.getEdges().filter((e) => e.source === nodeId);
637
+ }
638
+ /**
639
+ * Get edges to a node
640
+ */
641
+ getEdgesTo(nodeId) {
642
+ return this.getEdges().filter((e) => e.target === nodeId);
643
+ }
644
+ /**
645
+ * Find path between nodes
646
+ */
647
+ findPath(startId, endId) {
648
+ const visited = /* @__PURE__ */ new Set();
649
+ const path = [];
650
+ const dfs = (currentId) => {
651
+ if (visited.has(currentId)) {
652
+ return false;
653
+ }
654
+ visited.add(currentId);
655
+ const node = this.nodes.get(currentId);
656
+ if (!node) {
657
+ return false;
658
+ }
659
+ path.push(node);
660
+ if (currentId === endId) {
661
+ return true;
662
+ }
663
+ for (const edge of this.getEdgesFrom(currentId)) {
664
+ if (dfs(edge.target)) {
665
+ return true;
666
+ }
667
+ }
668
+ path.pop();
669
+ return false;
670
+ };
671
+ return dfs(startId) ? path : null;
672
+ }
673
+ /**
674
+ * Get graph statistics
675
+ */
676
+ getStats() {
677
+ const nodesByType = {};
678
+ let totalDuration = 0;
679
+ let durationCount = 0;
680
+ for (const node of this.nodes.values()) {
681
+ nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
682
+ const duration = node.metadata?.durationMs;
683
+ if (duration) {
684
+ totalDuration += duration;
685
+ durationCount++;
686
+ }
687
+ }
688
+ return {
689
+ nodeCount: this.nodes.size,
690
+ edgeCount: this.edges.size,
691
+ nodesByType,
692
+ avgDuration: durationCount > 0 ? totalDuration / durationCount : 0
693
+ };
694
+ }
695
+ /**
696
+ * Clear the graph
697
+ */
698
+ clear() {
699
+ this.nodes.clear();
700
+ this.edges.clear();
701
+ }
702
+ /**
703
+ * Export graph structure
704
+ */
705
+ export() {
706
+ return {
707
+ id: generateId("graph"),
708
+ nodes: this.getNodes(),
709
+ edges: this.getEdges(),
710
+ metadata: this.getStats()
711
+ };
712
+ }
713
+ /**
714
+ * Export to Mermaid format
715
+ */
716
+ toMermaid() {
717
+ const lines = ["flowchart TD"];
718
+ for (const node of this.nodes.values()) {
719
+ const shape = this.getMermaidShape(node);
720
+ lines.push(` ${node.id}${shape}`);
721
+ }
722
+ for (const edge of this.edges.values()) {
723
+ const label = edge.label ? ` |${edge.label}|` : "";
724
+ const arrow = edge.style?.lineType === "dashed" ? "-.->" : "-->";
725
+ lines.push(` ${edge.source}${arrow}${label}${edge.target}`);
726
+ }
727
+ return lines.join("\n");
728
+ }
729
+ /**
730
+ * Get Mermaid shape for node
731
+ */
732
+ getMermaidShape(node) {
733
+ const label = node.label.replace(/"/g, "'");
734
+ switch (node.style?.shape) {
735
+ case "ellipse":
736
+ return `([${label}])`;
737
+ case "diamond":
738
+ return `{${label}}`;
739
+ case "hexagon":
740
+ return `{{${label}}}`;
741
+ default:
742
+ return `[${label}]`;
743
+ }
744
+ }
745
+ /**
746
+ * Export to DOT format (Graphviz)
747
+ */
748
+ toDOT() {
749
+ const lines = ["digraph G {", " rankdir=TB;"];
750
+ for (const node of this.nodes.values()) {
751
+ const shape = this.getDOTShape(node);
752
+ const fill = node.style?.fill ?? "#ffffff";
753
+ lines.push(
754
+ ` "${node.id}" [label="${node.label}", shape=${shape}, style=filled, fillcolor="${fill}"];`
755
+ );
756
+ }
757
+ for (const edge of this.edges.values()) {
758
+ const label = edge.label ? `, label="${edge.label}"` : "";
759
+ const style = edge.style?.lineType === "dashed" ? ", style=dashed" : "";
760
+ lines.push(
761
+ ` "${edge.source}" -> "${edge.target}" [${label}${style}];`
762
+ );
763
+ }
764
+ lines.push("}");
765
+ return lines.join("\n");
766
+ }
767
+ /**
768
+ * Get DOT shape for node
769
+ */
770
+ getDOTShape(node) {
771
+ switch (node.style?.shape) {
772
+ case "ellipse":
773
+ return "ellipse";
774
+ case "diamond":
775
+ return "diamond";
776
+ case "hexagon":
777
+ return "hexagon";
778
+ default:
779
+ return "box";
780
+ }
781
+ }
782
+ };
783
+ function createFlowGraphBuilder(options) {
784
+ return new FlowGraphBuilder(options);
785
+ }
786
+
787
+ export { DecisionTreeBuilder, FlowGraphBuilder, createDecisionTreeBuilder, createFlowGraphBuilder };
788
+ //# sourceMappingURL=index.js.map
789
+ //# sourceMappingURL=index.js.map