[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/php-ai-client/src/Messages/DTO/ -> MessagePart.php (source)

   1  <?php
   2  
   3  declare (strict_types=1);
   4  namespace WordPress\AiClient\Messages\DTO;
   5  
   6  use WordPress\AiClient\Common\AbstractDataTransferObject;
   7  use WordPress\AiClient\Common\Exception\InvalidArgumentException;
   8  use WordPress\AiClient\Common\Exception\RuntimeException;
   9  use WordPress\AiClient\Files\DTO\File;
  10  use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum;
  11  use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum;
  12  use WordPress\AiClient\Tools\DTO\FunctionCall;
  13  use WordPress\AiClient\Tools\DTO\FunctionResponse;
  14  /**
  15   * Represents a part of a message.
  16   *
  17   * Messages can contain multiple parts of different types, such as text, files,
  18   * function calls, etc. This DTO encapsulates one such part.
  19   *
  20   * @since 0.1.0
  21   *
  22   * @phpstan-import-type FileArrayShape from File
  23   * @phpstan-import-type FunctionCallArrayShape from FunctionCall
  24   * @phpstan-import-type FunctionResponseArrayShape from FunctionResponse
  25   *
  26   * @phpstan-type MessagePartArrayShape array{
  27   *     channel: string,
  28   *     type: string,
  29   *     thoughtSignature?: string,
  30   *     text?: string,
  31   *     file?: FileArrayShape,
  32   *     functionCall?: FunctionCallArrayShape,
  33   *     functionResponse?: FunctionResponseArrayShape
  34   * }
  35   *
  36   * @extends AbstractDataTransferObject<MessagePartArrayShape>
  37   */
  38  class MessagePart extends AbstractDataTransferObject
  39  {
  40      public const KEY_CHANNEL = 'channel';
  41      public const KEY_TYPE = 'type';
  42      public const KEY_THOUGHT_SIGNATURE = 'thoughtSignature';
  43      public const KEY_TEXT = 'text';
  44      public const KEY_FILE = 'file';
  45      public const KEY_FUNCTION_CALL = 'functionCall';
  46      public const KEY_FUNCTION_RESPONSE = 'functionResponse';
  47      /**
  48       * @var MessagePartChannelEnum The channel this message part belongs to.
  49       */
  50      private MessagePartChannelEnum $channel;
  51      /**
  52       * @var MessagePartTypeEnum The type of this message part.
  53       */
  54      private MessagePartTypeEnum $type;
  55      /**
  56       * @var string|null Thought signature for extended thinking.
  57       */
  58      private ?string $thoughtSignature = null;
  59      /**
  60       * @var string|null Text content (when type is TEXT).
  61       */
  62      private ?string $text = null;
  63      /**
  64       * @var File|null File data (when type is FILE).
  65       */
  66      private ?File $file = null;
  67      /**
  68       * @var FunctionCall|null Function call request (when type is FUNCTION_CALL).
  69       */
  70      private ?FunctionCall $functionCall = null;
  71      /**
  72       * @var FunctionResponse|null Function response (when type is FUNCTION_RESPONSE).
  73       */
  74      private ?FunctionResponse $functionResponse = null;
  75      /**
  76       * Constructor that accepts various content types and infers the message part type.
  77       *
  78       * @since 0.1.0
  79       *
  80       * @param mixed $content The content of this message part.
  81       * @param MessagePartChannelEnum|null $channel The channel this part belongs to. Defaults to CONTENT.
  82       * @param string|null $thoughtSignature Optional thought signature for extended thinking.
  83       * @throws InvalidArgumentException If an unsupported content type is provided.
  84       */
  85      public function __construct($content, ?MessagePartChannelEnum $channel = null, ?string $thoughtSignature = null)
  86      {
  87          $this->channel = $channel ?? MessagePartChannelEnum::content();
  88          $this->thoughtSignature = $thoughtSignature;
  89          if (is_string($content)) {
  90              $this->type = MessagePartTypeEnum::text();
  91              $this->text = $content;
  92          } elseif ($content instanceof File) {
  93              $this->type = MessagePartTypeEnum::file();
  94              $this->file = $content;
  95          } elseif ($content instanceof FunctionCall) {
  96              $this->type = MessagePartTypeEnum::functionCall();
  97              $this->functionCall = $content;
  98          } elseif ($content instanceof FunctionResponse) {
  99              $this->type = MessagePartTypeEnum::functionResponse();
 100              $this->functionResponse = $content;
 101          } else {
 102              $type = is_object($content) ? get_class($content) : gettype($content);
 103              throw new InvalidArgumentException(sprintf('Unsupported content type %s. Expected string, File, ' . 'FunctionCall, or FunctionResponse.', $type));
 104          }
 105      }
 106      /**
 107       * Gets the channel this message part belongs to.
 108       *
 109       * @since 0.1.0
 110       *
 111       * @return MessagePartChannelEnum The channel.
 112       */
 113      public function getChannel(): MessagePartChannelEnum
 114      {
 115          return $this->channel;
 116      }
 117      /**
 118       * Gets the type of this message part.
 119       *
 120       * @since 0.1.0
 121       *
 122       * @return MessagePartTypeEnum The type.
 123       */
 124      public function getType(): MessagePartTypeEnum
 125      {
 126          return $this->type;
 127      }
 128      /**
 129       * Gets the thought signature.
 130       *
 131       * @since 1.3.0
 132       *
 133       * @return string|null The thought signature or null if not set.
 134       */
 135      public function getThoughtSignature(): ?string
 136      {
 137          return $this->thoughtSignature;
 138      }
 139      /**
 140       * Gets the text content.
 141       *
 142       * @since 0.1.0
 143       *
 144       * @return string|null The text content or null if not a text part.
 145       */
 146      public function getText(): ?string
 147      {
 148          return $this->text;
 149      }
 150      /**
 151       * Gets the file.
 152       *
 153       * @since 0.1.0
 154       *
 155       * @return File|null The file or null if not a file part.
 156       */
 157      public function getFile(): ?File
 158      {
 159          return $this->file;
 160      }
 161      /**
 162       * Gets the function call.
 163       *
 164       * @since 0.1.0
 165       *
 166       * @return FunctionCall|null The function call or null if not a function call part.
 167       */
 168      public function getFunctionCall(): ?FunctionCall
 169      {
 170          return $this->functionCall;
 171      }
 172      /**
 173       * Gets the function response.
 174       *
 175       * @since 0.1.0
 176       *
 177       * @return FunctionResponse|null The function response or null if not a function response part.
 178       */
 179      public function getFunctionResponse(): ?FunctionResponse
 180      {
 181          return $this->functionResponse;
 182      }
 183      /**
 184       * {@inheritDoc}
 185       *
 186       * @since 0.1.0
 187       */
 188      public static function getJsonSchema(): array
 189      {
 190          $channelSchema = ['type' => 'string', 'enum' => MessagePartChannelEnum::getValues(), 'description' => 'The channel this message part belongs to.'];
 191          $thoughtSignatureSchema = ['type' => 'string', 'description' => 'Thought signature for extended thinking.'];
 192          return ['oneOf' => [['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::text()->value], self::KEY_TEXT => ['type' => 'string', 'description' => 'Text content.'], self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_TEXT], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::file()->value], self::KEY_FILE => File::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FILE], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionCall()->value], self::KEY_FUNCTION_CALL => FunctionCall::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_CALL], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionResponse()->value], self::KEY_FUNCTION_RESPONSE => FunctionResponse::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_RESPONSE], 'additionalProperties' => \false]]];
 193      }
 194      /**
 195       * {@inheritDoc}
 196       *
 197       * @since 0.1.0
 198       *
 199       * @return MessagePartArrayShape
 200       */
 201      public function toArray(): array
 202      {
 203          $data = [self::KEY_CHANNEL => $this->channel->value, self::KEY_TYPE => $this->type->value];
 204          if ($this->text !== null) {
 205              $data[self::KEY_TEXT] = $this->text;
 206          } elseif ($this->file !== null) {
 207              $data[self::KEY_FILE] = $this->file->toArray();
 208          } elseif ($this->functionCall !== null) {
 209              $data[self::KEY_FUNCTION_CALL] = $this->functionCall->toArray();
 210          } elseif ($this->functionResponse !== null) {
 211              $data[self::KEY_FUNCTION_RESPONSE] = $this->functionResponse->toArray();
 212          } else {
 213              throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. ' . 'This should not be a possible condition.');
 214          }
 215          if ($this->thoughtSignature !== null) {
 216              $data[self::KEY_THOUGHT_SIGNATURE] = $this->thoughtSignature;
 217          }
 218          return $data;
 219      }
 220      /**
 221       * {@inheritDoc}
 222       *
 223       * @since 0.1.0
 224       */
 225      public static function fromArray(array $array): self
 226      {
 227          if (isset($array[self::KEY_CHANNEL])) {
 228              $channel = MessagePartChannelEnum::from($array[self::KEY_CHANNEL]);
 229          } else {
 230              $channel = null;
 231          }
 232          $thoughtSignature = $array[self::KEY_THOUGHT_SIGNATURE] ?? null;
 233          // Check which properties are set to determine how to construct the MessagePart
 234          if (isset($array[self::KEY_TEXT])) {
 235              return new self($array[self::KEY_TEXT], $channel, $thoughtSignature);
 236          } elseif (isset($array[self::KEY_FILE])) {
 237              return new self(File::fromArray($array[self::KEY_FILE]), $channel, $thoughtSignature);
 238          } elseif (isset($array[self::KEY_FUNCTION_CALL])) {
 239              return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel, $thoughtSignature);
 240          } elseif (isset($array[self::KEY_FUNCTION_RESPONSE])) {
 241              return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel, $thoughtSignature);
 242          } else {
 243              throw new InvalidArgumentException('MessagePart requires one of: text, file, functionCall, or functionResponse.');
 244          }
 245      }
 246      /**
 247       * Performs a deep clone of the message part.
 248       *
 249       * This method ensures that nested objects (file, function call, function response)
 250       * are cloned to prevent modifications to the cloned part from affecting the original.
 251       *
 252       * @since 0.4.2
 253       */
 254      public function __clone()
 255      {
 256          if ($this->file !== null) {
 257              $this->file = clone $this->file;
 258          }
 259          if ($this->functionCall !== null) {
 260              $this->functionCall = clone $this->functionCall;
 261          }
 262          if ($this->functionResponse !== null) {
 263              $this->functionResponse = clone $this->functionResponse;
 264          }
 265      }
 266  }


Generated : Sat Jun 13 09:38:55 2026 Cross-referenced by PHPXref