Services
The AI Context module registers the following services in
ai_context.services.yml.
Each core 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.request_factory
Status: Public
Class: Drupal\ai_context\Service\AiContextRequestFactory
Builds AiContextRequest value objects from agent config or raw parameters,
and provides convenience methods for non-agent modules that just need
rendered context.
Request-building methods:
fromAgent(string $agentId, string $task, ...)-- builds a request from a saved agent config entry.fromParameters(AiContextRequestParamsData $params)-- builds a request 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 $currentEntity = NULL,
?string $consumerId = NULL,
): string
// Full result with cache metadata, selected IDs, token usage.
public function getResult(
array $scopes = [],
?int $maxTokens = NULL,
?EntityInterface $currentEntity = NULL,
?string $consumerId = NULL,
): AiContextResult
Usage examples:
/** @var \Drupal\ai_context\Service\AiContextRequestFactory $factory */
$factory = \Drupal::service('ai_context.request_factory');
// Get rendered context text (returns empty string if nothing matches).
$text = $factory->getRenderedContext(
scopes: ['use_case' => ['working_with_text']],
maxTokens: 2000,
currentEntity: $node,
consumerId: 'my_module',
);
// Or get the full result object with cache metadata and selected item IDs.
$result = $factory->getResult(
scopes: ['use_case' => ['working_with_text']],
maxTokens: 2000,
currentEntity: $node,
consumerId: 'my_module',
);
$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.
- currentEntity: An entity object for contextual scope matching.
- consumerId: Identifier for logging/debugging (e.g., your module name).
These methods use match_all selection mode by default, so all published
candidates that pass context filters are considered.
Agent config lookup:
findAgentConfig(string $agentId): array— canonical reader for per-agent context config fromai_context.agents. Other services should use this rather than reading config directly.isLoopAware(string $agentId): bool— whether the agent has loop-aware context injection enabled.
See AiContextRequest and selection modes and AiContextResult 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 agent configuration form 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— normalizes raw submitted values into a scope map (scope_id => [value_ids]).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_map = $builder->extractValues(is_array($submitted) ? $submitted : []);
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(AiContextRequest $request): AiContextResult
The AiContextResult contains the rendered text, selected item metadata,
token usage, and CacheableMetadata. See AiContextResult
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
map (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 map. 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[]— candidate IDs for PHP-level scoring (superset; false positives OK, false negatives not)
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 agent subscriptions when dynamic scope options are deleted (for example a site section removed from configuration).
Key methods:
removeScopeValues(string $scopeId, array $values)— strip values from item scope maps, agent subscriptions, and the scope indexremoveEntityItemReference(string $entityTypeId, int|string $entityId)— clear DER target references when a referenced entity is deleted
Triggered automatically when site section config changes via
AiContextScopeConfigSubscriber (see Event subscribers below). Custom scope
plugins can invoke the same service when their option lists change.
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 AiContextResult::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 agent settings form budget
summary. Returns AiContextSubscriptionBudgetSummary, an internal value
object consumed by AiContextAgentForm.
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
AiContextResult::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 current content language, matching the translation the selector injects.
- Always-include items bypass scope handling; never-include items are dropped; inheriting children are only counted through their parent.
- Hard context filters (
filterByCurrentContext()) 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-include, context-auto-included, scope-scored.
Results are cached per agent configuration, module settings, scope-plugin
configuration, 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.agents, and each config:ai_context.scope_settings.*
object. The agent settings lazy builder varies by target entity, detected
content language, and URL path.
Key methods:
summarize(array $agent_config): AiContextSubscriptionBudgetSummary— within-budget, conditional-range, or over-budget totals for one agent 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.current_entity_resolver
Status: Internal
Class: Drupal\ai_context\Service\AiContextCurrentEntityResolver
Resolves the current entity from the route or request context. Used to match context items scoped to entity types or specific entities.
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 agent, on which route, and which entities were involved.
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, and(entity_item_type, entity_item_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)-- 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.buildScopeSection(AiContextItem $entity)-- empty-state SCOPE heading and message when global is disabled and no non-global scopes are in use. 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.*) plus taxonomy and entity cache tags for selected values on enabled plugins.
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.
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.
Request and result value objects
AiContextRequestParamsData
Status: Public
Class: Drupal\ai_context\Model\AiContextRequestParamsData
Immutable DTO passed to AiContextRequestFactory::fromParameters().
Centralizes validation and normalization of raw selection parameters before an
AiContextRequest is built. Use AiContextRequestParamsData::fromArray() when
constructing from plugin context, form values, or other untyped input.
See AiContextRequest for the fields carried into the resulting request.
AiContextRequest and selection modes
Status: Public
Class: Drupal\ai_context\Model\AiContextRequest
Immutable value object built by AiContextRequestFactory::fromAgent() or
fromParameters(). Callers should obtain results through
ai_context.request_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 |
Item IDs to force in or out |
consumerId |
Agent or module ID for logging only |
entityType / entityId |
Optional target for entity/bundle matching |
maxGlobalItems / maxTokens |
Per-request limits; NULL falls back to module settings |
selectionMode |
minimal or match_all |
Selection modes (constant SELECTION_MODE_* on the class):
minimal(default forfromAgent()andfromParameters()): when scope subscriptions are empty, only global items, context-auto inclusions (entity_item and entity_type auto-inclusions), and always/never overrides are considered.match_all: when scope subscriptions are empty, all published candidates that pass hard context filters are considered. Used bygetRenderedContext()/getResult()and available to any caller that passesselection_mode: match_allexplicitly.
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.
AiContextResult
Status: Public
Class: Drupal\ai_context\Model\AiContextResult
Immutable result from context selection via ai_context.request_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.request_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 descending weight order, with plugin ID used to break ties. Core and custom
plugins are interleaved according to their declared weight.
getScopePluginDisplayWeights() exposes the same order as sequential
#weight values for forms and tabs. Plugin weight controls both UI placement
and subscription scoring influence; weights below 1 are treated as 1 during
scoring only.
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).
logger.channel.ai_context
Standard Drupal logger channel for ai_context log messages.
Event subscribers (registered as services)
ai_context.system_prompt_subscriber
Class: Drupal\ai_context\EventSubscriber\AiContextSystemPromptSubscriber
Listens for BuildSystemPromptEvent and AgentStartedExecutionEvent.
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
Cleans stale site section scope values when site section configuration changes. 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.