@d34dman/flowdrop 0.0.56 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +92 -0
  2. package/dist/adapters/agentspec/AgentSpecAdapter.js +658 -0
  3. package/dist/adapters/agentspec/agentAdapter.d.ts +59 -0
  4. package/dist/adapters/agentspec/agentAdapter.js +91 -0
  5. package/dist/adapters/agentspec/autoLayout.d.ts +34 -0
  6. package/dist/adapters/agentspec/autoLayout.js +127 -0
  7. package/dist/adapters/agentspec/index.d.ts +35 -0
  8. package/dist/adapters/agentspec/index.js +37 -0
  9. package/dist/adapters/agentspec/nodeTypeRegistry.d.ts +62 -0
  10. package/dist/adapters/agentspec/nodeTypeRegistry.js +589 -0
  11. package/dist/adapters/agentspec/validator.d.ts +34 -0
  12. package/dist/adapters/agentspec/validator.js +169 -0
  13. package/dist/components/ConfigForm.svelte +46 -12
  14. package/dist/components/ConfigForm.svelte.d.ts +8 -0
  15. package/dist/components/SchemaForm.svelte +34 -12
  16. package/dist/components/SchemaForm.svelte.d.ts +8 -0
  17. package/dist/components/form/FormFieldset.svelte +142 -0
  18. package/dist/components/form/FormFieldset.svelte.d.ts +11 -0
  19. package/dist/components/form/FormUISchemaRenderer.svelte +140 -0
  20. package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +32 -0
  21. package/dist/components/form/index.d.ts +2 -0
  22. package/dist/components/form/index.js +3 -0
  23. package/dist/config/agentSpecEndpoints.d.ts +70 -0
  24. package/dist/config/agentSpecEndpoints.js +65 -0
  25. package/dist/config/endpoints.d.ts +6 -0
  26. package/dist/core/index.d.ts +17 -1
  27. package/dist/core/index.js +17 -0
  28. package/dist/form/index.d.ts +2 -0
  29. package/dist/form/index.js +3 -0
  30. package/dist/helpers/workflowEditorHelper.d.ts +24 -0
  31. package/dist/helpers/workflowEditorHelper.js +55 -0
  32. package/dist/services/agentSpecExecutionService.d.ts +106 -0
  33. package/dist/services/agentSpecExecutionService.js +333 -0
  34. package/dist/types/agentspec.d.ts +318 -0
  35. package/dist/types/agentspec.js +48 -0
  36. package/dist/types/events.d.ts +28 -1
  37. package/dist/types/index.d.ts +13 -0
  38. package/dist/types/index.js +1 -0
  39. package/dist/types/uischema.d.ts +144 -0
  40. package/dist/types/uischema.js +51 -0
  41. package/dist/utils/uischema.d.ts +52 -0
  42. package/dist/utils/uischema.js +88 -0
  43. package/package.json +1 -1
@@ -0,0 +1,658 @@
1
+ /**
2
+ * Agent Spec Adapter — Bidirectional conversion between FlowDrop and Agent Spec
3
+ *
4
+ * Converts between FlowDrop's StandardWorkflow format and Oracle's Open Agent Spec
5
+ * JSON format. Handles the key structural differences:
6
+ * - Unified edges (FlowDrop) ↔ control-flow + data-flow edges (Agent Spec)
7
+ * - Node IDs (FlowDrop) ↔ node names (Agent Spec)
8
+ * - Visual positions (FlowDrop) ↔ no positions (Agent Spec)
9
+ *
10
+ * @see https://github.com/oracle/agent-spec
11
+ */
12
+ import { getAgentSpecNodeMetadata, createAgentSpecNodeMetadata, extractComponentType, AGENTSPEC_NAMESPACE } from './nodeTypeRegistry.js';
13
+ import { computeAutoLayout } from './autoLayout.js';
14
+ import { v4 as uuidv4 } from 'uuid';
15
+ // ============================================================================
16
+ // Handle ID Helpers
17
+ // ============================================================================
18
+ /**
19
+ * Build a FlowDrop handle ID from node ID, direction, and port ID.
20
+ * Format: `${nodeId}-${direction}-${portId}`
21
+ */
22
+ function buildHandleId(nodeId, direction, portId) {
23
+ return `${nodeId}-${direction}-${portId}`;
24
+ }
25
+ /**
26
+ * Extract port ID from a FlowDrop handle ID.
27
+ * Handles format: `${nodeId}-output-${portId}` or `${nodeId}-input-${portId}`
28
+ */
29
+ function extractPortId(handleId) {
30
+ if (!handleId)
31
+ return null;
32
+ const outputMatch = handleId.lastIndexOf('-output-');
33
+ if (outputMatch !== -1) {
34
+ return handleId.substring(outputMatch + '-output-'.length);
35
+ }
36
+ const inputMatch = handleId.lastIndexOf('-input-');
37
+ if (inputMatch !== -1) {
38
+ return handleId.substring(inputMatch + '-input-'.length);
39
+ }
40
+ // Short format: the handleId IS the port ID
41
+ return handleId;
42
+ }
43
+ /**
44
+ * Extract direction from a FlowDrop handle ID.
45
+ */
46
+ function extractDirection(handleId) {
47
+ if (!handleId)
48
+ return null;
49
+ if (handleId.lastIndexOf('-output-') !== -1)
50
+ return 'output';
51
+ if (handleId.lastIndexOf('-input-') !== -1)
52
+ return 'input';
53
+ return null;
54
+ }
55
+ // ============================================================================
56
+ // Property ↔ Port Conversion
57
+ // ============================================================================
58
+ /**
59
+ * Convert an Agent Spec Property to a FlowDrop NodePort.
60
+ */
61
+ function agentSpecPropertyToNodePort(prop, portType) {
62
+ // Map JSON Schema types to FlowDrop data types
63
+ let dataType;
64
+ switch (prop.type) {
65
+ case 'string':
66
+ dataType = 'string';
67
+ break;
68
+ case 'number':
69
+ case 'float':
70
+ dataType = 'number';
71
+ break;
72
+ case 'integer':
73
+ dataType = 'number';
74
+ break;
75
+ case 'boolean':
76
+ dataType = 'boolean';
77
+ break;
78
+ case 'array':
79
+ dataType = 'array';
80
+ break;
81
+ case 'object':
82
+ dataType = 'json';
83
+ break;
84
+ default:
85
+ dataType = 'mixed';
86
+ }
87
+ return {
88
+ id: prop.title,
89
+ name: prop.title,
90
+ type: portType,
91
+ dataType,
92
+ required: false,
93
+ description: prop.description
94
+ };
95
+ }
96
+ /**
97
+ * Convert a FlowDrop NodePort to an Agent Spec Property.
98
+ */
99
+ function nodePortToAgentSpecProperty(port) {
100
+ // Map FlowDrop data types to JSON Schema types
101
+ let type;
102
+ switch (port.dataType) {
103
+ case 'string':
104
+ type = 'string';
105
+ break;
106
+ case 'number':
107
+ case 'float':
108
+ type = 'number';
109
+ break;
110
+ case 'integer':
111
+ type = 'integer';
112
+ break;
113
+ case 'boolean':
114
+ type = 'boolean';
115
+ break;
116
+ case 'array':
117
+ type = 'array';
118
+ break;
119
+ case 'json':
120
+ case 'object':
121
+ type = 'object';
122
+ break;
123
+ default:
124
+ type = 'string';
125
+ }
126
+ const prop = {
127
+ title: port.id,
128
+ type
129
+ };
130
+ if (port.description)
131
+ prop.description = port.description;
132
+ if (port.defaultValue !== undefined)
133
+ prop.default = port.defaultValue;
134
+ return prop;
135
+ }
136
+ // ============================================================================
137
+ // AgentSpecAdapter
138
+ // ============================================================================
139
+ export class AgentSpecAdapter {
140
+ // ========================================================================
141
+ // FlowDrop → Agent Spec (Export)
142
+ // ========================================================================
143
+ /**
144
+ * Convert a FlowDrop StandardWorkflow to an Agent Spec Flow.
145
+ *
146
+ * Handles:
147
+ * - Node conversion with config → node attributes
148
+ * - Edge splitting into control-flow and data-flow
149
+ * - Position preservation in metadata
150
+ * - Gateway branch → from_branch mapping
151
+ */
152
+ toAgentSpec(workflow) {
153
+ // Build node ID → name mapping
154
+ // Agent Spec uses node names as references; FlowDrop uses structured IDs
155
+ const nodeIdToName = new Map();
156
+ for (const node of workflow.nodes) {
157
+ const name = this.resolveNodeName(node);
158
+ nodeIdToName.set(node.id, name);
159
+ }
160
+ // Convert nodes
161
+ const agentSpecNodes = workflow.nodes.map((node) => this.convertNodeToAgentSpec(node, nodeIdToName));
162
+ // Split edges into control-flow and data-flow
163
+ const controlFlowEdges = [];
164
+ const dataFlowEdges = [];
165
+ for (const edge of workflow.edges) {
166
+ const sourceNode = workflow.nodes.find((n) => n.id === edge.source);
167
+ if (!sourceNode)
168
+ continue;
169
+ const sourcePortId = extractPortId(edge.sourceHandle);
170
+ const sourcePortDataType = this.getSourcePortDataType(sourceNode, sourcePortId);
171
+ if (sourcePortDataType === 'trigger') {
172
+ controlFlowEdges.push(this.convertToControlFlowEdge(edge, sourceNode, nodeIdToName));
173
+ }
174
+ else {
175
+ dataFlowEdges.push(this.convertToDataFlowEdge(edge, nodeIdToName));
176
+ }
177
+ }
178
+ // Find start node
179
+ const startNodeName = this.findStartNodeName(agentSpecNodes, nodeIdToName);
180
+ return {
181
+ component_type: 'flow',
182
+ name: workflow.name,
183
+ description: workflow.description,
184
+ start_node: startNodeName,
185
+ nodes: agentSpecNodes,
186
+ control_flow_connections: controlFlowEdges,
187
+ data_flow_connections: dataFlowEdges.length > 0 ? dataFlowEdges : null,
188
+ metadata: {
189
+ 'flowdrop:workflow_id': workflow.id,
190
+ 'flowdrop:version': workflow.metadata?.version,
191
+ ...(workflow.metadata?.author ? { 'flowdrop:author': workflow.metadata.author } : {}),
192
+ ...(workflow.metadata?.tags ? { 'flowdrop:tags': workflow.metadata.tags } : {})
193
+ }
194
+ };
195
+ }
196
+ /**
197
+ * Export a FlowDrop StandardWorkflow as Agent Spec JSON string.
198
+ */
199
+ exportJSON(workflow) {
200
+ return JSON.stringify(this.toAgentSpec(workflow), null, 2);
201
+ }
202
+ // ========================================================================
203
+ // Agent Spec → FlowDrop (Import)
204
+ // ========================================================================
205
+ /**
206
+ * Convert an Agent Spec Flow to a FlowDrop StandardWorkflow.
207
+ *
208
+ * Handles:
209
+ * - Auto-layout (Agent Spec has no positions)
210
+ * - Edge merging (control-flow + data-flow → unified edges)
211
+ * - Node type mapping via registry
212
+ * - Component type preservation in extensions
213
+ */
214
+ fromAgentSpec(agentSpecFlow) {
215
+ // Compute positions for nodes
216
+ const positions = computeAutoLayout(agentSpecFlow);
217
+ // Build name → FlowDrop node ID mapping
218
+ const nameToNodeId = new Map();
219
+ const nodeCountByType = new Map();
220
+ for (const asNode of agentSpecFlow.nodes) {
221
+ const typeId = `${AGENTSPEC_NAMESPACE}.${asNode.component_type}`;
222
+ const count = (nodeCountByType.get(typeId) || 0) + 1;
223
+ nodeCountByType.set(typeId, count);
224
+ const nodeId = `${typeId}.${count}`;
225
+ nameToNodeId.set(asNode.name, nodeId);
226
+ }
227
+ // Convert nodes
228
+ const nodes = agentSpecFlow.nodes.map((asNode) => {
229
+ const nodeId = nameToNodeId.get(asNode.name);
230
+ const position = positions.get(asNode.name) || { x: 0, y: 0 };
231
+ return this.convertNodeFromAgentSpec(asNode, nodeId, position);
232
+ });
233
+ // Convert edges (merge control-flow + data-flow into unified edges)
234
+ const edges = [];
235
+ // Control-flow edges → trigger port connections
236
+ for (const cfEdge of agentSpecFlow.control_flow_connections) {
237
+ const edge = this.convertFromControlFlowEdge(cfEdge, nameToNodeId, nodes);
238
+ if (edge)
239
+ edges.push(edge);
240
+ }
241
+ // Data-flow edges → data port connections
242
+ if (agentSpecFlow.data_flow_connections) {
243
+ for (const dfEdge of agentSpecFlow.data_flow_connections) {
244
+ const edge = this.convertFromDataFlowEdge(dfEdge, nameToNodeId);
245
+ if (edge)
246
+ edges.push(edge);
247
+ }
248
+ }
249
+ return {
250
+ id: agentSpecFlow.metadata?.['flowdrop:workflow_id'] || uuidv4(),
251
+ name: agentSpecFlow.name,
252
+ description: agentSpecFlow.description,
253
+ nodes,
254
+ edges,
255
+ metadata: {
256
+ version: agentSpecFlow.metadata?.['flowdrop:version'] || '1.0.0',
257
+ createdAt: new Date().toISOString(),
258
+ updatedAt: new Date().toISOString(),
259
+ author: agentSpecFlow.metadata?.['flowdrop:author'],
260
+ tags: agentSpecFlow.metadata?.['flowdrop:tags']
261
+ }
262
+ };
263
+ }
264
+ /**
265
+ * Import an Agent Spec flow from a JSON string.
266
+ */
267
+ importJSON(json) {
268
+ const parsed = JSON.parse(json);
269
+ return this.fromAgentSpec(parsed);
270
+ }
271
+ // ========================================================================
272
+ // Node Conversion (Private)
273
+ // ========================================================================
274
+ /**
275
+ * Resolve a stable name for a FlowDrop node in Agent Spec.
276
+ * Uses the node's config instanceTitle, label, or falls back to ID.
277
+ */
278
+ resolveNodeName(node) {
279
+ const instanceTitle = node.data.config?.instanceTitle;
280
+ if (instanceTitle)
281
+ return instanceTitle;
282
+ return node.data.label || node.id;
283
+ }
284
+ /**
285
+ * Convert a FlowDrop StandardNode to an Agent Spec node.
286
+ */
287
+ convertNodeToAgentSpec(node, nodeIdToName) {
288
+ const componentType = this.resolveComponentType(node);
289
+ const name = nodeIdToName.get(node.id) || node.id;
290
+ // Convert data ports (skip trigger/tool ports — those are handled as edges)
291
+ const dataInputs = node.data.metadata.inputs
292
+ .filter((p) => p.dataType !== 'trigger' && p.dataType !== 'tool')
293
+ .map((p) => nodePortToAgentSpecProperty(p));
294
+ const dataOutputs = node.data.metadata.outputs
295
+ .filter((p) => p.dataType !== 'trigger' && p.dataType !== 'tool')
296
+ .map((p) => nodePortToAgentSpecProperty(p));
297
+ // Build base node
298
+ const base = {
299
+ component_type: componentType,
300
+ name,
301
+ description: node.data.metadata.description || undefined,
302
+ inputs: dataInputs.length > 0 ? dataInputs : undefined,
303
+ outputs: dataOutputs.length > 0 ? dataOutputs : undefined,
304
+ metadata: {
305
+ 'flowdrop:position': node.position,
306
+ 'flowdrop:node_id': node.id,
307
+ 'flowdrop:node_type_id': node.data.metadata.id
308
+ }
309
+ };
310
+ // Add type-specific attributes from config
311
+ return this.addNodeSpecificAttributes(base, node);
312
+ }
313
+ /**
314
+ * Add Agent Spec type-specific attributes from FlowDrop config.
315
+ */
316
+ addNodeSpecificAttributes(asNode, fdNode) {
317
+ const config = fdNode.data.config || {};
318
+ switch (asNode.component_type) {
319
+ case 'llm_node': {
320
+ const llmNode = asNode;
321
+ if (config.prompt_template)
322
+ llmNode.prompt_template = config.prompt_template;
323
+ if (config.system_prompt)
324
+ llmNode.system_prompt = config.system_prompt;
325
+ if (config.llm_config_ref)
326
+ llmNode.llm_config = config.llm_config_ref;
327
+ return llmNode;
328
+ }
329
+ case 'branching_node': {
330
+ const branchNode = asNode;
331
+ const branches = config.branches;
332
+ branchNode.branches = branches
333
+ ? branches.map((b) => ({
334
+ name: b.name,
335
+ condition: b.condition || undefined,
336
+ description: b.description || undefined
337
+ }))
338
+ : [];
339
+ return branchNode;
340
+ }
341
+ case 'api_node': {
342
+ const apiNode = asNode;
343
+ if (config.endpoint)
344
+ apiNode.endpoint = config.endpoint;
345
+ if (config.method)
346
+ apiNode.method = config.method;
347
+ if (config.headers) {
348
+ try {
349
+ apiNode.headers =
350
+ typeof config.headers === 'string'
351
+ ? JSON.parse(config.headers)
352
+ : config.headers;
353
+ }
354
+ catch {
355
+ // Ignore parse errors
356
+ }
357
+ }
358
+ return apiNode;
359
+ }
360
+ case 'agent_node': {
361
+ const agentNode = asNode;
362
+ if (config.agent_ref)
363
+ agentNode.agent = config.agent_ref;
364
+ return agentNode;
365
+ }
366
+ case 'flow_node': {
367
+ const flowNode = asNode;
368
+ if (config.flow_ref)
369
+ flowNode.flow = config.flow_ref;
370
+ return flowNode;
371
+ }
372
+ case 'map_node': {
373
+ const mapNode = asNode;
374
+ if (config.input_collection)
375
+ mapNode.input_collection = config.input_collection;
376
+ if (config.output_collection)
377
+ mapNode.output_collection = config.output_collection;
378
+ if (config.map_flow_ref)
379
+ mapNode.map_flow = config.map_flow_ref;
380
+ return mapNode;
381
+ }
382
+ case 'tool_node': {
383
+ const toolNode = asNode;
384
+ if (config.tool_ref)
385
+ toolNode.tool = config.tool_ref;
386
+ return toolNode;
387
+ }
388
+ default:
389
+ return asNode;
390
+ }
391
+ }
392
+ /**
393
+ * Resolve Agent Spec component type from a FlowDrop node.
394
+ */
395
+ resolveComponentType(node) {
396
+ // Check extensions first (round-trip preservation)
397
+ const ext = node.data.metadata.extensions?.['agentspec:component_type'];
398
+ if (ext && typeof ext === 'string') {
399
+ return ext;
400
+ }
401
+ // Infer from FlowDrop node type ID
402
+ const fromId = extractComponentType(node.data.metadata.id);
403
+ if (fromId)
404
+ return fromId;
405
+ // Infer from FlowDrop visual type + category
406
+ const nodeType = node.data.metadata.type;
407
+ const category = node.data.metadata.category;
408
+ if (nodeType === 'terminal' && category === 'triggers')
409
+ return 'start_node';
410
+ if (nodeType === 'terminal' && category === 'outputs')
411
+ return 'end_node';
412
+ if (nodeType === 'gateway')
413
+ return 'branching_node';
414
+ if (nodeType === 'tool')
415
+ return 'tool_node';
416
+ if (category === 'ai' || category === 'models')
417
+ return 'llm_node';
418
+ if (category === 'agents')
419
+ return 'agent_node';
420
+ if (category === 'data')
421
+ return 'api_node';
422
+ // Default fallback
423
+ return 'llm_node';
424
+ }
425
+ /**
426
+ * Convert an Agent Spec node to a FlowDrop StandardNode.
427
+ */
428
+ convertNodeFromAgentSpec(asNode, nodeId, position) {
429
+ // Restore position from metadata if available (round-trip)
430
+ const savedPosition = asNode.metadata?.['flowdrop:position'];
431
+ const finalPosition = savedPosition || position;
432
+ // Convert inputs/outputs to FlowDrop ports
433
+ const dataInputs = (asNode.inputs || []).map((p) => agentSpecPropertyToNodePort(p, 'input'));
434
+ const dataOutputs = (asNode.outputs || []).map((p) => agentSpecPropertyToNodePort(p, 'output'));
435
+ // Get base metadata from registry and merge with actual ports
436
+ const metadata = createAgentSpecNodeMetadata(asNode.component_type, undefined, // Let the method build from base
437
+ undefined);
438
+ if (!metadata) {
439
+ throw new Error(`Unknown Agent Spec component type: ${asNode.component_type}`);
440
+ }
441
+ // Merge data ports with the base trigger/tool ports from registry
442
+ const triggerInputs = metadata.inputs.filter((p) => p.dataType === 'trigger' || p.dataType === 'tool');
443
+ const triggerOutputs = metadata.outputs.filter((p) => p.dataType === 'trigger' || p.dataType === 'tool');
444
+ const finalMetadata = {
445
+ ...metadata,
446
+ description: asNode.description || metadata.description,
447
+ inputs: [...triggerInputs, ...dataInputs],
448
+ outputs: [...triggerOutputs, ...dataOutputs],
449
+ extensions: {
450
+ ...metadata.extensions,
451
+ 'agentspec:component_type': asNode.component_type,
452
+ 'agentspec:original_name': asNode.name
453
+ }
454
+ };
455
+ // Build config from Agent Spec node-specific attributes
456
+ const config = this.extractConfigFromAgentSpec(asNode);
457
+ return {
458
+ id: nodeId,
459
+ type: asNode.metadata?.['flowdrop:node_type_id'] || metadata.id,
460
+ position: finalPosition,
461
+ data: {
462
+ label: asNode.name,
463
+ config,
464
+ metadata: finalMetadata
465
+ }
466
+ };
467
+ }
468
+ /**
469
+ * Extract FlowDrop config values from Agent Spec node-specific attributes.
470
+ */
471
+ extractConfigFromAgentSpec(asNode) {
472
+ const config = {};
473
+ switch (asNode.component_type) {
474
+ case 'llm_node': {
475
+ const llm = asNode;
476
+ if (llm.prompt_template)
477
+ config.prompt_template = llm.prompt_template;
478
+ if (llm.system_prompt)
479
+ config.system_prompt = llm.system_prompt;
480
+ if (llm.llm_config) {
481
+ config.llm_config_ref =
482
+ typeof llm.llm_config === 'string' ? llm.llm_config : llm.llm_config.name;
483
+ }
484
+ break;
485
+ }
486
+ case 'branching_node': {
487
+ const branch = asNode;
488
+ config.branches = branch.branches.map((b) => ({
489
+ name: b.name,
490
+ label: b.name,
491
+ condition: b.condition || '',
492
+ isDefault: !b.condition
493
+ }));
494
+ break;
495
+ }
496
+ case 'api_node': {
497
+ const api = asNode;
498
+ if (api.endpoint)
499
+ config.endpoint = api.endpoint;
500
+ if (api.method)
501
+ config.method = api.method;
502
+ if (api.headers)
503
+ config.headers = JSON.stringify(api.headers, null, 2);
504
+ break;
505
+ }
506
+ case 'agent_node': {
507
+ const agent = asNode;
508
+ if (agent.agent) {
509
+ config.agent_ref =
510
+ typeof agent.agent === 'string' ? agent.agent : agent.agent.name;
511
+ }
512
+ break;
513
+ }
514
+ case 'flow_node': {
515
+ const flow = asNode;
516
+ if (flow.flow) {
517
+ config.flow_ref = typeof flow.flow === 'string' ? flow.flow : flow.flow.name;
518
+ }
519
+ break;
520
+ }
521
+ case 'map_node': {
522
+ const map = asNode;
523
+ if (map.input_collection)
524
+ config.input_collection = map.input_collection;
525
+ if (map.output_collection)
526
+ config.output_collection = map.output_collection;
527
+ if (map.map_flow) {
528
+ config.map_flow_ref =
529
+ typeof map.map_flow === 'string' ? map.map_flow : map.map_flow.name;
530
+ }
531
+ break;
532
+ }
533
+ case 'tool_node': {
534
+ const tool = asNode;
535
+ if (tool.tool) {
536
+ config.tool_ref = typeof tool.tool === 'string' ? tool.tool : tool.tool.name;
537
+ }
538
+ break;
539
+ }
540
+ }
541
+ return config;
542
+ }
543
+ // ========================================================================
544
+ // Edge Conversion (Private)
545
+ // ========================================================================
546
+ /**
547
+ * Get the data type of a source port from a FlowDrop node.
548
+ */
549
+ getSourcePortDataType(node, portId) {
550
+ if (!portId)
551
+ return null;
552
+ // Check static output ports
553
+ const port = node.data.metadata.outputs.find((p) => p.id === portId);
554
+ if (port)
555
+ return port.dataType;
556
+ // Check if it's a gateway branch (always trigger)
557
+ if (node.data.metadata.type === 'gateway') {
558
+ const branches = node.data.config?.branches;
559
+ if (branches?.some((b) => b.name === portId)) {
560
+ return 'trigger';
561
+ }
562
+ }
563
+ // Check dynamic outputs
564
+ const dynamicOutputs = node.data.config?.dynamicOutputs;
565
+ if (dynamicOutputs) {
566
+ const dp = dynamicOutputs.find((p) => p.name === portId);
567
+ if (dp)
568
+ return dp.dataType;
569
+ }
570
+ return null;
571
+ }
572
+ /**
573
+ * Convert a FlowDrop trigger edge to an Agent Spec ControlFlowEdge.
574
+ */
575
+ convertToControlFlowEdge(edge, sourceNode, nodeIdToName) {
576
+ const fromNode = nodeIdToName.get(edge.source) || edge.source;
577
+ const toNode = nodeIdToName.get(edge.target) || edge.target;
578
+ const sourcePortId = extractPortId(edge.sourceHandle);
579
+ // Determine from_branch for gateway nodes
580
+ let fromBranch;
581
+ if (sourceNode.data.metadata.type === 'gateway' && sourcePortId) {
582
+ const branches = sourceNode.data.config?.branches;
583
+ if (branches?.some((b) => b.name === sourcePortId)) {
584
+ fromBranch = sourcePortId;
585
+ }
586
+ }
587
+ return {
588
+ name: `${fromNode}_to_${toNode}${fromBranch ? `_${fromBranch}` : ''}`,
589
+ from_node: fromNode,
590
+ to_node: toNode,
591
+ from_branch: fromBranch
592
+ };
593
+ }
594
+ /**
595
+ * Convert a FlowDrop data edge to an Agent Spec DataFlowEdge.
596
+ */
597
+ convertToDataFlowEdge(edge, nodeIdToName) {
598
+ const sourceNode = nodeIdToName.get(edge.source) || edge.source;
599
+ const destNode = nodeIdToName.get(edge.target) || edge.target;
600
+ const sourceOutput = extractPortId(edge.sourceHandle) || 'output';
601
+ const destInput = extractPortId(edge.targetHandle) || 'input';
602
+ return {
603
+ name: `${sourceNode}_${sourceOutput}_to_${destNode}_${destInput}`,
604
+ source_node: sourceNode,
605
+ source_output: sourceOutput,
606
+ destination_node: destNode,
607
+ destination_input: destInput
608
+ };
609
+ }
610
+ /**
611
+ * Convert an Agent Spec ControlFlowEdge to a FlowDrop edge.
612
+ */
613
+ convertFromControlFlowEdge(cfEdge, nameToNodeId, nodes) {
614
+ const sourceId = nameToNodeId.get(cfEdge.from_node);
615
+ const targetId = nameToNodeId.get(cfEdge.to_node);
616
+ if (!sourceId || !targetId)
617
+ return null;
618
+ // Determine source handle
619
+ let sourcePortId = 'trigger'; // default for non-branch control flow
620
+ if (cfEdge.from_branch) {
621
+ sourcePortId = cfEdge.from_branch;
622
+ }
623
+ return {
624
+ id: uuidv4(),
625
+ source: sourceId,
626
+ target: targetId,
627
+ sourceHandle: buildHandleId(sourceId, 'output', sourcePortId),
628
+ targetHandle: buildHandleId(targetId, 'input', 'trigger')
629
+ };
630
+ }
631
+ /**
632
+ * Convert an Agent Spec DataFlowEdge to a FlowDrop edge.
633
+ */
634
+ convertFromDataFlowEdge(dfEdge, nameToNodeId) {
635
+ const sourceId = nameToNodeId.get(dfEdge.source_node);
636
+ const targetId = nameToNodeId.get(dfEdge.destination_node);
637
+ if (!sourceId || !targetId)
638
+ return null;
639
+ return {
640
+ id: uuidv4(),
641
+ source: sourceId,
642
+ target: targetId,
643
+ sourceHandle: buildHandleId(sourceId, 'output', dfEdge.source_output),
644
+ targetHandle: buildHandleId(targetId, 'input', dfEdge.destination_input)
645
+ };
646
+ }
647
+ /**
648
+ * Find the start node name from converted Agent Spec nodes.
649
+ */
650
+ findStartNodeName(nodes, nodeIdToName) {
651
+ // Look for an explicit start_node
652
+ const startNode = nodes.find((n) => n.component_type === 'start_node');
653
+ if (startNode)
654
+ return startNode.name;
655
+ // Fall back to the first node
656
+ return nodes.length > 0 ? nodes[0].name : 'start';
657
+ }
658
+ }