Skip to content

Commit 4fd5f00

Browse files
authored
fix(copilot): validation (#2215)
* Fix validation error * Fix lint
1 parent 002713e commit 4fd5f00

File tree

1 file changed

+32
-88
lines changed

1 file changed

+32
-88
lines changed

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

Lines changed: 32 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ interface SkippedItem {
6464
details?: Record<string, any>
6565
}
6666

67+
/**
68+
* Logs and records a skipped item
69+
*/
70+
function logSkippedItem(skippedItems: SkippedItem[], item: SkippedItem): void {
71+
validationLogger.warn(`Skipped ${item.operationType} operation: ${item.reason}`, {
72+
type: item.type,
73+
operationType: item.operationType,
74+
blockId: item.blockId,
75+
...(item.details && { details: item.details }),
76+
})
77+
skippedItems.push(item)
78+
}
79+
6780
/**
6881
* Result of input validation
6982
*/
@@ -79,8 +92,7 @@ interface ValidationResult {
7992
function validateInputsForBlock(
8093
blockType: string,
8194
inputs: Record<string, any>,
82-
blockId: string,
83-
existingInputs?: Record<string, any>
95+
blockId: string
8496
): ValidationResult {
8597
const errors: ValidationError[] = []
8698
const blockConfig = getBlock(blockType)
@@ -99,9 +111,6 @@ function validateInputsForBlock(
99111
subBlockMap.set(subBlock.id, subBlock)
100112
}
101113

102-
// Merge existing inputs with new inputs to evaluate conditions properly
103-
const mergedInputs = { ...existingInputs, ...inputs }
104-
105114
for (const [key, value] of Object.entries(inputs)) {
106115
// Skip runtime subblock IDs
107116
if (TRIGGER_RUNTIME_SUBBLOCK_IDS.includes(key)) {
@@ -128,17 +137,10 @@ function validateInputsForBlock(
128137
continue
129138
}
130139

131-
// Check if the field's condition is met
132-
if (subBlockConfig.condition && !evaluateCondition(subBlockConfig.condition, mergedInputs)) {
133-
errors.push({
134-
blockId,
135-
blockType,
136-
field: key,
137-
value,
138-
error: `Field "${key}" condition not met - this field is not applicable for the current configuration`,
139-
})
140-
continue
141-
}
140+
// Note: We do NOT check subBlockConfig.condition here.
141+
// Conditions are for UI display logic (show/hide fields in the editor).
142+
// For API/Copilot, any valid field in the block schema should be accepted.
143+
// The runtime will use the relevant fields based on the actual operation.
142144

143145
// Validate value based on subBlock type
144146
const validationResult = validateValueForSubBlockType(
@@ -158,44 +160,6 @@ function validateInputsForBlock(
158160
return { validInputs: validatedInputs, errors }
159161
}
160162

161-
/**
162-
* Evaluates a condition object against current inputs
163-
*/
164-
function evaluateCondition(
165-
condition: SubBlockConfig['condition'],
166-
inputs: Record<string, any>
167-
): boolean {
168-
if (!condition) return true
169-
170-
// Handle function conditions
171-
const resolvedCondition = typeof condition === 'function' ? condition() : condition
172-
173-
const fieldValue = inputs[resolvedCondition.field]
174-
const expectedValues = Array.isArray(resolvedCondition.value)
175-
? resolvedCondition.value
176-
: [resolvedCondition.value]
177-
178-
let matches = expectedValues.includes(fieldValue)
179-
if (resolvedCondition.not) {
180-
matches = !matches
181-
}
182-
183-
// Handle AND condition
184-
if (matches && resolvedCondition.and) {
185-
const andFieldValue = inputs[resolvedCondition.and.field]
186-
const andExpectedValues = Array.isArray(resolvedCondition.and.value)
187-
? resolvedCondition.and.value
188-
: [resolvedCondition.and.value]
189-
let andMatches = andExpectedValues.includes(andFieldValue)
190-
if (resolvedCondition.and.not) {
191-
andMatches = !andMatches
192-
}
193-
matches = matches && andMatches
194-
}
195-
196-
return matches
197-
}
198-
199163
/**
200164
* Result of validating a single value
201165
*/
@@ -920,7 +884,7 @@ function applyOperationsToWorkflowState(
920884
switch (operation_type) {
921885
case 'delete': {
922886
if (!modifiedState.blocks[block_id]) {
923-
skippedItems.push({
887+
logSkippedItem(skippedItems, {
924888
type: 'block_not_found',
925889
operationType: 'delete',
926890
blockId: block_id,
@@ -953,7 +917,7 @@ function applyOperationsToWorkflowState(
953917

954918
case 'edit': {
955919
if (!modifiedState.blocks[block_id]) {
956-
skippedItems.push({
920+
logSkippedItem(skippedItems, {
957921
type: 'block_not_found',
958922
operationType: 'edit',
959923
blockId: block_id,
@@ -970,7 +934,7 @@ function applyOperationsToWorkflowState(
970934
blockKeys: Object.keys(block),
971935
blockData: JSON.stringify(block),
972936
})
973-
skippedItems.push({
937+
logSkippedItem(skippedItems, {
974938
type: 'block_not_found',
975939
operationType: 'edit',
976940
blockId: block_id,
@@ -983,19 +947,8 @@ function applyOperationsToWorkflowState(
983947
if (params?.inputs) {
984948
if (!block.subBlocks) block.subBlocks = {}
985949

986-
// Get existing input values for condition evaluation
987-
const existingInputs: Record<string, any> = {}
988-
Object.entries(block.subBlocks).forEach(([key, subBlock]: [string, any]) => {
989-
existingInputs[key] = subBlock?.value
990-
})
991-
992950
// Validate inputs against block configuration
993-
const validationResult = validateInputsForBlock(
994-
block.type,
995-
params.inputs,
996-
block_id,
997-
existingInputs
998-
)
951+
const validationResult = validateInputsForBlock(block.type, params.inputs, block_id)
999952
validationErrors.push(...validationResult.errors)
1000953

1001954
Object.entries(validationResult.validInputs).forEach(([inputKey, value]) => {
@@ -1119,7 +1072,7 @@ function applyOperationsToWorkflowState(
11191072
// Validate type before setting (skip validation for container types)
11201073
const blockConfig = getBlock(params.type)
11211074
if (!blockConfig && !isContainerType) {
1122-
skippedItems.push({
1075+
logSkippedItem(skippedItems, {
11231076
type: 'invalid_block_type',
11241077
operationType: 'edit',
11251078
blockId: block_id,
@@ -1269,7 +1222,7 @@ function applyOperationsToWorkflowState(
12691222
existingBlocks: Object.keys(modifiedState.blocks),
12701223
}
12711224
)
1272-
skippedItems.push({
1225+
logSkippedItem(skippedItems, {
12731226
type: 'invalid_edge_target',
12741227
operationType: 'edit',
12751228
blockId: block_id,
@@ -1322,7 +1275,7 @@ function applyOperationsToWorkflowState(
13221275

13231276
case 'add': {
13241277
if (!params?.type || !params?.name) {
1325-
skippedItems.push({
1278+
logSkippedItem(skippedItems, {
13261279
type: 'missing_required_params',
13271280
operationType: 'add',
13281281
blockId: block_id,
@@ -1338,7 +1291,7 @@ function applyOperationsToWorkflowState(
13381291
// Validate block type before adding (skip validation for container types)
13391292
const addBlockConfig = getBlock(params.type)
13401293
if (!addBlockConfig && !isContainerType) {
1341-
skippedItems.push({
1294+
logSkippedItem(skippedItems, {
13421295
type: 'invalid_block_type',
13431296
operationType: 'add',
13441297
blockId: block_id,
@@ -1427,7 +1380,7 @@ function applyOperationsToWorkflowState(
14271380
case 'insert_into_subflow': {
14281381
const subflowId = params?.subflowId
14291382
if (!subflowId || !params?.type || !params?.name) {
1430-
skippedItems.push({
1383+
logSkippedItem(skippedItems, {
14311384
type: 'missing_required_params',
14321385
operationType: 'insert_into_subflow',
14331386
blockId: block_id,
@@ -1443,7 +1396,7 @@ function applyOperationsToWorkflowState(
14431396

14441397
const subflowBlock = modifiedState.blocks[subflowId]
14451398
if (!subflowBlock) {
1446-
skippedItems.push({
1399+
logSkippedItem(skippedItems, {
14471400
type: 'invalid_subflow_parent',
14481401
operationType: 'insert_into_subflow',
14491402
blockId: block_id,
@@ -1478,20 +1431,11 @@ function applyOperationsToWorkflowState(
14781431

14791432
// Update inputs if provided (with validation)
14801433
if (params.inputs) {
1481-
// Get existing input values for condition evaluation
1482-
const existingInputs: Record<string, any> = {}
1483-
Object.entries(existingBlock.subBlocks || {}).forEach(
1484-
([key, subBlock]: [string, any]) => {
1485-
existingInputs[key] = subBlock?.value
1486-
}
1487-
)
1488-
14891434
// Validate inputs against block configuration
14901435
const validationResult = validateInputsForBlock(
14911436
existingBlock.type,
14921437
params.inputs,
1493-
block_id,
1494-
existingInputs
1438+
block_id
14951439
)
14961440
validationErrors.push(...validationResult.errors)
14971441

@@ -1537,7 +1481,7 @@ function applyOperationsToWorkflowState(
15371481
// Validate block type before creating (skip validation for container types)
15381482
const insertBlockConfig = getBlock(params.type)
15391483
if (!insertBlockConfig && !isContainerType) {
1540-
skippedItems.push({
1484+
logSkippedItem(skippedItems, {
15411485
type: 'invalid_block_type',
15421486
operationType: 'insert_into_subflow',
15431487
blockId: block_id,
@@ -1566,7 +1510,7 @@ function applyOperationsToWorkflowState(
15661510
case 'extract_from_subflow': {
15671511
const subflowId = params?.subflowId
15681512
if (!subflowId) {
1569-
skippedItems.push({
1513+
logSkippedItem(skippedItems, {
15701514
type: 'missing_required_params',
15711515
operationType: 'extract_from_subflow',
15721516
blockId: block_id,
@@ -1577,7 +1521,7 @@ function applyOperationsToWorkflowState(
15771521

15781522
const block = modifiedState.blocks[block_id]
15791523
if (!block) {
1580-
skippedItems.push({
1524+
logSkippedItem(skippedItems, {
15811525
type: 'block_not_found',
15821526
operationType: 'extract_from_subflow',
15831527
blockId: block_id,

0 commit comments

Comments
 (0)