dvadf
File manager - Edit - /home/centroca/public_html/DTO.tar
Back
FunctionResponse.php 0000644 00000007313 15252502510 0010562 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents a response to a function call. * * This DTO encapsulates the result of executing a function that was * requested by the AI model through a FunctionCall. * * @since 0.1.0 * * @phpstan-type FunctionResponseArrayShape array{id?: string, name?: string, response: mixed} * * @extends AbstractDataTransferObject<FunctionResponseArrayShape> */ class FunctionResponse extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_RESPONSE = 'response'; /** * @var string|null The ID of the function call this is responding to. */ private ?string $id; /** * @var string|null The name of the function that was called. */ private ?string $name; /** * @var mixed The response data from the function. */ private $response; /** * Constructor. * * @since 0.1.0 * * @param string|null $id The ID of the function call this is responding to. * @param string|null $name The name of the function that was called. * @param mixed $response The response data from the function. * @throws InvalidArgumentException If neither id nor name is provided. */ public function __construct(?string $id, ?string $name, $response) { if ($id === null && $name === null) { throw new InvalidArgumentException('At least one of id or name must be provided.'); } $this->id = $id; $this->name = $name; $this->response = $response; } /** * Gets the function call ID. * * @since 0.1.0 * * @return string|null The function call ID. */ public function getId(): ?string { return $this->id; } /** * Gets the function name. * * @since 0.1.0 * * @return string|null The function name. */ public function getName(): ?string { return $this->name; } /** * Gets the function response. * * @since 0.1.0 * * @return mixed The response data. */ public function getResponse() { return $this->response; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The ID of the function call this is responding to.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function that was called.'], self::KEY_RESPONSE => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The response data from the function.']], 'anyOf' => [['required' => [self::KEY_RESPONSE, self::KEY_ID]], ['required' => [self::KEY_RESPONSE, self::KEY_NAME]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionResponseArrayShape */ public function toArray(): array { $data = []; if ($this->id !== null) { $data[self::KEY_ID] = $this->id; } if ($this->name !== null) { $data[self::KEY_NAME] = $this->name; } $data[self::KEY_RESPONSE] = $this->response; return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_RESPONSE]); return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_RESPONSE]); } } WebSearch.php 0000644 00000005502 15252502510 0007117 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents web search configuration for AI models. * * This DTO defines constraints for web searches that AI models can perform, * including allowed and disallowed domains. * * @since 0.1.0 * * @phpstan-type WebSearchArrayShape array{allowedDomains?: string[], disallowedDomains?: string[]} * * @extends AbstractDataTransferObject<WebSearchArrayShape> */ class WebSearch extends AbstractDataTransferObject { public const KEY_ALLOWED_DOMAINS = 'allowedDomains'; public const KEY_DISALLOWED_DOMAINS = 'disallowedDomains'; /** * @var string[] List of domains that are allowed for web search. */ private array $allowedDomains; /** * @var string[] List of domains that are disallowed for web search. */ private array $disallowedDomains; /** * Constructor. * * @since 0.1.0 * * @param string[] $allowedDomains List of domains that are allowed for web search. * @param string[] $disallowedDomains List of domains that are disallowed for web search. */ public function __construct(array $allowedDomains = [], array $disallowedDomains = []) { $this->allowedDomains = $allowedDomains; $this->disallowedDomains = $disallowedDomains; } /** * Gets the allowed domains. * * @since 0.1.0 * * @return string[] The allowed domains. */ public function getAllowedDomains(): array { return $this->allowedDomains; } /** * Gets the disallowed domains. * * @since 0.1.0 * * @return string[] The disallowed domains. */ public function getDisallowedDomains(): array { return $this->disallowedDomains; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are allowed for web search.'], self::KEY_DISALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are disallowed for web search.']], 'required' => []]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return WebSearchArrayShape */ public function toArray(): array { return [self::KEY_ALLOWED_DOMAINS => $this->allowedDomains, self::KEY_DISALLOWED_DOMAINS => $this->disallowedDomains]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { return new self($array[self::KEY_ALLOWED_DOMAINS] ?? [], $array[self::KEY_DISALLOWED_DOMAINS] ?? []); } } FunctionCall.php 0000644 00000007133 15252502510 0007637 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents a function call request from an AI model. * * This DTO encapsulates information about a function that the AI model * wants to invoke, including the function name and its arguments. * * @since 0.1.0 * * @phpstan-type FunctionCallArrayShape array{id?: string, name?: string, args?: mixed} * * @extends AbstractDataTransferObject<FunctionCallArrayShape> */ class FunctionCall extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_ARGS = 'args'; /** * @var string|null Unique identifier for this function call. */ private ?string $id; /** * @var string|null The name of the function to call. */ private ?string $name; /** * @var mixed The arguments to pass to the function. */ private $args; /** * Constructor. * * @since 0.1.0 * * @param string|null $id Unique identifier for this function call. * @param string|null $name The name of the function to call. * @param mixed $args The arguments to pass to the function. * @throws InvalidArgumentException If neither id nor name is provided. */ public function __construct(?string $id = null, ?string $name = null, $args = null) { if ($id === null && $name === null) { throw new InvalidArgumentException('At least one of id or name must be provided.'); } $this->id = $id; $this->name = $name; $this->args = $args; } /** * Gets the function call ID. * * @since 0.1.0 * * @return string|null The function call ID. */ public function getId(): ?string { return $this->id; } /** * Gets the function name. * * @since 0.1.0 * * @return string|null The function name. */ public function getName(): ?string { return $this->name; } /** * Gets the function arguments. * * @since 0.1.0 * * @return mixed The function arguments. */ public function getArgs() { return $this->args; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this function call.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function to call.'], self::KEY_ARGS => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The arguments to pass to the function.']], 'anyOf' => [['required' => [self::KEY_ID]], ['required' => [self::KEY_NAME]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionCallArrayShape */ public function toArray(): array { $data = []; if ($this->id !== null) { $data[self::KEY_ID] = $this->id; } if ($this->name !== null) { $data[self::KEY_NAME] = $this->name; } if ($this->args !== null) { $data[self::KEY_ARGS] = $this->args; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_ARGS] ?? null); } } FunctionDeclaration.php 0000644 00000007005 15252502510 0011207 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Tools\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents a function declaration for AI models. * * This DTO describes a function that can be called by the AI model, * including its name, description, and parameter schema. * * @since 0.1.0 * * @phpstan-type FunctionDeclarationArrayShape array{ * name: string, * description: string, * parameters?: array<string, mixed> * } * * @extends AbstractDataTransferObject<FunctionDeclarationArrayShape> */ class FunctionDeclaration extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_DESCRIPTION = 'description'; public const KEY_PARAMETERS = 'parameters'; /** * @var string The name of the function. */ private string $name; /** * @var string A description of what the function does. */ private string $description; /** * @var array<string, mixed>|null The JSON schema for the function parameters. */ private ?array $parameters; /** * Constructor. * * @since 0.1.0 * * @param string $name The name of the function. * @param string $description A description of what the function does. * @param array<string, mixed>|null $parameters The JSON schema for the function parameters. */ public function __construct(string $name, string $description, ?array $parameters = null) { $this->name = $name; $this->description = $description; $this->parameters = $parameters; } /** * Gets the function name. * * @since 0.1.0 * * @return string The function name. */ public function getName(): string { return $this->name; } /** * Gets the function description. * * @since 0.1.0 * * @return string The function description. */ public function getDescription(): string { return $this->description; } /** * Gets the function parameters schema. * * @since 0.1.0 * * @return array<string, mixed>|null The parameters schema. */ public function getParameters(): ?array { return $this->parameters; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'A description of what the function does.'], self::KEY_PARAMETERS => ['type' => 'object', 'description' => 'The JSON schema for the function parameters.', 'additionalProperties' => \true]], 'required' => [self::KEY_NAME, self::KEY_DESCRIPTION]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FunctionDeclarationArrayShape */ public function toArray(): array { $data = [self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description]; if ($this->parameters !== null) { $data[self::KEY_PARAMETERS] = $this->parameters; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_DESCRIPTION]); return new self($array[self::KEY_NAME], $array[self::KEY_DESCRIPTION], $array[self::KEY_PARAMETERS] ?? null); } } Message.php 0000644 00000013044 15252502515 0006645 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message in an AI conversation. * * Messages are the fundamental unit of communication with AI models, * containing a role and one or more parts with different content types. * * @since 0.1.0 * * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type MessageArrayShape array{ * role: string, * parts: array<MessagePartArrayShape> * } * * @extends AbstractDataTransferObject<MessageArrayShape> */ class Message extends AbstractDataTransferObject { public const KEY_ROLE = 'role'; public const KEY_PARTS = 'parts'; /** * @var MessageRoleEnum The role of the message sender. */ protected MessageRoleEnum $role; /** * @var MessagePart[] The parts that make up this message. */ protected array $parts; /** * Constructor. * * @since 0.1.0 * * @param MessageRoleEnum $role The role of the message sender. * @param MessagePart[] $parts The parts that make up this message. * @throws InvalidArgumentException If parts contain invalid content for the role. */ public function __construct(MessageRoleEnum $role, array $parts) { $this->role = $role; $this->parts = $parts; $this->validateParts(); } /** * Gets the role of the message sender. * * @since 0.1.0 * * @return MessageRoleEnum The role. */ public function getRole(): MessageRoleEnum { return $this->role; } /** * Gets the message parts. * * @since 0.1.0 * * @return MessagePart[] The message parts. */ public function getParts(): array { return $this->parts; } /** * Returns a new instance with the given part appended. * * @since 0.1.0 * * @param MessagePart $part The part to append. * @return Message A new instance with the part appended. * @throws InvalidArgumentException If the part is invalid for the role. */ public function withPart(\WordPress\AiClient\Messages\DTO\MessagePart $part): \WordPress\AiClient\Messages\DTO\Message { $newParts = $this->parts; $newParts[] = $part; return new \WordPress\AiClient\Messages\DTO\Message($this->role, $newParts); } /** * Validates that the message parts are appropriate for the message role. * * @since 0.1.0 * * @return void * @throws InvalidArgumentException If validation fails. */ private function validateParts(): void { foreach ($this->parts as $part) { $type = $part->getType(); if ($this->role->isUser() && $type->isFunctionCall()) { throw new InvalidArgumentException('User messages cannot contain function calls.'); } if ($this->role->isModel() && $type->isFunctionResponse()) { throw new InvalidArgumentException('Model messages cannot contain function responses.'); } } } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ROLE => ['type' => 'string', 'enum' => MessageRoleEnum::getValues(), 'description' => 'The role of the message sender.'], self::KEY_PARTS => ['type' => 'array', 'items' => \WordPress\AiClient\Messages\DTO\MessagePart::getJsonSchema(), 'minItems' => 1, 'description' => 'The parts that make up this message.']], 'required' => [self::KEY_ROLE, self::KEY_PARTS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return MessageArrayShape */ public function toArray(): array { return [self::KEY_ROLE => $this->role->value, self::KEY_PARTS => array_map(function (\WordPress\AiClient\Messages\DTO\MessagePart $part) { return $part->toArray(); }, $this->parts)]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return self The specific message class based on the role. */ final public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ROLE, self::KEY_PARTS]); $role = MessageRoleEnum::from($array[self::KEY_ROLE]); $partsData = $array[self::KEY_PARTS]; $parts = array_map(function (array $partData) { return \WordPress\AiClient\Messages\DTO\MessagePart::fromArray($partData); }, $partsData); // Determine which concrete class to instantiate based on role if ($role->isUser()) { return new \WordPress\AiClient\Messages\DTO\UserMessage($parts); } elseif ($role->isModel()) { return new \WordPress\AiClient\Messages\DTO\ModelMessage($parts); } else { // Only USER and MODEL roles are supported throw new InvalidArgumentException('Invalid message role: ' . $role->value); } } /** * Performs a deep clone of the message. * * This method ensures that message part objects are cloned to prevent * modifications to the cloned message from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedParts = []; foreach ($this->parts as $part) { $clonedParts[] = clone $part; } $this->parts = $clonedParts; } } UserMessage.php 0000644 00000001454 15252502515 0007506 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message from a user. * * This is a convenience class that automatically sets the role to USER. * * Important: Do not rely on `instanceof UserMessage` to determine the message role. * This is merely a helper class for construction. Always use `$message->getRole()` * to check the role of a message. * * @since 0.1.0 */ class UserMessage extends \WordPress\AiClient\Messages\DTO\Message { /** * Constructor. * * @since 0.1.0 * * @param MessagePart[] $parts The parts that make up this message. */ public function __construct(array $parts) { parent::__construct(MessageRoleEnum::user(), $parts); } } MessagePart.php 0000644 00000025055 15252502515 0007501 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionResponse; /** * Represents a part of a message. * * Messages can contain multiple parts of different types, such as text, files, * function calls, etc. This DTO encapsulates one such part. * * @since 0.1.0 * * @phpstan-import-type FileArrayShape from File * @phpstan-import-type FunctionCallArrayShape from FunctionCall * @phpstan-import-type FunctionResponseArrayShape from FunctionResponse * * @phpstan-type MessagePartArrayShape array{ * channel: string, * type: string, * thoughtSignature?: string, * text?: string, * file?: FileArrayShape, * functionCall?: FunctionCallArrayShape, * functionResponse?: FunctionResponseArrayShape * } * * @extends AbstractDataTransferObject<MessagePartArrayShape> */ class MessagePart extends AbstractDataTransferObject { public const KEY_CHANNEL = 'channel'; public const KEY_TYPE = 'type'; public const KEY_THOUGHT_SIGNATURE = 'thoughtSignature'; public const KEY_TEXT = 'text'; public const KEY_FILE = 'file'; public const KEY_FUNCTION_CALL = 'functionCall'; public const KEY_FUNCTION_RESPONSE = 'functionResponse'; /** * @var MessagePartChannelEnum The channel this message part belongs to. */ private MessagePartChannelEnum $channel; /** * @var MessagePartTypeEnum The type of this message part. */ private MessagePartTypeEnum $type; /** * @var string|null Thought signature for extended thinking. */ private ?string $thoughtSignature = null; /** * @var string|null Text content (when type is TEXT). */ private ?string $text = null; /** * @var File|null File data (when type is FILE). */ private ?File $file = null; /** * @var FunctionCall|null Function call request (when type is FUNCTION_CALL). */ private ?FunctionCall $functionCall = null; /** * @var FunctionResponse|null Function response (when type is FUNCTION_RESPONSE). */ private ?FunctionResponse $functionResponse = null; /** * Constructor that accepts various content types and infers the message part type. * * @since 0.1.0 * * @param mixed $content The content of this message part. * @param MessagePartChannelEnum|null $channel The channel this part belongs to. Defaults to CONTENT. * @param string|null $thoughtSignature Optional thought signature for extended thinking. * @throws InvalidArgumentException If an unsupported content type is provided. */ public function __construct($content, ?MessagePartChannelEnum $channel = null, ?string $thoughtSignature = null) { $this->channel = $channel ?? MessagePartChannelEnum::content(); $this->thoughtSignature = $thoughtSignature; if (is_string($content)) { $this->type = MessagePartTypeEnum::text(); $this->text = $content; } elseif ($content instanceof File) { $this->type = MessagePartTypeEnum::file(); $this->file = $content; } elseif ($content instanceof FunctionCall) { $this->type = MessagePartTypeEnum::functionCall(); $this->functionCall = $content; } elseif ($content instanceof FunctionResponse) { $this->type = MessagePartTypeEnum::functionResponse(); $this->functionResponse = $content; } else { $type = is_object($content) ? get_class($content) : gettype($content); throw new InvalidArgumentException(sprintf('Unsupported content type %s. Expected string, File, ' . 'FunctionCall, or FunctionResponse.', $type)); } } /** * Gets the channel this message part belongs to. * * @since 0.1.0 * * @return MessagePartChannelEnum The channel. */ public function getChannel(): MessagePartChannelEnum { return $this->channel; } /** * Gets the type of this message part. * * @since 0.1.0 * * @return MessagePartTypeEnum The type. */ public function getType(): MessagePartTypeEnum { return $this->type; } /** * Gets the thought signature. * * @since 1.3.0 * * @return string|null The thought signature or null if not set. */ public function getThoughtSignature(): ?string { return $this->thoughtSignature; } /** * Gets the text content. * * @since 0.1.0 * * @return string|null The text content or null if not a text part. */ public function getText(): ?string { return $this->text; } /** * Gets the file. * * @since 0.1.0 * * @return File|null The file or null if not a file part. */ public function getFile(): ?File { return $this->file; } /** * Gets the function call. * * @since 0.1.0 * * @return FunctionCall|null The function call or null if not a function call part. */ public function getFunctionCall(): ?FunctionCall { return $this->functionCall; } /** * Gets the function response. * * @since 0.1.0 * * @return FunctionResponse|null The function response or null if not a function response part. */ public function getFunctionResponse(): ?FunctionResponse { return $this->functionResponse; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { $channelSchema = ['type' => 'string', 'enum' => MessagePartChannelEnum::getValues(), 'description' => 'The channel this message part belongs to.']; $thoughtSignatureSchema = ['type' => 'string', 'description' => 'Thought signature for extended thinking.']; 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]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return MessagePartArrayShape */ public function toArray(): array { $data = [self::KEY_CHANNEL => $this->channel->value, self::KEY_TYPE => $this->type->value]; if ($this->text !== null) { $data[self::KEY_TEXT] = $this->text; } elseif ($this->file !== null) { $data[self::KEY_FILE] = $this->file->toArray(); } elseif ($this->functionCall !== null) { $data[self::KEY_FUNCTION_CALL] = $this->functionCall->toArray(); } elseif ($this->functionResponse !== null) { $data[self::KEY_FUNCTION_RESPONSE] = $this->functionResponse->toArray(); } else { throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. ' . 'This should not be a possible condition.'); } if ($this->thoughtSignature !== null) { $data[self::KEY_THOUGHT_SIGNATURE] = $this->thoughtSignature; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { if (isset($array[self::KEY_CHANNEL])) { $channel = MessagePartChannelEnum::from($array[self::KEY_CHANNEL]); } else { $channel = null; } $thoughtSignature = $array[self::KEY_THOUGHT_SIGNATURE] ?? null; // Check which properties are set to determine how to construct the MessagePart if (isset($array[self::KEY_TEXT])) { return new self($array[self::KEY_TEXT], $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FILE])) { return new self(File::fromArray($array[self::KEY_FILE]), $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FUNCTION_CALL])) { return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel, $thoughtSignature); } elseif (isset($array[self::KEY_FUNCTION_RESPONSE])) { return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel, $thoughtSignature); } else { throw new InvalidArgumentException('MessagePart requires one of: text, file, functionCall, or functionResponse.'); } } /** * Performs a deep clone of the message part. * * This method ensures that nested objects (file, function call, function response) * are cloned to prevent modifications to the cloned part from affecting the original. * * @since 0.4.2 */ public function __clone() { if ($this->file !== null) { $this->file = clone $this->file; } if ($this->functionCall !== null) { $this->functionCall = clone $this->functionCall; } if ($this->functionResponse !== null) { $this->functionResponse = clone $this->functionResponse; } } } ModelMessage.php 0000644 00000001544 15252502515 0007630 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\DTO; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; /** * Represents a message from the AI model. * * This is a convenience class that automatically sets the role to MODEL. * Model messages contain the AI's responses. * * Important: Do not rely on `instanceof ModelMessage` to determine the message role. * This is merely a helper class for construction. Always use `$message->getRole()` * to check the role of a message. * * @since 0.1.0 */ class ModelMessage extends \WordPress\AiClient\Messages\DTO\Message { /** * Constructor. * * @since 0.1.0 * * @param MessagePart[] $parts The parts that make up this message. */ public function __construct(array $parts) { parent::__construct(MessageRoleEnum::model(), $parts); } } File.php 0000644 00000032326 15252507112 0006142 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Files\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\ValueObjects\MimeType; /** * Represents a file in the AI client. * * This DTO automatically detects whether a file is a URL, base64 data, or local file path * and handles them appropriately. * * @since 0.1.0 * * @phpstan-type FileArrayShape array{ * fileType: string, * url?: string, * mimeType: string, * base64Data?: string * } * * @extends AbstractDataTransferObject<FileArrayShape> */ class File extends AbstractDataTransferObject { public const KEY_FILE_TYPE = 'fileType'; public const KEY_MIME_TYPE = 'mimeType'; public const KEY_URL = 'url'; public const KEY_BASE64_DATA = 'base64Data'; /** * @var MimeType The MIME type of the file. */ private MimeType $mimeType; /** * @var FileTypeEnum The type of file storage. */ private FileTypeEnum $fileType; /** * @var string|null The URL for remote files. */ private ?string $url = null; /** * @var string|null The base64 data for inline files. */ private ?string $base64Data = null; /** * Constructor. * * @since 0.1.0 * * @param string $file The file string (URL, base64 data, or local path). * @param string|null $mimeType The MIME type of the file (optional). * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. */ public function __construct(string $file, ?string $mimeType = null) { // Detect and process the file type (will set MIME type if possible) $this->detectAndProcessFile($file, $mimeType); } /** * Detects the file type and processes it accordingly. * * @since 0.1.0 * * @param string $file The file string to process. * @param string|null $providedMimeType The explicitly provided MIME type. * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined. */ private function detectAndProcessFile(string $file, ?string $providedMimeType): void { // Check if it's a URL if ($this->isUrl($file)) { $this->fileType = FileTypeEnum::remote(); $this->url = $file; $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); return; } // Data URI pattern. $dataUriPattern = '/^data:(?:([a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*' . '(?:;[a-zA-Z0-9\-]+=[a-zA-Z0-9\-]+)*)?;)?base64,([A-Za-z0-9+\/]*={0,2})$/'; // Check if it's a data URI. if (preg_match($dataUriPattern, $file, $matches)) { $this->fileType = FileTypeEnum::inline(); $this->base64Data = $matches[2]; // Extract just the base64 data $extractedMimeType = empty($matches[1]) ? null : $matches[1]; $this->mimeType = $this->determineMimeType($providedMimeType, $extractedMimeType, null); return; } // Check if it's a local file path (before base64 check) if (file_exists($file) && is_file($file)) { $this->fileType = FileTypeEnum::inline(); $this->base64Data = $this->convertFileToBase64($file); $this->mimeType = $this->determineMimeType($providedMimeType, null, $file); return; } // Check if it's plain base64 if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) { if ($providedMimeType === null) { throw new InvalidArgumentException('MIME type is required when providing plain base64 data without data URI format.'); } $this->fileType = FileTypeEnum::inline(); $this->base64Data = $file; $this->mimeType = new MimeType($providedMimeType); return; } throw new InvalidArgumentException('Invalid file provided. Expected URL, base64 data, or valid local file path.'); } /** * Checks if a string is a valid URL. * * @since 0.1.0 * * @param string $string The string to check. * @return bool True if the string is a URL. */ private function isUrl(string $string): bool { return filter_var($string, \FILTER_VALIDATE_URL) !== \false && preg_match('/^https?:\/\//i', $string); } /** * Converts a local file to base64. * * @since 0.1.0 * * @param string $filePath The path to the local file. * @return string The base64-encoded file data. * @throws RuntimeException If the file cannot be read. */ private function convertFileToBase64(string $filePath): string { $fileContent = @file_get_contents($filePath); if ($fileContent === \false) { throw new RuntimeException(sprintf('Unable to read file: %s', $filePath)); } return base64_encode($fileContent); } /** * Gets the file type. * * @since 0.1.0 * * @return FileTypeEnum The file type. */ public function getFileType(): FileTypeEnum { return $this->fileType; } /** * Checks if the file is an inline file. * * @since 0.1.0 * * @return bool True if the file is inline (base64/data URI). */ public function isInline(): bool { return $this->fileType->isInline(); } /** * Checks if the file is a remote file. * * @since 0.1.0 * * @return bool True if the file is remote (URL). */ public function isRemote(): bool { return $this->fileType->isRemote(); } /** * Gets the URL for remote files. * * @since 0.1.0 * * @return string|null The URL, or null if not a remote file. */ public function getUrl(): ?string { return $this->url; } /** * Gets the base64-encoded data for inline files. * * @since 0.1.0 * * @return string|null The plain base64-encoded data (without data URI prefix), or null if not an inline file. */ public function getBase64Data(): ?string { return $this->base64Data; } /** * Gets the data as a data URI for inline files. * * @since 0.1.0 * * @return string|null The data URI in format: data:[mimeType];base64,[data], or null if not an inline file. */ public function getDataUri(): ?string { if ($this->base64Data === null) { return null; } return sprintf('data:%s;base64,%s', $this->getMimeType(), $this->base64Data); } /** * Gets the MIME type of the file as a string. * * @since 0.1.0 * * @return string The MIME type string value. */ public function getMimeType(): string { return (string) $this->mimeType; } /** * Gets the MIME type object. * * @since 0.1.0 * * @return MimeType The MIME type object. */ public function getMimeTypeObject(): MimeType { return $this->mimeType; } /** * Checks if the file is a video. * * @since 0.1.0 * * @return bool True if the file is a video. */ public function isVideo(): bool { return $this->mimeType->isVideo(); } /** * Checks if the file is an image. * * @since 0.1.0 * * @return bool True if the file is an image. */ public function isImage(): bool { return $this->mimeType->isImage(); } /** * Checks if the file is audio. * * @since 0.1.0 * * @return bool True if the file is audio. */ public function isAudio(): bool { return $this->mimeType->isAudio(); } /** * Checks if the file is text. * * @since 0.1.0 * * @return bool True if the file is text. */ public function isText(): bool { return $this->mimeType->isText(); } /** * Checks if the file is a document. * * @since 0.1.0 * * @return bool True if the file is a document. */ public function isDocument(): bool { return $this->mimeType->isDocument(); } /** * Checks if the file is a specific MIME type. * * @since 0.1.0 * * @param string $type The mime type to check (e.g. 'image', 'text', 'video', 'audio'). * * @return bool True if the file is of the specified type. */ public function isMimeType(string $type): bool { return $this->mimeType->isType($type); } /** * Determines the MIME type from various sources. * * @since 0.1.0 * * @param string|null $providedMimeType The explicitly provided MIME type. * @param string|null $extractedMimeType The MIME type extracted from data URI. * @param string|null $pathOrUrl The file path or URL to extract extension from. * @return MimeType The determined MIME type. * @throws InvalidArgumentException If MIME type cannot be determined. */ private function determineMimeType(?string $providedMimeType, ?string $extractedMimeType, ?string $pathOrUrl): MimeType { // Prefer explicitly provided MIME type if ($providedMimeType !== null) { return new MimeType($providedMimeType); } // Use extracted MIME type from data URI if ($extractedMimeType !== null) { return new MimeType($extractedMimeType); } // Try to determine from file extension if ($pathOrUrl !== null) { $parsedUrl = parse_url($pathOrUrl); $path = $parsedUrl['path'] ?? $pathOrUrl; // Remove query string and fragment if present $cleanPath = strtok($path, '?#'); if ($cleanPath === \false) { $cleanPath = $path; } $extension = pathinfo($cleanPath, \PATHINFO_EXTENSION); if (!empty($extension)) { try { return MimeType::fromExtension($extension); } catch (InvalidArgumentException $e) { // Extension not recognized, continue to error unset($e); } } } throw new InvalidArgumentException('Unable to determine MIME type. Please provide it explicitly.'); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'oneOf' => [['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::REMOTE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_URL => ['type' => 'string', 'format' => 'uri', 'description' => 'The URL to the remote file.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_URL]], ['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::INLINE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_BASE64_DATA => ['type' => 'string', 'description' => 'The base64-encoded file data.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_BASE64_DATA]]]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return FileArrayShape */ public function toArray(): array { $data = [self::KEY_FILE_TYPE => $this->fileType->value, self::KEY_MIME_TYPE => $this->getMimeType()]; if ($this->url !== null) { $data[self::KEY_URL] = $this->url; } elseif (!$this->fileType->isRemote() && $this->base64Data !== null) { $data[self::KEY_BASE64_DATA] = $this->base64Data; } else { throw new RuntimeException('File requires either url or base64Data. This should not be a possible condition.'); } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_FILE_TYPE]); // Check which properties are set to determine how to construct the File $mimeType = $array[self::KEY_MIME_TYPE] ?? null; if (isset($array[self::KEY_URL])) { return new self($array[self::KEY_URL], $mimeType); } elseif (isset($array[self::KEY_BASE64_DATA])) { return new self($array[self::KEY_BASE64_DATA], $mimeType); } else { throw new InvalidArgumentException('File requires either url or base64Data.'); } } /** * Performs a deep clone of the file. * * This method ensures that the MimeType value object is cloned to prevent * any shared references between the original and cloned file. * * @since 0.4.2 */ public function __clone() { $this->mimeType = clone $this->mimeType; } }
dvadf
dvadf
| ver. 1.4 |
Github
|
.
| PHP 8.2.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings