Files
dify/api/services/dify_builder/agent/graph_translate.py
T
Charles YaoandClaude Opus 4.8 bcd0f59100 fix(dify-builder): prevent duplicate start node ids on multiple starts
Only map the first start-type node to canonical 'start'; additional starts
retain their original ids to prevent collisions and ambiguous intents.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 11:36:02 -07:00

54 lines
2.0 KiB
Python

"""Translate a WorkflowGenerator graph into Dify Builder MutationIntents.
The generator emits ReactFlow nodes ({id, type:"custom", data:{type:<real>,...}})
and edges. We emit one create_node per node (config = node.data minus the
redundant "type" key -- apply_create_node re-sets it) followed by one connect
per edge, so every connect endpoint already exists at apply time. The start
node is renamed to the conventional id "start" so it reconciles with a draft
that already has one (the handler's present-filter drops the duplicate)."""
from typing import Any
from core.dify_builder.models import MutationIntent
_START_ID = "start"
def to_intents(graph: dict[str, Any]) -> list[MutationIntent]:
nodes = graph.get("nodes") or []
edges = graph.get("edges") or []
# rename map: the generator start id -> canonical "start"
# Only map the first start node; additional starts keep their original ids
remap: dict[str, str] = {}
for node in nodes:
data = node.get("data") or {}
if data.get("type") == "start" and node.get("id") and not remap:
remap[str(node["id"])] = _START_ID
creates: list[MutationIntent] = []
for node in nodes:
data = node.get("data") or {}
node_type = data.get("type")
node_id = node.get("id")
if not node_type or not node_id:
continue
config = {k: v for k, v in data.items() if k != "type"}
creates.append(MutationIntent(op="create_node", args={
"node_type": str(node_type),
"config": config,
"node_id": remap.get(str(node_id), str(node_id)),
}))
connects: list[MutationIntent] = []
for edge in edges:
src, tgt = edge.get("source"), edge.get("target")
if not src or not tgt:
continue
connects.append(MutationIntent(op="connect", args={
"from_node": remap.get(str(src), str(src)),
"to_node": remap.get(str(tgt), str(tgt)),
}))
return creates + connects