Inject context into ai_ckeditor requests
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3581955. --> Reported by: [jessehs](https://www.drupal.org/user/620440) Related to !116 >>> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>The <code>ai_context</code> module cannot inject scope-matched context items into <code>ai_ckeditor</code> AI requests because <code>ai_ckeditor</code> bypasses the <code>ai_agents</code> pipeline (and its <code>BuildSystemPromptEvent</code>) entirely &mdash; it calls the AI provider directly in its own streaming controller. There is no event or hook point for <code>ai_context</code> to intercept.</p> <p>This issue is the <code>ai_context</code> companion to the <code>ai_ckeditor</code> entity context and event extensibility work tracked in the <code>drupal/ai</code> module issue queue. See <strong>#3581952</strong> for the full problem description, architecture overview, and complete end-to-end testing instructions.</p> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p>This merge request adds an event subscriber that listens to the new <code>ai_ckeditor.pre_request</code> event (introduced by the companion <code>ai_ckeditor</code> merge request in <strong>#3581952</strong>). It is applied to the <code>drupal/ai_context</code> module and requires the companion merge request on <code>drupal/ai</code> to be merged first.</p> <h4>New file: <code>src/EventSubscriber/AiContextCKEditorSubscriber.php</code></h4> <ul> <li>Implements <code>EventSubscriberInterface</code> with a <code>class_exists()</code> guard so <code>ai_context</code> does not hard-depend on <code>ai_ckeditor</code>.</li> <li>Listens to <code>ai_ckeditor.pre_request</code>.</li> <li>Extracts <code>entityType</code> and <code>entityId</code> from the event and builds a context request using <code>AiContextRequestFactory::fromParameters()</code> with <code>SELECTION_MODE_MATCH_ALL</code>.</li> <li>Runs <code>AiContextSelector::select()</code> to find matching context items (global items, path-scoped items, target-entity-matched items) and appends the rendered context to the system prompt via <code>$event-&gt;setSystemPrompt()</code>.</li> <li>Records usage via <code>AiContextUsageTracker</code>.</li> </ul> <p><strong>Why <code>MATCH_ALL</code> instead of <code>fromAgent()</code>:</strong> The <code>fromAgent()</code> factory method hardcodes <code>SELECTION_MODE_MINIMAL</code>, which requires explicit scope subscriptions in <code>ai_context.agents</code> config to select non-global items. Since CKEditor is not an agent and typically has no such config entry, <code>MINIMAL</code> mode silently drops all non-global scoped items (e.g., a blog context item scoped to <code>/blogs/*</code>) even after they pass hard context filtering. <code>MATCH_ALL</code> includes all items that pass hard context filters, which is the correct behavior for a non-agent consumer.</p> <h4>Modified file: <code>ai_context.services.yml</code></h4> <ul> <li>Registers <code>ai_context.event_subscriber.ckeditor</code> with dependencies on <code>@ai_context.selector</code>, <code>@ai_context.request_factory</code>, <code>@ai_context.usage_tracker</code>, and <code>@config.factory</code>.</li> </ul> <h3 id="summary-remaining-tasks">Remaining tasks</h3> <ol> <li>Review and feedback on the approach.</li> <li>Consider adding kernel tests for the event subscriber and context injection logic.</li> </ol> <h3>Testing instructions</h3> <p>This merge request must be tested together with the companion <code>ai_ckeditor</code> merge request. See <strong>#3581952</strong> for complete end-to-end testing instructions covering setup, application order, and verification steps.</p> <p>The key test scenario for this change specifically:</p> <ol> <li>Create a global <code>ai_context</code> item with an easily verifiable instruction (e.g., brand voice guidelines that include an obvious marker sentence)</li> <li>Edit a node and trigger an AI CKEditor action (e.g., Tone change)</li> <li>Verify that the context item content is injected into the AI response &mdash; confirming the event subscriber received the <code>ai_ckeditor.pre_request</code> event and appended the context to the system prompt</li> <li>Check <em>Admin &gt; Configuration &gt; AI &gt; AI Context &gt; Usage</em> to verify usage tracking recorded the <code>ai_ckeditor</code> consumer</li> </ol> <h3>API changes</h3> <ul> <li><strong><code>AiContextCKEditorSubscriber</code>:</strong> New event subscriber using <code>SELECTION_MODE_MATCH_ALL</code> via <code>fromParameters()</code> for correct non-agent consumer behavior. Soft dependency on <code>ai_ckeditor</code> via <code>class_exists()</code> guard.</li> </ul> <h3 id="summary-ai-usage">AI usage (if applicable)</h3> <p>[x] AI Generated Code<br> <br>This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human.</p> > Related issue: [Issue #3581952](https://www.drupal.org/node/3581952)
issue