Add a tool-less Drupal Canvas Agent that answers general questions and names...
Description
Adds the canvas_agent ai_agent config entity to canvas_dev_ai, labelled "Drupal Canvas Agent". It answers general questions about Canvas and names the Tool that performs a task. It carries no tools.
Adds a [canvas_dev_ai:available_tools] token. It renders the Tools from canvas_dev_ai.settings, using the label and description of each ai_agent. CanvasDevAiHooks builds that list once, for both the JavaScript settings and the token, so the chat and the system prompt cannot describe the Tools differently, and the prompt cannot name a Tool that is not offered.
Two deliberate deviations from the issue:
main_agentis unchanged, so the agent is not selectable or active yet. Per the issue discussion, this MR creates the agent only, and settingmain_agentmoves to a follow-up.- The token reads
canvas_dev_ai.settingsinhook_tokens()rather than taking a context fromCanvasDevAiBuilder::buildTokenContexts(). This also resolves the token outside a chat request, so the stored prompt never renders raw. Happy to move it tobuildTokenContexts()to match theavailable_regionsprecedent if you prefer.
What changed
modules/canvas_dev_ai/config/install/ai_agents.ai_agent.canvas_agent.yml (new) — the agent. Empty tools, orchestration_agent: false, max_loops: 1. The prompt covers what Canvas does, which Tool to select, where Tools are enabled, and points at the documentation when it does not know. dependencies.enforced.module is canvas_dev_ai, so uninstalling the feature flag removes the agent with it.
modules/canvas_dev_ai/src/Hook/CanvasDevAiHooks.php — adds hook_token_info() and hook_tokens() for [canvas_dev_ai:available_tools], and extracts getToolDescriptors(). The JavaScript settings and the token now build the same list from the same method, so the chat and the prompt cannot describe a Tool differently. jsSettingsAlter() behaviour is unchanged, including omitting the key entirely when no Tool is selected.
hook_tokens() takes BubbleableMetadata and bubbles canvas_dev_ai.settings plus each offered agent as cacheable dependencies. testAvailableToolsToken and testAvailableToolsTokenWithoutTools assert the bubbled cache tags.
Behaviour
the ### Available Tools section of the rendered prompt, observed on a site via AiAgentEntityWrapper::applyTokens():
canvas_dev_ai.settings tools |
Rendered section |
|---|---|
canvas_ai_orchestrator, canvas_page_builder_agent |
both, as * **<label>**: <description> |
canvas_component_agent |
that one only |
Labels and descriptions come from the ai_agent entities, not from config, so a Tool cannot be described one way in the chat and another in the prompt.
Tests
CanvasDevAiAgentSelectionTest (+4 cases): the agent installs, lists no tools, is not itself offered as a Tool, is removed on uninstall, and the token renders the configured Tools — including the empty case, where an unreplaced token would otherwise reach the prompt as literal text.
Suite: 9 → 13 tests across the two files, 0 failures
Verification
phpunit — 13 tests, 0 failures locally; CI green on 11.3 and 11.4 (Unit, Kernel, Functional)phpcs— clean on all three filesphpstan— no errors on all three filescspell— clean- Live check on an installed site: the token resolves through
applyTokens()and tracks config changes
Two choices to confirm
main_agent is unchanged. The issue says to set it, superseding what #3591919 (closed) ships. Per the issue discussion this MR creates the agent only, and main_agent moves to a follow-up — so the agent is not selectable or active yet. CanvasDevAiAgentSelectionForm::SELECTABLE_AGENTS is untouched for the same reason.
The token reads config in hook_tokens() rather than taking a context from CanvasDevAiBuilder::buildTokenContexts(), as the issue specifies. This also resolves the token outside a chat request, so the stored prompt never renders raw, and it keeps this MR out of CanvasDevAiBuilder. Happy to move it to match the available_regions precedent if you prefer.
Closes #3591924