Fix #config_target support for the ai_provider_configuration element
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3580935. -->
Reported by: [svendecabooter](https://www.drupal.org/user/35369)
Related to !1342
>>>
<p>[Tracker]<br>
<strong>Update Summary: </strong>[One-line status update for stakeholders]<br>
<strong>Short Description: </strong>[One-line issue summary for stakeholders]<br>
<strong>Check-in Date: </strong>MM/DD/YYYY<br>
<em>Metadata is used by the <a href="https://www.drupalstarforge.ai/" title="AI Tracker">AI Tracker.</a> Docs and additional fields <a href="https://www.drupalstarforge.ai/ai-dashboard/docs" title="AI Issue Tracker Documentation">here</a>.</em><br>
[/Tracker]</p>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>The <code>ai_provider_configuration</code> form element <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3556181" title="Status: Closed (fixed)">#3556181: Create a form element for selecting providers</a></span> and the <code>ai.provider_config</code> schema type <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3552774" title="Status: Closed (fixed)">#3552774: Create linkable schema for providers</a></span> were designed to work together. In <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3552774" title="Status: Closed (fixed)">#3552774: Create linkable schema for providers</a></span> <a href="https://www.drupal.org/project/ai/issues/3552774#comment-16391026">comment #15</a>, @marcus_johansson described the intended workflow:</p>
<ol>
<li>Set up the `ai_provider_configuration` form element in your form.</li>
<li>Store the value using the `ai.provider_config` config schema.</li>
<li>Load the configured provider using a helper method from the plugin manager.</li>
</ol>
<p>However, step 2 doesn't work seamlessly because the form element and the schema use different value formats:</p>
<p>Form element returns:</p>
<div class="codeblock">
<pre><span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">[<br> </span><span style="color: #DD0000">'provider' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'openai'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'model' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'gpt-4.1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'config' </span><span style="color: #007700">=> [</span><span style="color: #DD0000">'temperature' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0.7</span><span style="color: #007700">],<br>]<br></span><span style="color: #0000BB">?></span></span></pre></div>
<p>Schema expects:</p>
<pre>use_default: false<br>provider_id: openai<br>model_id: gpt-4.1<br>configuration:<br> temperature: 0.7</pre><p>This means:</p>
<ul>
<li>#config_target cannot be used with the ai_provider_configuration element, because ConfigFormBase::storeConfigKeyValueMap() would write the element's value (with provider/model/config keys) directly to config, which doesn't match the ai.provider_config schema (which expects use_default/provider_id/model_id/configuration keys). The element also sets '#default_value' => NULL in its getInfo(), which prevents ConfigFormBase::loadDefaultValuesFromConfig() from auto-populating the value.</li>
<li>Every consumer module must manually translate between the two formats in submitForm() and when setting #default_value, adding error-prone boilerplate.</li>
</ul>
<p>The documentation added in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/ai/issues/3556181" title="Status: Closed (fixed)">#3556181: Create a form element for selecting providers</a></span> (in ai_provider_configuration_element.md) even shows this manual translation in its "Saving to Configuration" example, confirming the gap.</p>
<h4 id="summary-steps-reproduce">Steps to reproduce (required for bugs, but not feature requests)</h4>
<ol>
<li>Create a ConfigFormBase with an ai_provider_configuration element.</li>
<li>Try to use #config_target pointing at a config key with type: ai.provider_config.</li>
<li>Observe that:
<ul>
<li>The default value is not auto-populated from config (because getInfo() sets #default_value => NULL).</li>
<li>On submit, the wrong key names are written to config (provider instead of provider_id, etc.).</li>
<li>The use_default boolean is never set.</li>
</ul>
</li>
</ol>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>Add #config_target support to the ai_provider_configuration element by:</p>
<ol>
<li>Adding a toConfig() static method that translates the element's provider/model/config value to the schema's use_default/provider_id/model_id/configuration format.</li>
<li>Adding a fromConfig() static method that does the reverse (for populating #default_value from config).</li>
<li>These can be used as the ConfigTarget callable pair, enabling:</li>
</ol>
<pre>'#config_target' => new ConfigTarget(<br> 'my_module.settings',<br> 'provider_config',<br> toConfig: [AiProviderConfiguration::class, 'toConfig'],<br> fromConfig: [AiProviderConfiguration::class, 'fromConfig'],<br>),</pre><p>Or another method to achieve the same mapping logic automatically.</p>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<ul>
<li>Implement the translation layer or value format change</li>
<li>Update the element's getInfo() so #default_value doesn't unconditionally block ConfigFormBase auto-population when #config_target is used</li>
<li>Update documentation examples</li>
<li>Add test coverage for #config_target usage with the element</li>
</ul>
<h3 id="summary-ai-usage">AI usage (if applicable)</h3>
<p>[ x ] AI Assisted Issue<br>
This issue was generated with AI assistance, but was reviewed and refined by the creator.</p>
<p>[ x ] AI Generated Code<br>
This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.</p>
> Related issue: [Issue #3552774](https://www.drupal.org/node/3552774)
> Related issue: [Issue #3580948](https://www.drupal.org/node/3580948)
> Related issue: [Issue #3580910](https://www.drupal.org/node/3580910)
issue