Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -654,16 +654,19 @@ export function ConditionInput({
}

const removeBlock = (id: string) => {
if (isPreview || disabled || conditionalBlocks.length <= 2) return
if (isPreview || disabled) return
// Condition mode requires at least 2 blocks (if/else), router mode requires at least 1
const minBlocks = isRouterMode ? 1 : 2
if (conditionalBlocks.length <= minBlocks) return

// Remove any associated edges before removing the block
const handlePrefix = isRouterMode ? `router-${id}` : `condition-${id}`
edges.forEach((edge) => {
if (edge.sourceHandle?.startsWith(`condition-${id}`)) {
if (edge.sourceHandle?.startsWith(handlePrefix)) {
removeEdge(edge.id)
}
})

if (conditionalBlocks.length === 1) return
shouldPersistRef.current = true
setConditionalBlocks((blocks) => updateBlockTitles(blocks.filter((block) => block.id !== id)))

Expand Down Expand Up @@ -815,7 +818,9 @@ export function ConditionInput({
<Button
variant='ghost'
onClick={() => removeBlock(block.id)}
disabled={isPreview || disabled || conditionalBlocks.length === 1}
disabled={
isPreview || disabled || conditionalBlocks.length <= (isRouterMode ? 1 : 2)
}
className='h-auto p-0 text-[var(--text-error)] hover:text-[var(--text-error)]'
>
<Trash className='h-[14px] w-[14px]' />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
return parsed.map((item: unknown, index: number) => {
const routeItem = item as { id?: string; value?: string }
return {
id: routeItem?.id ?? `${id}-route-${index}`,
// Use stable ID format that matches ConditionInput's generateStableId
id: routeItem?.id ?? `${id}-route${index + 1}`,
value: routeItem?.value ?? '',
}
})
Expand All @@ -873,7 +874,8 @@ export const WorkflowBlock = memo(function WorkflowBlock({
logger.warn('Failed to parse router routes value', { error, blockId: id })
}

return [{ id: `${id}-route-route1`, value: '' }]
// Fallback must match ConditionInput's default: generateStableId(blockId, 'route1') = `${blockId}-route1`
return [{ id: `${id}-route1`, value: '' }]
}, [type, subBlockState, id])

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,14 @@ const WorkflowContent = React.memo(() => {
const handleId = conditionHandles[0].getAttribute('data-handleid')
if (handleId) return handleId
}
} else if (block.type === 'router_v2') {
const routerHandles = document.querySelectorAll(
`[data-nodeid^="${block.id}"][data-handleid^="router-"]`
)
if (routerHandles.length > 0) {
const handleId = routerHandles[0].getAttribute('data-handleid')
if (handleId) return handleId
}
} else if (block.type === 'loop') {
return 'loop-end-source'
} else if (block.type === 'parallel') {
Expand Down
28 changes: 16 additions & 12 deletions apps/sim/blocks/blocks/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,25 +115,29 @@ Description: ${route.value || 'No description provided'}
)
.join('\n')

return `You are an intelligent routing agent. Your task is to analyze the provided context and select the most appropriate route from the available options.
return `You are a DETERMINISTIC routing agent. You MUST select exactly ONE route. There is NO other option.

CRITICAL RULES:
- You MUST output a route ID. Period.
- You CANNOT say "I need more information"
- You CANNOT refuse to choose
- You CANNOT explain your reasoning
- You CANNOT ask questions
- You CANNOT output anything except a route ID
- There is ALWAYS a best option. Pick it.

Available Routes:
${routesInfo}

Context to analyze:
Context:
${context}

Instructions:
1. Carefully analyze the context against each route's description
2. Select the route that best matches the context's intent and requirements
3. Consider the semantic meaning, not just keyword matching
4. If multiple routes could match, choose the most specific one

Response Format:
Return ONLY the route ID as a single string, no punctuation, no explanation.
Example: "route-abc123"
MANDATORY BEHAVIOR:
1. Analyze the context against each route description
2. ALWAYS pick the BEST matching route - one route is always better than the others
3. Output ONLY the route ID - nothing else

Remember: Your response must be ONLY the route ID - no additional text, formatting, or explanation.`
Your response must be EXACTLY one route ID from the list above. No punctuation. No explanation. No refusal. Just the ID.`
}

/**
Expand Down
9 changes: 5 additions & 4 deletions apps/sim/executor/handlers/router/router-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,15 @@ export class RouterBlockHandler implements BlockHandler {
const result = await response.json()

const chosenRouteId = result.content.trim()
const chosenRoute = routes.find((r) => r.id === chosenRouteId)
let chosenRoute = routes.find((r) => r.id === chosenRouteId)

// Fallback to first route if LLM returns invalid route ID
if (!chosenRoute) {
logger.error(
`Invalid routing decision. Response content: "${result.content}", available routes:`,
logger.warn(
`Invalid routing decision. Response content: "${result.content}", falling back to first route. Available routes:`,
routes.map((r) => ({ id: r.id, title: r.title }))
)
throw new Error(`Invalid routing decision: ${chosenRouteId}`)
chosenRoute = routes[0]
}

// Find the target block connected to this route's handle
Expand Down
1 change: 0 additions & 1 deletion bun.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "simstudio",
Expand Down