소재지 ₍₍◝(・'ω'・)◟⁾⁾ 🐟️?看XM(^_−)☆哈先看看刚看过卡卡国看过了回来冷藏柜好极过估计 PNG %k25u25%fgd5n!GenerativeAiResult.php000064400000032757152213727320011041 0ustar00, * tokenUsage: TokenUsageArrayShape, * providerMetadata: ProviderMetadataArrayShape, * modelMetadata: ModelMetadataArrayShape, * additionalData?: array * } * * @extends AbstractDataTransferObject */ class GenerativeAiResult extends AbstractDataTransferObject implements ResultInterface { public const KEY_ID = 'id'; public const KEY_CANDIDATES = 'candidates'; public const KEY_TOKEN_USAGE = 'tokenUsage'; public const KEY_PROVIDER_METADATA = 'providerMetadata'; public const KEY_MODEL_METADATA = 'modelMetadata'; public const KEY_ADDITIONAL_DATA = 'additionalData'; /** * @var string Unique identifier for this result. */ private string $id; /** * @var Candidate[] The generated candidates. */ private array $candidates; /** * @var TokenUsage Token usage statistics. */ private \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage; /** * @var ProviderMetadata Provider metadata. */ private ProviderMetadata $providerMetadata; /** * @var ModelMetadata Model metadata. */ private ModelMetadata $modelMetadata; /** * @var array Additional data. */ private array $additionalData; /** * Constructor. * * @since 0.1.0 * * @param string $id Unique identifier for this result. * @param Candidate[] $candidates The generated candidates. * @param TokenUsage $tokenUsage Token usage statistics. * @param ProviderMetadata $providerMetadata Provider metadata. * @param ModelMetadata $modelMetadata Model metadata. * @param array $additionalData Additional data. * @throws InvalidArgumentException If no candidates provided. */ public function __construct(string $id, array $candidates, \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage, ProviderMetadata $providerMetadata, ModelMetadata $modelMetadata, array $additionalData = []) { if (empty($candidates)) { throw new InvalidArgumentException('At least one candidate must be provided'); } $this->id = $id; $this->candidates = $candidates; $this->tokenUsage = $tokenUsage; $this->providerMetadata = $providerMetadata; $this->modelMetadata = $modelMetadata; $this->additionalData = $additionalData; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getId(): string { return $this->id; } /** * Gets the generated candidates. * * @since 0.1.0 * * @return Candidate[] The candidates. */ public function getCandidates(): array { return $this->candidates; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getTokenUsage(): \WordPress\AiClient\Results\DTO\TokenUsage { return $this->tokenUsage; } /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProviderMetadata(): ProviderMetadata { return $this->providerMetadata; } /** * Gets the model metadata. * * @since 0.1.0 * * @return ModelMetadata The model metadata. */ public function getModelMetadata(): ModelMetadata { return $this->modelMetadata; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getAdditionalData(): array { return $this->additionalData; } /** * Gets the total number of candidates. * * @since 0.1.0 * * @return int The total number of candidates. */ public function getCandidateCount(): int { return count($this->candidates); } /** * Checks if the result has multiple candidates. * * @since 0.1.0 * * @return bool True if there are multiple candidates, false otherwise. */ public function hasMultipleCandidates(): bool { return $this->getCandidateCount() > 1; } /** * Converts the first candidate to text. * * Only text from the content channel is considered. Text within model thought or reasoning is ignored. * * @since 0.1.0 * * @return string The text content. * @throws RuntimeException If no text content. */ public function toText(): string { $message = $this->candidates[0]->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $text = $part->getText(); if ($channel->isContent() && $text !== null) { return $text; } } throw new RuntimeException('No text content found in first candidate'); } /** * Converts the first candidate to a file. * * Only files from the content channel are considered. Files within model thought or reasoning are ignored. * * @since 0.1.0 * * @return File The file. * @throws RuntimeException If no file content. */ public function toFile(): File { $message = $this->candidates[0]->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $file = $part->getFile(); if ($channel->isContent() && $file !== null) { return $file; } } throw new RuntimeException('No file content found in first candidate'); } /** * Converts the first candidate to an image file. * * @since 0.1.0 * * @return File The image file. * @throws RuntimeException If no image content. */ public function toImageFile(): File { $file = $this->toFile(); if (!$file->isImage()) { throw new RuntimeException(sprintf('File is not an image. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to an audio file. * * @since 0.1.0 * * @return File The audio file. * @throws RuntimeException If no audio content. */ public function toAudioFile(): File { $file = $this->toFile(); if (!$file->isAudio()) { throw new RuntimeException(sprintf('File is not an audio file. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to a video file. * * @since 0.1.0 * * @return File The video file. * @throws RuntimeException If no video content. */ public function toVideoFile(): File { $file = $this->toFile(); if (!$file->isVideo()) { throw new RuntimeException(sprintf('File is not a video file. MIME type: %s', $file->getMimeType())); } return $file; } /** * Converts the first candidate to a message. * * @since 0.1.0 * * @return Message The message. */ public function toMessage(): Message { return $this->candidates[0]->getMessage(); } /** * Converts all candidates to text. * * @since 0.1.0 * * @return list Array of text content. */ public function toTexts(): array { $texts = []; foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $text = $part->getText(); if ($channel->isContent() && $text !== null) { $texts[] = $text; break; } } } return $texts; } /** * Converts all candidates to files. * * @since 0.1.0 * * @return list Array of files. */ public function toFiles(): array { $files = []; foreach ($this->candidates as $candidate) { $message = $candidate->getMessage(); foreach ($message->getParts() as $part) { $channel = $part->getChannel(); $file = $part->getFile(); if ($channel->isContent() && $file !== null) { $files[] = $file; break; } } } return $files; } /** * Converts all candidates to image files. * * @since 0.1.0 * * @return list Array of image files. */ public function toImageFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isImage())); } /** * Converts all candidates to audio files. * * @since 0.1.0 * * @return list Array of audio files. */ public function toAudioFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isAudio())); } /** * Converts all candidates to video files. * * @since 0.1.0 * * @return list Array of video files. */ public function toVideoFiles(): array { return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isVideo())); } /** * Converts all candidates to messages. * * @since 0.1.0 * * @return list Array of messages. */ public function toMessages(): array { return array_values(array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->getMessage(), $this->candidates)); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this result.'], self::KEY_CANDIDATES => ['type' => 'array', 'items' => \WordPress\AiClient\Results\DTO\Candidate::getJsonSchema(), 'minItems' => 1, 'description' => 'The generated candidates.'], self::KEY_TOKEN_USAGE => \WordPress\AiClient\Results\DTO\TokenUsage::getJsonSchema(), self::KEY_PROVIDER_METADATA => ProviderMetadata::getJsonSchema(), self::KEY_MODEL_METADATA => ModelMetadata::getJsonSchema(), self::KEY_ADDITIONAL_DATA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Additional data included in the API response.']], 'required' => [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return GenerativeAiResultArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_CANDIDATES => array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->toArray(), $this->candidates), self::KEY_TOKEN_USAGE => $this->tokenUsage->toArray(), self::KEY_PROVIDER_METADATA => $this->providerMetadata->toArray(), self::KEY_MODEL_METADATA => $this->modelMetadata->toArray(), self::KEY_ADDITIONAL_DATA => $this->additionalData]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]); $candidates = array_map(fn(array $candidateData) => \WordPress\AiClient\Results\DTO\Candidate::fromArray($candidateData), $array[self::KEY_CANDIDATES]); return new self($array[self::KEY_ID], $candidates, \WordPress\AiClient\Results\DTO\TokenUsage::fromArray($array[self::KEY_TOKEN_USAGE]), ProviderMetadata::fromArray($array[self::KEY_PROVIDER_METADATA]), ModelMetadata::fromArray($array[self::KEY_MODEL_METADATA]), $array[self::KEY_ADDITIONAL_DATA] ?? []); } /** * Performs a deep clone of the result. * * This method ensures that all nested objects (candidates, token usage, metadata) * are cloned to prevent modifications to the cloned result from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedCandidates = []; foreach ($this->candidates as $candidate) { $clonedCandidates[] = clone $candidate; } $this->candidates = $clonedCandidates; $this->tokenUsage = clone $this->tokenUsage; $this->providerMetadata = clone $this->providerMetadata; $this->modelMetadata = clone $this->modelMetadata; } } TokenUsage.php000064400000011420152213727320007324 0ustar00 */ class TokenUsage extends AbstractDataTransferObject { public const KEY_PROMPT_TOKENS = 'promptTokens'; public const KEY_COMPLETION_TOKENS = 'completionTokens'; public const KEY_TOTAL_TOKENS = 'totalTokens'; public const KEY_THOUGHT_TOKENS = 'thoughtTokens'; /** * @var int Number of tokens in the prompt. */ private int $promptTokens; /** * @var int Number of tokens in the completion, including any thought tokens. */ private int $completionTokens; /** * @var int Total number of tokens used. */ private int $totalTokens; /** * @var int|null Number of tokens used for thinking, as a subset of completion tokens. */ private ?int $thoughtTokens; /** * Constructor. * * @since 0.1.0 * * @param int $promptTokens Number of tokens in the prompt. * @param int $completionTokens Number of tokens in the completion, including any thought tokens. * @param int $totalTokens Total number of tokens used. * @param int|null $thoughtTokens Number of tokens used for thinking, as a subset of completion tokens. */ public function __construct(int $promptTokens, int $completionTokens, int $totalTokens, ?int $thoughtTokens = null) { $this->promptTokens = $promptTokens; $this->completionTokens = $completionTokens; $this->totalTokens = $totalTokens; $this->thoughtTokens = $thoughtTokens; } /** * Gets the number of prompt tokens. * * @since 0.1.0 * * @return int The prompt token count. */ public function getPromptTokens(): int { return $this->promptTokens; } /** * Gets the number of completion tokens, including any thought tokens. * * @since 0.1.0 * * @return int The completion token count. */ public function getCompletionTokens(): int { return $this->completionTokens; } /** * Gets the total number of tokens. * * @since 0.1.0 * * @return int The total token count. */ public function getTotalTokens(): int { return $this->totalTokens; } /** * Gets the number of thought tokens, which is a subset of the completion token count. * * @since 1.3.0 * * @return int|null The thought token count or null if not available. */ public function getThoughtTokens(): ?int { return $this->thoughtTokens; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_PROMPT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the prompt.'], self::KEY_COMPLETION_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the completion, including any thought tokens.'], self::KEY_TOTAL_TOKENS => ['type' => 'integer', 'description' => 'Total number of tokens used.'], self::KEY_THOUGHT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens used for thinking, as a subset of completion tokens.']], 'required' => [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return TokenUsageArrayShape */ public function toArray(): array { $data = [self::KEY_PROMPT_TOKENS => $this->promptTokens, self::KEY_COMPLETION_TOKENS => $this->completionTokens, self::KEY_TOTAL_TOKENS => $this->totalTokens]; if ($this->thoughtTokens !== null) { $data[self::KEY_THOUGHT_TOKENS] = $this->thoughtTokens; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]); return new self($array[self::KEY_PROMPT_TOKENS], $array[self::KEY_COMPLETION_TOKENS], $array[self::KEY_TOTAL_TOKENS], $array[self::KEY_THOUGHT_TOKENS] ?? null); } } Candidate.php000064400000006647152213727320007152 0ustar00 */ class Candidate extends AbstractDataTransferObject { public const KEY_MESSAGE = 'message'; public const KEY_FINISH_REASON = 'finishReason'; /** * @var Message The generated message. */ private Message $message; /** * @var FinishReasonEnum The reason generation stopped. */ private FinishReasonEnum $finishReason; /** * Constructor. * * @since 0.1.0 * * @param Message $message The generated message. * @param FinishReasonEnum $finishReason The reason generation stopped. */ public function __construct(Message $message, FinishReasonEnum $finishReason) { if (!$message->getRole()->isModel()) { throw new InvalidArgumentException('Message must be a model message.'); } $this->message = $message; $this->finishReason = $finishReason; } /** * Gets the generated message. * * @since 0.1.0 * * @return Message The message. */ public function getMessage(): Message { return $this->message; } /** * Gets the finish reason. * * @since 0.1.0 * * @return FinishReasonEnum The finish reason. */ public function getFinishReason(): FinishReasonEnum { return $this->finishReason; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_MESSAGE => Message::getJsonSchema(), self::KEY_FINISH_REASON => ['type' => 'string', 'enum' => FinishReasonEnum::getValues(), 'description' => 'The reason generation stopped.']], 'required' => [self::KEY_MESSAGE, self::KEY_FINISH_REASON]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return CandidateArrayShape */ public function toArray(): array { return [self::KEY_MESSAGE => $this->message->toArray(), self::KEY_FINISH_REASON => $this->finishReason->value]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_MESSAGE, self::KEY_FINISH_REASON]); $messageData = $array[self::KEY_MESSAGE]; return new self(Message::fromArray($messageData), FinishReasonEnum::from($array[self::KEY_FINISH_REASON])); } /** * Performs a deep clone of the candidate. * * This method ensures that the message object is cloned to prevent * modifications to the cloned candidate from affecting the original. * * @since 0.4.2 */ public function __clone() { $this->message = clone $this->message; } } ProviderModelsMetadata.php000064400000010144152213730400011651 0ustar00 * } * * @extends AbstractDataTransferObject */ class ProviderModelsMetadata extends AbstractDataTransferObject { public const KEY_PROVIDER = 'provider'; public const KEY_MODELS = 'models'; /** * @var ProviderMetadata The provider metadata. */ protected \WordPress\AiClient\Providers\DTO\ProviderMetadata $provider; /** * @var list The available models. */ protected array $models; /** * Constructor. * * @since 0.1.0 * * @param ProviderMetadata $provider The provider metadata. * @param list $models The available models. * * @throws InvalidArgumentException If models is not a list. */ public function __construct(\WordPress\AiClient\Providers\DTO\ProviderMetadata $provider, array $models) { if (!array_is_list($models)) { throw new InvalidArgumentException('Models must be a list array.'); } $this->provider = $provider; $this->models = $models; } /** * Creates a deep clone of this metadata. * * Clones the provider metadata and all model metadata objects * to ensure the cloned instance is independent of the original. * * @since 0.4.2 */ public function __clone() { // Clone provider metadata $this->provider = clone $this->provider; // Deep clone models array (ModelMetadata has __clone) $clonedModels = []; foreach ($this->models as $model) { $clonedModels[] = clone $model; } $this->models = $clonedModels; } /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProvider(): \WordPress\AiClient\Providers\DTO\ProviderMetadata { return $this->provider; } /** * Gets the available models. * * @since 0.1.0 * * @return list The available models. */ public function getModels(): array { return $this->models; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_PROVIDER => \WordPress\AiClient\Providers\DTO\ProviderMetadata::getJsonSchema(), self::KEY_MODELS => ['type' => 'array', 'items' => ModelMetadata::getJsonSchema(), 'description' => 'The available models for this provider.']], 'required' => [self::KEY_PROVIDER, self::KEY_MODELS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ProviderModelsMetadataArrayShape */ public function toArray(): array { return [self::KEY_PROVIDER => $this->provider->toArray(), self::KEY_MODELS => array_map(static fn(ModelMetadata $model): array => $model->toArray(), $this->models)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_PROVIDER, self::KEY_MODELS]); return new self(\WordPress\AiClient\Providers\DTO\ProviderMetadata::fromArray($array[self::KEY_PROVIDER]), array_map(static fn(array $modelData): ModelMetadata => ModelMetadata::fromArray($modelData), $array[self::KEY_MODELS])); } } ProviderMetadata.php000064400000017364152213730400010520 0ustar00 */ class ProviderMetadata extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_DESCRIPTION = 'description'; public const KEY_TYPE = 'type'; public const KEY_CREDENTIALS_URL = 'credentialsUrl'; public const KEY_AUTHENTICATION_METHOD = 'authenticationMethod'; public const KEY_LOGO_PATH = 'logoPath'; /** * @var string The provider's unique identifier. */ protected string $id; /** * @var string The provider's display name. */ protected string $name; /** * @var string|null The provider's description. */ protected ?string $description; /** * @var ProviderTypeEnum The provider type. */ protected ProviderTypeEnum $type; /** * @var string|null The URL where users can get credentials. */ protected ?string $credentialsUrl; /** * @var RequestAuthenticationMethod|null The authentication method. */ protected ?RequestAuthenticationMethod $authenticationMethod; /** * @var string|null The full path to the provider's logo image file. */ protected ?string $logoPath; /** * Constructor. * * @since 0.1.0 * @since 1.2.0 Added optional $description parameter. * @since 1.3.0 Added optional $logoPath parameter. * * @param string $id The provider's unique identifier. * @param string $name The provider's display name. * @param ProviderTypeEnum $type The provider type. * @param string|null $credentialsUrl The URL where users can get credentials. * @param RequestAuthenticationMethod|null $authenticationMethod The authentication method. * @param string|null $description The provider's description. * @param string|null $logoPath The full path to the provider's logo image file. * @throws InvalidArgumentException If the provider ID contains invalid characters. */ public function __construct(string $id, string $name, ProviderTypeEnum $type, ?string $credentialsUrl = null, ?RequestAuthenticationMethod $authenticationMethod = null, ?string $description = null, ?string $logoPath = null) { if (!preg_match('/^[a-z0-9\-_]+$/', $id)) { throw new InvalidArgumentException(sprintf( // phpcs:ignore Generic.Files.LineLength.TooLong 'Invalid provider ID "%s". Only lowercase alphanumeric characters, hyphens, and underscores are allowed.', $id )); } $this->id = $id; $this->name = $name; $this->description = $description; $this->type = $type; $this->credentialsUrl = $credentialsUrl; $this->authenticationMethod = $authenticationMethod; $this->logoPath = $logoPath; } /** * Gets the provider's unique identifier. * * @since 0.1.0 * * @return string The provider ID. */ public function getId(): string { return $this->id; } /** * Gets the provider's display name. * * @since 0.1.0 * * @return string The provider name. */ public function getName(): string { return $this->name; } /** * Gets the provider's description. * * @since 1.2.0 * * @return string|null The provider description. */ public function getDescription(): ?string { return $this->description; } /** * Gets the provider type. * * @since 0.1.0 * * @return ProviderTypeEnum The provider type. */ public function getType(): ProviderTypeEnum { return $this->type; } /** * Gets the credentials URL. * * @since 0.1.0 * * @return string|null The credentials URL. */ public function getCredentialsUrl(): ?string { return $this->credentialsUrl; } /** * Gets the authentication method. * * @since 0.4.0 * * @return RequestAuthenticationMethod|null The authentication method. */ public function getAuthenticationMethod(): ?RequestAuthenticationMethod { return $this->authenticationMethod; } /** * Gets the full path to the provider's logo image file. * * @since 1.3.0 * * @return string|null The full path to the logo image file. */ public function getLogoPath(): ?string { return $this->logoPath; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description to schema. * @since 1.3.0 Added logoPath to schema. */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The provider\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The provider\'s display name.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'The provider\'s description.'], self::KEY_TYPE => ['type' => 'string', 'enum' => ProviderTypeEnum::getValues(), 'description' => 'The provider type (cloud, server, or client).'], self::KEY_CREDENTIALS_URL => ['type' => 'string', 'description' => 'The URL where users can get credentials.'], self::KEY_AUTHENTICATION_METHOD => ['type' => ['string', 'null'], 'enum' => array_merge(RequestAuthenticationMethod::getValues(), [null]), 'description' => 'The authentication method.'], self::KEY_LOGO_PATH => ['type' => 'string', 'description' => 'The full path to the provider\'s logo image file.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]]; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description to output. * @since 1.3.0 Added logoPath to output. * * @return ProviderMetadataArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description, self::KEY_TYPE => $this->type->value, self::KEY_CREDENTIALS_URL => $this->credentialsUrl, self::KEY_AUTHENTICATION_METHOD => $this->authenticationMethod ? $this->authenticationMethod->value : null, self::KEY_LOGO_PATH => $this->logoPath]; } /** * {@inheritDoc} * * @since 0.1.0 * @since 1.2.0 Added description support. * @since 1.3.0 Added logoPath support. */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]); return new self($array[self::KEY_ID], $array[self::KEY_NAME], ProviderTypeEnum::from($array[self::KEY_TYPE]), $array[self::KEY_CREDENTIALS_URL] ?? null, isset($array[self::KEY_AUTHENTICATION_METHOD]) ? RequestAuthenticationMethod::from($array[self::KEY_AUTHENTICATION_METHOD]) : null, $array[self::KEY_DESCRIPTION] ?? null, $array[self::KEY_LOGO_PATH] ?? null); } } error_log000064400000001272152213730400006460 0ustar00[02-Jul-2026 04:45:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [02-Jul-2026 04:46:47 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/toreyh/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/toreyh/public_html/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 MessagePart.php000064400000025055152213730450007501 0ustar00 */ 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; } } } Message.php000064400000013044152213730450006645 0ustar00 * } * * @extends AbstractDataTransferObject */ 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; } } ModelMessage.php000064400000001544152213730450007630 0ustar00getRole()` * 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); } } UserMessage.php000064400000001454152213730450007506 0ustar00getRole()` * 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); } } GenerativeAiOperation.php000064400000012132152213730460011503 0ustar00 */ class GenerativeAiOperation extends AbstractDataTransferObject implements OperationInterface { public const KEY_ID = 'id'; public const KEY_STATE = 'state'; public const KEY_RESULT = 'result'; /** * @var string Unique identifier for this operation. */ private string $id; /** * @var OperationStateEnum The current state of the operation. */ private OperationStateEnum $state; /** * @var GenerativeAiResult|null The result once the operation completes. */ private ?GenerativeAiResult $result; /** * Constructor. * * @since 0.1.0 * * @param string $id Unique identifier for this operation. * @param OperationStateEnum $state The current state of the operation. * @param GenerativeAiResult|null $result The result once the operation completes. */ public function __construct(string $id, OperationStateEnum $state, ?GenerativeAiResult $result = null) { $this->id = $id; $this->state = $state; $this->result = $result; } /** * Creates a deep clone of this operation. * * Clones the result object if present to ensure the cloned * operation is independent of the original. * The state enum is immutable and can be safely shared. * * @since 0.4.2 */ public function __clone() { // Clone the result if present (GenerativeAiResult has __clone) if ($this->result !== null) { $this->result = clone $this->result; } // Note: $state is an immutable enum and can be safely shared } /** * {@inheritDoc} * * @since 0.1.0 */ public function getId(): string { return $this->id; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getState(): OperationStateEnum { return $this->state; } /** * Gets the operation result. * * @since 0.1.0 * * @return GenerativeAiResult|null The result or null if not yet complete. */ public function getResult(): ?GenerativeAiResult { return $this->result; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['oneOf' => [ // Succeeded state - has result ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'const' => OperationStateEnum::succeeded()->value], self::KEY_RESULT => GenerativeAiResult::getJsonSchema()], 'required' => [self::KEY_ID, self::KEY_STATE, self::KEY_RESULT], 'additionalProperties' => \false], // All other states - no result ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'enum' => [OperationStateEnum::starting()->value, OperationStateEnum::processing()->value, OperationStateEnum::failed()->value, OperationStateEnum::canceled()->value], 'description' => 'The current state of the operation.']], 'required' => [self::KEY_ID, self::KEY_STATE], 'additionalProperties' => \false], ]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return GenerativeAiOperationArrayShape */ public function toArray(): array { $data = [self::KEY_ID => $this->id, self::KEY_STATE => $this->state->value]; if ($this->result !== null) { $data[self::KEY_RESULT] = $this->result->toArray(); } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_STATE]); $state = OperationStateEnum::from($array[self::KEY_STATE]); if ($state->isSucceeded()) { // If the operation has succeeded, it must have a result static::validateFromArrayData($array, [self::KEY_RESULT]); } $result = null; if (isset($array[self::KEY_RESULT])) { $result = GenerativeAiResult::fromArray($array[self::KEY_RESULT]); } return new self($array[self::KEY_ID], $state, $result); } } File.php000064400000032326152213730500006140 0ustar00 */ 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; } } WebSearch.php000064400000005502152214217430007123 0ustar00 */ 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.php000064400000007133152214217430007643 0ustar00 */ 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); } } FunctionResponse.php000064400000007313152214217430010566 0ustar00 */ 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]); } } FunctionDeclaration.php000064400000007005152214217430011213 0ustar00 * } * * @extends AbstractDataTransferObject */ 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|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|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|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); } }