@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.
- package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +92 -0
- package/dist/adapters/agentspec/AgentSpecAdapter.js +658 -0
- package/dist/adapters/agentspec/agentAdapter.d.ts +59 -0
- package/dist/adapters/agentspec/agentAdapter.js +91 -0
- package/dist/adapters/agentspec/autoLayout.d.ts +34 -0
- package/dist/adapters/agentspec/autoLayout.js +127 -0
- package/dist/adapters/agentspec/index.d.ts +35 -0
- package/dist/adapters/agentspec/index.js +37 -0
- package/dist/adapters/agentspec/nodeTypeRegistry.d.ts +62 -0
- package/dist/adapters/agentspec/nodeTypeRegistry.js +589 -0
- package/dist/adapters/agentspec/validator.d.ts +34 -0
- package/dist/adapters/agentspec/validator.js +169 -0
- package/dist/components/ConfigForm.svelte +46 -12
- package/dist/components/ConfigForm.svelte.d.ts +8 -0
- package/dist/components/SchemaForm.svelte +34 -12
- package/dist/components/SchemaForm.svelte.d.ts +8 -0
- package/dist/components/form/FormFieldset.svelte +142 -0
- package/dist/components/form/FormFieldset.svelte.d.ts +11 -0
- package/dist/components/form/FormUISchemaRenderer.svelte +140 -0
- package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +32 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +3 -0
- package/dist/config/agentSpecEndpoints.d.ts +70 -0
- package/dist/config/agentSpecEndpoints.js +65 -0
- package/dist/config/endpoints.d.ts +6 -0
- package/dist/core/index.d.ts +17 -1
- package/dist/core/index.js +17 -0
- package/dist/form/index.d.ts +2 -0
- package/dist/form/index.js +3 -0
- package/dist/helpers/workflowEditorHelper.d.ts +24 -0
- package/dist/helpers/workflowEditorHelper.js +55 -0
- package/dist/services/agentSpecExecutionService.d.ts +106 -0
- package/dist/services/agentSpecExecutionService.js +333 -0
- package/dist/types/agentspec.d.ts +318 -0
- package/dist/types/agentspec.js +48 -0
- package/dist/types/events.d.ts +28 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.js +1 -0
- package/dist/types/uischema.d.ts +144 -0
- package/dist/types/uischema.js +51 -0
- package/dist/utils/uischema.d.ts +52 -0
- package/dist/utils/uischema.js +88 -0
- 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
|
+
}
|