Skip to content

Commit a112fde

Browse files
committed
Fix comments
1 parent a15dd4c commit a112fde

File tree

1 file changed

+164
-24
lines changed

1 file changed

+164
-24
lines changed

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

Lines changed: 164 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,11 +2581,13 @@ async function validateWorkflowSelectorIds(
25812581
* Pre-validates credential and apiKey inputs in operations before they are applied.
25822582
* - Validates oauth-input (credential) IDs belong to the user
25832583
* - Filters out apiKey inputs for hosted models when isHosted is true
2584+
* - Also validates credentials and apiKeys in nestedNodes (blocks inside loop/parallel)
25842585
* Returns validation errors for any removed inputs.
25852586
*/
25862587
async function preValidateCredentialInputs(
25872588
operations: EditWorkflowOperation[],
2588-
context: { userId: string }
2589+
context: { userId: string },
2590+
workflowState?: Record<string, unknown>
25892591
): Promise<{ filteredOperations: EditWorkflowOperation[]; errors: ValidationError[] }> {
25902592
const { isHosted } = await import('@/lib/core/config/feature-flags')
25912593
const { getHostedModels } = await import('@/providers/utils')
@@ -2600,53 +2602,146 @@ async function preValidateCredentialInputs(
26002602
blockType: string
26012603
fieldName: string
26022604
value: string
2605+
nestedBlockId?: string
26032606
}> = []
26042607

26052608
const hostedApiKeyInputs: Array<{
26062609
operationIndex: number
26072610
blockId: string
26082611
blockType: string
26092612
model: string
2613+
nestedBlockId?: string
26102614
}> = []
26112615

26122616
const hostedModelsLower = isHosted ? new Set(getHostedModels().map((m) => m.toLowerCase())) : null
26132617

2614-
operations.forEach((op, opIndex) => {
2615-
if (!op.params?.inputs || !op.params?.type) return
2616-
2617-
const blockConfig = getBlock(op.params.type)
2618+
/**
2619+
* Collect credential inputs from a block's inputs based on its block config
2620+
*/
2621+
function collectCredentialInputs(
2622+
blockConfig: ReturnType<typeof getBlock>,
2623+
inputs: Record<string, unknown>,
2624+
opIndex: number,
2625+
blockId: string,
2626+
blockType: string,
2627+
nestedBlockId?: string
2628+
) {
26182629
if (!blockConfig) return
26192630

2620-
// Find oauth-input subblocks
26212631
for (const subBlockConfig of blockConfig.subBlocks) {
26222632
if (subBlockConfig.type !== 'oauth-input') continue
26232633

2624-
const inputValue = op.params.inputs[subBlockConfig.id]
2634+
const inputValue = inputs[subBlockConfig.id]
26252635
if (!inputValue || typeof inputValue !== 'string' || inputValue.trim() === '') continue
26262636

26272637
credentialInputs.push({
26282638
operationIndex: opIndex,
2629-
blockId: op.block_id,
2630-
blockType: op.params.type,
2639+
blockId,
2640+
blockType,
26312641
fieldName: subBlockConfig.id,
26322642
value: inputValue,
2643+
nestedBlockId,
26332644
})
26342645
}
2646+
}
26352647

2636-
// Check for apiKey inputs on hosted models
2637-
if (hostedModelsLower && op.params.inputs.apiKey) {
2638-
const modelValue = op.params.inputs.model
2639-
if (modelValue && typeof modelValue === 'string') {
2640-
if (hostedModelsLower.has(modelValue.toLowerCase())) {
2641-
hostedApiKeyInputs.push({
2642-
operationIndex: opIndex,
2643-
blockId: op.block_id,
2644-
blockType: op.params.type,
2645-
model: modelValue,
2646-
})
2648+
/**
2649+
* Check if apiKey should be filtered for a block with the given model
2650+
*/
2651+
function collectHostedApiKeyInput(
2652+
inputs: Record<string, unknown>,
2653+
modelValue: string | undefined,
2654+
opIndex: number,
2655+
blockId: string,
2656+
blockType: string,
2657+
nestedBlockId?: string
2658+
) {
2659+
if (!hostedModelsLower || !inputs.apiKey) return
2660+
if (!modelValue || typeof modelValue !== 'string') return
2661+
2662+
if (hostedModelsLower.has(modelValue.toLowerCase())) {
2663+
hostedApiKeyInputs.push({
2664+
operationIndex: opIndex,
2665+
blockId,
2666+
blockType,
2667+
model: modelValue,
2668+
nestedBlockId,
2669+
})
2670+
}
2671+
}
2672+
2673+
operations.forEach((op, opIndex) => {
2674+
// Process main block inputs
2675+
if (op.params?.inputs && op.params?.type) {
2676+
const blockConfig = getBlock(op.params.type)
2677+
if (blockConfig) {
2678+
// Collect credentials from main block
2679+
collectCredentialInputs(
2680+
blockConfig,
2681+
op.params.inputs as Record<string, unknown>,
2682+
opIndex,
2683+
op.block_id,
2684+
op.params.type
2685+
)
2686+
2687+
// Check for apiKey inputs on hosted models
2688+
let modelValue = (op.params.inputs as Record<string, unknown>).model as string | undefined
2689+
2690+
// For edit operations, if model is not being changed, check existing block's model
2691+
if (
2692+
!modelValue &&
2693+
op.operation_type === 'edit' &&
2694+
(op.params.inputs as Record<string, unknown>).apiKey &&
2695+
workflowState
2696+
) {
2697+
const existingBlock = (workflowState.blocks as Record<string, unknown>)?.[op.block_id] as
2698+
| Record<string, unknown>
2699+
| undefined
2700+
const existingSubBlocks = existingBlock?.subBlocks as Record<string, unknown> | undefined
2701+
const existingModelSubBlock = existingSubBlocks?.model as
2702+
| Record<string, unknown>
2703+
| undefined
2704+
modelValue = existingModelSubBlock?.value as string | undefined
26472705
}
2706+
2707+
collectHostedApiKeyInput(
2708+
op.params.inputs as Record<string, unknown>,
2709+
modelValue,
2710+
opIndex,
2711+
op.block_id,
2712+
op.params.type
2713+
)
26482714
}
26492715
}
2716+
2717+
// Process nested nodes (blocks inside loop/parallel containers)
2718+
const nestedNodes = op.params?.nestedNodes as
2719+
| Record<string, Record<string, unknown>>
2720+
| undefined
2721+
if (nestedNodes) {
2722+
Object.entries(nestedNodes).forEach(([childId, childBlock]) => {
2723+
const childType = childBlock.type as string | undefined
2724+
const childInputs = childBlock.inputs as Record<string, unknown> | undefined
2725+
if (!childType || !childInputs) return
2726+
2727+
const childBlockConfig = getBlock(childType)
2728+
if (!childBlockConfig) return
2729+
2730+
// Collect credentials from nested block
2731+
collectCredentialInputs(
2732+
childBlockConfig,
2733+
childInputs,
2734+
opIndex,
2735+
op.block_id,
2736+
childType,
2737+
childId
2738+
)
2739+
2740+
// Check for apiKey inputs on hosted models in nested block
2741+
const modelValue = childInputs.model as string | undefined
2742+
collectHostedApiKeyInput(childInputs, modelValue, opIndex, op.block_id, childType, childId)
2743+
})
2744+
}
26502745
})
26512746

26522747
const hasCredentialsToValidate = credentialInputs.length > 0
@@ -2665,7 +2760,32 @@ async function preValidateCredentialInputs(
26652760

26662761
for (const apiKeyInput of hostedApiKeyInputs) {
26672762
const op = filteredOperations[apiKeyInput.operationIndex]
2668-
if (op.params?.inputs?.apiKey) {
2763+
2764+
// Handle nested block apiKey filtering
2765+
if (apiKeyInput.nestedBlockId) {
2766+
const nestedNodes = op.params?.nestedNodes as
2767+
| Record<string, Record<string, unknown>>
2768+
| undefined
2769+
const nestedBlock = nestedNodes?.[apiKeyInput.nestedBlockId]
2770+
const nestedInputs = nestedBlock?.inputs as Record<string, unknown> | undefined
2771+
if (nestedInputs?.apiKey) {
2772+
nestedInputs.apiKey = undefined
2773+
logger.debug('Filtered apiKey for hosted model in nested block', {
2774+
parentBlockId: apiKeyInput.blockId,
2775+
nestedBlockId: apiKeyInput.nestedBlockId,
2776+
model: apiKeyInput.model,
2777+
})
2778+
2779+
errors.push({
2780+
blockId: apiKeyInput.nestedBlockId,
2781+
blockType: apiKeyInput.blockType,
2782+
field: 'apiKey',
2783+
value: '[redacted]',
2784+
error: `Cannot set API key for hosted model "${apiKeyInput.model}" - API keys are managed by the platform when using hosted models`,
2785+
})
2786+
}
2787+
} else if (op.params?.inputs?.apiKey) {
2788+
// Handle main block apiKey filtering
26692789
op.params.inputs.apiKey = undefined
26702790
logger.debug('Filtered apiKey for hosted model', {
26712791
blockId: apiKeyInput.blockId,
@@ -2699,7 +2819,25 @@ async function preValidateCredentialInputs(
26992819
if (!invalidSet.has(credInput.value)) continue
27002820

27012821
const op = filteredOperations[credInput.operationIndex]
2702-
if (op.params?.inputs?.[credInput.fieldName]) {
2822+
2823+
// Handle nested block credential removal
2824+
if (credInput.nestedBlockId) {
2825+
const nestedNodes = op.params?.nestedNodes as
2826+
| Record<string, Record<string, unknown>>
2827+
| undefined
2828+
const nestedBlock = nestedNodes?.[credInput.nestedBlockId]
2829+
const nestedInputs = nestedBlock?.inputs as Record<string, unknown> | undefined
2830+
if (nestedInputs?.[credInput.fieldName]) {
2831+
delete nestedInputs[credInput.fieldName]
2832+
logger.info('Removed invalid credential from nested block', {
2833+
parentBlockId: credInput.blockId,
2834+
nestedBlockId: credInput.nestedBlockId,
2835+
field: credInput.fieldName,
2836+
invalidValue: credInput.value,
2837+
})
2838+
}
2839+
} else if (op.params?.inputs?.[credInput.fieldName]) {
2840+
// Handle main block credential removal
27032841
delete op.params.inputs[credInput.fieldName]
27042842
logger.info('Removed invalid credential from operation', {
27052843
blockId: credInput.blockId,
@@ -2709,8 +2847,9 @@ async function preValidateCredentialInputs(
27092847
}
27102848

27112849
const warningInfo = validationResult.warning ? `. ${validationResult.warning}` : ''
2850+
const errorBlockId = credInput.nestedBlockId ?? credInput.blockId
27122851
errors.push({
2713-
blockId: credInput.blockId,
2852+
blockId: errorBlockId,
27142853
blockType: credInput.blockType,
27152854
field: credInput.fieldName,
27162855
value: credInput.value,
@@ -2818,7 +2957,8 @@ export const editWorkflowServerTool: BaseServerTool<EditWorkflowParams, any> = {
28182957
if (context?.userId) {
28192958
const { filteredOperations, errors: credErrors } = await preValidateCredentialInputs(
28202959
operations,
2821-
{ userId: context.userId }
2960+
{ userId: context.userId },
2961+
workflowState
28222962
)
28232963
operationsToApply = filteredOperations
28242964
credentialErrors.push(...credErrors)

0 commit comments

Comments
 (0)