diff --git a/ai_eca.post_update.php b/ai_eca.post_update.php new file mode 100644 index 0000000000000000000000000000000000000000..b0d71f0c9acd24a0b8aa54fd75e134888ab04489 --- /dev/null +++ b/ai_eca.post_update.php @@ -0,0 +1,13 @@ +<?php + +/** + * @file + * Post update functions for AI ECA module. + */ + +/** + * Ensure that cache is cleared. + */ +function ai_eca_post_update_prepare_mcp() { + // Empty update to cause a cache flush to rebuilt the service container. +} diff --git a/ai_integration_eca.info.yml b/ai_integration_eca.info.yml index 0855494141616fe9bb8575c1f8e2def3f4b2d088..bb32b45704af228fa6daa0f922972bda93d1d454 100644 --- a/ai_integration_eca.info.yml +++ b/ai_integration_eca.info.yml @@ -9,3 +9,5 @@ dependencies: - ai:ai - drupal:file - eca:eca + - schemata:schemata_json_schema + - token:token diff --git a/ai_integration_eca.services.yml b/ai_integration_eca.services.yml index a7502590f4380fcd7a8ed418e0db68dbc1129c58..626d640fc610af89895b43ba201e03548173c9fb 100644 --- a/ai_integration_eca.services.yml +++ b/ai_integration_eca.services.yml @@ -4,6 +4,37 @@ services: arguments: - 'ai_integration_eca' - ai_integration_eca.provider_validator: - class: Drupal\ai_integration_eca\Service\AiProviderValidator + ai_integration_eca.services.provider_validator: + class: Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidator arguments: ['@validation.basic_recursive_validator_factory'] + + ai_integration_eca.services.data_provider: + class: Drupal\ai_integration_eca\Services\DataProvider\DataProvider + arguments: + - '@eca.service.modeller' + - '@eca.service.condition' + - '@eca.service.action' + - '@entity_type.manager' + - '@ai_integration_eca.services.model_mapper' + - '@serializer' + - '@token.tree_builder' + + ai_integration_eca.services.eca_repository: + class: Drupal\ai_integration_eca\Services\EcaRepository\EcaRepository + arguments: + - '@entity_type.manager' + - '@ai_integration_eca.services.model_mapper' + - '@typed_data_manager' + + ai_integration_eca.services.model_mapper: + class: Drupal\ai_integration_eca\Services\ModelMapper\ModelMapper + arguments: + - '@typed_data_manager' + - '@serializer' + + # Typed data definitions in general can take many forms. This handles final items. + serializer.normalizer.data_definition.schema_json_ai_integration_eca.json: + class: Drupal\ai_integration_eca\Normalizer\json\DataDefinitionNormalizer + decorates: serializer.normalizer.data_definition.schema_json.json + arguments: + - '@serializer.normalizer.data_definition.schema_json_ai_integration_eca.json.inner' diff --git a/composer.json b/composer.json index e5b7ed2dbc60e1894151c373839413e3dc657495..b470d1004b3c951d579a24ba8d71ac63ed14ee39 100644 --- a/composer.json +++ b/composer.json @@ -8,15 +8,16 @@ "source": "https://drupal.org/project/ai_integration_eca" }, "require": { - "drupal/ai": "1.0.x-dev@dev", - "drupal/ai_agents": "1.0.x-dev@dev", + "drupal/ai": "^1.0", "drupal/core": "^10.3 || ^11", "drupal/eca": "^2.0", "drupal/token": "^1.15", + "drupal/schemata": "^1.0", "illuminate/support": "^10.48 || ^11.34" }, "require-dev": { - "drupal/schemata": "^1.0", + "drupal/ai_agents": "^1.0", + "drupal/mcp": "^1.0@alpha", "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1" } } diff --git a/modules/agents/ai_integration_eca_agents.info.yml b/modules/agents/ai_integration_eca_agents.info.yml index 7366050432b3001b38d45d4457f21f8fe3f5948b..ca73bbdc31b1c1c158a62644b4ca974537025a1c 100644 --- a/modules/agents/ai_integration_eca_agents.info.yml +++ b/modules/agents/ai_integration_eca_agents.info.yml @@ -7,5 +7,3 @@ dependencies: - ai_integration_eca:ai_integration_eca - ai_agents:ai_agents - eca:eca_ui - - schemata:schemata_json_schema - - token:token diff --git a/modules/agents/ai_integration_eca_agents.services.yml b/modules/agents/ai_integration_eca_agents.services.yml index 3cea354a1288ad068a897cd2f8eeec853a0fc42a..99f891efaa17181da83dbc95a45a2958eb24cf5b 100644 --- a/modules/agents/ai_integration_eca_agents.services.yml +++ b/modules/agents/ai_integration_eca_agents.services.yml @@ -1,35 +1,4 @@ services: - ai_integration_eca_agents.services.data_provider: - class: Drupal\ai_integration_eca_agents\Services\DataProvider\DataProvider - arguments: - - '@eca.service.modeller' - - '@eca.service.condition' - - '@eca.service.action' - - '@entity_type.manager' - - '@ai_integration_eca_agents.services.model_mapper' - - '@serializer' - - '@token.tree_builder' - - ai_integration_eca_agents.services.eca_repository: - class: Drupal\ai_integration_eca_agents\Services\EcaRepository\EcaRepository - arguments: - - '@entity_type.manager' - - '@ai_integration_eca_agents.services.model_mapper' - - '@typed_data_manager' - - ai_integration_eca_agents.services.model_mapper: - class: Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapper - arguments: - - '@typed_data_manager' - - '@serializer' - - # Typed data definitions in general can take many forms. This handles final items. - serializer.normalizer.data_definition.schema_json_ai_integration_eca_agents.json: - class: Drupal\ai_integration_eca_agents\Normalizer\json\DataDefinitionNormalizer - decorates: serializer.normalizer.data_definition.schema_json.json - arguments: - - '@serializer.normalizer.data_definition.schema_json_ai_integration_eca_agents.json.inner' - Drupal\ai_integration_eca_agents\Hook\FormHooks: class: Drupal\ai_integration_eca_agents\Hook\FormHooks autowire: true diff --git a/modules/agents/drush.services.yml b/modules/agents/drush.services.yml index 4be908f67c43f4832f367c02e92733395994c821..1647346ae96b5168832efff0e867e0a25d9e1d36 100644 --- a/modules/agents/drush.services.yml +++ b/modules/agents/drush.services.yml @@ -2,6 +2,6 @@ services: ai_integration_eca_agents.debug_data_provider: class: Drupal\ai_integration_eca_agents\Command\DebugDataProviderCommand arguments: - - '@ai_integration_eca_agents.services.data_provider' + - '@ai_integration_eca.services.data_provider' tags: - { name: console.command } diff --git a/modules/agents/src/Command/DebugDataProviderCommand.php b/modules/agents/src/Command/DebugDataProviderCommand.php index 91d47e9d8056de7d1dc2150cec6342af6baca682..04ba5af1f39d391acbb85078781acaabf7f9a0ed 100644 --- a/modules/agents/src/Command/DebugDataProviderCommand.php +++ b/modules/agents/src/Command/DebugDataProviderCommand.php @@ -5,8 +5,8 @@ declare(strict_types=1); namespace Drupal\ai_integration_eca_agents\Command; use Drupal\Component\Serialization\Json; -use Drupal\ai_integration_eca_agents\Services\DataProvider\DataProviderInterface; -use Drupal\ai_integration_eca_agents\Services\DataProvider\DataViewModeEnum; +use Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface; +use Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; diff --git a/modules/agents/src/Plugin/AiAgent/Eca.php b/modules/agents/src/Plugin/AiAgent/Eca.php index 36f70cc06995b09bf164eeae60a533b5d39a7f4a..6ea758c86877f298d0b2886679a10c6c1568d8a9 100644 --- a/modules/agents/src/Plugin/AiAgent/Eca.php +++ b/modules/agents/src/Plugin/AiAgent/Eca.php @@ -2,8 +2,6 @@ namespace Drupal\ai_integration_eca_agents\Plugin\AiAgent; -use Drupal\ai_integration_eca_agents\Schema\Eca as EcaSchema; -use Drupal\ai_integration_eca_agents\TypedData\EcaModelDefinition; use Drupal\Component\Render\MarkupInterface; use Drupal\Component\Serialization\Json; use Drupal\Core\Access\AccessResult; @@ -13,9 +11,11 @@ use Drupal\ai_agents\Attribute\AiAgent; use Drupal\ai_agents\PluginBase\AiAgentBase; use Drupal\ai_agents\PluginInterfaces\AiAgentInterface; use Drupal\ai_agents\Task\TaskInterface; -use Drupal\ai_integration_eca_agents\Services\DataProvider\DataProviderInterface; -use Drupal\ai_integration_eca_agents\Services\DataProvider\DataViewModeEnum; -use Drupal\ai_integration_eca_agents\Services\EcaRepository\EcaRepositoryInterface; +use Drupal\ai_integration_eca\Schema\Eca as EcaSchema; +use Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface; +use Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum; +use Drupal\ai_integration_eca\Services\EcaRepository\EcaRepositoryInterface; +use Drupal\ai_integration_eca\TypedData\EcaModelDefinition; use Drupal\eca\Entity\Eca as EcaEntity; use Illuminate\Support\Arr; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -47,14 +47,14 @@ class Eca extends AiAgentBase implements ContainerFactoryPluginInterface { /** * The ECA data provider. * - * @var \Drupal\ai_integration_eca_agents\Services\DataProvider\DataProviderInterface + * @var \Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface */ protected DataProviderInterface $dataProvider; /** * The ECA helper. * - * @var \Drupal\ai_integration_eca_agents\Services\EcaRepository\EcaRepositoryInterface + * @var \Drupal\ai_integration_eca\Services\EcaRepository\EcaRepositoryInterface */ protected EcaRepositoryInterface $ecaRepository; @@ -70,8 +70,8 @@ class Eca extends AiAgentBase implements ContainerFactoryPluginInterface { */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); - $instance->dataProvider = $container->get('ai_integration_eca_agents.services.data_provider'); - $instance->ecaRepository = $container->get('ai_integration_eca_agents.services.eca_repository'); + $instance->dataProvider = $container->get('ai_integration_eca.services.data_provider'); + $instance->ecaRepository = $container->get('ai_integration_eca.services.eca_repository'); $instance->serializer = $container->get('serializer'); $instance->dto = [ 'task_description' => '', diff --git a/modules/agents/src/Plugin/AiAgentValidation/EcaValidation.php b/modules/agents/src/Plugin/AiAgentValidation/EcaValidation.php index 72119198368bb7b9f0567deaba90b28e4674bcd8..36cc30d78c13b7c9836bcb408701f54bc71e6cfe 100644 --- a/modules/agents/src/Plugin/AiAgentValidation/EcaValidation.php +++ b/modules/agents/src/Plugin/AiAgentValidation/EcaValidation.php @@ -2,7 +2,6 @@ namespace Drupal\ai_integration_eca_agents\Plugin\AiAgentValidation; -use Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface; use Drupal\Component\Serialization\Json; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -12,6 +11,7 @@ use Drupal\ai_agents\Attribute\AiAgentValidation; use Drupal\ai_agents\Exception\AgentRetryableValidationException; use Drupal\ai_agents\PluginBase\AiAgentValidationPluginBase; use Drupal\ai_agents\PluginInterfaces\AiAgentValidationInterface; +use Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface; use Drupal\Core\TypedData\Exception\MissingDataException; use Illuminate\Support\Arr; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -35,7 +35,7 @@ class EcaValidation extends AiAgentValidationPluginBase implements ContainerFact /** * The model mapper. * - * @var \Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface + * @var \Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface */ protected ModelMapperInterface $modelMapper; @@ -45,7 +45,7 @@ class EcaValidation extends AiAgentValidationPluginBase implements ContainerFact public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): AiAgentValidationInterface { $instance = new static($configuration, $plugin_id, $plugin_definition); $instance->promptJsonDecoder = $container->get('ai.prompt_json_decode'); - $instance->modelMapper = $container->get('ai_integration_eca_agents.services.model_mapper'); + $instance->modelMapper = $container->get('ai_integration_eca.services.model_mapper'); return $instance; } diff --git a/modules/mcp/ai_integration_eca_mcp.info.yml b/modules/mcp/ai_integration_eca_mcp.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..0a80b85ef22f43e372669e7353d98bafe58018db --- /dev/null +++ b/modules/mcp/ai_integration_eca_mcp.info.yml @@ -0,0 +1,8 @@ +name: 'AI Integration - ECA: MCP' +type: module +description: 'Provide context to LLMs via the Model Context Protocol' +core_version_requirement: ^10.3 || ^11 +package: AI +dependencies: + - ai_integration_eca:ai_integration_eca + - mcp:mcp diff --git a/modules/mcp/src/Plugin/Mcp/EcaMcpPluginBase.php b/modules/mcp/src/Plugin/Mcp/EcaMcpPluginBase.php new file mode 100644 index 0000000000000000000000000000000000000000..62b2b1c28d57b85965b7dfcc6c5c8332b1f55e3b --- /dev/null +++ b/modules/mcp/src/Plugin/Mcp/EcaMcpPluginBase.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\ai_integration_eca_mcp\Plugin\Mcp; + +use Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\mcp\Plugin\McpPluginBase; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Abstract class for MCP-plugins using ECA-functionality. + */ +abstract class EcaMcpPluginBase extends McpPluginBase implements ContainerFactoryPluginInterface { + + /** + * The data provider. + * + * @var \Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface + */ + protected DataProviderInterface $dataProvider; + + /** + * The serializer. + * + * @var \Symfony\Component\Serializer\SerializerInterface + */ + protected SerializerInterface $serializer; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): EcaMcpPluginBase { + $instance = new static($configuration, $plugin_id, $plugin_definition); + $instance->dataProvider = $container->get('ai_integration_eca.services.data_provider'); + $instance->serializer = $container->get('serializer'); + + return $instance; + } + +} diff --git a/modules/mcp/src/Plugin/Mcp/EcaModelBuild.php b/modules/mcp/src/Plugin/Mcp/EcaModelBuild.php new file mode 100644 index 0000000000000000000000000000000000000000..18f394fd4adf787c9cc272af92fef2d5fa01c47d --- /dev/null +++ b/modules/mcp/src/Plugin/Mcp/EcaModelBuild.php @@ -0,0 +1,86 @@ +<?php + +namespace Drupal\ai_integration_eca_mcp\Plugin\Mcp; + +use Drupal\ai_integration_eca\Schema\Eca; +use Drupal\ai_integration_eca\TypedData\EcaModelDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\mcp\Attribute\Mcp; +use Drupal\mcp\ServerFeatures\Resource; +use Drupal\mcp\ServerFeatures\Tool; + +/** + * Builds ECA models. + */ +#[Mcp( + id: 'eca-model-build', + name: new TranslatableMarkup('ECA Model Build'), + description: new TranslatableMarkup('Builds models and exposes the schema.'), +)] +class EcaModelBuild extends EcaMcpPluginBase { + + /** + * {@inheritdoc} + */ + public function getResources(): array { + $resources = parent::getResources(); + + $resources[] = new Resource( + uri: 'schema', + name: new TranslatableMarkup('ECA Model schema'), + description: new TranslatableMarkup('Get the schema of the ECA Model entity'), + mimeType: 'application/json', + text: NULL, + ); + + return $resources; + } + + /** + * {@inheritdoc} + */ + public function readResource(string $resourceId): array { + $definition = EcaModelDefinition::create(); + $schema = new Eca($definition, $definition->getPropertyDefinitions()); + $schema = $this->serializer->serialize($schema, 'schema_json:json', []); + + return [ + new Resource( + uri: 'schema', + name: new TranslatableMarkup('ECA Model schema'), + description: new TranslatableMarkup('Get the schema of the ECA Model entity'), + mimeType: 'application/json', + text: $schema, + ), + ]; + } + + /** + * {@inheritdoc} + */ + public function getTools(): array { + $tools = parent::getTools(); + + $tools[] = new Tool( + name: 'schema', + description: new TranslatableMarkup('Retrieve the ECA Model schema.'), + inputSchema: [ + 'type' => 'object', + 'properties' => new \stdClass(), + ], + ); + + return $tools; + } + + /** + * {@inheritdoc} + */ + public function executeTool(string $toolId, mixed $arguments): array { + return match ($toolId) { + 'schema' => [['type' => 'resource', 'resource' => $this->readResource($toolId)[0]]], + default => parent::executeTool($toolId, $arguments), + }; + } + +} diff --git a/modules/mcp/src/Plugin/Mcp/EcaModelLookup.php b/modules/mcp/src/Plugin/Mcp/EcaModelLookup.php new file mode 100644 index 0000000000000000000000000000000000000000..86f0b0e906ba4758f695b870c4d709d2bed14042 --- /dev/null +++ b/modules/mcp/src/Plugin/Mcp/EcaModelLookup.php @@ -0,0 +1,253 @@ +<?php + +namespace Drupal\ai_integration_eca_mcp\Plugin\Mcp; + +use Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\mcp\Attribute\Mcp; +use Drupal\mcp\ServerFeatures\Resource; +use Drupal\mcp\ServerFeatures\ResourceTemplate; +use Drupal\mcp\ServerFeatures\Tool; +use Illuminate\Support\Arr; + +/** + * Exposes ECA models. + */ +#[Mcp( + id: 'eca-model-lookup', + name: new TranslatableMarkup('ECA Model Lookup'), + description: new TranslatableMarkup('Provides Models as a Resource or via lookup tools'), +)] +class EcaModelLookup extends EcaMcpPluginBase { + + /** + * {@inheritdoc} + */ + public function defaultConfiguration(): array { + $config = parent::defaultConfiguration(); + + $config['config']['only_enabled'] = TRUE; + + return $config; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { + $config = $this->getConfiguration(); + + $form = parent::buildConfigurationForm($form, $form_state); + $form['only_enabled'] = [ + '#type' => 'checkbox', + '#title' => new TranslatableMarkup('Only expose enabled models'), + '#default_value' => $config['config']['only_enabled'] ?? TRUE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getResources(): array { + $resources = parent::getResources(); + + $resources[] = new Resource( + uri: 'model', + name: new TranslatableMarkup('@definition: Models', [ + '@definition' => $this->getPluginDefinition()['name'], + ]), + description: new TranslatableMarkup('Get all models.'), + mimeType: 'application/json', + text: NULL, + ); + + return $resources; + } + + /** + * {@inheritdoc} + */ + public function getResourceTemplates(): array { + $templates = parent::getResourceTemplates(); + + $templates[] = new ResourceTemplate( + uriTemplate: 'model/{id}', + name: sprintf('%s: Model', $this->getPluginDefinition()['name']), + description: new TranslatableMarkup('Get a specific model.'), + mimeType: 'application/json', + ); + + return $templates; + } + + /** + * {@inheritdoc} + */ + public function readResource(string $resourceId): array { + // Fetch resource and sub-resource from the provided URI. + // - 'eca-model-lookup://model'. 'event' is the resource ID. + // - 'eca-model-lookup://model/process_ilwj9jq': + // -- 'model' is the resource ID. + // -- 'process_ilwj9jq' is the sub-resource. + $result = preg_match("#^(?'resource_id'[^/]+)(?:/(?'sub_resource'.*))?$#", $resourceId, $matches); + if (!$result) { + throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)); + } + + // If a sub-resource is requested, fetch it from the database and return the + // full data object. + $data = []; + $uri = ''; + if (!empty($matches['sub_resource'])) { + $this->dataProvider->setViewMode(DataViewModeEnum::Full); + $data = $this->dataProvider->getModels([$matches['sub_resource']], $this->getConfiguration()['config']['only_enabled']); + $uri = $resourceId; + + if (empty($data)) { + throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)); + } + } + + // If a generic resource is requested, fetch them from the codebase and + // return a slimmed down list of plugins. + if (empty($data)) { + $this->dataProvider->setViewMode(DataViewModeEnum::Teaser); + $data = $this->dataProvider->getModels([], $this->getConfiguration()['config']['only_enabled']); + } + + // Wrap everything in a Resource. + $response = []; + foreach ($data as $model) { + $response[] = new Resource( + uri: empty($uri) ? sprintf('%s/%s', $resourceId, $model['model_id']) : $uri, + name: $model['label'], + description: $model['description'] ?? '', + mimeType: 'application/json', + text: json_encode(Arr::except($model, [ + 'model_id', + 'label', + 'description', + ])), + ); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function getTools(): array { + $tools = parent::getTools(); + + $tools[] = new Tool( + name: 'lookup', + description: new TranslatableMarkup('Lookup ECA models using filters. Multiple filters are combined with AND logic.'), + inputSchema: [ + 'type' => 'object', + 'properties' => [ + 'filters' => [ + 'type' => 'array', + 'description' => new TranslatableMarkup('Array of filter conditions.'), + 'items' => [ + 'type' => 'object', + 'required' => ['field', 'value'], + 'properties' => [ + 'field' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Field name to filter on.'), + 'enum' => [ + 'label', + ], + ], + 'value' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Value to filter by.'), + ], + 'operator' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Comparison operator.'), + 'default' => '=', + 'enum' => [ + '=', + '<>', + 'CONTAINS', + 'STARTS_WITH', + 'ENDS_WITH', + ], + ], + ], + ], + ], + 'limit' => [ + 'type' => 'integer', + 'description' => new TranslatableMarkup('Maximum number of results'), + 'default' => 10, + ], + 'offset' => [ + 'type' => 'integer', + 'description' => new TranslatableMarkup('Starting point for pagination'), + 'default' => 0, + ], + ], + ], + ); + + return $tools; + } + + /** + * {@inheritdoc} + */ + public function executeTool(string $toolId, mixed $arguments): array { + return match ($toolId) { + 'lookup' => $this->lookupModels($arguments), + default => parent::executeTool($toolId, $arguments), + }; + } + + /** + * Lookup models by applying filtering arguments. + * + * @param array $arguments + * The filters. + * + * @return array + * Returns an array of resources. + */ + protected function lookupModels(array $arguments): array { + assert(!empty($arguments['filters']), new TranslatableMarkup('Filters must be an array.')); + + $filters = $arguments['filters']; + + if ($this->getConfiguration()['config']['only_enabled']) { + $filters[] = [ + 'field' => 'status', + 'value' => TRUE, + ]; + } + + try { + $models = $this->dataProvider->filterModels($filters, $arguments['limit'] ?? 10, $arguments['offset'] ?? 0); + } + catch (\Exception $e) { + return [ + 'type' => 'text', + 'text' => sprintf('Error: %s', $e->getMessage()), + ]; + } + + return array_reduce($models, function (array $carry, $model) { + $carry[] = [ + 'type' => 'resource', + 'resource' => $this->readResource(sprintf('model/%s', $model['model_id']))[0], + ]; + + return $carry; + }, []); + } + +} diff --git a/modules/mcp/src/Plugin/Mcp/EcaPluginLookup.php b/modules/mcp/src/Plugin/Mcp/EcaPluginLookup.php new file mode 100644 index 0000000000000000000000000000000000000000..8b170b55c311eeae81d9b4b62a7ca63b6c46c163 --- /dev/null +++ b/modules/mcp/src/Plugin/Mcp/EcaPluginLookup.php @@ -0,0 +1,324 @@ +<?php + +namespace Drupal\ai_integration_eca_mcp\Plugin\Mcp; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ai_integration_eca\EcaElementType; +use Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum; +use Drupal\mcp\Attribute\Mcp; +use Drupal\mcp\ServerFeatures\Resource; +use Drupal\mcp\ServerFeatures\ResourceTemplate; +use Drupal\mcp\ServerFeatures\Tool; +use Illuminate\Support\Arr; + +/** + * Exposes ECA plugins. + */ +#[Mcp( + id: 'eca-plugin-lookup', + name: new TranslatableMarkup('ECA Plugin Lookup'), + description: new TranslatableMarkup('Provides Events, Conditions and Actions as a Resource or via lookup tools'), +)] +class EcaPluginLookup extends EcaMcpPluginBase { + + /** + * {@inheritdoc} + */ + public function defaultConfiguration(): array { + $config = parent::defaultConfiguration(); + + $config['config']['plugin_types'] = array_keys(EcaElementType::getCoreTypes()); + + return $config; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state): array { + $config = $this->getConfiguration(); + + $form = parent::buildConfigurationForm($form, $form_state); + $form['plugin_types'] = [ + '#type' => 'checkboxes', + '#title' => new TranslatableMarkup('Plugin types'), + '#options' => EcaElementType::getCoreTypes(), + '#default_value' => $config['config']['plugin_types'] ?? array_keys(EcaElementType::getCoreTypes()), + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function getResources(): array { + $resources = parent::getResources(); + $pluginTypes = $this->getAvailablePluginTypes(); + + foreach ($pluginTypes as $pluginType) { + $resources[] = new Resource( + uri: $pluginType, + name: new TranslatableMarkup('@definition: @name', [ + '@definition' => $this->getPluginDefinition()['name'], + '@name' => ucfirst(EcaElementType::from($pluginType)->getPlural()), + ]), + description: new TranslatableMarkup("Get all implementations of plugin type '@type'", [ + '@type' => $pluginType, + ]), + mimeType: 'application/json', + text: NULL, + ); + } + + return $resources; + } + + /** + * {@inheritdoc} + */ + public function getResourceTemplates(): array { + $templates = parent::getResourceTemplates(); + $pluginTypes = $this->getAvailablePluginTypes(); + + foreach ($pluginTypes as $pluginType) { + $templates[] = new ResourceTemplate( + uriTemplate: sprintf('%s/{id}', $pluginType), + name: sprintf('%s: %s', $this->getPluginDefinition()['name'], EcaElementType::from($pluginType)->name), + description: new TranslatableMarkup("Get a specific implementation of plugin type '@type'", [ + '@type' => $pluginType, + ]), + mimeType: 'application/json', + ); + } + + return $templates; + } + + /** + * {@inheritdoc} + */ + public function readResource(string $resourceId): array { + // Fetch resource and sub-resource from the provided URI. + // - 'eca-plugin-lookup://event'. 'event' is the resource ID. + // - 'eca-plugin-lookup://event/content_entity:presave': + // -- 'event' is the resource ID. + // -- 'content_entity:presave' is the sub-resource. + $result = preg_match("#^(?'resource_id'[^/]+)(?:/(?'sub_resource'.*))?$#", $resourceId, $matches); + if (!$result) { + throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)); + } + + if (!$this->isPluginTypeEnabled($matches['resource_id'])) { + throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)); + } + + // If a sub-resource is requested, fetch it from the codebase and return the + // full data object. + $data = []; + $uri = ''; + if (!empty($matches['sub_resource'])) { + $this->dataProvider->setViewMode(DataViewModeEnum::Full); + $data = $this->dataProvider->getComponents([$matches['sub_resource']]); + $data = $data[EcaElementType::from($matches['resource_id'])->getPlural()]; + $uri = $resourceId; + + if (empty($data)) { + throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)); + } + } + + // If a generic resource is requested, fetch them from the codebase and + // return a slimmed down list of plugins. + if (empty($data)) { + $this->dataProvider->setViewMode(DataViewModeEnum::Teaser); + $data = match (EcaElementType::from($resourceId)) { + EcaElementType::Event => $this->dataProvider->getEvents(), + EcaElementType::Condition => $this->dataProvider->getConditions(), + EcaElementType::Action => $this->dataProvider->getActions(), + default => throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $resourceId)), + }; + } + + // Wrap everything in a Resource. + $response = []; + foreach ($data as $plugin) { + $response[] = new Resource( + uri: empty($uri) ? sprintf('%s/%s', $resourceId, $plugin['plugin_id']) : $uri, + name: $plugin['name'], + description: $plugin['description'] ?? '', + mimeType: 'application/json', + text: json_encode(Arr::except($plugin, ['plugin_id', 'name', 'description'])), + ); + } + + return $response; + } + + /** + * {@inheritdoc} + */ + public function getTools(): array { + $tools = parent::getTools(); + + $tools[] = new Tool( + name: 'lookup', + description: new TranslatableMarkup('Lookup ECA plugins using filters. Multiple filters are combined with AND logic.'), + inputSchema: [ + 'type' => 'object', + 'required' => ['pluginType'], + 'properties' => [ + 'pluginType' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Plugin type name. Available options: @options.', [ + '@options' => implode(', ', array_keys(EcaElementType::getCoreTypes())), + ]), + ], + 'filters' => [ + 'type' => 'array', + 'description' => new TranslatableMarkup('Array of filter conditions.'), + 'items' => [ + 'type' => 'object', + 'required' => ['field', 'value'], + 'properties' => [ + 'field' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Field name to filter on.'), + 'enum' => [ + 'plugin_id', + 'name', + 'module', + ], + ], + 'value' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Value to filter by.'), + ], + 'operator' => [ + 'type' => 'string', + 'description' => new TranslatableMarkup('Comparison operator.'), + 'default' => '=', + 'enum' => [ + '=', + '<>', + 'CONTAINS', + 'STARTS_WITH', + 'ENDS_WITH', + ], + ], + ], + ], + ], + 'limit' => [ + 'type' => 'integer', + 'description' => new TranslatableMarkup('Maximum number of results'), + 'default' => 10, + ], + 'offset' => [ + 'type' => 'integer', + 'description' => new TranslatableMarkup('Starting point for pagination'), + 'default' => 0, + ], + ], + ], + ); + + return $tools; + } + + /** + * {@inheritdoc} + */ + public function executeTool(string $toolId, mixed $arguments): array { + return match ($toolId) { + 'lookup' => $this->lookupPlugins($arguments), + default => [], + }; + } + + /** + * Lookup plugins by applying filtering arguments. + * + * @param array $arguments + * The filters. + * + * @return array + * Returns an array of resources. + */ + protected function lookupPlugins(array $arguments): array { + assert(!empty($arguments['pluginType']), new TranslatableMarkup('Plugin type must be a string.')); + assert($this->isPluginTypeEnabled($arguments['pluginType']), sprintf("Plugin type '%s' is not enabled.", $arguments['pluginType'])); + + $pluginType = $arguments['pluginType']; + $this->dataProvider->setViewMode(DataViewModeEnum::Full); + $data = match (EcaElementType::from($pluginType)) { + EcaElementType::Event => $this->dataProvider->getEvents(), + EcaElementType::Condition => $this->dataProvider->getConditions(), + EcaElementType::Action => $this->dataProvider->getActions(), + default => throw new \InvalidArgumentException(sprintf('Unknown resource ID: %s', $pluginType)), + }; + + if (empty($data)) { + return [ + 'type' => 'text', + 'text' => new TranslatableMarkup('Error: no applicable plugins found.'), + ]; + } + + $data = collect($data); + foreach ($arguments['filters'] ?? [] as $filter) { + assert(!empty($filter['field']), new TranslatableMarkup('A filter needs to be defined by a field.')); + assert(!empty($filter['value']), new TranslatableMarkup('A filter needs to be defined by a value.')); + + $data = match ($filter['operator'] ?? '=') { + 'CONTAINS' => $data->filter(function (array $plugin) use ($filter) { + return !empty($plugin[$filter['field']]) && str_contains(mb_strtolower($plugin[$filter['field']]), mb_strtolower($filter['value'])); + }), + 'STARTS_WITH' => $data->filter(function (array $plugin) use ($filter) { + return !empty($plugin[$filter['field']]) && str_starts_with(mb_strtolower($plugin[$filter['field']]), mb_strtolower($filter['value'])); + }), + 'ENDS_WITH' => $data->filter(function (array $plugin) use ($filter) { + return !empty($plugin[$filter['field']]) && str_ends_with(mb_strtolower($plugin[$filter['field']]), mb_strtolower($filter['value'])); + }), + '=' => $data->where($filter['field'], '===', $filter['value']), + default => $data->where($filter['field'], $filter['operator'], $filter['value']), + }; + } + + return $data + ->slice($arguments['offset'] ?? 0, $arguments['limit'] ?? 10) + ->reduce(function (array $carry, array $plugin) use ($pluginType) { + $carry[] = [ + 'type' => 'resource', + 'resource' => $this->readResource(sprintf('%s/%s', $pluginType, $plugin['plugin_id']))[0], + ]; + + return $carry; + }, []); + } + + /** + * Get the available plugin types. + * + * @return array + * Returns the available plugin types. + */ + protected function getAvailablePluginTypes(): array { + return array_filter(array_values($this->getConfiguration()['config']['plugin_types'])); + } + + /** + * Determine if a plugin type is enabled. + * + * @param string $pluginType + * The plugin type to validate. + * + * @return bool + * Returns whether the plugin type is enabled. + */ + protected function isPluginTypeEnabled(string $pluginType): bool { + return in_array($pluginType, $this->getAvailablePluginTypes(), TRUE); + } + +} diff --git a/modules/agents/src/EcaElementType.php b/src/EcaElementType.php similarity index 68% rename from modules/agents/src/EcaElementType.php rename to src/EcaElementType.php index a18e3f469566ed670e47f3c2e65f49589b31b303..3c08aa31642bff59519f1be48b8d356f57ff950f 100644 --- a/modules/agents/src/EcaElementType.php +++ b/src/EcaElementType.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents; +namespace Drupal\ai_integration_eca; /** * Enum of the element types. @@ -30,7 +30,7 @@ enum EcaElementType: string { /** * Returns a list of types, keyed by their plural form. * - * @return \Drupal\ai_integration_eca_agents\EcaElementType[] + * @return \Drupal\ai_integration_eca\EcaElementType[] * The list of types, keyed by their plural form. */ public static function getPluralMap(): array { @@ -42,4 +42,18 @@ enum EcaElementType: string { ]; } + /** + * Returns a list of core types. + * + * @return array + * the list of core types. + */ + public static function getCoreTypes(): array { + return [ + self::Event->value => self::Event->name, + self::Condition->value => self::Condition->name, + self::Action->value => self::Action->name, + ]; + } + } diff --git a/modules/agents/src/EntityViolationException.php b/src/EntityViolationException.php similarity index 97% rename from modules/agents/src/EntityViolationException.php rename to src/EntityViolationException.php index 433bb4ba7139cb1fb5c3ea7c7464e602c2195445..86967a56e4cbcb30956a3fd05c59d602425ea6ca 100644 --- a/modules/agents/src/EntityViolationException.php +++ b/src/EntityViolationException.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents; +namespace Drupal\ai_integration_eca; use Drupal\Core\Config\ConfigException; use Symfony\Component\Validator\ConstraintViolationListInterface; diff --git a/modules/agents/src/MissingEventException.php b/src/MissingEventException.php similarity index 78% rename from modules/agents/src/MissingEventException.php rename to src/MissingEventException.php index 06d79611e60aacdfd0f5edef0b1fb3db667b49a4..92fef32ec7fa291e9185f0e549d888bc457d4efe 100644 --- a/modules/agents/src/MissingEventException.php +++ b/src/MissingEventException.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents; +namespace Drupal\ai_integration_eca; use Drupal\Core\Config\ConfigException; diff --git a/modules/agents/src/Normalizer/json/DataDefinitionNormalizer.php b/src/Normalizer/json/DataDefinitionNormalizer.php similarity index 97% rename from modules/agents/src/Normalizer/json/DataDefinitionNormalizer.php rename to src/Normalizer/json/DataDefinitionNormalizer.php index ff359822e30b45e8b9af65cd8db695ce4ebb1d7c..b8adf0e38e2754038665f110a2ee2b1eef7a2137 100644 --- a/modules/agents/src/Normalizer/json/DataDefinitionNormalizer.php +++ b/src/Normalizer/json/DataDefinitionNormalizer.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Normalizer\json; +namespace Drupal\ai_integration_eca\Normalizer\json; use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\schemata_json_schema\Normalizer\json\JsonNormalizerBase; diff --git a/src/Plugin/Action/AiConfigActionBase.php b/src/Plugin/Action/AiConfigActionBase.php index 7b4a8ce93bab00b196e81a285af0659521d2a1ac..6c3464f796622aa8cebfbc8297cacf544e97ceb9 100644 --- a/src/Plugin/Action/AiConfigActionBase.php +++ b/src/Plugin/Action/AiConfigActionBase.php @@ -6,7 +6,7 @@ use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\ai_integration_eca\Service\AiProviderValidatorInterface; +use Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidatorInterface; use Drupal\eca\Service\YamlParser; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Validator\ConstraintViolationInterface; @@ -27,7 +27,7 @@ abstract class AiConfigActionBase extends AiActionBase { /** * The AI Provider validator. * - * @var \Drupal\ai_integration_eca\Service\AiProviderValidatorInterface + * @var \Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidatorInterface */ protected AiProviderValidatorInterface $validator; @@ -37,7 +37,7 @@ abstract class AiConfigActionBase extends AiActionBase { public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->yamlParser = $container->get('eca.service.yaml_parser'); - $instance->validator = $container->get('ai_integration_eca.provider_validator'); + $instance->validator = $container->get('ai_integration_eca.services.provider_validator'); return $instance; } diff --git a/modules/agents/src/Plugin/DataType/EcaGateway.php b/src/Plugin/DataType/EcaGateway.php similarity index 74% rename from modules/agents/src/Plugin/DataType/EcaGateway.php rename to src/Plugin/DataType/EcaGateway.php index b06c35a597273fc9cf7afbd110abad3ab832a292..87f0dfedcad17403ae770001f4549f70a453c427 100644 --- a/modules/agents/src/Plugin/DataType/EcaGateway.php +++ b/src/Plugin/DataType/EcaGateway.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\DataType; +namespace Drupal\ai_integration_eca\Plugin\DataType; -use Drupal\ai_integration_eca_agents\TypedData\EcaGatewayDefinition; +use Drupal\ai_integration_eca\TypedData\EcaGatewayDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Map; diff --git a/modules/agents/src/Plugin/DataType/EcaModel.php b/src/Plugin/DataType/EcaModel.php similarity index 81% rename from modules/agents/src/Plugin/DataType/EcaModel.php rename to src/Plugin/DataType/EcaModel.php index 43fd34cde7744dc599adc74ed18331919f1672d2..bcd12cb279b6445a470975b1e7dd532351ade590 100644 --- a/modules/agents/src/Plugin/DataType/EcaModel.php +++ b/src/Plugin/DataType/EcaModel.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\DataType; +namespace Drupal\ai_integration_eca\Plugin\DataType; -use Drupal\ai_integration_eca_agents\TypedData\EcaModelDefinition; +use Drupal\ai_integration_eca\TypedData\EcaModelDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Map; diff --git a/modules/agents/src/Plugin/DataType/EcaPlugin.php b/src/Plugin/DataType/EcaPlugin.php similarity index 83% rename from modules/agents/src/Plugin/DataType/EcaPlugin.php rename to src/Plugin/DataType/EcaPlugin.php index f4bdac8a730dcb016ab9758488ad8e901552fd92..efc2b816de0cf914f53c798be4766604121d76c9 100644 --- a/modules/agents/src/Plugin/DataType/EcaPlugin.php +++ b/src/Plugin/DataType/EcaPlugin.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\DataType; +namespace Drupal\ai_integration_eca\Plugin\DataType; -use Drupal\ai_integration_eca_agents\TypedData\EcaPluginDefinition; +use Drupal\ai_integration_eca\TypedData\EcaPluginDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Map; diff --git a/modules/agents/src/Plugin/DataType/EcaSuccessor.php b/src/Plugin/DataType/EcaSuccessor.php similarity index 74% rename from modules/agents/src/Plugin/DataType/EcaSuccessor.php rename to src/Plugin/DataType/EcaSuccessor.php index 0c4e959c90d98f859ed9f1884c5a9a410386f02d..5c2fbf8bfb69a5847cc92c10f131a923f44cda51 100644 --- a/modules/agents/src/Plugin/DataType/EcaSuccessor.php +++ b/src/Plugin/DataType/EcaSuccessor.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\DataType; +namespace Drupal\ai_integration_eca\Plugin\DataType; -use Drupal\ai_integration_eca_agents\TypedData\EcaSuccessorDefinition; +use Drupal\ai_integration_eca\TypedData\EcaSuccessorDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\Attribute\DataType; use Drupal\Core\TypedData\Plugin\DataType\Map; diff --git a/modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php b/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php similarity index 94% rename from modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php rename to src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php index a4745c85bb1be1c9858250ab8f5fd8417e96261d..ec586a30fa653386ba1aa0f682837a6ecd62b53f 100644 --- a/modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php +++ b/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraint.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\Validation\Constraint; +namespace Drupal\ai_integration_eca\Plugin\Validation\Constraint; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Validation\Attribute\Constraint; diff --git a/modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php b/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php similarity index 91% rename from modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php rename to src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php index 4a8bc4823362c7c0de585ecc0e2fb94679e79648..8b5181c3b388ad310d05be2e2786fafdf11e9fc5 100644 --- a/modules/agents/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php +++ b/src/Plugin/Validation/Constraint/SuccessorsAreValidConstraintValidator.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Plugin\Validation\Constraint; +namespace Drupal\ai_integration_eca\Plugin\Validation\Constraint; -use Drupal\ai_integration_eca_agents\EcaElementType; +use Drupal\ai_integration_eca\EcaElementType; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -38,11 +38,11 @@ class SuccessorsAreValidConstraintValidator extends ConstraintValidator { /** * Validate the successors of an element. * - * @param \Drupal\ai_integration_eca_agents\Plugin\Validation\Constraint\SuccessorsAreValidConstraint $constraint + * @param \Drupal\ai_integration_eca\Plugin\Validation\Constraint\SuccessorsAreValidConstraint $constraint * The constraint. * @param array $element * The element to validate. - * @param \Drupal\ai_integration_eca_agents\EcaElementType $type + * @param \Drupal\ai_integration_eca\EcaElementType $type * The type of the element. * @param array $lookup * The lookup-array containing all the referencable IDs. diff --git a/modules/agents/src/Schema/Eca.php b/src/Schema/Eca.php similarity index 97% rename from modules/agents/src/Schema/Eca.php rename to src/Schema/Eca.php index b1bb9cc02c143e92a8acd248d514146bfc840036..5a3e773ebf52c870b930abe1b589d25f002fd7b7 100644 --- a/modules/agents/src/Schema/Eca.php +++ b/src/Schema/Eca.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Schema; +namespace Drupal\ai_integration_eca\Schema; use Drupal\Core\Cache\RefinableCacheableDependencyTrait; use Drupal\Core\TypedData\DataDefinitionInterface; diff --git a/src/Service/AiProviderValidator.php b/src/Services/AiProviderValidator/AiProviderValidator.php similarity index 98% rename from src/Service/AiProviderValidator.php rename to src/Services/AiProviderValidator/AiProviderValidator.php index fd1355238252786af9136dd8b79760f433b85690..0b3613f1a3090af02b33721b457a96855e2e9d60 100644 --- a/src/Service/AiProviderValidator.php +++ b/src/Services/AiProviderValidator/AiProviderValidator.php @@ -1,16 +1,12 @@ <?php -namespace Drupal\ai_integration_eca\Service; +namespace Drupal\ai_integration_eca\Services\AiProviderValidator; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Validation\BasicRecursiveValidatorFactory; use Drupal\ai\AiProviderInterface; use Drupal\ai\Plugin\ProviderProxy; use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintViolation; -use Symfony\Component\Validator\ConstraintViolationInterface; -use Symfony\Component\Validator\ConstraintViolationList; -use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\NotBlank; @@ -18,6 +14,10 @@ use Symfony\Component\Validator\Constraints\Optional; use Symfony\Component\Validator\Constraints\Range; use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Constraints\Type; +use Symfony\Component\Validator\ConstraintViolation; +use Symfony\Component\Validator\ConstraintViolationInterface; +use Symfony\Component\Validator\ConstraintViolationList; +use Symfony\Component\Validator\ConstraintViolationListInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; /** diff --git a/src/Service/AiProviderValidatorInterface.php b/src/Services/AiProviderValidator/AiProviderValidatorInterface.php similarity index 88% rename from src/Service/AiProviderValidatorInterface.php rename to src/Services/AiProviderValidator/AiProviderValidatorInterface.php index e396df3950beaabfdbfb5d2769313630b8a61796..e95f6931a8f005be91d8033037eea5dc06d6c5be 100644 --- a/src/Service/AiProviderValidatorInterface.php +++ b/src/Services/AiProviderValidator/AiProviderValidatorInterface.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca\Service; +namespace Drupal\ai_integration_eca\Services\AiProviderValidator; use Drupal\ai\AiProviderInterface; use Drupal\ai\Plugin\ProviderProxy; @@ -34,7 +34,7 @@ interface AiProviderValidatorInterface { * @param array<string, \Symfony\Component\Validator\Constraint> $constraints * Additional constraints to set, keyed by field. * - * @return \Drupal\ai\Service\AiProviderValidator\AiProviderValidatorInterface + * @return \Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidatorInterface * Returns the called object. */ public function addConstraints(array $constraints): AiProviderValidatorInterface; diff --git a/modules/agents/src/Services/DataProvider/DataProvider.php b/src/Services/DataProvider/DataProvider.php similarity index 78% rename from modules/agents/src/Services/DataProvider/DataProvider.php rename to src/Services/DataProvider/DataProvider.php index 15d421c848f6362c4843b78a2f2d87df493edbaf..8008e3f238797b76e0976300b7cb49ab1621252b 100644 --- a/modules/agents/src/Services/DataProvider/DataProvider.php +++ b/src/Services/DataProvider/DataProvider.php @@ -1,14 +1,16 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\DataProvider; +namespace Drupal\ai_integration_eca\Services\DataProvider; -use Drupal\ai_integration_eca_agents\EcaElementType; use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormState; use Drupal\Core\Plugin\PluginFormInterface; -use Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface; +use Drupal\ai_integration_eca\EcaElementType; +use Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\eca\Attributes\Token; use Drupal\eca\Entity\Eca; use Drupal\eca\Service\Actions; @@ -26,7 +28,7 @@ class DataProvider implements DataProviderInterface { /** * The view mode which determines how many details are returned. * - * @var \Drupal\ai_integration_eca_agents\Services\DataProvider\DataViewModeEnum + * @var \Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum */ protected DataViewModeEnum $viewMode = DataViewModeEnum::Teaser; @@ -41,7 +43,7 @@ class DataProvider implements DataProviderInterface { * The actions. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager. - * @param \Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface $modelMapper + * @param \Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface $modelMapper * The model mapper. * @param \Symfony\Component\Serializer\Normalizer\NormalizerInterface $normalizer * The normalizer. @@ -125,7 +127,7 @@ class DataProvider implements DataProviderInterface { /** * {@inheritdoc} */ - public function getModels(array $filterIds = []): array { + public function getModels(array $filterIds = [], $onlyEnabled = TRUE): array { /** @var \Drupal\eca\Entity\EcaStorage $storage */ $storage = $this->entityTypeManager->getStorage('eca'); $models = $storage->loadMultiple(); @@ -136,18 +138,39 @@ class DataProvider implements DataProviderInterface { }); } - return array_reduce($models, function (array $carry, Eca $eca) { - $model = $this->modelMapper->fromEntity($eca); - $data = array_filter($this->normalizer->normalize($model)); + if ($onlyEnabled) { + $models = array_filter($models, function (Eca $model) { + return $model->status(); + }); + } - if ($this->viewMode === DataViewModeEnum::Teaser) { - $data = Arr::only($data, ['model_id', 'label', 'description']); - } + return $this->reduceModels($models); + } - $carry[] = $data; + /** + * {@inheritdoc} + */ + public function filterModels(array $filters, int $limit = 10, int $offset = 0): array { + $query = $this->entityTypeManager->getStorage('eca')->getQuery() + ->accessCheck(FALSE) + ->range($offset, $limit); - return $carry; - }, []); + foreach ($filters as $filter) { + assert(!empty($filter['field']), new TranslatableMarkup('A filter needs to be defined by a field.')); + assert(!empty($filter['value']), new TranslatableMarkup('A filter needs to be defined by a value.')); + + assert($this->entityTypeManager->getDefinition('eca')->hasKey($filter['field']), new TranslatableMarkup('Unknown field: @field.', [ + '@field' => $filter['field'], + ])); + + $query->condition($filter['field'], $filter['value'], $filter['operator'] ?? '='); + } + + $ids = $query->execute(); + $models = $this->entityTypeManager->getStorage('eca') + ->loadMultiple($ids); + + return $this->reduceModels($models); } /** @@ -254,4 +277,29 @@ class DataProvider implements DataProviderInterface { }, []); } + /** + * Reduce a collection of ECA models. + * + * @param \Drupal\eca\Entity\Eca[] $models + * + * @return array + * Returns a reduced collection of ECA model data. + * + * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface + */ + protected function reduceModels(array $models): array { + return array_reduce($models, function (array $carry, Eca $eca) { + $model = $this->modelMapper->fromEntity($eca); + $data = array_filter($this->normalizer->normalize($model)); + + if ($this->viewMode === DataViewModeEnum::Teaser) { + $data = Arr::only($data, ['model_id', 'label', 'description']); + } + + $carry[] = $data; + + return $carry; + }, []); + } + } diff --git a/modules/agents/src/Services/DataProvider/DataProviderInterface.php b/src/Services/DataProvider/DataProviderInterface.php similarity index 64% rename from modules/agents/src/Services/DataProvider/DataProviderInterface.php rename to src/Services/DataProvider/DataProviderInterface.php index d6b76010ddc090201e853f869e5433c603a9e1a3..13337efb2f390865961edcb9b5461b2214c32c06 100644 --- a/modules/agents/src/Services/DataProvider/DataProviderInterface.php +++ b/src/Services/DataProvider/DataProviderInterface.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\DataProvider; +namespace Drupal\ai_integration_eca\Services\DataProvider; /** * Interface for ECA data provider. @@ -47,11 +47,28 @@ interface DataProviderInterface { * * @param array $filterIds * An optional array of IDs to filter by. + * @param bool $onlyEnabled + * Only return the enabled models. * * @return array * Returns the models. */ - public function getModels(array $filterIds = []): array; + public function getModels(array $filterIds = [], bool $onlyEnabled = TRUE): array; + + /** + * Filter models. + * + * @param array $filters + * An optional array of field- and value-options to filter by. + * @param int $limit + * The amount of models to filter. + * @param int $offset + * The starting point for pagination + * + * @return array + * Returns the models. + */ + public function filterModels(array $filters, int $limit = 10, int $offset = 0): array; /** * Get the available tokens. @@ -67,10 +84,10 @@ interface DataProviderInterface { * This is used for controlling how much details are exposed for the different * components. * - * @param \Drupal\ai_integration_eca_agents\Services\DataProvider\DataViewModeEnum $viewMode + * @param \Drupal\ai_integration_eca\Services\DataProvider\DataViewModeEnum $viewMode * The view mode. * - * @return \Drupal\ai_integration_eca_agents\Services\DataProvider\DataProviderInterface + * @return \Drupal\ai_integration_eca\Services\DataProvider\DataProviderInterface * Returns the altered instance. */ public function setViewMode(DataViewModeEnum $viewMode): DataProviderInterface; diff --git a/modules/agents/src/Services/DataProvider/DataViewModeEnum.php b/src/Services/DataProvider/DataViewModeEnum.php similarity index 70% rename from modules/agents/src/Services/DataProvider/DataViewModeEnum.php rename to src/Services/DataProvider/DataViewModeEnum.php index dd50a156517fcb0e609e7c672849fd0c77056388..910d59c301bc7835730402923f9639cec0947c26 100644 --- a/modules/agents/src/Services/DataProvider/DataViewModeEnum.php +++ b/src/Services/DataProvider/DataViewModeEnum.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\DataProvider; +namespace Drupal\ai_integration_eca\Services\DataProvider; /** * Enumeration determining the view mode of the data. diff --git a/modules/agents/src/Services/EcaRepository/EcaRepository.php b/src/Services/EcaRepository/EcaRepository.php similarity index 85% rename from modules/agents/src/Services/EcaRepository/EcaRepository.php rename to src/Services/EcaRepository/EcaRepository.php index 834eb51aec2174c336b7d691893d7549a2089e60..69f36cb089e3432eeecfbed84411aa9bee47fc28 100644 --- a/modules/agents/src/Services/EcaRepository/EcaRepository.php +++ b/src/Services/EcaRepository/EcaRepository.php @@ -1,13 +1,13 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\EcaRepository; +namespace Drupal\ai_integration_eca\Services\EcaRepository; -use Drupal\ai_integration_eca_agents\EntityViolationException; -use Drupal\ai_integration_eca_agents\MissingEventException; -use Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface; use Drupal\Component\Utility\Random; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\TypedData\TypedDataManagerInterface; +use Drupal\ai_integration_eca\EntityViolationException; +use Drupal\ai_integration_eca\MissingEventException; +use Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface; use Drupal\eca\Entity\Eca; /** @@ -20,7 +20,7 @@ class EcaRepository implements EcaRepositoryInterface { * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager. - * @param \Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface $modelMapper + * @param \Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface $modelMapper * The model mapper. * @param \Drupal\Core\TypedData\TypedDataManagerInterface $typedDataManager * The typed data manager. @@ -65,7 +65,7 @@ class EcaRepository implements EcaRepositoryInterface { $eca->resetComponents(); // Set events. - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $plugin */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $plugin */ foreach ($model->get('events') as $plugin) { $successors = $plugin->get('successors')->getValue() ?? []; foreach ($successors as &$successor) { @@ -84,7 +84,7 @@ class EcaRepository implements EcaRepositoryInterface { } // Set conditions. - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $plugin */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $plugin */ foreach ($model->get('conditions') as $plugin) { $eca->addCondition( $plugin->get('element_id')->getString(), @@ -94,7 +94,7 @@ class EcaRepository implements EcaRepositoryInterface { } // Set gateways. - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaGateway $plugin */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaGateway $plugin */ foreach ($model->get('gateways') as $plugin) { $successors = $plugin->get('successors')->getValue() ?? []; foreach ($successors as &$successor) { @@ -111,7 +111,7 @@ class EcaRepository implements EcaRepositoryInterface { } // Set actions. - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaGateway $plugin */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaGateway $plugin */ foreach ($model->get('actions') as $plugin) { $successors = $plugin->get('successors')->getValue() ?? []; foreach ($successors as &$successor) { diff --git a/modules/agents/src/Services/EcaRepository/EcaRepositoryInterface.php b/src/Services/EcaRepository/EcaRepositoryInterface.php similarity index 91% rename from modules/agents/src/Services/EcaRepository/EcaRepositoryInterface.php rename to src/Services/EcaRepository/EcaRepositoryInterface.php index 9b4cfadb13c0ad97d8d67449e5b1125d43b9151f..f6055a83451ab32a02063d2a1caf802736f66d7d 100644 --- a/modules/agents/src/Services/EcaRepository/EcaRepositoryInterface.php +++ b/src/Services/EcaRepository/EcaRepositoryInterface.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\EcaRepository; +namespace Drupal\ai_integration_eca\Services\EcaRepository; use Drupal\eca\Entity\Eca; diff --git a/modules/agents/src/Services/ModelMapper/ModelMapper.php b/src/Services/ModelMapper/ModelMapper.php similarity index 86% rename from modules/agents/src/Services/ModelMapper/ModelMapper.php rename to src/Services/ModelMapper/ModelMapper.php index 56fcc8d5672aff81a4aaa283e852ade3170ed507..9069a31e05feb8bbd2c20942ab2004d065d68b4e 100644 --- a/modules/agents/src/Services/ModelMapper/ModelMapper.php +++ b/src/Services/ModelMapper/ModelMapper.php @@ -1,13 +1,13 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\ModelMapper; - -use Drupal\ai_integration_eca_agents\EcaElementType; -use Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel; -use Drupal\ai_integration_eca_agents\TypedData\EcaGatewayDefinition; -use Drupal\ai_integration_eca_agents\TypedData\EcaModelDefinition; -use Drupal\ai_integration_eca_agents\TypedData\EcaPluginDefinition; -use Drupal\ai_integration_eca_agents\TypedData\EcaSuccessorDefinition; +namespace Drupal\ai_integration_eca\Services\ModelMapper; + +use Drupal\ai_integration_eca\EcaElementType; +use Drupal\ai_integration_eca\Plugin\DataType\EcaModel; +use Drupal\ai_integration_eca\TypedData\EcaGatewayDefinition; +use Drupal\ai_integration_eca\TypedData\EcaModelDefinition; +use Drupal\ai_integration_eca\TypedData\EcaPluginDefinition; +use Drupal\ai_integration_eca\TypedData\EcaSuccessorDefinition; use Drupal\Core\TypedData\ComplexDataInterface; use Drupal\Core\TypedData\Exception\MissingDataException; use Drupal\Core\TypedData\TypedDataManagerInterface; @@ -40,7 +40,7 @@ class ModelMapper implements ModelMapperInterface { */ public function fromPayload(array $payload): EcaModel { $definition = EcaModelDefinition::create(); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaModel $model */ $model = $this->typedDataManager->create($definition, $payload); /** @var \Symfony\Component\Validator\ConstraintViolationList $violations */ @@ -57,7 +57,7 @@ class ModelMapper implements ModelMapperInterface { */ public function fromEntity(Eca $entity): EcaModel { $modelDef = EcaModelDefinition::create(); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaModel $model */ $model = $this->typedDataManager->create($modelDef); // Basic properties. @@ -73,7 +73,7 @@ class ModelMapper implements ModelMapperInterface { $def = EcaPluginDefinition::create(); $def->setSetting('data_type', EcaElementType::Event); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $model */ $model = $this->typedDataManager->create($def); $model->set('element_id', $event->getId()); $model->set('plugin_id', $event->getPlugin()->getPluginId()); @@ -92,7 +92,7 @@ class ModelMapper implements ModelMapperInterface { $plugins = array_reduce(array_keys($conditions), function (array $carry, string $conditionId) use ($conditions) { $def = EcaPluginDefinition::create(); $def->setSetting('data_type', EcaElementType::Condition); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $model */ $model = $this->typedDataManager->create($def); $model->set('element_id', $conditionId); $model->set('plugin_id', $conditions[$conditionId]['plugin']); @@ -111,7 +111,7 @@ class ModelMapper implements ModelMapperInterface { $def = EcaPluginDefinition::create(); $def->setSetting('data_type', EcaElementType::Action); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $model */ $model = $this->typedDataManager->create($def); $model->set('element_id', $actionId); $model->set('plugin_id', $actions[$actionId]['plugin']); @@ -130,7 +130,7 @@ class ModelMapper implements ModelMapperInterface { $plugins = array_reduce(array_keys($gateways), function (array $carry, string $gatewayId) use ($gateways) { $successors = $this->mapSuccessorsToModel($gateways[$gatewayId]['successors']); - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaPlugin $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaPlugin $model */ $model = $this->typedDataManager->create(EcaGatewayDefinition::create()); $model->set('gateway_id', $gatewayId); $model->set('type', $gateways[$gatewayId]['type']); @@ -186,7 +186,7 @@ class ModelMapper implements ModelMapperInterface { */ protected function mapSuccessorsToModel(array $successors): array { return array_filter(array_reduce($successors, function ($carry, array $successor) { - /** @var \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaSuccessor $model */ + /** @var \Drupal\ai_integration_eca\Plugin\DataType\EcaSuccessor $model */ $model = $this->typedDataManager->create(EcaSuccessorDefinition::create()); $model->set('element_id', $successor['id']); $model->set('condition', $successor['condition']); diff --git a/modules/agents/src/Services/ModelMapper/ModelMapperInterface.php b/src/Services/ModelMapper/ModelMapperInterface.php similarity index 68% rename from modules/agents/src/Services/ModelMapper/ModelMapperInterface.php rename to src/Services/ModelMapper/ModelMapperInterface.php index ac67052ddf1137863dedb044daa2266173d47597..aad07e37c313ea22d42050120f22836d8ed310a5 100644 --- a/modules/agents/src/Services/ModelMapper/ModelMapperInterface.php +++ b/src/Services/ModelMapper/ModelMapperInterface.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\Services\ModelMapper; +namespace Drupal\ai_integration_eca\Services\ModelMapper; -use Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel; +use Drupal\ai_integration_eca\Plugin\DataType\EcaModel; use Drupal\eca\Entity\Eca; /** @@ -16,7 +16,7 @@ interface ModelMapperInterface { * @param array $payload * The external payload. * - * @return \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel + * @return \Drupal\ai_integration_eca\Plugin\DataType\EcaModel * Returns the ECA Model typed data. */ public function fromPayload(array $payload): EcaModel; @@ -27,7 +27,7 @@ interface ModelMapperInterface { * @param \Drupal\eca\Entity\Eca $entity * The ECA entity. * - * @return \Drupal\ai_integration_eca_agents\Plugin\DataType\EcaModel + * @return \Drupal\ai_integration_eca\Plugin\DataType\EcaModel * Returns the ECA Model typed data. */ public function fromEntity(Eca $entity): EcaModel; diff --git a/modules/agents/src/TypedData/EcaGatewayDefinition.php b/src/TypedData/EcaGatewayDefinition.php similarity index 96% rename from modules/agents/src/TypedData/EcaGatewayDefinition.php rename to src/TypedData/EcaGatewayDefinition.php index d7ef72ac7d03020fa3f108b5d754b268cdbb5d0c..119fd79db3c8e1103d5e5ac30e877f9a3ef1682c 100644 --- a/modules/agents/src/TypedData/EcaGatewayDefinition.php +++ b/src/TypedData/EcaGatewayDefinition.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\TypedData; +namespace Drupal\ai_integration_eca\TypedData; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\ComplexDataDefinitionBase; diff --git a/modules/agents/src/TypedData/EcaModelDefinition.php b/src/TypedData/EcaModelDefinition.php similarity index 96% rename from modules/agents/src/TypedData/EcaModelDefinition.php rename to src/TypedData/EcaModelDefinition.php index 3bb7c35ab843e09b5304c898ce56b398d37ca669..9eb2527b8ad2175c08ac1e67ff813a82b99a449b 100644 --- a/modules/agents/src/TypedData/EcaModelDefinition.php +++ b/src/TypedData/EcaModelDefinition.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\TypedData; +namespace Drupal\ai_integration_eca\TypedData; -use Drupal\ai_integration_eca_agents\EcaElementType; +use Drupal\ai_integration_eca\EcaElementType; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\ComplexDataDefinitionBase; use Drupal\Core\TypedData\DataDefinition; diff --git a/modules/agents/src/TypedData/EcaPluginDefinition.php b/src/TypedData/EcaPluginDefinition.php similarity index 97% rename from modules/agents/src/TypedData/EcaPluginDefinition.php rename to src/TypedData/EcaPluginDefinition.php index fabe07b49a4639dbcbeb978c69cb7335ce7bbb8f..7b0d623683e4591324bc47c89699597af943e296 100644 --- a/modules/agents/src/TypedData/EcaPluginDefinition.php +++ b/src/TypedData/EcaPluginDefinition.php @@ -1,8 +1,8 @@ <?php -namespace Drupal\ai_integration_eca_agents\TypedData; +namespace Drupal\ai_integration_eca\TypedData; -use Drupal\ai_integration_eca_agents\EcaElementType; +use Drupal\ai_integration_eca\EcaElementType; use Drupal\Core\Action\ActionInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\ComplexDataDefinitionBase; diff --git a/modules/agents/src/TypedData/EcaSuccessorDefinition.php b/src/TypedData/EcaSuccessorDefinition.php similarity index 95% rename from modules/agents/src/TypedData/EcaSuccessorDefinition.php rename to src/TypedData/EcaSuccessorDefinition.php index c056813f855b3a3b38766465f92b24c26a128aa4..13541294c931ad4d40f59ce778536a2aafecfdb7 100644 --- a/modules/agents/src/TypedData/EcaSuccessorDefinition.php +++ b/src/TypedData/EcaSuccessorDefinition.php @@ -1,6 +1,6 @@ <?php -namespace Drupal\ai_integration_eca_agents\TypedData; +namespace Drupal\ai_integration_eca\TypedData; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\TypedData\ComplexDataDefinitionBase; diff --git a/modules/agents/tests/assets/from_payload_0.json b/tests/assets/from_payload_0.json similarity index 100% rename from modules/agents/tests/assets/from_payload_0.json rename to tests/assets/from_payload_0.json diff --git a/modules/agents/tests/assets/from_payload_1.json b/tests/assets/from_payload_1.json similarity index 100% rename from modules/agents/tests/assets/from_payload_1.json rename to tests/assets/from_payload_1.json diff --git a/modules/agents/tests/assets/from_payload_2.json b/tests/assets/from_payload_2.json similarity index 100% rename from modules/agents/tests/assets/from_payload_2.json rename to tests/assets/from_payload_2.json diff --git a/modules/agents/tests/assets/from_payload_3.json b/tests/assets/from_payload_3.json similarity index 100% rename from modules/agents/tests/assets/from_payload_3.json rename to tests/assets/from_payload_3.json diff --git a/modules/agents/tests/assets/from_payload_4.json b/tests/assets/from_payload_4.json similarity index 100% rename from modules/agents/tests/assets/from_payload_4.json rename to tests/assets/from_payload_4.json diff --git a/tests/src/Kernel/Plugin/Action/AiActionTestBase.php b/tests/src/Kernel/Plugin/Action/AiActionTestBase.php index 2f137f32b5d74cda256e24dba851a0334ee83fd2..eb7f4739e5bc14a16eac3a941bf8bd055dcc531f 100644 --- a/tests/src/Kernel/Plugin/Action/AiActionTestBase.php +++ b/tests/src/Kernel/Plugin/Action/AiActionTestBase.php @@ -21,7 +21,11 @@ abstract class AiActionTestBase extends KernelTestBase { 'eca', 'file', 'key', + 'schemata', + 'schemata_json_schema', + 'serialization', 'system', + 'token', 'user', ]; diff --git a/modules/agents/tests/src/Kernel/AiEcaAgentsKernelTestBase.php b/tests/src/Kernel/Services/AiIntegrationEcaServicesKernelTestBase.php similarity index 74% rename from modules/agents/tests/src/Kernel/AiEcaAgentsKernelTestBase.php rename to tests/src/Kernel/Services/AiIntegrationEcaServicesKernelTestBase.php index fd75d73c1491c72366fd9293a5ddd49770a03687..d0869b0d0995ce77813a90f482f92db895f46011 100644 --- a/modules/agents/tests/src/Kernel/AiEcaAgentsKernelTestBase.php +++ b/tests/src/Kernel/Services/AiIntegrationEcaServicesKernelTestBase.php @@ -1,22 +1,21 @@ <?php -namespace Drupal\Tests\ai_integration_eca_agents\Kernel; +namespace Drupal\Tests\ai_integration_eca\Kernel\Services; use Drupal\Component\Serialization\Json; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\NodeType; /** - * Base class for Kernel-tests regarding AI ECA Agents. + * Base class for Kernel-tests regarding AI Integration - ECA. */ -abstract class AiEcaAgentsKernelTestBase extends KernelTestBase { +abstract class AiIntegrationEcaServicesKernelTestBase extends KernelTestBase { /** * {@inheritdoc} */ protected static $modules = [ 'ai_integration_eca', - 'ai_integration_eca_agents', 'eca', 'eca_base', 'eca_content', @@ -41,7 +40,7 @@ abstract class AiEcaAgentsKernelTestBase extends KernelTestBase { */ public static function payloadProvider(): \Generator { yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_0.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_0.json', __DIR__))), [ 'events' => 1, 'conditions' => 0, @@ -51,21 +50,21 @@ abstract class AiEcaAgentsKernelTestBase extends KernelTestBase { ]; yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_1.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_1.json', __DIR__))), [], NULL, 'model_id: This value should not be null', ]; yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_2.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_2.json', __DIR__))), [], NULL, 'events: This value should not be null', ]; yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_3.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_3.json', __DIR__))), [ 'events' => 1, 'conditions' => 1, @@ -76,7 +75,7 @@ abstract class AiEcaAgentsKernelTestBase extends KernelTestBase { ]; yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_3.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_3.json', __DIR__))), [ 'events' => 1, 'conditions' => 1, @@ -89,7 +88,7 @@ abstract class AiEcaAgentsKernelTestBase extends KernelTestBase { ]; yield [ - Json::decode(file_get_contents(sprintf('%s/../../assets/from_payload_4.json', __DIR__))), + Json::decode(file_get_contents(sprintf('%s/../../../assets/from_payload_4.json', __DIR__))), [], NULL, "Invalid successor ID 'condition_check_title' for Event 'event_page_published'. Must reference a gateway or action.", diff --git a/tests/src/Kernel/Service/AiProviderValidatorTest.php b/tests/src/Kernel/Services/AiProviderValidatorTest.php similarity index 88% rename from tests/src/Kernel/Service/AiProviderValidatorTest.php rename to tests/src/Kernel/Services/AiProviderValidatorTest.php index 94a23576568c43dabf798ac4adc0b3e76f053dd1..ae30ff3dc71cbe0a4902f1ff9f1eaf9de48494bb 100644 --- a/tests/src/Kernel/Service/AiProviderValidatorTest.php +++ b/tests/src/Kernel/Services/AiProviderValidatorTest.php @@ -1,11 +1,11 @@ <?php -namespace Drupal\Tests\ai_integration_eca\Kernel\Service; +namespace Drupal\Tests\ai_integration_eca\Kernel\Services; use Drupal\KernelTests\KernelTestBase; use Drupal\TestTools\Random; use Drupal\ai\AiProviderPluginManager; -use Drupal\ai_integration_eca\Service\AiProviderValidatorInterface; +use Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidatorInterface; use Symfony\Component\Validator\Constraints\Required; use Symfony\Component\Validator\Constraints\Type; @@ -21,10 +21,15 @@ class AiProviderValidatorTest extends KernelTestBase { */ protected static $modules = [ 'ai', - 'ai_test', 'ai_integration_eca', + 'ai_test', + 'eca', 'key', + 'schemata', + 'schemata_json_schema', + 'serialization', 'system', + 'token', 'user', ]; @@ -38,7 +43,7 @@ class AiProviderValidatorTest extends KernelTestBase { /** * The AI Provider validator. * - * @var \Drupal\ai\Service\AiProviderValidator\AiProviderValidatorInterface|null + * @var \Drupal\ai_integration_eca\Services\AiProviderValidator\AiProviderValidatorInterface|null */ protected ?AiProviderValidatorInterface $validator; @@ -51,8 +56,8 @@ class AiProviderValidatorTest extends KernelTestBase { $this->installEntitySchema('user'); $this->installConfig(static::$modules); - $this->aiProvider = \Drupal::service('ai.provider'); - $this->validator = \Drupal::service('ai_integration_eca.provider_validator'); + $this->aiProvider = $this->container->get('ai.provider'); + $this->validator = $this->container->get('ai_integration_eca.services.provider_validator'); } /** diff --git a/modules/agents/tests/src/Kernel/EcaModelSchemaTest.php b/tests/src/Kernel/Services/EcaModelSchemaTest.php similarity index 83% rename from modules/agents/tests/src/Kernel/EcaModelSchemaTest.php rename to tests/src/Kernel/Services/EcaModelSchemaTest.php index 64c8212f1805b11c9e1ec1b3e9ae39df693cb92d..8cc74fb704bfdd1d544fe728e56b29bdb2253875 100644 --- a/modules/agents/tests/src/Kernel/EcaModelSchemaTest.php +++ b/tests/src/Kernel/Services/EcaModelSchemaTest.php @@ -1,17 +1,17 @@ <?php -namespace Drupal\Tests\ai_integration_eca_agents\Kernel; +namespace Drupal\Tests\ai_integration_eca\Kernel\Services; -use Drupal\ai_integration_eca_agents\Schema\Eca as EcaSchema; -use Drupal\ai_integration_eca_agents\TypedData\EcaModelDefinition; use Drupal\KernelTests\KernelTestBase; +use Drupal\ai_integration_eca\Schema\Eca as EcaSchema; +use Drupal\ai_integration_eca\TypedData\EcaModelDefinition; use Spatie\Snapshots\MatchesSnapshots; use Symfony\Component\Serializer\SerializerInterface; /** * Kernel test for the ECA Model data type. * - * @group ai_integration_eca_agents + * @group ai_integration_eca */ class EcaModelSchemaTest extends KernelTestBase { @@ -22,7 +22,6 @@ class EcaModelSchemaTest extends KernelTestBase { */ protected static $modules = [ 'ai_integration_eca', - 'ai_integration_eca_agents', 'eca', 'serialization', 'schemata', diff --git a/modules/agents/tests/src/Kernel/EcaRepositoryTest.php b/tests/src/Kernel/Services/EcaRepositoryTest.php similarity index 69% rename from modules/agents/tests/src/Kernel/EcaRepositoryTest.php rename to tests/src/Kernel/Services/EcaRepositoryTest.php index 0b01899ebe52b2be0f9d2142e6e45cc34af56c14..bb1dcd7e93d2e0cc3c82965927150505c510950e 100644 --- a/modules/agents/tests/src/Kernel/EcaRepositoryTest.php +++ b/tests/src/Kernel/Services/EcaRepositoryTest.php @@ -1,20 +1,20 @@ <?php -namespace Drupal\Tests\ai_integration_eca_agents\Kernel; +namespace Drupal\Tests\ai_integration_eca\Kernel\Services; -use Drupal\ai_integration_eca_agents\Services\EcaRepository\EcaRepositoryInterface; +use Drupal\ai_integration_eca\Services\EcaRepository\EcaRepositoryInterface; /** * Tests various input data for generating ECA models. * - * @group ai_integration_eca_agents + * @group ai_integration_eca */ -class EcaRepositoryTest extends AiEcaAgentsKernelTestBase { +class EcaRepositoryTest extends AiIntegrationEcaServicesKernelTestBase { /** * The ECA repository. * - * @var \Drupal\ai_integration_eca_agents\Services\EcaRepository\EcaRepositoryInterface|null + * @var \Drupal\ai_integration_eca\Services\EcaRepository\EcaRepositoryInterface|null */ protected ?EcaRepositoryInterface $ecaRepository; @@ -49,7 +49,7 @@ class EcaRepositoryTest extends AiEcaAgentsKernelTestBase { protected function setUp(): void { parent::setUp(); - $this->ecaRepository = \Drupal::service('ai_integration_eca_agents.services.eca_repository'); + $this->ecaRepository = $this->container->get('ai_integration_eca.services.eca_repository'); } } diff --git a/modules/agents/tests/src/Kernel/ModelMapperTest.php b/tests/src/Kernel/Services/ModelMapperTest.php similarity index 83% rename from modules/agents/tests/src/Kernel/ModelMapperTest.php rename to tests/src/Kernel/Services/ModelMapperTest.php index 9e726498dbc29c8b5f96d2bd3afcd84eb56260d6..34c118f4414cd39da8e710e47ccfc42dcbbea579 100644 --- a/modules/agents/tests/src/Kernel/ModelMapperTest.php +++ b/tests/src/Kernel/Services/ModelMapperTest.php @@ -1,18 +1,18 @@ <?php -namespace Drupal\Tests\ai_integration_eca_agents\Kernel; +namespace Drupal\Tests\ai_integration_eca\Kernel\Services; -use Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface; use Spatie\Snapshots\MatchesSnapshots; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; /** * Tests various input data for generation ECA Model typed data. * - * @group ai_integration_eca_agents + * @group ai_integration_eca */ -class ModelMapperTest extends AiEcaAgentsKernelTestBase { +class ModelMapperTest extends AiIntegrationEcaServicesKernelTestBase { use MatchesSnapshots; @@ -21,7 +21,6 @@ class ModelMapperTest extends AiEcaAgentsKernelTestBase { */ protected static $modules = [ 'ai_integration_eca', - 'ai_integration_eca_agents', 'eca', 'eca_base', 'eca_content', @@ -44,7 +43,7 @@ class ModelMapperTest extends AiEcaAgentsKernelTestBase { /** * The model mapper. * - * @var \Drupal\ai_integration_eca_agents\Services\ModelMapper\ModelMapperInterface|null + * @var \Drupal\ai_integration_eca\Services\ModelMapper\ModelMapperInterface|null */ protected ?ModelMapperInterface $modelMapper; @@ -128,9 +127,9 @@ class ModelMapperTest extends AiEcaAgentsKernelTestBase { protected function setUp(): void { parent::setUp(); - $this->modelMapper = \Drupal::service('ai_integration_eca_agents.services.model_mapper'); - $this->entityTypeManager = \Drupal::entityTypeManager(); - $this->normalizer = \Drupal::service('serializer'); + $this->modelMapper = $this->container->get('ai_integration_eca.services.model_mapper'); + $this->entityTypeManager = $this->container->get('entity_type.manager'); + $this->normalizer = $this->container->get('serializer'); } } diff --git a/modules/agents/tests/src/Kernel/__snapshots__/EcaModelSchemaTest__testSchema__1.json b/tests/src/Kernel/Services/__snapshots__/EcaModelSchemaTest__testSchema__1.json similarity index 100% rename from modules/agents/tests/src/Kernel/__snapshots__/EcaModelSchemaTest__testSchema__1.json rename to tests/src/Kernel/Services/__snapshots__/EcaModelSchemaTest__testSchema__1.json diff --git a/modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 0__1.json b/tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 0__1.json similarity index 100% rename from modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 0__1.json rename to tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 0__1.json diff --git a/modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 1__1.json b/tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 1__1.json similarity index 100% rename from modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 1__1.json rename to tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 1__1.json diff --git a/modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 2__1.json b/tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 2__1.json similarity index 100% rename from modules/agents/tests/src/Kernel/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 2__1.json rename to tests/src/Kernel/Services/__snapshots__/ModelMapperTest__testMappingFromEntity with data set 2__1.json