The agentic chat lives in packages/Webkul/AiAgent; the provider/platform layer it calls lives in packages/Webkul/MagicAI. An LLM decides which tool to call, with arguments it invents — every tool is an untrusted-input entry point, and every read it runs can be asked for the whole catalog.
REQUIRED BACKGROUND: unopim-standards (security.md, scale.md, localization.md) for the rules; unopim-exec to resolve commands; unopim-verify before calling anything done. Credential storage for an AI connector is in unopim-plugin-development credentials.md — do not re-derive it here.
Reference files
| File | Read when |
|---|---|
| tools.md | Adding, overriding or disabling a chat tool; permission layers; approval modes; safe queries; testing a tool |
| commands.md | Running or writing an ai-agent:* command; scheduling; the unopim:magic-ai:platform:* commands |
Exemplars — copy shape from these
| Need | File |
|---|---|
| The contract | packages/Webkul/AiAgent/src/Chat/Contracts/PimTool.php |
| Registry and metadata | packages/Webkul/AiAgent/src/Chat/ToolRegistry.php, packages/Webkul/AiAgent/Config/ai-agent.php |
| Read tool, portable JSON SQL, capped limit | packages/Webkul/AiAgent/src/Chat/Tools/SearchProducts.php |
| Write tool that honours review mode | packages/Webkul/AiAgent/src/Chat/Tools/CreateProduct.php |
| Per-request availability gate | packages/Webkul/AiAgent/src/Chat/Contracts/AuthorizesContext.php |
| PII in tool output | packages/Webkul/AiAgent/src/Chat/Tools/ManageUsers.php |
| Batched, resumable command | packages/Webkul/AiAgent/src/Console/Commands/IndexProductEmbeddings.php |
| Registry tests | packages/Webkul/AiAgent/tests/Unit/ToolRegistryExtensibilityTest.php |
Invented API vs real API
| Invented | Real |
|---|---|
A class implementing Laravel\Ai\Contracts\Tool registered directly |
Implement PimTool::register(ChatContext $context): Tool; return an anonymous class extending Chat\Tools\ContextualTool |
Tools in config/ai.php or AiAgent::tool() |
ai-agent.tools map (class => metadata) read once by the ToolRegistry singleton |
mergeConfigFrom(…, 'ai-agent') from a plugin to add a tool |
Shallow merge — it drops the plugin's tools or wipes core's. Use callAfterResolving(ToolRegistry::class, …); see tools.md |
auth()->user()->can(...) inside handle() |
$this->context->hasPermission($key) via the ChecksPermission trait |
php artisan ai-agent:enrich-products, ai-agent:analyze-image |
Classes in packages/Webkul/AiAgent/src/Examples/, never registered — the commands do not exist |
A src/Database/Migrations/ folder in AiAgent |
Root-level packages/Webkul/AiAgent/Database/Migration (singular) — the provider loads only that path |
Decision procedure — new chat tool
- Search
packages/Webkul/AiAgent/src/Chat/Tools/first. A gap in an existing tool is a parameter on it, not a second tool with an overlapping description — the model picks between near-duplicates at random. - Pick the ACL key the tool's action maps to in an existing
acl.php(catalog.products,catalog.products.edit, …). A key that matches nothing still passes forpermission_type = allroles, so a typo ships invisibly — see tools.md. - Write the class:
implements PimTool; anonymousContextualToolwithname(),description(),schema(),handle();use ChecksPermissionand calldenyUnlessAllowed()first inhandle(). - Bound every read: clamp
limitwithmin(max(…)), a hard ceiling, an explicitorderBy, and allow-list every code the model passes before it reaches SQL. - Write tools: set
'write' => true, callshouldQueueForApproval()/queueChange()before mutating, and go through repositories orProductWriterServiceso events, history and completeness fire. - Register: core → one line in
Config/ai-agent.php; plugin →callAfterResolvingin its provider. - Test in
packages/Webkul/AiAgent/tests/Unit/Tools/— the only AiAgent directory in aphpunit.xmlsuite. Its sibling Feature folder belongs to no suite and never runs in CI. Cover: permission denied, limit clamp, invalid code rejected, review-mode queueing.
UnoPim-specific pitfalls
- Two permission layers, both required.
permissionmetadata hides the tool from the LLM inToolRegistry::build();denyUnlessAllowed()insidehandle()is the enforcement. A tool registered with metadatapermission => nullis offered to every admin who can open the chat. - Approval mode is mostly prompt text.
suggestandrevieware instructions to the model (AgentRunner::buildSystemPrompt()); only a tool that callsshouldQueueForApproval()actually queues. A write tool without it mutates in every mode. writemetadata drives the UI.AgentRunner::flagMutation()reads it to tell the widget to reload. A mutating tool flaggedwrite => falseleaves a stale product page.- Singleton capture (Octane).
ToolRegistryis a singleton and builds each tool once per worker, so constructor dependencies live for the worker's lifetime. Inject only stateless services; resolvescoped()ones (ProductWriterService,VisionService) insidehandle(). Keep all request state onChatContext. - Portability. Product values are JSON. Use
GrammarQueryManager::getGrammar()->jsonExtract()/jsonContains()and prefix raw table aliases withDB::getTablePrefix(), exactly asSearchProductsdoes.packages/Webkul/AiAgent/tests/Unit/Tools/PgsqlCompatibilityTest.phpgreps for MySQL-only JSON functions — add your tool to it. - Elasticsearch is optional. The vector store needs both
ai-agent.vector_store.enabledandelasticsearch.enabled(ProductEmbeddingIndex::isEnabled());FindSimilarProductsfalls back to a database pool. Any tool using embeddings needs the same fallback. - Model-facing vs user-facing text.
name(),description(), schema descriptions and JSONerrorstrings are read by the LLM and stay English like every core tool. Anything rendered in the admin UI or a command's output goes throughtrans()in all 33 locales (unopim-standards localization.md). - Tool output is data sent to a third-party model. Return the minimum fields; mask PII as
ManageUsersdoes; never return credential or platform rows.
Verification
unopim-verify gates 1–4 and 6 always. Gate 5 (Playwright) only when the chat widget Blade or its JS changed — otherwise state the skip. Run --testsuite="AiAgent Unit Test" (CI runs it on MySQL and PostgreSQL). MagicAI Unit Test / MagicAI Feature Test are absent from the MySQL and PostgreSQL workflows' suite lists — only the MariaDB workflow runs them — so run them yourself after touching MagicAI.