From 3c550b7f479f4bd580a3bd6dba74b8924fb969de Mon Sep 17 00:00:00 2001
From: Jan Kellermann <44717-werk21@users.noreply.drupalcode.org>
Date: Thu, 16 Jan 2025 01:59:59 +0000
Subject: [PATCH 1/9] Invoke ai_operationtype_alter.

---
 src/AiProviderPluginManager.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/AiProviderPluginManager.php b/src/AiProviderPluginManager.php
index ecfbe8661..30651ff9e 100644
--- a/src/AiProviderPluginManager.php
+++ b/src/AiProviderPluginManager.php
@@ -295,6 +295,9 @@ final class AiProviderPluginManager extends DefaultPluginManager {
       }
     }
 
+    // Invoke ai_operationtype_alter.
+    $this->moduleHandler->invokeAll('ai_operationtype_alter', [&$operation_types]);
+
     // Save to cache.
     $this->cacheBackend->set('ai_operation_types', $operation_types);
 
-- 
GitLab


From f466faf20c6c8c1baf7fb225316919174caedaac Mon Sep 17 00:00:00 2001
From: Jan Kellermann <44717-werk21@users.noreply.drupalcode.org>
Date: Thu, 16 Jan 2025 08:55:29 +0000
Subject: [PATCH 2/9] cspell: Add operationtype

---
 .cspell-project-words.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt
index f53924a2d..19daa0249 100644
--- a/.cspell-project-words.txt
+++ b/.cspell-project-words.txt
@@ -124,6 +124,7 @@ octect
 octocat
 oden
 oneshot
+operationtype
 outro
 overriden
 paranthesis
-- 
GitLab


From 73d861ed2b420fc3ded6e3adeb79180d638066de Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Tue, 21 Jan 2025 11:08:35 +0100
Subject: [PATCH 3/9] Issue #3500120: Add test for hook_ai_operationtype_alter

---
 .../ai_test/src/Hook/OperationTypeHook.php    | 23 +++++
 .../src/OperationType/Echo/EchoInput.php      | 37 ++++++++
 .../src/OperationType/Echo/EchoInterface.php  | 33 +++++++
 .../src/OperationType/Echo/EchoOutput.php     | 90 +++++++++++++++++++
 .../src/Plugin/AiProvider/EchoProvider.php    | 16 +++-
 .../Custom/EchoOperationHookTest.php          | 47 ++++++++++
 6 files changed, 245 insertions(+), 1 deletion(-)
 create mode 100644 tests/modules/ai_test/src/Hook/OperationTypeHook.php
 create mode 100644 tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
 create mode 100644 tests/modules/ai_test/src/OperationType/Echo/EchoInterface.php
 create mode 100644 tests/modules/ai_test/src/OperationType/Echo/EchoOutput.php
 create mode 100644 tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php

diff --git a/tests/modules/ai_test/src/Hook/OperationTypeHook.php b/tests/modules/ai_test/src/Hook/OperationTypeHook.php
new file mode 100644
index 000000000..3561e7ff2
--- /dev/null
+++ b/tests/modules/ai_test/src/Hook/OperationTypeHook.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\ai_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hooks to interact with operation types.
+ */
+class OperationTypeHook {
+
+  /**
+   * Implements hook_ai_operationtype_alter().
+   */
+  #[Hook('ai_operationtype_alter')]
+  public function echoOperationType(array &$operation_types) {
+    $operation_types[] = [
+      'id' => 'echo',
+      'label' => 'Echo',
+    ];
+  }
+
+}
diff --git a/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php b/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
new file mode 100644
index 000000000..c38cfb8da
--- /dev/null
+++ b/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\ai_test\OperationType\Echo;
+
+use Drupal\ai\OperationType\InputInterface;
+
+/**
+ * Input object for echo operations.
+ */
+class EchoInput implements InputInterface {
+
+  /**
+   * The constructor.
+   *
+   * @param string $input
+   *   The echo input.
+   */
+  public function __construct(private string $input) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toString(): string {
+    return $this->input;
+  }
+
+  /**
+   * Return the input as string.
+   *
+   * @return string
+   *   The input as string.
+   */
+  public function __toString(): string {
+    return $this->toString();
+  }
+
+}
diff --git a/tests/modules/ai_test/src/OperationType/Echo/EchoInterface.php b/tests/modules/ai_test/src/OperationType/Echo/EchoInterface.php
new file mode 100644
index 000000000..e86638a33
--- /dev/null
+++ b/tests/modules/ai_test/src/OperationType/Echo/EchoInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\ai_test\OperationType\Echo;
+
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\ai\Attribute\OperationType;
+use Drupal\ai\OperationType\OperationTypeInterface;
+
+/**
+ * Interface for text translation models.
+ */
+#[OperationType(
+  id: 'echo',
+  label: new TranslatableMarkup('Echo'),
+)]
+interface EchoInterface extends OperationTypeInterface {
+
+  /**
+   * Translate the text.
+   *
+   * @param \Drupal\ai_test\OperationType\Echo\EchoInput|string $input
+   *   The input to echo.
+   * @param string $model_id
+   *   The model id to use.
+   * @param array $options
+   *   Extra tags to set.
+   *
+   * @return \Drupal\ai_test\OperationType\Echo\EchoOutput
+   *   The translation output.
+   */
+  public function echo(string|EchoInput $input, string $model_id, array $options = []): EchoOutput;
+
+}
diff --git a/tests/modules/ai_test/src/OperationType/Echo/EchoOutput.php b/tests/modules/ai_test/src/OperationType/Echo/EchoOutput.php
new file mode 100644
index 000000000..0d8efcb9b
--- /dev/null
+++ b/tests/modules/ai_test/src/OperationType/Echo/EchoOutput.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\ai_test\OperationType\Echo;
+
+use Drupal\ai\OperationType\OutputInterface;
+
+/**
+ * Data transfer output object text translation output.
+ */
+class EchoOutput implements OutputInterface {
+
+  /**
+   * The normalized translated text.
+   *
+   * @var string
+   */
+  private string $normalized;
+
+  /**
+   * The raw output from the AI provider.
+   *
+   * @var mixed
+   */
+  private mixed $rawOutput;
+
+  /**
+   * The metadata from the AI provider.
+   *
+   * @var mixed
+   */
+  private mixed $metadata;
+
+  /**
+   * The constructor.
+   *
+   * @param string $normalized
+   *   The metadata.
+   * @param mixed $rawOutput
+   *   The raw output.
+   * @param mixed $metadata
+   *   The metadata.
+   */
+  public function __construct(string $normalized, mixed $rawOutput, mixed $metadata) {
+    $this->normalized = $normalized;
+    $this->rawOutput = $rawOutput;
+    $this->metadata = $metadata;
+  }
+
+  /**
+   * Returns the translated text.
+   *
+   * @return string
+   *   The text.
+   */
+  public function getNormalized(): string {
+    return $this->normalized;
+  }
+
+  /**
+   * Gets the raw output from the AI provider.
+   *
+   * @return mixed
+   *   The raw output.
+   */
+  public function getRawOutput(): mixed {
+    return $this->rawOutput;
+  }
+
+  /**
+   * Gets the metadata from the AI provider.
+   *
+   * @return mixed
+   *   The metadata.
+   */
+  public function getMetadata(): mixed {
+    return $this->metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function toArray(): array {
+    return [
+      'normalized' => $this->normalized,
+      'rawOutput' => $this->rawOutput,
+      'metadata' => $this->metadata,
+    ];
+  }
+
+}
diff --git a/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php b/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
index 4427958df..0cde14848 100644
--- a/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
+++ b/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
@@ -34,6 +34,9 @@ use Drupal\ai\OperationType\TextToImage\TextToImageOutput;
 use Drupal\ai\OperationType\TextToSpeech\TextToSpeechInput;
 use Drupal\ai\OperationType\TextToSpeech\TextToSpeechInterface;
 use Drupal\ai\OperationType\TextToSpeech\TextToSpeechOutput;
+use Drupal\ai_test\OperationType\Echo\EchoInput;
+use Drupal\ai_test\OperationType\Echo\EchoInterface;
+use Drupal\ai_test\OperationType\Echo\EchoOutput;
 use Symfony\Component\Yaml\Yaml;
 
 /**
@@ -50,7 +53,8 @@ class EchoProvider extends AiProviderClientBase implements
   SpeechToTextInterface,
   TextToSpeechInterface,
   ImageClassificationInterface,
-  TextToImageInterface {
+  TextToImageInterface,
+  EchoInterface {
 
   /**
    * {@inheritdoc}
@@ -230,4 +234,14 @@ class EchoProvider extends AiProviderClientBase implements
     return new ImageClassificationOutput($output, $response, []);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function echo(string|EchoInput $input, string $model_id, array $options = []): EchoOutput {
+    if (!$input instanceof EchoInput) {
+      $input = new EchoInput($input);
+    }
+    return new EchoOutput((string) $input, ['echo' => (string) $input], []);
+  }
+
 }
diff --git a/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php b/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php
new file mode 100644
index 000000000..47773fb6f
--- /dev/null
+++ b/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\ai\Kernel\OperationType\Custom;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * This tests the echo calling.
+ *
+ * @coversDefaultClass \Drupal\ai_test\OperationType\Echo\EchoInterface
+ *
+ * @group ai
+ */
+class EchoOperationHookTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = [
+    'ai',
+    'ai_test',
+    'key',
+    'file',
+    'system',
+  ];
+
+  /**
+   * Test adding a provider via a hook.
+   */
+  public function testCustomEchoOperation(): void {
+    /** @var \Drupal\ai\AiProviderPluginManager $manager */
+    $manager = \Drupal::service('ai.provider');
+    self::assertContains(['id' => 'echo', 'label' => 'Echo'], $manager->getOperationTypes());
+    /** @var \Drupal\ai_test\Plugin\AiProvider\EchoProvider $provider */
+    $provider = $manager->createInstance('echoai');
+
+    $input = $this->randomString();
+    $output = $provider->echo($input, 'test');
+    self::assertEquals($input, $output->getNormalized());
+    self::assertEquals(['echo' => $input], $output->getRawOutput());
+  }
+
+}
-- 
GitLab


From c4906b0695d35baaecc0dcab7c8f1b9ac828c2fa Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Tue, 21 Jan 2025 11:25:11 +0100
Subject: [PATCH 4/9] add echo as supported operation type

---
 tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php b/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
index 0cde14848..cf0520dff 100644
--- a/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
+++ b/tests/modules/ai_test/src/Plugin/AiProvider/EchoProvider.php
@@ -106,6 +106,7 @@ class EchoProvider extends AiProviderClientBase implements
       'text_to_speech',
       'moderation',
       'image_classification',
+      'echo',
     ];
   }
 
-- 
GitLab


From 9be17526f0a7bfd61e24073d97592a9788931593 Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Tue, 28 Jan 2025 14:06:20 +0100
Subject: [PATCH 5/9] Issue #3500120: Make operation types plugins without a
 plugin manager

---
 src/AiProviderPluginManager.php               | 43 ++++++-------------
 .../ai_test/src/Hook/OperationTypeHook.php    |  5 +--
 .../Custom/EchoOperationHookTest.php          |  4 +-
 3 files changed, 17 insertions(+), 35 deletions(-)

diff --git a/src/AiProviderPluginManager.php b/src/AiProviderPluginManager.php
index 30651ff9e..5a4b4ec8c 100644
--- a/src/AiProviderPluginManager.php
+++ b/src/AiProviderPluginManager.php
@@ -270,38 +270,21 @@ final class AiProviderPluginManager extends DefaultPluginManager {
    *   The list of operation types.
    */
   public function getOperationTypes(): array {
-    // Load from cache.
-    $data = $this->cacheBackend->get('ai_operation_types');
-    if (!empty($data->data)) {
-      return $data->data;
-    }
-    // Look in the OperationType/** directories.
-    $operation_types = [];
-    $base_path = $this->moduleHandler->getModule('ai')->getPath() . '/src/OperationType';
-    $directories = new \RecursiveDirectoryIterator($base_path);
-    $iterator = new \RecursiveIteratorIterator($directories);
-    $regex = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH);
-    foreach ($regex as $file) {
-      $interface = $this->getInterfaceFromFile($file[0]);
-      if ($interface && $this->doesInterfaceExtend($interface, OperationTypeInterface::class)) {
-        $reflection = new \ReflectionClass($interface);
-        $attributes = $reflection->getAttributes(OperationType::class);
-        foreach ($attributes as $attribute) {
-          $operation_types[] = [
-            'id' => $attribute->newInstance()->id,
-            'label' => $attribute->newInstance()->label->render(),
-          ];
-        }
+    $manager = new class(
+      'OperationType',
+      $this->namespaces,
+      $this->moduleHandler,
+      OperationTypeInterface::class,
+      OperationType::class
+    ) extends DefaultPluginManager {
+      public function finishSetup(): void {
+        $this->alterInfo('ai_operationtype');
       }
-    }
-
-    // Invoke ai_operationtype_alter.
-    $this->moduleHandler->invokeAll('ai_operationtype_alter', [&$operation_types]);
-
-    // Save to cache.
-    $this->cacheBackend->set('ai_operation_types', $operation_types);
+    };
+    $manager->finishSetup();
+    $manager->setCacheBackend($this->cacheBackend, 'ai_operation_types');
 
-    return $operation_types;
+    return $manager->getDefinitions();
   }
 
   /**
diff --git a/tests/modules/ai_test/src/Hook/OperationTypeHook.php b/tests/modules/ai_test/src/Hook/OperationTypeHook.php
index 3561e7ff2..2e5bbca8e 100644
--- a/tests/modules/ai_test/src/Hook/OperationTypeHook.php
+++ b/tests/modules/ai_test/src/Hook/OperationTypeHook.php
@@ -14,10 +14,7 @@ class OperationTypeHook {
    */
   #[Hook('ai_operationtype_alter')]
   public function echoOperationType(array &$operation_types) {
-    $operation_types[] = [
-      'id' => 'echo',
-      'label' => 'Echo',
-    ];
+    $operation_types['echo']['label'] = 'Echo altered';
   }
 
 }
diff --git a/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php b/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php
index 47773fb6f..69622d521 100644
--- a/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php
+++ b/tests/src/Kernel/OperationType/Custom/EchoOperationHookTest.php
@@ -34,7 +34,9 @@ class EchoOperationHookTest extends KernelTestBase {
   public function testCustomEchoOperation(): void {
     /** @var \Drupal\ai\AiProviderPluginManager $manager */
     $manager = \Drupal::service('ai.provider');
-    self::assertContains(['id' => 'echo', 'label' => 'Echo'], $manager->getOperationTypes());
+    $types = $manager->getOperationTypes();
+    self::assertContains('echo', array_keys($types));
+    self::assertEquals('Echo altered', $types['echo']['label']);
     /** @var \Drupal\ai_test\Plugin\AiProvider\EchoProvider $provider */
     $provider = $manager->createInstance('echoai');
 
-- 
GitLab


From 5f8b00611d728b24aecc7b9993168923807aa47e Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Tue, 28 Jan 2025 14:35:33 +0100
Subject: [PATCH 6/9] Issue #3500120: Add comments to operation types plugin
 manager

---
 src/AiProviderPluginManager.php | 65 +++++++--------------------------
 1 file changed, 13 insertions(+), 52 deletions(-)

diff --git a/src/AiProviderPluginManager.php b/src/AiProviderPluginManager.php
index 5a4b4ec8c..917d0c899 100644
--- a/src/AiProviderPluginManager.php
+++ b/src/AiProviderPluginManager.php
@@ -270,6 +270,13 @@ final class AiProviderPluginManager extends DefaultPluginManager {
    *   The list of operation types.
    */
   public function getOperationTypes(): array {
+    // Load from cache.
+    $data = $this->cacheBackend->get('ai_operation_types');
+    if (!empty($data->data)) {
+      return $data->data;
+    }
+
+    // We use a plugin manager only to discover the possible operation types.
     $manager = new class(
       'OperationType',
       $this->namespaces,
@@ -277,11 +284,17 @@ final class AiProviderPluginManager extends DefaultPluginManager {
       OperationTypeInterface::class,
       OperationType::class
     ) extends DefaultPluginManager {
+
+      /**
+       * Call protected methods usually called in the constructor.
+       */
       public function finishSetup(): void {
         $this->alterInfo('ai_operationtype');
       }
+
     };
     $manager->finishSetup();
+    // The in situ plugin manager uses the same cache backend.
     $manager->setCacheBackend($this->cacheBackend, 'ai_operation_types');
 
     return $manager->getDefinitions();
@@ -355,58 +368,6 @@ final class AiProviderPluginManager extends DefaultPluginManager {
     return TRUE;
   }
 
-  /**
-   * Extracts the fully qualified interface name from a file.
-   *
-   * @param string $file
-   *   The file path.
-   *
-   * @return string|null
-   *   The fully qualified interface name, or NULL if not found.
-   */
-  protected function getInterfaceFromFile($file) {
-    $contents = file_get_contents($file);
-
-    // Match namespace and interface declarations.
-    if (preg_match('/namespace\s+([^;]+);/i', $contents, $matches)) {
-      $namespace = $matches[1];
-    }
-
-    // Match on starts with interface and has extends in it.
-    if (preg_match('/interface\s+([^ ]+)\s+extends\s+([^ ]+)/i', $contents, $matches) && isset($namespace)) {
-      $interface = $matches[1];
-      return $namespace . '\\' . $interface;
-    }
-
-    return NULL;
-  }
-
-  /**
-   * Checks if an interface extends another interface.
-   *
-   * @param string $interface
-   *   The interface name.
-   * @param string $baseInterface
-   *   The base interface name.
-   *
-   * @return bool
-   *   TRUE if the interface extends the base interface, FALSE otherwise.
-   */
-  protected function doesInterfaceExtend($interface, $baseInterface) {
-    try {
-      $reflection = new \ReflectionClass($interface);
-
-      if ($reflection->isInterface() && in_array($baseInterface, $reflection->getInterfaceNames())) {
-        return TRUE;
-      }
-    }
-    catch (\ReflectionException $e) {
-      // Ignore.
-    }
-
-    return FALSE;
-  }
-
   /**
    * Gives notice that a provider is disabled.
    *
-- 
GitLab


From 1fa1d85b7bd233b25b18f1b5085ac55848333f03 Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Thu, 30 Jan 2025 13:12:52 +0100
Subject: [PATCH 7/9] Add support for Drupal versions with LegacyHook

---
 tests/modules/ai_test/ai_test.module       | 14 ++++++++++++++
 tests/modules/ai_test/ai_test.services.yml |  4 ++++
 2 files changed, 18 insertions(+)
 create mode 100644 tests/modules/ai_test/ai_test.module
 create mode 100644 tests/modules/ai_test/ai_test.services.yml

diff --git a/tests/modules/ai_test/ai_test.module b/tests/modules/ai_test/ai_test.module
new file mode 100644
index 000000000..d9db21e57
--- /dev/null
+++ b/tests/modules/ai_test/ai_test.module
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * AI test module file.
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\ai_test\Hook\OperationTypeHook;
+
+#[LegacyHook]
+function ai_test_ai_operationtype_alter(array &$operation_types) {
+  \Drupal::service(OperationTypeHook::class)->echoOperationType($operation_types);
+}
diff --git a/tests/modules/ai_test/ai_test.services.yml b/tests/modules/ai_test/ai_test.services.yml
new file mode 100644
index 000000000..d6fa111b4
--- /dev/null
+++ b/tests/modules/ai_test/ai_test.services.yml
@@ -0,0 +1,4 @@
+services:
+  Drupal\ai_test\Hook\OperationTypeHook:
+    class: Drupal\ai_test\Hook\OperationTypeHook
+    autowire: true
-- 
GitLab


From 72f1ae495659adbeb1597b62f35550699b0c6938 Mon Sep 17 00:00:00 2001
From: Fabian Bircher <5370-bircher@users.noreply.drupalcode.org>
Date: Thu, 30 Jan 2025 14:27:05 +0100
Subject: [PATCH 8/9] fix phpcs

---
 tests/modules/ai_test/ai_test.module | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/tests/modules/ai_test/ai_test.module b/tests/modules/ai_test/ai_test.module
index d9db21e57..55ab09338 100644
--- a/tests/modules/ai_test/ai_test.module
+++ b/tests/modules/ai_test/ai_test.module
@@ -8,6 +8,9 @@
 use Drupal\Core\Hook\Attribute\LegacyHook;
 use Drupal\ai_test\Hook\OperationTypeHook;
 
+/**
+ * Implements hook_ai_operationtype_alter().
+ */
 #[LegacyHook]
 function ai_test_ai_operationtype_alter(array &$operation_types) {
   \Drupal::service(OperationTypeHook::class)->echoOperationType($operation_types);
-- 
GitLab


From e15ed5c0132f32962bdda528d6e8a3e83426e207 Mon Sep 17 00:00:00 2001
From: Marcus Johansson <me@marcusmailbox.com>
Date: Tue, 11 Mar 2025 20:56:19 +0100
Subject: [PATCH 9/9] Updated the echo input

---
 .../src/OperationType/Echo/EchoInput.php      | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php b/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
index c38cfb8da..26de696dd 100644
--- a/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
+++ b/tests/modules/ai_test/src/OperationType/Echo/EchoInput.php
@@ -24,6 +24,27 @@ class EchoInput implements InputInterface {
     return $this->input;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDebugData(): array {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDebugData(array $debugData): void {
+    // Do nothing.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDebugDataValue(string $key, $value): void {
+    // Do nothing.
+  }
+
   /**
    * Return the input as string.
    *
-- 
GitLab