( block: BlockState, blockConfig: any, params: Record<string, any> )
| 548 | * result. Single source of truth shared by both, so they can never drift. |
| 549 | */ |
| 550 | export function collectBlockFieldIssues( |
| 551 | block: BlockState, |
| 552 | blockConfig: any, |
| 553 | params: Record<string, any> |
| 554 | ): BlockFieldIssues { |
| 555 | // Disabled blocks and trigger-mode blocks are not validated (mirrors runtime). |
| 556 | if (block.enabled === false) { |
| 557 | return { missingRequiredFields: [], inactiveModeValues: [] } |
| 558 | } |
| 559 | if ( |
| 560 | block.triggerMode === true || |
| 561 | blockConfig.category === 'triggers' || |
| 562 | params.triggerMode === true |
| 563 | ) { |
| 564 | return { missingRequiredFields: [], inactiveModeValues: [] } |
| 565 | } |
| 566 | |
| 567 | const missingFields: string[] = [] |
| 568 | const displayAdvancedOptions = block.advancedMode ?? false |
| 569 | const isTriggerContext = block.triggerMode ?? false |
| 570 | const isTriggerCategory = blockConfig.category === 'triggers' |
| 571 | const canonicalIndex = buildCanonicalIndex(blockConfig.subBlocks || []) |
| 572 | const canonicalModeOverrides = block.data?.canonicalModes |
| 573 | const allValues = buildSubBlockValues(block.subBlocks) |
| 574 | |
| 575 | // Get the tool configuration to check parameter visibility |
| 576 | const toolAccess = blockConfig.tools?.access |
| 577 | const currentToolId = toolAccess?.length > 0 ? selectToolId(blockConfig, params) : null |
| 578 | const currentTool = currentToolId ? getTool(currentToolId) : null |
| 579 | |
| 580 | // Validate tool parameters (for blocks with tools). |
| 581 | // Lookup contract: a tool param's value lives under its own paramId in `params`. |
| 582 | // Block subBlocks align via either `id === paramId` or `canonicalParamId === paramId`. |
| 583 | if (currentTool) { |
| 584 | Object.entries(currentTool.params || {}).forEach(([paramId, paramConfig]: [string, any]) => { |
| 585 | if (paramConfig.required && paramConfig.visibility === 'user-only') { |
| 586 | const matchingConfigs = |
| 587 | blockConfig.subBlocks?.filter( |
| 588 | (sb: any) => sb.id === paramId || sb.canonicalParamId === paramId |
| 589 | ) || [] |
| 590 | |
| 591 | let shouldValidateParam = true |
| 592 | |
| 593 | if (matchingConfigs.length > 0) { |
| 594 | shouldValidateParam = matchingConfigs.some((subBlockConfig: any) => { |
| 595 | const includedByMode = shouldSerializeSubBlock( |
| 596 | subBlockConfig, |
| 597 | allValues, |
| 598 | displayAdvancedOptions, |
| 599 | isTriggerContext, |
| 600 | isTriggerCategory, |
| 601 | canonicalIndex, |
| 602 | canonicalModeOverrides |
| 603 | ) |
| 604 | |
| 605 | const isRequired = (() => { |
| 606 | if (!subBlockConfig.required) return false |
| 607 | if (typeof subBlockConfig.required === 'boolean') return subBlockConfig.required |
no test coverage detected