fix: #3631526 Plain error messages for moderation, keys, rate limits and timeouts

Issue: #3631526 "Initialize the AI Media module"

Chunk 2 of the UX and integration plan (20 October 2026): moderation and error wording (plan sections 2.3 and 6.2).

Depends on !8 (closed) (3631526-guardrail-stops), which depends on !6 (merged). Until those merge, this diff includes them; the change of this MR is its last commit.

What changed

  • Moderation: AiUnsafePromptException may come from the site's own moderation (AI External Moderation) as well as the provider, so the message no longer blames the provider: "This description was flagged as unsafe and was not sent. Change it and try again." Administrators get a link to the AI moderation settings, and the exception text (which names the moderation provider) under "Details".
  • Keys: the AI module wraps HTTP client errors as AiBadRequestException, so a refused key read as "could not use this description". The HTTP status in the exception chain is now read: 401 and 403 give "Generation is not available right now" (administrators: "Check the API key."), 429 gives the rate limit message.
  • Timeout: a timed-out request reads "The AI service took too long. Try again, or ask for something simpler." Administrators see the AI module's request_timeout value and a link to the AI settings where it is changed.
  • Wording per plan 6.2: "The AI service could not use this description", "Too many requests right now. Wait a minute and try again." The administrator detail is labelled "Details", not "Provider message".
  • AI Media adds no moderation of its own. The troubleshooting page now lists every message, its cause and what administrators see.
  • Unchanged: the description is kept, focus returns to it, and the error is announced assertively once; the format check still runs before any call.

How it was tested

  • Kernel ErrorMessagesTest (9 cases, editor and administrator each): moderation flag, 401, 403, quota, rate limit exception, 429, timeout (cURL error 28), 400 and an unknown error. A test subscriber throws each error the way the AI module wraps it. All 15 Kernel tests of the module pass.
  • phpcs (Drupal, DrupalPractice), phpstan level 1, cspell, eslint, stylelint: clean.

Not tested

  • A real moderation endpoint flagging a prompt, a real revoked key and a real timeout (simulated in the Kernel test, no paid calls).
  • No new e2e scenario: Echo cannot be made to fail like a provider in the browser suite; the existing suite runs in CI.

AI-Generated: Yes. Written with Claude Code (Claude Opus 5.5) under the Drupal AI policy; tested by the author before submission.

Checkpoints:

  • File an issue
  • Addition/Change/Update/Fix
  • Testing to ensure no regression
  • Automated unit testing coverage
  • Automated functional testing coverage
  • UX/UI designer responsibilities
  • Readability
  • Accessibility
  • Performance
  • Security
  • Developer Documentation
  • User Guide Documentation
  • Reviewed by human
  • Code review by maintainers
  • Full testing and approval
  • Credit contributors
  • Review with the product owner
  • Release notes snippet
  • Release

Merge request reports

Loading
Loading