Canvas AI: Expose Props of Blocks to the Agent
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3569120. -->
Reported by: [akhil babu](https://www.drupal.org/user/3632866)
Related to !498
>>>
<h3 id="overview">Overview</h3>
<p>Currently CanvasAiPageBuilderHelper::getAllComponentsKeyedBySource() does this</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">$source </span><span style="color: #007700">=== </span><span style="color: #0000BB">SingleDirectoryComponent</span><span style="color: #007700">::</span><span style="color: #0000BB">SOURCE_PLUGIN_ID</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">processSdc</span><span style="color: #007700">(</span><span style="color: #0000BB">$component</span><span style="color: #007700">, </span><span style="color: #0000BB">$sdc_definitions</span><span style="color: #007700">, </span><span style="color: #0000BB">$output</span><span style="color: #007700">);<br> }<br> elseif (</span><span style="color: #0000BB">$source </span><span style="color: #007700">=== </span><span style="color: #0000BB">JsComponent</span><span style="color: #007700">::</span><span style="color: #0000BB">SOURCE_PLUGIN_ID</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">processCodeComponents</span><span style="color: #007700">(</span><span style="color: #0000BB">$component</span><span style="color: #007700">, </span><span style="color: #0000BB">$output</span><span style="color: #007700">, </span><span style="color: #0000BB">$available_components</span><span style="color: #007700">[</span><span style="color: #0000BB">$component_id</span><span style="color: #007700">]);<br> }<br> else {<br> </span><span style="color: #FF8000">// Other sources: id, name, description (description = name)<br> </span><span style="color: #0000BB">$output</span><span style="color: #007700">[</span><span style="color: #0000BB">$source</span><span style="color: #007700">][</span><span style="color: #DD0000">'components'</span><span style="color: #007700">][</span><span style="color: #0000BB">$component_id</span><span style="color: #007700">] = [<br> </span><span style="color: #DD0000">'id' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$component_id</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'name' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$component</span><span style="color: #007700">-></span><span style="color: #0000BB">label</span><span style="color: #007700">(),<br> </span><span style="color: #DD0000">'description' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$component</span><span style="color: #007700">-></span><span style="color: #0000BB">label</span><span style="color: #007700">(),<br> ];<br> }<br></span><span style="color: #0000BB">?></span></span></pre></div>
<p>So for blocks, only the ID, name, and description are passed as context to the agent. However, this causes issues when the agent decides to use a block with required props (e.g., the System Branding block), because AiResponseValidator::validateComponentStructure() will throw errors like:</p>
<p><code>Component validation errors: components.0.[block.system_branding_block].props.: 'label' is a required key. components.0.[block.system_branding_block].props.: 'label_display' is a required key. components.0.[block.system_branding_block].props.: 'use_site_logo' is a required key. components.0.[block.system_branding_block].props.: 'use_site_name' is a required key. components.0.[block.system_branding_block].props.: 'use_site_slogan' is a required key</code></p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Expose the props of blocks to the agent.</p>
<p>Currently, _addNewComponentToLayout in layoutModelSlice.ts only sets field values for components that have the propSources property . Block components do not have this property.</p>
<pre> const buildInitialData = (component: CanvasComponent): ComponentModel => {<br> if (isPropSourceComponent(component)) { // Block components return false for this check.<br> const initialData: EvaluatedComponentModel = {<br> resolved: {},<br> source: {},<br> };<br> Object.keys(component.propSources).forEach((propName) => {<br> const prop = component.propSources[propName];<br> // These will be needed when we support client-side preview updates.<br> initialData.resolved[propName] = prop.default_values?.resolved || [];<br> // These are the values the server needs.<br> // @todo Reduce the verbosity of this in https://drupal.org/i/3463996<br> // and https://drupal.org/i/3528043 to send less data.<br> initialData.source[propName] = {<br> expression: prop.expression,<br> sourceType: prop.sourceType,<br> value: prop.default_values?.source || [],<br> sourceTypeSettings: prop.sourceTypeSettings || undefined,<br> };<br> });<br> return initialData;<br> }<br> return {<br> resolved: {},<br> };</pre><p>This means it is not possible to place a block component with specific configuration values. For example, when the AI agent tries to place a Site Branding block with values like:</p>
<pre> { <br> "use_site_logo": true, <br> "use_site_name": true <br> }</pre><p>
these values are ignored because block components lack propSources. As a result, block components are always placed with their default configuration, regardless of what values the AI specifies.</p>
<h3 id="ui-changes">Testing steps</h3>
<ul>
<li>Install ai_api_explorer module</li>
<li>Go to /admin/config/ai/explorers/tools_explorer, Select the 'Get component context' tool and click 'Run function'</li>
<li>The props of block components must be visible in the output. Previously it included only block id, name and description</li>
</ul>
<p>Eg:</p>
<pre>block.events_block:<br> id: block.events_block<br> name: 'Events Feed'<br> description: 'Events Feed'<br> props:<br> label:<br> name: Description<br> description: Description<br> type: label<br> default: 'Events Feed'<br> required: true<br> label_display:<br> name: 'Display title'<br> description: 'Display title'<br> type: string<br> default: '0'<br> required: true<br> enum: ['0', visible]<br> context_mapping:<br> name: 'Context assignments'<br> description: 'Context assignments'<br> type: sequence<br> default: null</pre>
issue