bug: #3586624 Streaming chat forces plain-text hostname filtering, corrupting...
Description
Closes #3586624.
Streaming forced plain text mode on the hostname filter. That mode uses a plain
regex with no idea about HTML, so it blanked the URL inside href and left the
tag behind. A good link came back as <a href="">label</a> - it looked fine but
went nowhere, and nothing warned the editor.
The old code only forced plain text mode when not inside a PHP Fiber. That check never worked, because a streamed response is sent after the kernel has finished, outside any Fiber. So plain text mode was always on for streaming. The check is now removed and streaming uses the same HTML-aware filtering as everything else.
Two supporting changes make that safe, and are the least obvious part of the diff:
HostnameFilter::filterText()now returns the text untouched when it removed nothing. Otherwise every chunk gets re-parsed and rewritten, turningTom & JerryintoTom & Jerryand adding stray closing tags.StreamedChatMessageIterator::filterChunk()balances a chunk's tags before filtering and unwraps them after, so the parser cannot drop or add tags in the middle of a response.
Also included on purpose: a per-request HostnameFilterDto now works on streams.
It did nothing there before. This gives sites a way to keep the old behaviour if
they want it, using plainTextMode: TRUE.
Things to know:
-Plain URLs in ordinary text are no longer removed from streamed output. -Streaming now behaves like non-streaming, which has always worked this way. -The log message is now "Removed disallowed AI output URL" and appears for both streamed and non-streamed runs. -"Replace Links & Delete Images" now works on streams. It was ignored before.
Testing instructions
- At /admin/config/ai/guardrails/global, clear the pregenerate guardrail and save. It blocks prompts over 50 characters.
- At /admin/config/ai/settings, make sure Trusted Domains does not include drupal.org, and Untrusted AI Generated Links & Images is "Delete Images & Links".
- Enable ai_api_explorer. Open /admin/config/ai/explorers > Chat Generator. In Message 1 paste:
Output the following HTML exactly as given, with no commentary, no code
fences, and no changes:
<p>Read <a href="https://www.drupal.org/project/ai">the AI module page</a> for details.</p>- Submit with Streamed unchecked, then again with Streamed checked. Check both with View Source, not the rendered page. Both should give: the AI module page Before the fix, the streamed run gave the AI module page.
- Set Untrusted AI Generated Links & Images to "Replace Links & Delete Images" and run the streamed version again. Expected: https://www.drupal.org/project/ai
- Add drupal.org and www.drupal.org to Trusted Domains and run it again. Expected: the paragraph comes back exactly as it was sent
Checklist
- I have linked the related issue in the MR title or description
- I have performed a self-review of my own code
- I have added or updated tests, or explained in the description why this change is not covered by tests
- I have updated documentation for any new or changed functionality
- I have written testing instructions and verified them locally
- I have noted any required post-merge steps (config imports, cache rebuilds, manual changes)
- This MR contains no breaking API or hook changes, or they are explicitly documented in the description
AI Compliance
Note
Check the one that best describes your usage, or leave all unchecked if AI was not significantly used.
-
AI Assisted Code
Mainly written by a human; AI used for autocomplete or partial generation under full human supervision. -
AI Generated Code
Mainly generated by AI, reviewed and approved by a human before this MR was created. -
Vibe Coded
Generated by AI and only functionally reviewed before this MR was created.
Closes #3586624