@artflo-ai/artflo-openclaw-plugin 0.0.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.
Files changed (59) hide show
  1. package/README.md +102 -0
  2. package/dist/index.js +73 -0
  3. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-00-216Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  4. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-00-217Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  5. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-05-727Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  6. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-30-218Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  7. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-30-218Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  8. package/dist/logs/ws-traffic-traces/2026-03-27T20-30-35-728Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  9. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-00-218Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  10. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-00-219Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  11. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-05-729Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  12. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-30-220Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  13. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-30-220Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  14. package/dist/logs/ws-traffic-traces/2026-03-27T20-31-35-729Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  15. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-00-221Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  16. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-00-221Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  17. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-05-730Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  18. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-30-222Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  19. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-30-222Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  20. package/dist/logs/ws-traffic-traces/2026-03-27T20-32-35-731Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  21. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-00-223Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  22. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-00-223Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  23. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-05-732Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  24. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-30-223Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  25. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-30-223Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  26. package/dist/logs/ws-traffic-traces/2026-03-27T20-33-35-734Z+08-lifecycle-heartbeat_ping-2c637466-6c6f-6172-400c-15145e021b0a.json +9 -0
  27. package/dist/logs/ws-traffic-traces/2026-03-27T20-34-00-228Z+08-lifecycle-heartbeat_ping-2b637466-6c6f-6172-470c-151459021b0a.json +9 -0
  28. package/dist/logs/ws-traffic-traces/2026-03-27T20-34-00-229Z+08-lifecycle-heartbeat_ping-2d637466-6c6f-6172-410c-15145f021b0a.json +9 -0
  29. package/dist/src/config.js +57 -0
  30. package/dist/src/constants.js +35 -0
  31. package/dist/src/core/api/api-base.js +12 -0
  32. package/dist/src/core/api/upload-file.js +59 -0
  33. package/dist/src/core/canvas/canvas-session-manager.js +189 -0
  34. package/dist/src/core/canvas/canvas-websocket-client.js +453 -0
  35. package/dist/src/core/canvas/create-canvas.js +37 -0
  36. package/dist/src/core/canvas/types.js +23 -0
  37. package/dist/src/core/canvas/ws-trace.js +42 -0
  38. package/dist/src/core/config/fetch-client-params.js +20 -0
  39. package/dist/src/core/config/fetch-vip-info.js +30 -0
  40. package/dist/src/core/config/model-config-transformer.js +104 -0
  41. package/dist/src/core/executor/element-builders.js +216 -0
  42. package/dist/src/core/executor/execute-plan.js +1221 -0
  43. package/dist/src/core/executor/execution-trace.js +34 -0
  44. package/dist/src/core/layout/layout-service.js +366 -0
  45. package/dist/src/core/plan/analyze-plan-groups.js +71 -0
  46. package/dist/src/core/plan/types.js +1 -0
  47. package/dist/src/core/plan/validate-plan.js +159 -0
  48. package/dist/src/paths.js +16 -0
  49. package/dist/src/services/canvas-session-registry.js +57 -0
  50. package/dist/src/tools/register-tools.js +669 -0
  51. package/dist/src/tools/tool-trace.js +19 -0
  52. package/openclaw.plugin.json +33 -0
  53. package/package.json +42 -0
  54. package/skills/artflo-canvas/SKILL.md +118 -0
  55. package/skills/artflo-canvas/references/graph-rules.md +53 -0
  56. package/skills/artflo-canvas/references/layout-notes.md +31 -0
  57. package/skills/artflo-canvas/references/node-schema.json +948 -0
  58. package/skills/artflo-canvas/references/node-schema.md +188 -0
  59. package/skills/artflo-canvas/references/planning-guide.md +321 -0
@@ -0,0 +1,42 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { resolvePluginLogDir, localTimestamp } from '../../paths.js';
4
+ function sanitizeSegment(value) {
5
+ return value.replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 80) || 'trace';
6
+ }
7
+ function redact(value) {
8
+ if (Array.isArray(value)) {
9
+ return value.map((item) => redact(item));
10
+ }
11
+ if (typeof value === 'object' && value !== null) {
12
+ const output = {};
13
+ for (const [key, child] of Object.entries(value)) {
14
+ if (key === 'access_token' ||
15
+ key === 'access-token' ||
16
+ key === 'accessToken' ||
17
+ key === 'apiKey' ||
18
+ key === 'X-API-Key') {
19
+ output[key] = '[REDACTED]';
20
+ }
21
+ else {
22
+ output[key] = redact(child);
23
+ }
24
+ }
25
+ return output;
26
+ }
27
+ return value;
28
+ }
29
+ export async function writeWsTrace(args) {
30
+ const timestamp = localTimestamp();
31
+ const baseDir = resolvePluginLogDir('ws-traffic-traces');
32
+ await mkdir(baseDir, { recursive: true });
33
+ const filePath = path.join(baseDir, `${timestamp}-${args.direction}-${sanitizeSegment(args.event)}${args.canvasId ? `-${sanitizeSegment(args.canvasId)}` : ''}.json`);
34
+ await writeFile(filePath, JSON.stringify({
35
+ createdAt: new Date().toISOString(),
36
+ direction: args.direction,
37
+ event: args.event,
38
+ canvasId: args.canvasId ?? null,
39
+ payload: redact(args.payload),
40
+ }, null, 2), 'utf8');
41
+ return filePath;
42
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Fetch appKey, vipAppId, vipGroup from the subscribe-client-params API.
3
+ * GET {webApiBaseUrl}/api/subscribe-client-params
4
+ */
5
+ export async function fetchClientParams(webApiBaseUrl) {
6
+ const url = `${webApiBaseUrl.replace(/\/+$/, '')}/api/subscribe-client-params`;
7
+ const response = await fetch(url);
8
+ if (!response.ok) {
9
+ throw new Error(`fetchClientParams failed: HTTP ${response.status}`);
10
+ }
11
+ const body = (await response.json());
12
+ if (body.code !== 0 || !body.data) {
13
+ throw new Error(`fetchClientParams failed: code=${body.code}`);
14
+ }
15
+ return {
16
+ appKey: body.data.appKey || 'A40CB4D42E28F808',
17
+ vipAppId: body.data.appId || '7029803307044000000',
18
+ vipGroup: body.data.vipGroup || 'group_artflo',
19
+ };
20
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Fetch user VIP / subscription status from the Artflo subscription API.
3
+ */
4
+ export async function fetchVipInfo(config) {
5
+ const url = new URL(`${config.vipApiBaseUrl}/h5/user/vip_info_by_group.json`);
6
+ url.searchParams.set('app_id', config.vipAppId);
7
+ url.searchParams.set('vip_group', config.vipGroup);
8
+ const response = await fetch(url.toString(), {
9
+ method: 'GET',
10
+ headers: {
11
+ 'X-API-Key': config.apiKey,
12
+ 'app_id': config.vipAppId,
13
+ 'country_code': config.countryCode,
14
+ 'platform': '4',
15
+ 'system_type': '2',
16
+ },
17
+ });
18
+ if (!response.ok) {
19
+ return { isVip: false, membershipLevel: 'Free', membershipName: 'Free' };
20
+ }
21
+ const body = (await response.json());
22
+ if (body.code !== 0 || !body.data) {
23
+ return { isVip: false, membershipLevel: 'Free', membershipName: 'Free' };
24
+ }
25
+ return {
26
+ isVip: body.data.is_vip === true || body.data.use_vip === true,
27
+ membershipLevel: body.data.membership?.level_name ?? 'Free',
28
+ membershipName: body.data.membership?.display_name ?? 'Free',
29
+ };
30
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Transform raw canvas_config from WebSocket join:success into an
3
+ * LLM-friendly summary that OpenClaw agents can use for planning.
4
+ *
5
+ * Adapted from artflo-agent-v2/src/workflow-agent/planning/model-config-transformer.ts
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Internal mappings
9
+ // ---------------------------------------------------------------------------
10
+ const CATEGORY_INFO = {
11
+ 1: { name: 'Text2Image', description: '根据文本描述生成图片', configKey: 'text_to_image_model' },
12
+ 2: { name: 'Image2Image', description: '基于输入图片和文本提示生成新图片', configKey: 'image_to_image_model' },
13
+ 3: { name: 'Text2Video', description: '根据文本描述生成视频', configKey: 'text_to_video_model' },
14
+ 4: { name: 'Image2Video', description: '基于输入图片生成视频', configKey: 'image_to_video_model' },
15
+ 8: { name: 'Image23D', description: '从图片生成 3D 模型', configKey: 'image_to_3d_model' },
16
+ };
17
+ const MODEL_DESCRIPTIONS = {
18
+ PralineV2: '旗舰级画质,细节表现力极强,各种场景下均可选',
19
+ 'GummyV4.5': '海报可选',
20
+ Gummy: '海报可选',
21
+ 'GummyV3.1': '海报首选',
22
+ Truffle: '业界顶尖的艺术审美,电影级构图与质感,高质量艺术图片可选',
23
+ 'FudgeV-Z': '高质量亚洲人像摄影可选',
24
+ Lollipop: '顶级的平面设计与排版能力,Logo设计首选,英文海报可选',
25
+ };
26
+ const OUTPUT_TOOL_DESCRIPTIONS = {
27
+ 5: '移除图片背景,生成透明背景图',
28
+ 6: '将视频转换为 GIF 动图',
29
+ 10: '提升图片分辨率和清晰度',
30
+ };
31
+ // ---------------------------------------------------------------------------
32
+ // Transform helpers
33
+ // ---------------------------------------------------------------------------
34
+ function transformModelItem(model, isVip) {
35
+ const available = !model.disable &&
36
+ (!model.subscribe_available ||
37
+ (model.subscribe_available && isVip) ||
38
+ model.is_free_limit);
39
+ const item = {
40
+ key: model.model_key,
41
+ name: model.model_name,
42
+ description: MODEL_DESCRIPTIONS[model.model_key] ?? '',
43
+ isDefault: model.is_default,
44
+ available,
45
+ requiresSubscription: model.subscribe_available,
46
+ isFreeLimit: model.is_free_limit,
47
+ };
48
+ if (model.support_ratio?.length)
49
+ item.supportedRatios = model.support_ratio;
50
+ if (model.support_resolution?.length)
51
+ item.supportedResolutions = model.support_resolution;
52
+ if (model.default_resolution)
53
+ item.defaultResolution = model.default_resolution;
54
+ if (model.support_duration?.length)
55
+ item.supportedDurations = model.support_duration;
56
+ if (model.support_image_count > 0)
57
+ item.maxImageCount = model.support_image_count;
58
+ if (model.video_sound_configs?.length) {
59
+ item.soundOptions = model.video_sound_configs.map((c) => c.key);
60
+ }
61
+ if (model.generate_3d_type_configs?.length) {
62
+ item.generate3dTypes = model.generate_3d_type_configs.map((c) => c.key);
63
+ }
64
+ return item;
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Public API
68
+ // ---------------------------------------------------------------------------
69
+ export function transformCanvasConfig(config, isVip) {
70
+ const categories = [];
71
+ for (const [toolTypeStr, info] of Object.entries(CATEGORY_INFO)) {
72
+ const toolType = Number(toolTypeStr);
73
+ const models = config[info.configKey];
74
+ if (!Array.isArray(models) || models.length === 0)
75
+ continue;
76
+ const transformed = models.map((m) => transformModelItem(m, isVip));
77
+ if (transformed.some((m) => m.available)) {
78
+ categories.push({
79
+ name: info.name,
80
+ description: info.description,
81
+ toolType,
82
+ models: transformed,
83
+ });
84
+ }
85
+ }
86
+ const outputTools = [];
87
+ if (Array.isArray(config.output_tools)) {
88
+ for (const tool of config.output_tools) {
89
+ outputTools.push({
90
+ toolType: tool.tool_type,
91
+ name: tool.name,
92
+ description: OUTPUT_TOOL_DESCRIPTIONS[tool.tool_type] ?? tool.name,
93
+ isFreeLimit: tool.is_free_limit,
94
+ });
95
+ }
96
+ }
97
+ const promptRefineCategories = [];
98
+ if (Array.isArray(config.prompt_refine_category)) {
99
+ for (const cat of config.prompt_refine_category) {
100
+ promptRefineCategories.push({ key: cat.key, name: cat.name, model: cat.model });
101
+ }
102
+ }
103
+ return { categories, outputTools, promptRefineCategories };
104
+ }
@@ -0,0 +1,216 @@
1
+ import { calculateSmartPositions } from '../layout/layout-service.js';
2
+ export const CANVAS_NODE_TYPES = {
3
+ EDGE: 100000,
4
+ INPUT: 110000,
5
+ IMAGE: 110001,
6
+ VIDEO: 110004,
7
+ RESULT: 110005,
8
+ SELECTOR: 110006,
9
+ PROCESS: 130000,
10
+ REFINE: 130500,
11
+ CROP: 190000,
12
+ };
13
+ function cloneData(value) {
14
+ return JSON.parse(JSON.stringify(value));
15
+ }
16
+ export function buildNodeElement(args) {
17
+ const { id, node, sourceNodeId, canvasElements, positionOverride, planId } = args;
18
+ let canvasType = CANVAS_NODE_TYPES.PROCESS;
19
+ const elementData = cloneData((node.data || {}));
20
+ switch (node.type) {
21
+ case 'input':
22
+ canvasType = CANVAS_NODE_TYPES.INPUT;
23
+ elementData.prompt = elementData.prompt || '';
24
+ // If sources contains URLs (from LLM plan), convert them to medias format.
25
+ // Input node medias is a key-value map: { "handle_id": { url, name, type, ... } }
26
+ if (Array.isArray(elementData.sources) &&
27
+ elementData.sources.length > 0 &&
28
+ typeof elementData.sources[0] === 'string' &&
29
+ elementData.sources[0].startsWith('http')) {
30
+ const sourceUrls = elementData.sources;
31
+ const mediasMap = {};
32
+ for (const url of sourceUrls) {
33
+ const id = crypto.randomUUID();
34
+ mediasMap[id] = {
35
+ id,
36
+ url,
37
+ name: 'image',
38
+ type: 'image',
39
+ width: 1024,
40
+ height: 1024,
41
+ };
42
+ }
43
+ elementData.medias = mediasMap;
44
+ elementData.sources = 'image';
45
+ }
46
+ else {
47
+ elementData.medias = elementData.medias || {};
48
+ elementData.sources = elementData.sources || 'image';
49
+ }
50
+ break;
51
+ case 'process':
52
+ canvasType = CANVAS_NODE_TYPES.PROCESS;
53
+ elementData.tool_type = elementData.tool_type ?? 1;
54
+ elementData.status = elementData.status ?? 0;
55
+ elementData.show_execute = elementData.show_execute ?? true;
56
+ elementData.planning = false;
57
+ elementData.executed = false;
58
+ elementData.count = elementData.count ?? 1;
59
+ if (elementData.model && !elementData.current_model) {
60
+ elementData.current_model = elementData.model;
61
+ delete elementData.model;
62
+ }
63
+ elementData.output_type =
64
+ elementData.output_type ?? getOutputType(Number(elementData.tool_type ?? 1));
65
+ {
66
+ const toolType = Number(elementData.tool_type ?? 1);
67
+ const isVideoToolType = toolType === 3 || toolType === 4;
68
+ if (isVideoToolType) {
69
+ const hasExplicitRatio = typeof elementData.ratio === 'string' &&
70
+ elementData.ratio !== '' &&
71
+ elementData.ratio !== 'auto';
72
+ elementData.ratio = hasExplicitRatio ? elementData.ratio : '16:9';
73
+ elementData.resolution = elementData.resolution ?? '1K';
74
+ }
75
+ else {
76
+ elementData.ratio = elementData.ratio ?? 'auto';
77
+ elementData.resolution = elementData.resolution ?? '2K';
78
+ }
79
+ }
80
+ if (sourceNodeId) {
81
+ const sourceElement = canvasElements.find((element) => element.id === sourceNodeId);
82
+ const sourceData = sourceElement?.data;
83
+ if (sourceElement?.type === CANVAS_NODE_TYPES.INPUT && typeof sourceData?.prompt === 'string') {
84
+ elementData.prompt = sourceData.prompt;
85
+ }
86
+ }
87
+ if (!elementData.medias ||
88
+ (Array.isArray(elementData.medias) && elementData.medias.length === 0) ||
89
+ (typeof elementData.medias === 'object' &&
90
+ !Array.isArray(elementData.medias) &&
91
+ Object.keys(elementData.medias).length === 0)) {
92
+ let mediaType = 'image';
93
+ const toolType = Number(elementData.tool_type ?? 1);
94
+ if (toolType === 3 || toolType === 4) {
95
+ mediaType = 'video';
96
+ }
97
+ else if (toolType === 8) {
98
+ mediaType = '3d';
99
+ }
100
+ elementData.medias = [
101
+ {
102
+ id: crypto.randomUUID(),
103
+ url: '',
104
+ name: mediaType,
105
+ type: mediaType,
106
+ width: 1024,
107
+ height: 1024,
108
+ },
109
+ ];
110
+ }
111
+ break;
112
+ case 'refine':
113
+ canvasType = CANVAS_NODE_TYPES.REFINE;
114
+ elementData.status = elementData.status ?? 0;
115
+ elementData.show_execute = elementData.show_execute ?? true;
116
+ elementData.current_model =
117
+ elementData.model || elementData.current_model || 'gemini-2.5-flash-preview';
118
+ if (elementData.promptCategory) {
119
+ elementData.prompt_category = elementData.promptCategory;
120
+ delete elementData.promptCategory;
121
+ }
122
+ if (elementData.splitMode !== undefined) {
123
+ elementData.split_mode = elementData.splitMode;
124
+ delete elementData.splitMode;
125
+ }
126
+ if (elementData.customPrompt) {
127
+ elementData.custom_prompt = elementData.customPrompt;
128
+ delete elementData.customPrompt;
129
+ }
130
+ elementData.separator = elementData.separator ?? '#@';
131
+ break;
132
+ case 'selector':
133
+ canvasType = CANVAS_NODE_TYPES.SELECTOR;
134
+ elementData.medias = elementData.medias || {};
135
+ elementData.selectedId = elementData.selectedId || '';
136
+ break;
137
+ case 'crop':
138
+ canvasType = CANVAS_NODE_TYPES.CROP;
139
+ elementData.status = elementData.status ?? 0;
140
+ elementData.cropMode = elementData.cropMode ?? 5;
141
+ break;
142
+ case 'image':
143
+ canvasType = CANVAS_NODE_TYPES.IMAGE;
144
+ elementData.width = elementData.width ?? 512;
145
+ elementData.height = elementData.height ?? 512;
146
+ break;
147
+ case 'video':
148
+ canvasType = CANVAS_NODE_TYPES.VIDEO;
149
+ elementData.width = elementData.width ?? 512;
150
+ elementData.height = elementData.height ?? 512;
151
+ break;
152
+ case 'batch':
153
+ canvasType = CANVAS_NODE_TYPES.PROCESS;
154
+ elementData.tool_type = elementData.tool_type ?? 1;
155
+ break;
156
+ }
157
+ const layoutResult = positionOverride
158
+ ? null
159
+ : calculateSmartPositions({
160
+ canvasElements,
161
+ sourceNodeId,
162
+ nodeType: node.type === 'input'
163
+ ? 'input'
164
+ : node.type === 'selector' || node.type === 'image' || node.type === 'video'
165
+ ? 'result'
166
+ : 'process',
167
+ count: 1,
168
+ });
169
+ const position = positionOverride ||
170
+ layoutResult?.positions[0] || { x: 100, y: 100 };
171
+ return {
172
+ id,
173
+ type: canvasType,
174
+ index: 'a0',
175
+ position,
176
+ data: {
177
+ ...elementData,
178
+ plan_ref: typeof elementData.plan_ref === 'string' ? elementData.plan_ref : node.ref,
179
+ plan_type: typeof elementData.plan_type === 'string' ? elementData.plan_type : node.type,
180
+ ...(planId ? { plan_id: planId } : {}),
181
+ },
182
+ __layoutDebug: layoutResult
183
+ ? {
184
+ strategy: layoutResult.strategy,
185
+ }
186
+ : {
187
+ strategy: 'plan-flow-override',
188
+ },
189
+ };
190
+ }
191
+ function getOutputType(toolType) {
192
+ if (toolType === 3 || toolType === 4) {
193
+ return 1;
194
+ }
195
+ if (toolType === 8) {
196
+ return 3;
197
+ }
198
+ return 0;
199
+ }
200
+ export function buildEdgeElement(args) {
201
+ const { id, edge, sourceId, targetId } = args;
202
+ return {
203
+ id,
204
+ type: CANVAS_NODE_TYPES.EDGE,
205
+ index: 'a0',
206
+ source: sourceId,
207
+ sourceHandle: edge.fromHandle,
208
+ target: targetId,
209
+ targetHandle: edge.toHandle,
210
+ data: {
211
+ ref: edge.ref || `${edge.from}-${edge.to}`,
212
+ fromRef: edge.from,
213
+ toRef: edge.to,
214
+ },
215
+ };
216
+ }