feat: #3586638 Add AI request tag registry with static and dynamic discovery
Description
Adds a tag registry service (ai.tag_registry) so modules can declare, up
front, which AI request tags they may emit — closing the gap described in
#3586638, where tags are free-form strings nobody declares until a request
has already run.
Two discovery paths feed the registry:
- Static — a new
hook_ai_tags_info()(documented in the newai.api.php, this module had none before). Any module — OOP#[Hook]class or legacy procedural.modulefunction — can implement it. ai core registers its own known tags insrc/Hook/AiTags.php(operation types pulled fromAiProviderPluginManager::getOperationTypes(), plusaiandshort_term_memory). - Dynamic — a new
AiTagProviderplugin type (src/Attribute/AiTagProvider.php+src/AiTag/AiTagProviderPluginManager.php), modeled on the existingAiGuardrailplugin pattern. Each plugin watches one config entity type and derives concrete tags from live entities of that type.ai_assistant_apiships the reference implementation (AiAssistantMessageTagProvider): anAiAssistantwith idfooimplies the tagai_assistant_api_assistant_message_foo, matching the literal already emitted byAiAssistantApiRunner. The registry recomputes automatically on entity CRUD via the entity's list cache tags — no custom entity-hook subscriber needed.
AiTagDefinition (src/AiTag/AiTagDefinition.php) carries a kind
(AiTagKind::CallSite vs Correlation) so consumers can tell a stable,
selectable tag (ai_agent_foo_request) apart from a high-cardinality
correlation tag (ai_assistant_thread_{thread_id}) that should never reach
a tag selector.
Scope note: per the issue's own triage, this MR is the registry core +
static discovery + one dynamic example end to end. Migrating
ai_external_moderation/ai_observability's free-text tag fields to
registry-backed selectors, a Drush command, and an admin listing page are
explicitly left as follow-ups — nothing about how tags are emitted or
collected at request time (ProviderProxy, AiProviderInterface,
AiProviderRequestBaseEvent) changes in this MR.
Closes #3586638
Testing instructions
This feature doesn't call any AI provider, so no API key setup is needed — the registry only reasons about which tags could be sent, not about making requests.
- Enable
aiandai_assistant_api. - Run the automated coverage:
vendor/bin/phpunit -c core modules/contrib/ai/tests/src/Kernel/AiTag/AiTagRegistryTest.php— asserts static tags are present, a per-assistant tag appears/disappears with the entity's lifecycle, andhook_ai_tags_info_alter()works. - Manual (optional): create an
ai_assistantconfig entity (any id), then inspect\Drupal::service('ai.tag_registry')->getDefinitions()— aai_assistant_api_assistant_message_<id>entry should appear. Delete the entity and confirm it disappears.
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) — none beyond the routine cache rebuild that follows any code deploy
- 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.