Skip to content

Commit 26e2146

Browse files
committed
Condition and router copilot syntax updates
1 parent 44ab750 commit 26e2146

File tree

2 files changed

+139
-86
lines changed

2 files changed

+139
-86
lines changed

apps/sim/lib/copilot/tools/server/workflow/edit-workflow.ts

Lines changed: 111 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,8 @@ function normalizeResponseFormat(value: any): string {
817817
interface EdgeHandleValidationResult {
818818
valid: boolean
819819
error?: string
820+
/** The normalized handle to use (e.g., simple 'if' normalized to 'condition-{uuid}') */
821+
normalizedHandle?: string
820822
}
821823

822824
/**
@@ -851,13 +853,6 @@ function validateSourceHandleForBlock(
851853
}
852854

853855
case 'condition': {
854-
if (!sourceHandle.startsWith(EDGE.CONDITION_PREFIX)) {
855-
return {
856-
valid: false,
857-
error: `Invalid source handle "${sourceHandle}" for condition block. Must start with "${EDGE.CONDITION_PREFIX}"`,
858-
}
859-
}
860-
861856
const conditionsValue = sourceBlock?.subBlocks?.conditions?.value
862857
if (!conditionsValue) {
863858
return {
@@ -866,6 +861,8 @@ function validateSourceHandleForBlock(
866861
}
867862
}
868863

864+
// validateConditionHandle accepts simple format (if, else-if-0, else),
865+
// legacy format (condition-{blockId}-if), and internal ID format (condition-{uuid})
869866
return validateConditionHandle(sourceHandle, sourceBlock.id, conditionsValue)
870867
}
871868

@@ -879,13 +876,6 @@ function validateSourceHandleForBlock(
879876
}
880877

881878
case 'router_v2': {
882-
if (!sourceHandle.startsWith(EDGE.ROUTER_PREFIX)) {
883-
return {
884-
valid: false,
885-
error: `Invalid source handle "${sourceHandle}" for router_v2 block. Must start with "${EDGE.ROUTER_PREFIX}"`,
886-
}
887-
}
888-
889879
const routesValue = sourceBlock?.subBlocks?.routes?.value
890880
if (!routesValue) {
891881
return {
@@ -894,6 +884,8 @@ function validateSourceHandleForBlock(
894884
}
895885
}
896886

887+
// validateRouterHandle accepts simple format (route-0, route-1),
888+
// legacy format (router-{blockId}-route-1), and internal ID format (router-{uuid})
897889
return validateRouterHandle(sourceHandle, sourceBlock.id, routesValue)
898890
}
899891

@@ -910,7 +902,12 @@ function validateSourceHandleForBlock(
910902

911903
/**
912904
* Validates condition handle references a valid condition in the block.
913-
* Accepts both internal IDs (condition-blockId-if) and semantic keys (condition-blockId-else-if)
905+
* Accepts multiple formats:
906+
* - Simple format: "if", "else-if-0", "else-if-1", "else"
907+
* - Legacy semantic format: "condition-{blockId}-if", "condition-{blockId}-else-if"
908+
* - Internal ID format: "condition-{conditionId}"
909+
*
910+
* Returns the normalized handle (condition-{conditionId}) for storage.
914911
*/
915912
function validateConditionHandle(
916913
sourceHandle: string,
@@ -943,48 +940,77 @@ function validateConditionHandle(
943940
}
944941
}
945942

946-
const validHandles = new Set<string>()
947-
const semanticPrefix = `condition-${blockId}-`
948-
let elseIfCount = 0
943+
// Build a map of all valid handle formats -> normalized handle (condition-{conditionId})
944+
const handleToNormalized = new Map<string, string>()
945+
const legacySemanticPrefix = `condition-${blockId}-`
946+
let elseIfIndex = 0
949947

950948
for (const condition of conditions) {
951-
if (condition.id) {
952-
validHandles.add(`condition-${condition.id}`)
953-
}
949+
if (!condition.id) continue
954950

951+
const normalizedHandle = `condition-${condition.id}`
955952
const title = condition.title?.toLowerCase()
953+
954+
// Always accept internal ID format
955+
handleToNormalized.set(normalizedHandle, normalizedHandle)
956+
956957
if (title === 'if') {
957-
validHandles.add(`${semanticPrefix}if`)
958+
// Simple format: "if"
959+
handleToNormalized.set('if', normalizedHandle)
960+
// Legacy format: "condition-{blockId}-if"
961+
handleToNormalized.set(`${legacySemanticPrefix}if`, normalizedHandle)
958962
} else if (title === 'else if') {
959-
elseIfCount++
960-
validHandles.add(
961-
elseIfCount === 1 ? `${semanticPrefix}else-if` : `${semanticPrefix}else-if-${elseIfCount}`
962-
)
963+
// Simple format: "else-if-0", "else-if-1", etc. (0-indexed)
964+
handleToNormalized.set(`else-if-${elseIfIndex}`, normalizedHandle)
965+
// Legacy format: "condition-{blockId}-else-if" for first, "condition-{blockId}-else-if-2" for second
966+
if (elseIfIndex === 0) {
967+
handleToNormalized.set(`${legacySemanticPrefix}else-if`, normalizedHandle)
968+
} else {
969+
handleToNormalized.set(`${legacySemanticPrefix}else-if-${elseIfIndex + 1}`, normalizedHandle)
970+
}
971+
elseIfIndex++
963972
} else if (title === 'else') {
964-
validHandles.add(`${semanticPrefix}else`)
973+
// Simple format: "else"
974+
handleToNormalized.set('else', normalizedHandle)
975+
// Legacy format: "condition-{blockId}-else"
976+
handleToNormalized.set(`${legacySemanticPrefix}else`, normalizedHandle)
965977
}
966978
}
967979

968-
if (validHandles.has(sourceHandle)) {
969-
return { valid: true }
980+
const normalizedHandle = handleToNormalized.get(sourceHandle)
981+
if (normalizedHandle) {
982+
return { valid: true, normalizedHandle }
970983
}
971984

972-
const validOptions = Array.from(validHandles).slice(0, 5)
973-
const moreCount = validHandles.size - validOptions.length
974-
let validOptionsStr = validOptions.join(', ')
975-
if (moreCount > 0) {
976-
validOptionsStr += `, ... and ${moreCount} more`
985+
// Build list of valid simple format options for error message
986+
const simpleOptions: string[] = []
987+
elseIfIndex = 0
988+
for (const condition of conditions) {
989+
const title = condition.title?.toLowerCase()
990+
if (title === 'if') {
991+
simpleOptions.push('if')
992+
} else if (title === 'else if') {
993+
simpleOptions.push(`else-if-${elseIfIndex}`)
994+
elseIfIndex++
995+
} else if (title === 'else') {
996+
simpleOptions.push('else')
997+
}
977998
}
978999

9791000
return {
9801001
valid: false,
981-
error: `Invalid condition handle "${sourceHandle}". Valid handles: ${validOptionsStr}`,
1002+
error: `Invalid condition handle "${sourceHandle}". Valid handles: ${simpleOptions.join(', ')}`,
9821003
}
9831004
}
9841005

9851006
/**
9861007
* Validates router handle references a valid route in the block.
987-
* Accepts both internal IDs (router-{routeId}) and semantic keys (router-{blockId}-route-1)
1008+
* Accepts multiple formats:
1009+
* - Simple format: "route-0", "route-1", "route-2" (0-indexed)
1010+
* - Legacy semantic format: "router-{blockId}-route-1" (1-indexed)
1011+
* - Internal ID format: "router-{routeId}"
1012+
*
1013+
* Returns the normalized handle (router-{routeId}) for storage.
9881014
*/
9891015
function validateRouterHandle(
9901016
sourceHandle: string,
@@ -1017,47 +1043,48 @@ function validateRouterHandle(
10171043
}
10181044
}
10191045

1020-
const validHandles = new Set<string>()
1021-
const semanticPrefix = `router-${blockId}-`
1046+
// Build a map of all valid handle formats -> normalized handle (router-{routeId})
1047+
const handleToNormalized = new Map<string, string>()
1048+
const legacySemanticPrefix = `router-${blockId}-`
10221049

10231050
for (let i = 0; i < routes.length; i++) {
10241051
const route = routes[i]
1052+
if (!route.id) continue
10251053

1026-
// Accept internal ID format: router-{uuid}
1027-
if (route.id) {
1028-
validHandles.add(`router-${route.id}`)
1029-
}
1054+
const normalizedHandle = `router-${route.id}`
1055+
1056+
// Always accept internal ID format: router-{uuid}
1057+
handleToNormalized.set(normalizedHandle, normalizedHandle)
10301058

1031-
// Accept 1-indexed route number format: router-{blockId}-route-1, router-{blockId}-route-2, etc.
1032-
validHandles.add(`${semanticPrefix}route-${i + 1}`)
1059+
// Simple format: route-0, route-1, etc. (0-indexed)
1060+
handleToNormalized.set(`route-${i}`, normalizedHandle)
1061+
1062+
// Legacy 1-indexed route number format: router-{blockId}-route-1
1063+
handleToNormalized.set(`${legacySemanticPrefix}route-${i + 1}`, normalizedHandle)
10331064

10341065
// Accept normalized title format: router-{blockId}-{normalized-title}
1035-
// Normalize: lowercase, replace spaces with dashes, remove special chars
10361066
if (route.title && typeof route.title === 'string') {
10371067
const normalizedTitle = route.title
10381068
.toLowerCase()
10391069
.replace(/\s+/g, '-')
10401070
.replace(/[^a-z0-9-]/g, '')
10411071
if (normalizedTitle) {
1042-
validHandles.add(`${semanticPrefix}${normalizedTitle}`)
1072+
handleToNormalized.set(`${legacySemanticPrefix}${normalizedTitle}`, normalizedHandle)
10431073
}
10441074
}
10451075
}
10461076

1047-
if (validHandles.has(sourceHandle)) {
1048-
return { valid: true }
1077+
const normalizedHandle = handleToNormalized.get(sourceHandle)
1078+
if (normalizedHandle) {
1079+
return { valid: true, normalizedHandle }
10491080
}
10501081

1051-
const validOptions = Array.from(validHandles).slice(0, 5)
1052-
const moreCount = validHandles.size - validOptions.length
1053-
let validOptionsStr = validOptions.join(', ')
1054-
if (moreCount > 0) {
1055-
validOptionsStr += `, ... and ${moreCount} more`
1056-
}
1082+
// Build list of valid simple format options for error message
1083+
const simpleOptions = routes.map((_, i) => `route-${i}`)
10571084

10581085
return {
10591086
valid: false,
1060-
error: `Invalid router handle "${sourceHandle}". Valid handles: ${validOptionsStr}`,
1087+
error: `Invalid router handle "${sourceHandle}". Valid handles: ${simpleOptions.join(', ')}`,
10611088
}
10621089
}
10631090

@@ -1172,10 +1199,13 @@ function createValidatedEdge(
11721199
return false
11731200
}
11741201

1202+
// Use normalized handle if available (e.g., 'if' -> 'condition-{uuid}')
1203+
const finalSourceHandle = sourceValidation.normalizedHandle || sourceHandle
1204+
11751205
modifiedState.edges.push({
11761206
id: crypto.randomUUID(),
11771207
source: sourceBlockId,
1178-
sourceHandle,
1208+
sourceHandle: finalSourceHandle,
11791209
target: targetBlockId,
11801210
targetHandle,
11811211
type: 'default',
@@ -1184,7 +1214,11 @@ function createValidatedEdge(
11841214
}
11851215

11861216
/**
1187-
* Adds connections as edges for a block
1217+
* Adds connections as edges for a block.
1218+
* Supports multiple target formats:
1219+
* - String: "target-block-id"
1220+
* - Object: { block: "target-block-id", handle?: "custom-target-handle" }
1221+
* - Array of strings or objects
11881222
*/
11891223
function addConnectionsAsEdges(
11901224
modifiedState: any,
@@ -1194,19 +1228,34 @@ function addConnectionsAsEdges(
11941228
skippedItems?: SkippedItem[]
11951229
): void {
11961230
Object.entries(connections).forEach(([sourceHandle, targets]) => {
1197-
const targetArray = Array.isArray(targets) ? targets : [targets]
1198-
targetArray.forEach((targetId: string) => {
1231+
if (targets === null) return
1232+
1233+
const addEdgeForTarget = (targetBlock: string, targetHandle?: string) => {
11991234
createValidatedEdge(
12001235
modifiedState,
12011236
blockId,
1202-
targetId,
1237+
targetBlock,
12031238
sourceHandle,
1204-
'target',
1239+
targetHandle || 'target',
12051240
'add_edge',
12061241
logger,
12071242
skippedItems
12081243
)
1209-
})
1244+
}
1245+
1246+
if (typeof targets === 'string') {
1247+
addEdgeForTarget(targets)
1248+
} else if (Array.isArray(targets)) {
1249+
targets.forEach((target: any) => {
1250+
if (typeof target === 'string') {
1251+
addEdgeForTarget(target)
1252+
} else if (target?.block) {
1253+
addEdgeForTarget(target.block, target.handle)
1254+
}
1255+
})
1256+
} else if (typeof targets === 'object' && targets?.block) {
1257+
addEdgeForTarget(targets.block, targets.handle)
1258+
}
12101259
})
12111260
}
12121261

0 commit comments

Comments
 (0)