Services
The AI Context module registers the following services in
ai_context.services.yml.
Each service below is marked Status: Public or Status: Internal. Public services are safe to depend on; internal services are implementation details that may change without deprecation. Services without an explicit label are internal; see Support services. See API stability for the full policy and the public API surface.
Core services
ai_context.selection_factory
Status: Public
Interface: Drupal\ai_context\Service\AiContextSelectionFactoryInterface
Class: Drupal\ai_context\Service\AiContextSelectionFactory
(@internal implementation; type-hint the interface)
Builds AiContextSelection value objects from consumer config or raw
parameters, and provides convenience methods for callers that just need
rendered context.
Selection-building methods:
fromConsumer(string $consumerId, string $task, ...)-- builds a selection from a saved consumer config entry.fromParameters(AiContextSelectionParams $params)-- builds a selection from a strongly-typed DTO (used by function call plugins and advanced consumers).
Convenience methods (build + select in one call):
// One-liner: returns the rendered context string (empty if nothing matches).
public function getRenderedContext(
array $scopes = [],
?int $maxTokens = NULL,
?EntityInterface $requestEntity = NULL,
?string $consumerId = NULL,
?string $selectionMode = NULL,
): string
// Full result with cache metadata, selected IDs, token usage.
public function getResult(
array $scopes = [],
?int $maxTokens = NULL,
?EntityInterface $requestEntity = NULL,
?string $consumerId = NULL,
?string $selectionMode = NULL,
): AiContextSelectionResult
Usage examples:
/** @var \Drupal\ai_context\Service\AiContextSelectionFactoryInterface $factory */
$factory = \Drupal::service('ai_context.selection_factory');
// Get rendered context text (returns empty string if nothing matches).
$text = $factory->getRenderedContext(
scopes: ['use_case' => ['working_with_text']],
maxTokens: 2000,
requestEntity: $node,
);
// Or get the full result object with cache metadata and selected item IDs.
$result = $factory->getResult(
scopes: ['use_case' => ['working_with_text']],
maxTokens: 2000,
requestEntity: $node,
);
$text = $result->getRenderedText();
$cacheMetadata = $result->getCacheableMetadata();
$selectedIds = $result->getSelectedItemIds();
Parameters:
- scopes: Scope subscriptions keyed by scope plugin ID. Pass an empty array to match all published context (subject to the token budget, the global item cap, and the selector's internal candidate bound).
- maxTokens: Maximum tokens for rendered text. NULL defers to module settings.
- requestEntity: The matching-environment entity for request-context
scope matching, not the selection object. Named-argument callers must
use
requestEntity:;currentEntity:no longer works. - consumerId: Canonical
{type}:{instance}ID. Merges that consumer's saved configuration. Other strings are ignored. Pulls without a consumer log asnone. - selectionMode: Optional trailing
SELECTION_MODE_*override; NULL or an omitted argument defers to the site-wide default fromai_context.settings:selection_mode(Relevant unless changed).
Under the shipped relevant site default: global, always-included,
exact-match, and situational-match items are considered without requiring
a subscription; pass SELECTION_MODE_BROAD to additionally fill leftover
budget with any published candidate that passes context filters.
Explicit scopes only affect the scope-scored group. Relevant and Broad
still scan the full published catalog. Pass SELECTION_MODE_MINIMAL to
prefilter.
On catalogs larger than 50 published items, the selector logs one watchdog warning per consumer per request. That warning is expected; it does not mean selection failed. See Debugging and #3586420.
Consumer config lookup:
fromConsumer(string $consumerId, string $task, ...)— builds a selection from a storedai_context.consumersrow plus plugin defaults.isPushAllowed(string $consumerId): bool— whether automatic push is allowed (push_enabled, type enablement, and availability).loadConsumerConfig(string $consumerId)— stored consumer config (AiContextConsumerStoredConfig) or NULL. Missing keys are not filled with defaults.resolveConsumerConfig(string $consumerId)— effective config (AiContextConsumerResolvedConfig): stored config over plugin defaults, plus type enablement and availability.
See AiContextSelection and selection modes and AiContextSelectionResult below.
ai_context.scope_subscription_form
Status: Public
Class: Drupal\ai_context\Service\AiContextScopeSubscriptionFormBuilder
Interface: Drupal\ai_context\Service\AiContextScopeSubscriptionFormBuilderInterface
Builds and processes scope subscription form widgets for consumers that need the same subscription UI as the consumer editor without depending on the internal scope plugin manager.
Methods:
hasSubscribableScopes(): bool— whether any enabled scope supports subscriptions.getNonSubscribableScopeLabels(): string[]— human-readable labels for scopes that do not support subscriptions (for example Global).buildWidgets(array &$form, FormStateInterface $form_state, array $defaults = [], bool $for_subscription = TRUE): array— per-scope subscription widgets keyed by scope plugin ID.extractValues(array $submitted, array $previous = []): array— normalizes raw submitted values into nested scope values (scope_id => [value_ids]). Pass previously stored subscriptions as$previousso disabled, unavailable, and missing scopes are kept.buildSummary(array $scope_subscriptions): array— summary render array for selected subscriptions.
Usage example (custom entity form with scope subscriptions):
/** @var \Drupal\ai_context\Service\AiContextScopeSubscriptionFormBuilderInterface $builder */
$builder = \Drupal::service('ai_context.scope_subscription_form');
if (!$builder->hasSubscribableScopes()) {
return;
}
$form['my_module']['scope_subscriptions'] = [
'#type' => 'container',
'#tree' => TRUE,
];
foreach ($builder->buildWidgets($form, $form_state, $current_scopes) as $scope_id => $element) {
$form['my_module']['scope_subscriptions'][$scope_id] = $element;
}
// In submit/entity builder:
$submitted = $form_state->getValue(['my_module', 'scope_subscriptions']) ?? [];
$scope_values = $builder->extractValues(
is_array($submitted) ? $submitted : [],
$current_scopes,
);
ai_context.selector
Status: Internal
Class: Drupal\ai_context\Service\AiContextSelector
Interface: Drupal\ai_context\Service\AiContextSelectorInterface
Orchestrates context selection: filtering, scope scoring, subcontext resolution, rendering, and dispatching stable selector pipeline events. It has zero knowledge of agents or consumer-specific configuration.
Items with inheritsParentScope() are
excluded from the top-level published-item scan; they enter selection with
their parent via subcontext resolution or through explicit always-include
overrides. Subcontext items with custom scope remain full top-level candidates.
See Parent scope.
Extension model: Contrib modules extend selection via documented selector
pipeline events — not by decorating this service. Decorating or replacing
ai_context.selector is unsupported; the selector and its internal pipeline
may change without deprecation. See Supported extension
model in Events.
Key method:
public function select(AiContextSelection $selection): AiContextSelectionResult
The AiContextSelectionResult contains the rendered text, selected item metadata,
token usage, and CacheableMetadata. See AiContextSelectionResult
for the full API. Pipeline event names and subscriber contracts are documented
in Events.
ai_context.scope_index
Status: Internal
Class: Drupal\ai_context\Service\AiContextScopeIndexService
Maintains the denormalized ai_context_item_scope_index table — one row per
(item_id, scope_id, value) triple mirroring each item's effective scope
(via AiContextItem::getScopeItem()). This lets the selector prefilter
candidates with SQL instead of loading every published item when scope
subscriptions are present.
Key methods:
indexItem(AiContextItem $item)— rebuild index rows for one item (called on insert/update). For subcontext items withinheritsParentScope(), rows reflect the parent's scope values. When a parent is updated, those children are reindexed in the entity update hook.deindexItem(int $item_id)— remove all rows for an itemgetItemIdsByScopeValues(string $scopeId, array $values): int[]— items matching any of the given values for one scopegetItemIdsByScope(array $scope_subscriptions, array $candidate_ids = []): int[]— items with at least one matching value for any subscribed scope. Missing item values earn no subscription score. Callers add global and exact-match candidates separatelycountPublishedGlobalItems(?AiContextItem $exclude_item = NULL): int— published items that store global on themselves (field query, not the index table, so inherited child rows are excluded)hasPublishedGlobalItem(): bool— whether that count is greater than zero
The entity_item scope uses a separate DER field and is not indexed here.
ai_context.scope_cleanup
Status: Internal
Class: Drupal\ai_context\Service\AiContextScopeCleanupService
Removes stale scope values from context items and consumer subscriptions.
This service only strips values. Plugins that implement
AiContextScopeStoredValueInterface decide which stored values are
stale and whether a Drupal change should trigger a scrub.
Key methods:
removeScopeValues(string $scopeId, array $values)— strip values from item scope values, consumer subscriptions, and the scope indexscrubScope(string $scopeId)— ask one opted-in plugin which of its stored values are stale and remove themscrubStaleScopeValues()— ask every opted-in plugin. Use Case is skipped because it does not implement the interface.scrubScopesAfterEntityDelete(EntityInterface $entity)— scrub plugins that opt in for this deletescrubScopesAfterBundleDelete(string $entityTypeId, string $bundle)— scrub plugins that opt in for this bundle deletescrubScopeAfterSettingsChange(string $scopeId)— scrub one plugin after its settings config is savedremoveDeletedConsumerConfig(AiContextConsumerId $consumerId)— drop the storedai_context.consumersrow for a deleted instanceremoveEntityItemReference(string $entityTypeId, int|string $entityId)— clear DER target references when a referenced entity is deleted
Triggered automatically from hook_entity_delete(),
hook_entity_bundle_delete(), hook_modules_uninstalled(), and
AiContextScopeConfigSubscriber (see Event subscribers below).
Subscription collection reads raw ai_context.consumers (no
settings.php overrides) so removal writes the same storage it
read. Custom scopes opt in through
AiContextScopeStoredValueInterface; do not invent a second cleanup
path.
ai_context.scope_resolver
Status: Internal
Class: Drupal\ai_context\Service\AiContextScopeResolver
Scores context items against scope subscriptions and applies hard context
filters (e.g., language, site section). Replaces the former
AiContextScopeMatcher.
ai_context.renderer
Status: Internal
Class: Drupal\ai_context\Service\AiContextRenderer
Renders selected context items into a token-limited text block for prompt
injection. Truncation and assembled-output token checks use the AI module
TextChunker and @ai.tokenizer, configured from the same resolved
tokenizer model as ai_context.token_estimator (explicit
provider_config.model, the site chat default when use_default is
enabled, or gpt-4 when neither is available). Receives pre-translated
entities from the selector.
ai_context.token_estimator
Status: Internal
Class: Drupal\ai_context\Service\AiContextTokenEstimator
Estimates token counts via the AI module @ai.tokenizer service and
classifies budget-relative weight tiers for context text. Used by the renderer
for token budgeting, by the listing UI for token badges, and to persist
token_count on context items at save time. The tokenizer is a shared service
that other consumers re-point at their own model, so the configured model is
re-applied before every count. The selector uses this after TEXT_RENDERED to
recalculate AiContextSelectionResult::getTokensUsed() from the final rendered text.
The model name is only used to select a local tokenizer encoding.
Resolution matches chat provider lookup: an explicit
provider_config.model, else the site-wide AI chat default when
use_default is enabled, else gpt-4.
When the resolved tokenizer model changes,
AiContextSettingsConfigSubscriber hands the change to
ai_context.token_count_backfill_scheduler. That includes
ai_context.settings:provider_config and, when CCC is using the site
chat default, a change to ai.settings default_providers.chat. Config
saves act immediately; configuration imports record the change in state
(durable across the batch requests a UI import spans) and act at
ConfigEvents::IMPORT. See
Recounting after a model change.
ai_context.token_count_backfill_scheduler
Status: Internal
Class: Drupal\ai_context\Service\AiContextTokenCountBackfillScheduler
Owns the chunked token_count recount shared by update 10023, the settings
batch, and the cron queue worker, so all three write counts identically.
How a recount runs depends on what the request can finish:
| Context | Mechanism |
|---|---|
| CLI (Drush config set/import, scripts) | backfillAll(), synchronous |
| A request that processes a batch (settings form save) | Batch, draining the queue |
| Any other web request (programmatic save, UI config import) | Queue only, drained by cron |
Non-CLI scheduling always enqueues first, because a request that never processes a batch would otherwise drop the recount silently. The batch consumes the same queue, so a form save recounts immediately and leaves cron nothing to repeat.
Chunks write only the token_count column of the exact revision each count was
read from, keyed on ID, revision ID, and langcode. A concurrent editorial save
creates a newer revision, so the update matches no rows and the editor's own
preSave() count stands — the recount can never overwrite content changes.
Key methods:
scheduleIfTokenizerModelChanged(mixed $old, mixed $new): void— no-op when the resolved model is unchanged or no items existschedule(): void— queue or run a recount after the resolved model changed (including a site chat default change)backfillChunk(int $last_id): array{last_id: int, processed: int}— recount one chunk; aprocessedof0means the catalog is exhaustedbackfillAll(): int— recount everything, blockingprocessQueuedChunk(): array/drainQueue(): int— consume queued cursors
ai_context.subscription_budget
Status: Internal
Class: Drupal\ai_context\Service\AiContextSubscriptionBudgetCalculator
Calculates subscribed context token totals for the consumer settings form
budget summary. Returns AiContextSubscriptionBudgetSummary, an internal
value object consumed by AiContextConsumerForm.
Totals sum each item's stored guidance content (token_count). They do not
include render overhead such as item IDs, labels, purpose lines, guidance
indentation, block separators, or truncation. Compare with
AiContextSelectionResult::getTokensUsed() for runtime usage after selection.
Candidate narrowing mirrors AiContextSelector so the summary counts what
runtime selection would actually inject:
- Items are counted in the detected content language, matching the translation the selector injects.
- Always-include items bypass scope handling; never-include items are dropped; inaccessible items are dropped; inheriting children are only counted through their parent.
- Hard context filters (
filterByRequestContext()) apply to everything else, on every selection mode — not only MINIMAL without subscriptions. max_global_itemscaps globals (higher Priority first, then shorter content, then highest ID). Other groups use the selector's internal per-group candidate bound. Groups merge in this order: global, always-included, exact-match, scope-scored, situational-match (Relevant and Broad), and broad-fill.- Relevant (the shipped site-wide default when
selection_modeis omitted) includes situational-match items from scopes declaringsupportsSituationalMatch()— Languages, Site Sections, Entity Types, and Taxonomy Terms by default. Minimal does not. - Subscription scoring runs before situational-match so a Minimal match cannot be dropped when the situational-match group hits its internal cap. Situational-match classification remains as a fallback for candidates displaced from the final scope-scored group. Overflow situational-match items still feed broad-fill.
- Final scope-scored duplicates are removed before the broad-fill group is pruned, so they cannot consume its candidate allowance.
- Relevant and Broad stream published IDs and load entities in 200-item chunks. Each group is pruned and memory-cache entries introduced by the scan are released after every chunk. Broad-fill still runs when subscriptions exist.
Results are cached per consumer configuration, module settings, scope-plugin
configuration, effective permissions, and request context (scope context
values, target entity, content language, and request path) for one hour.
Cache tags include ai_context_item_list, config:ai_context.settings,
config:ai_context.consumers, and each config:ai_context.scope_settings.*
object, plus metadata supplied by the user.permissions cache context. The
consumer settings lazy builder varies by target entity, detected content language,
permissions, and URL path.
Key methods:
summarize(array $consumer_config): AiContextSubscriptionBudgetSummary— within-budget, conditional-range, or over-budget totals for one consumer config entry
ai_context.subcontext_resolver
Status: Internal
Class: Drupal\ai_context\Service\AiContextSubcontextResolver
Resolves child (subcontext) items for selected parent items. When conditional
AI decisions are enabled, filters conditional children via the configured
provider/model; without a provider or on failure, conditional children for
that parent are skipped. When conditional AI decisions are disabled,
conditional children are excluded from normal subcontext resolution. Skipped
entirely when
subcontext_enabled is FALSE in module settings. This service assumes
saved parent-child relationships already satisfy the entity integrity rules.
See Subcontext for when to use parent/child items.
ai_context.request_info_resolver
Status: Internal
Class: Drupal\ai_context\Service\AiContextRequestInfoResolver
Resolves request information used for matching: the entity (when there is one) and the effective page path. Used by Entity Type, Specific Entity, and Site Section scopes. Site builders should start with Page and entity context.
Entity sources, in order:
- Request attributes
ai_context_entity_typeandai_context_entity_id(set bysetEntity(),applyRequestInfo(), or a caller) - Route parameters (any loaded entity)
- Request body values (
entity_type/entity_idin POST or JSON)
Effective page path sources, in order:
- Request attribute
ai_context_path(set bysetPath(),applyRequestInfo(), or a caller) - Request body values (
pathin POST or JSON) $request->getPathInfo()
applyRequestInfo() copies untrusted path and entity values onto
the request. The usual source is a BuildSystemPromptEvent payload
(getTokens() means those keys, not LLM token counts). A route
entity wins over an array entity. A path-like path or route
value is stored and preferred over getPathInfo(). Route names are
ignored.
Entity and path resolution deliberately invert each other. A route
entity wins over a caller-supplied entity, because the route is
usually the page being viewed. A caller-supplied path wins over
getPathInfo(), because on API-proxy routes getPathInfo() is the
proxy endpoint, not the viewed page. Making the two rules identical
would either reopen this issue (path never overrides) or let a
payload replace the entity you are actually on (entity always
overrides).
Callers that run on API-proxy routes (for example a chatbot endpoint) must supply the viewed page path. Site Sections matches that hint instead of the proxy path. A supplied path is an untrusted context hint used for string matching only. It is not used to load files, follow redirects, or grant access. Absolute URLs are reduced to their path.
/** @var \Drupal\ai_context\Service\AiContextRequestInfoResolver $resolver */
$resolver = \Drupal::service('ai_context.request_info_resolver');
$request = \Drupal::request();
$request->attributes->set(
AiContextRequestInfoResolver::PATH,
'/node/1',
);
// Or:
$resolver->setPath('/node/1');
ai_context.usage_tracker
Status: Internal
Class: Drupal\ai_context\Service\AiContextUsageTracker
Records usage data when context items are selected. Tracks which items were
used, by which consumer, on which route, and which entities were involved.
recordUsage() requires a canonical consumer ID. The runner ID and
entity item are optional so non-agent consumers can record usage.
Performance notes:
recordUsage()batches the existing-record lookup with a single entity query usingcontext_item_id IN (...).attachEntity()batches duplicate checks for entity-specific records.recordToolUsed()buffers tool IDs in memory; callflushToolsUsed()once per agent run (handled automatically byAiContextAgentFinishedSubscriberonAgentFinishedExecutionEvent).- Lookup indexes on
runner_id,changed,(entity_item_type, entity_item_id), and(context_item_id, consumer_id)are managed by_ai_context_ensure_usage_indexes()inai_context.install.
Tracking is controlled by the usage_tracking_enabled setting (disabled by
default). When disabled, all tracker methods return immediately without
database access.
ai_context.children
Status: Internal
Class: Drupal\ai_context\Service\AiContextChildrenService
Provides parent/child hierarchy queries for context items and supports
cleanup of child references when a parent item is deleted. Revision view
pages use loadChildrenForParentAtTime() so Nav panel siblings match the
child list at the viewed revision timestamp.
ai_context.item_view_builder
Status: Internal
Class: Drupal\ai_context\Service\AiContextItemViewBuilder
Builds template variables for the ai_context_item theme hook.
AiContextThemeHooks::preprocessAiContextItem() delegates to preprocess()
on this service. Only the full view mode (canonical and revision routes)
receives sidebar and header variables; other view modes get content field
render arrays only.
Key methods:
preprocess(array &$variables)-- for full view mode, populates title, parent link, subcontext row, status badge, scope rows, author, nav items, andcontentfield render arrays; bubbles cache metadata onto$variables(the top-level preprocess array ThemeManager reads). Non-full view modes only exposecontent.buildStatus(AiContextItem $entity)-- published/moderation status label and CSS modifier, including "Published (Draft available)" when Content Moderation is enabled.buildScopeRows(AiContextItem $entity, ?CacheableMetadata $cacheability = NULL)-- sidebar scope rows. Global items show only the global row (Enabled). When at least one non-global scope has values, every enabled scope row is included (global first as Disabled, empty scopes show None); otherwise returns an empty array. When a collector is passed, Specific Entities, Context Tags, and Taxonomy Terms labels usegetSelectedValueLabelsWithCacheability()so access results and term cache tags bubble.buildScopeSection(AiContextItem $entity, ?CacheableMetadata $cacheability = NULL)-- empty-state SCOPE heading and message when global is disabled and no non-global scopes are in use. Full-view preprocess computes rows and section in one pass so labels are not formatted three times. The builders themselves stay independent (no memo) for kernel tests. View-specific label formatting (custom site section paths, collapsed entity type labels) is applied inbuildScopeRows().addScopeViewCacheMetadata(CacheableMetadata $cacheability, AiContextItem $entity)-- adds each scope plugin's config tag (including disabled plugins, sinceenabledis stored inai_context.scope_settings.*). Also adds thelanguages:language_interfacecontext so translated plugin labels, "None", and language names vary by UI language. Tag term tags and Specific Entities access cacheability are collected while building scope rows, not reconstructed here.
ai_context.item_validator
Status: Internal
Class: Drupal\ai_context\Service\AiContextItemValidator
Validates persistence-level invariants for context items. For subcontexts, it
prevents self-parenting, nested subcontexts, and converting an item with
children into a subcontext. It also requires subcontext_type when a parent
is selected. The published-global limit uses
AiContextScopeIndexService::countPublishedGlobalItems().
ai_context.scheduler_defaults
Status: Internal
Class: Drupal\ai_context\Service\AiContextSchedulerDefaults
Applies default moderation states when Scheduler publish/unpublish dates are
set on context items without an explicit publish_state or unpublish_state.
Used during entity save so editors only need to set schedule dates when
Content Moderation and Scheduler are enabled.
Key method:
applyScheduledModerationStates(ContentEntityInterface $entity)— setspublishedwhenpublish_onis set andunpublishedwhenunpublish_onis set, if the corresponding state field is empty.
Selection and result value objects
AiContextSelectionParams
Status: Public
Class: Drupal\ai_context\Model\AiContextSelectionParams
Immutable DTO passed to AiContextSelectionFactory::fromParameters().
Centralizes validation and normalization of raw selection parameters before an
AiContextSelection is built. Use AiContextSelectionParams::fromArray() when
constructing from plugin context, form values, or other untyped input.
consumer_id merges canonical {type}:{instance} config.
Non-canonical consumer_id values are dropped. Pulls without a
consumer log as none.
AiContextConsumerStoredConfig and AiContextConsumerResolvedConfig
Status: Public
loadConsumerConfig() returns AiContextConsumerStoredConfig or
NULL. Omitted overrides stay NULL or empty; they are not filled
with defaults. isPushEnabled() on stored config is TRUE,
FALSE, or NULL when the config inherits the type default.
resolveConsumerConfig() returns AiContextConsumerResolvedConfig.
That object is stored config over plugin defaults, plus
isTypeEnabled() and isTypeAvailable(). isPushEnabled() on
the resolved object is always a bool. isPushAllowed() ANDs the
push gates with that flag. Loop-aware is not on these objects;
use AiContextConsumerTypeAgent::isLoopAware(). Limits that are
still NULL inherit the site default when a selection is built.
Exported ai_context.consumers rows store always_include and
never_include as context item UUIDs. The stored and resolved
config objects expose those values as-is. The selection factory
resolves them to local entity IDs when building an
AiContextSelection. Legacy numeric IDs are still accepted at
runtime.
See AiContextSelection for the fields carried into the resulting selection.
AiContextSelection and selection modes
Status: Public
Class: Drupal\ai_context\Model\AiContextSelection
Immutable value object built by AiContextSelectionFactory::fromConsumer() or
fromParameters(). Callers should obtain results through
ai_context.selection_factory (getResult(), getRenderedContext(), or
fromParameters() followed by selection via the factory), not by injecting
ai_context.selector directly.
Constructor fields:
| Field | Description |
|---|---|
task |
Task description or user prompt (used for conditional subcontext) |
scopeSubscriptions |
Scope values keyed by scope plugin ID |
alwaysInclude / neverInclude |
Local item IDs to force in or out after the factory resolves stored UUIDs (legacy numeric IDs still work) |
consumerId |
Canonical consumer ID after merge, or NULL |
entityType / entityId |
Optional target for entity/bundle matching |
maxGlobalItems / maxTokens |
Per-request limits; NULL falls back to module settings |
selectionMode |
minimal, relevant, or broad |
Selection modes (constant SELECTION_MODE_* on the class) are a nested
ladder: each mode is a strict superset of the one before it, and hard filters
apply in every mode.
minimal: only global items, exact-match (Specific Entities matches), always/never overrides, and strict scope subscription matches are considered. An item with no values for a subscribed scope no longer earns automatic credit for that scope.relevant(default forfromConsumer(),fromParameters(),getResult(), andgetRenderedContext()): Minimal, plus items that scopes declaringsupportsSituationalMatch()(Languages, Site Sections, Entity Types, and Taxonomy Terms by default) positively match against the request context (matchesRequestContext()returnsTRUE), even without a subscription to that scope.broad: Relevant, plus any remaining published candidate that passes hard context filters, ordered by Priority, appended after the scope-scored bucket to fill leftover token budget. It supplements, never replaces, the scope-scored bucket.
There is no runtime alias for the removed match_all mode; update
10028 rewrites match_all to broad for rows it migrates out of
the legacy ai_context.agents config (any other unrecognized legacy
mode is dropped with a logged notice, so the row inherits the site
default), and passing an unrecognized mode explicitly (e.g. via
AiContextSelectionParams) throws \InvalidArgumentException. Values
read back from stored consumer config are handled more defensively: an
unrecognized stored value is logged and treated as relevant, so a
single corrupted consumer entry cannot break every selection.
Function call plugins expose selection_mode explicitly on
get_relevant_ai_context_items. See Function calls for
the stable plugin IDs (ai_context:list_ai_context_items,
ai_context:load_ai_context_item_by_id,
ai_context:get_relevant_ai_context_items) and agent tool workflow.
AiContextSelectionResult
Status: Public
Class: Drupal\ai_context\Model\AiContextSelectionResult
Immutable result from context selection via ai_context.selection_factory.
Implements CacheableDependencyInterface.
Methods:
| Method | Returns |
|---|---|
getRenderedText(): string |
Token-limited context block for prompt injection |
getSelectedItemIds(): string[] |
IDs of selected items |
getSelectedItems(bool $fullData = FALSE): array |
Item metadata keyed by ID; $fullData includes scope, tags, subcontext fields, content snippet |
getTokensUsed(): int |
Approximate tokens consumed by the final rendered text (recalculated after selector pipeline events) |
getMaxTokens(): int |
Max token cap applied during rendering |
getTruncatedItems(): string[] |
IDs of items omitted or trimmed because the token budget was insufficient |
getCacheableMetadata(): CacheableMetadata |
Full cache metadata object |
getCacheTags() / getCacheContexts() / getCacheMaxAge() |
CacheableDependencyInterface delegates |
Example:
$result = \Drupal::service('ai_context.selection_factory')->getResult(
scopes: ['language' => ['en']],
maxTokens: 1200,
);
$text = $result->getRenderedText();
$ids = $result->getSelectedItemIds();
$metadata = $result->getCacheableMetadata();
Plugin managers
plugin.manager.ai_context_scope
Status: Internal
Class: Drupal\ai_context\AiContextScopeManager
Plugin manager for scope plugins. Handles discovery, instantiation, and
ordering of AiContextScope plugins. getScopePlugins() returns all plugins
in ascending display_weight order, with plugin ID used to break ties. Core
and custom plugins are interleaved according to their declared
display_weight. The scoring_weight attribute is separate and controls
subscription scoring influence independently of display order; values below 1
are treated as 1 during scoring.
Support services
Status: Internal — All services in this section and the sections below (event subscribers, route subscribers, access checks, and other support services) are internal. They are not labeled individually; see API stability.
ai_context.markdown_renderer
Class: Drupal\ai_context\Markdown\MarkdownRenderer
Converts GitHub Flavored Markdown content to HTML for display (including tables).
ai_context.provider_request_context_factory
Class: Drupal\ai_context\Service\AiContextProviderRequestContextFactory
Builds an immutable AiContextProviderRequestContext from a
PreGenerateResponseEvent. Copies tags, conversation text, and
structured metadata only. Live chat and file objects are not retained.
Builder in this module is reserved for UI list/view/form builders.
logger.channel.ai_context
Standard Drupal logger channel for ai_context log messages.
Event subscribers (registered as services)
ai_context.build_system_prompt_subscriber
Class: Drupal\ai_context\EventSubscriber\AiContextBuildSystemPromptSubscriber
Listens for BuildSystemPromptEvent and AgentStartedExecutionEvent.
See Events.
ai_context.pre_generate_response_subscriber
Class: Drupal\ai_context\EventSubscriber\AiContextPreGenerateResponseSubscriber
Listens for PreGenerateResponseEvent at priority -100. See
Events.
ai_context.agent_tool_subscriber
Class: Drupal\ai_context\EventSubscriber\AiContextAgentToolSubscriber
Listens for AgentToolFinishedExecutionEvent.
See Events.
ai_context.scope_config_subscriber
Class: Drupal\ai_context\EventSubscriber\AiContextScopeConfigSubscriber
When any ai_context.scope_settings.{plugin_id} config is saved, asks
that plugin whether to scrub stored values. Skips the scrub while
config is being imported so plugins do not compare stored values to a
half-imported environment. Bundle-delete hooks still run during
import. See ai_context.scope_cleanup.
Route subscribers
ai_context.route_subscriber
Class: Drupal\ai_context\Routing\AiContextRouteSubscriber
Alters routes for the module.
ai_context.scope_route_subscriber
Class: Drupal\ai_context\Routing\AiContextScopeRouteSubscriber
Dynamically generates routes for per-scope settings forms based on discovered scope plugins.
Access checks
ai_context.access_check.overview_page
Class: Drupal\ai_context\Access\AiContextOverviewAccessCheck
Controls access to the overview page based on the show_overview_page
setting.
ai_context.access_check.usage_page
Class: Drupal\ai_context\Access\AiContextUsageAccessCheck
Controls access to the usage tracking page based on whether tracking is enabled.
Other services
ai_context.uninstall_validator
Class: Drupal\ai_context\AiContextUninstallValidator
Runs during module uninstall validation with higher priority than core's content validator. Its sole responsibility is schema repair, installing missing content entity schemas provided by this module before core queries entity tables. This prevents fatal errors on sites where entity tables were never created (for example config-sync installs without entity updates).
The AI Context Tags vocabulary (ai_context_tags) is not checked here: its
config declares an enforced dependency on ai_context, so Drupal will remove
the vocabulary and any terms automatically on uninstall. For older sites that
installed ai_context before this dependency was added, update hook
ai_context_update_10012() backfills the enforced dependency on active config.
Drupal\ai_context\Hook\DiffHooks
Class: Drupal\ai_context\Hook\DiffHooks
Autowired service for Diff module integration hooks.
Drupal\ai_context\Hook\DynamicEntityReferenceHooks
Class: Drupal\ai_context\Hook\DynamicEntityReferenceHooks
Autowired service for Dynamic Entity Reference module integration hooks.