Skip to content

Commit 84ddf57

Browse files
fix(copilot): only fold registry-known types into final batch state
Pass 1 recorded any non-empty type into finalType, but apply skips type changes to unknown types (keeps the existing block). An unknown type on an earlier op could poison a later type-less apiKey edit. Only advance finalType to a getBlock-resolvable type so the fallback matches what apply persists. Test covers empty and unknown types.
1 parent 1a01720 commit 84ddf57

2 files changed

Lines changed: 30 additions & 23 deletions

File tree

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

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -676,30 +676,33 @@ describe('preValidateCredentialInputs (hosted-tool blocks)', () => {
676676
expect(result.errors[0]).toMatchObject({ blockId: 'video-1', field: 'apiKey' })
677677
})
678678

679-
it('does not let a bogus type on an earlier op block stripping on a later edit', async () => {
680-
const operations = [
681-
{
682-
operation_type: 'edit' as const,
683-
block_id: 'video-1',
684-
params: { type: '', inputs: { prompt: 'x' } },
685-
},
686-
{
687-
operation_type: 'edit' as const,
688-
block_id: 'video-1',
689-
params: { inputs: { apiKey: '{{FAL_API_KEY}}' } },
690-
},
691-
]
692-
const workflowState = {
693-
blocks: {
694-
'video-1': { type: 'video_generator_v3', subBlocks: { provider: { value: 'falai' } } },
695-
},
696-
}
679+
it.each([{ type: '' }, { type: 'totally_unknown_type' }])(
680+
'does not let an invalid type (%o) on an earlier op block stripping on a later edit',
681+
async ({ type }) => {
682+
const operations = [
683+
{
684+
operation_type: 'edit' as const,
685+
block_id: 'video-1',
686+
params: { type, inputs: { prompt: 'x' } },
687+
},
688+
{
689+
operation_type: 'edit' as const,
690+
block_id: 'video-1',
691+
params: { inputs: { apiKey: '{{FAL_API_KEY}}' } },
692+
},
693+
]
694+
const workflowState = {
695+
blocks: {
696+
'video-1': { type: 'video_generator_v3', subBlocks: { provider: { value: 'falai' } } },
697+
},
698+
}
697699

698-
const result = await preValidateCredentialInputs(operations, ctx, workflowState)
700+
const result = await preValidateCredentialInputs(operations, ctx, workflowState)
699701

700-
expect(result.filteredOperations[1]?.params?.inputs?.apiKey).toBeUndefined()
701-
expect(result.errors).toHaveLength(1)
702-
})
702+
expect(result.filteredOperations[1]?.params?.inputs?.apiKey).toBeUndefined()
703+
expect(result.errors).toHaveLength(1)
704+
}
705+
)
703706

704707
it('uses same-batch state: a type-less apiKey edit after an earlier op makes the block hosted', async () => {
705708
// op1 switches provider to falai (hosted); op2 (type-less) sets apiKey. op2 must see op1's

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,11 @@ export async function preValidateCredentialInputs(
14631463
const priorType = finalType.has(stateKey)
14641464
? finalType.get(stateKey)
14651465
: (snapshotBlock(stateKey)?.type as string | undefined)
1466-
const resolved = validType(rawType) ?? priorType
1466+
// Only advance the type to one apply will honor: a registry-known type. An unknown type
1467+
// (apply skips the change and keeps the existing block) must not poison the fallback, or a
1468+
// later type-less apiKey edit would be judged against a block config that never applies.
1469+
const candidate = validType(rawType)
1470+
const resolved = (candidate && getBlock(candidate) ? candidate : undefined) ?? priorType
14671471
if (resolved) finalType.set(stateKey, resolved)
14681472
if (isHosted) {
14691473
const priorValues =

0 commit comments

Comments
 (0)