Add invoker-aware input/output transformations for tools
### Problem/Motivation A recurring need exists to alter the inputs and outputs of tools based on **who is invoking them**. Common examples include: - **AI Agents/MCP** need tokens resolved to entities on input, and entities downcasted back to tokens on output. - **AI Agents** benefit from output decoders that reduce token count sent back to the LLM. - **MCP servers** may need similar upcasting/downcasting but with different serialization constraints. - **Deterministic invokers** occasionally benefit from the same transformations (e.g., accepting a user email and resolving it to a user entity before tool execution). _This may be considered a per-tool flexibility and addressed in the footnote._ Currently, connector modules handle these transformations individually, but this leads to duplicated logic. Multiple invokers (AI Agents, MCP, etc.) would benefit from shared transformation infrastructure rather than each connector re-implementing upcasting, downcasting, and output reduction independently. ### Proposed resolution Introduce an invoker-aware mechanism for tools, allowing input/output transformations to be registered and applied based on the invoker identity. **API design** — support both passing the invoker at instantiation and setting it subsequently. The invoker argument is optional, preserving backward compatibility: ``` // Approach 1: Invoker passed on createInstance // (precedent: Drupal\Core\ImageToolkit\ImageToolkitOperationManager) $tool = $toolManager->createInstance('tool:my_tool', [], AiAgentsInvoker::ID); // Approach 2: Invoker set via method after instantiation $tool = $toolManager->createInstance('tool:my_tool'); $tool->setInvoker(AiAgentsInvoker::ID); // Standard usage remains unchanged when no invoker is needed $tool = $toolManager->createInstance('tool:my_tool'); $tool->setContextValue('key', 'value'); $tool->execute(); ``` **Event-driven transformation** — during execute(), events are dispatched that carry the invoker identity. External modules can subscribe to these events and conditionally apply transformations based on the invoker: - **Pre-invocation event** — allows subscribers to upcast/transform input values (e.g., resolve a token to an entity) when the invoker is a specific type. - **Post-invocation event** — allows subscribers to downcast/reduce output (e.g., encode entities back to tokens, reduce output verbosity for LLMs) when the invoker is a specific type. This allows modules like ai_agents or mcp to provide shared transformation subscribers without the tool itself needing awareness of invoker-specific concerns. Third-party modules can also hook into specific invokers to provide custom transformations. ``` // Example subscriber public function onPreInvoke(ToolPreInvokeEvent $event): void { if ($event->getInvoker() === AiAgentsInvoker::ID) { // Upcast token to entity $value = $event->getContextValue('user'); $event->setContextValue('user', $this->resolveToken($value)); } } ``` ### Alternatives considered - Connector modules handling all transformations individually — current approach; leads to duplicated logic across connectors and makes shared improvements difficult. - Decoration/middleware pattern — wrapping tools in decorator classes per-invoker. More flexible but harder to discover and debug. - Invoker-as-plugin with built-in transformers — invokers define their own transformations directly rather than via events. Simpler but less extensible by third-party modules. ### AI usage (if applicable) **Footnote: Per-tool type flexibility** Per-tool input flexibility (e.g., a MixedInputDefinition allowing a parameter to accept ['entity:user', 'email', 'integer']) is a related but separate concern. It will be addressed in a follow-up issue. - [x] **AI Assisted Issue:** This issue was generated with AI assistance, but was reviewed and refined by the creator. - [ ] **AI Assisted Code:** This code was mainly generated by a human, with AI autocompleting or parts AI generated, but under full human supervision. - [ ] **AI Generated Code:** This code was mainly generated by an AI with human guidance, and reviewed, tested, and refined by a human. - [ ] **Vibe Coded:** This code was generated by an AI and has only been functionally tested.
issue