File manager - Edit - /home/danvrahu/kingstaroilgas.com/wp-includes/Requests/library/php-ai-client.tar
Back
autoload.php 0000644 00000002602 15212525755 0007076 0 ustar 00 <?php /** * Autoloader for the bundled PHP AI Client library. * * This file is generated by tools/php-ai-client/installer.sh. * Do not edit directly. * * @package WordPress * @subpackage AI * @since 7.0.0 */ spl_autoload_register( static function ( $class_name ) { // Namespace prefix for the AI client. $client_prefix = 'WordPress\\AiClient\\'; $client_prefix_len = 19; // strlen( 'WordPress\\AiClient\\' ) // Namespace prefix for scoped dependencies (includes Psr\*, Http\*, etc.). $scoped_prefix = 'WordPress\\AiClientDependencies\\'; $scoped_prefix_len = 31; // strlen( 'WordPress\\AiClientDependencies\\' ) $base_dir = __DIR__; // 1. WordPress\AiClient\* → src/ if ( 0 === strncmp( $class_name, $client_prefix, $client_prefix_len ) ) { $relative_class = substr( $class_name, $client_prefix_len ); $file = $base_dir . '/src/' . str_replace( '\\', '/', $relative_class ) . '.php'; if ( file_exists( $file ) ) { require $file; } return; } // 2. WordPress\AiClientDependencies\* → third-party/ (strip prefix). if ( 0 === strncmp( $class_name, $scoped_prefix, $scoped_prefix_len ) ) { $relative_class = substr( $class_name, $scoped_prefix_len ); $file = $base_dir . '/third-party/' . str_replace( '\\', '/', $relative_class ) . '.php'; if ( file_exists( $file ) ) { require $file; } return; } } ); third-party/Nyholm/Psr7/Stream.php 0000644 00000025555 15212525755 0013105 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class Stream implements StreamInterface { use StreamTrait; /** @var resource|null A resource reference */ private $stream; /** @var bool */ private $seekable; /** @var bool */ private $readable; /** @var bool */ private $writable; /** @var array|mixed|void|bool|null */ private $uri; /** @var int|null */ private $size; /** @var array Hash of readable and writable stream types */ private const READ_WRITE_HASH = ['read' => ['r' => \true, 'w+' => \true, 'r+' => \true, 'x+' => \true, 'c+' => \true, 'rb' => \true, 'w+b' => \true, 'r+b' => \true, 'x+b' => \true, 'c+b' => \true, 'rt' => \true, 'w+t' => \true, 'r+t' => \true, 'x+t' => \true, 'c+t' => \true, 'a+' => \true], 'write' => ['w' => \true, 'w+' => \true, 'rw' => \true, 'r+' => \true, 'x+' => \true, 'c+' => \true, 'wb' => \true, 'w+b' => \true, 'r+b' => \true, 'x+b' => \true, 'c+b' => \true, 'w+t' => \true, 'r+t' => \true, 'x+t' => \true, 'c+t' => \true, 'a' => \true, 'a+' => \true]]; /** * @param resource $body */ public function __construct($body) { if (!\is_resource($body)) { throw new \InvalidArgumentException('First argument to Stream::__construct() must be resource'); } $this->stream = $body; $meta = \stream_get_meta_data($this->stream); $this->seekable = $meta['seekable'] && 0 === \fseek($this->stream, 0, \SEEK_CUR); $this->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]); $this->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]); } /** * Creates a new PSR-7 stream. * * @param string|resource|StreamInterface $body * * @throws \InvalidArgumentException */ public static function create($body = ''): StreamInterface { if ($body instanceof StreamInterface) { return $body; } if (\is_string($body)) { if (200000 <= \strlen($body)) { $body = self::openZvalStream($body); } else { $resource = \fopen('php://memory', 'r+'); \fwrite($resource, $body); \fseek($resource, 0); $body = $resource; } } if (!\is_resource($body)) { throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface'); } return new self($body); } /** * Closes the stream when the destructed. */ public function __destruct() { $this->close(); } public function close(): void { if (isset($this->stream)) { if (\is_resource($this->stream)) { \fclose($this->stream); } $this->detach(); } } public function detach() { if (!isset($this->stream)) { return null; } $result = $this->stream; unset($this->stream); $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = \false; return $result; } private function getUri() { if (\false !== $this->uri) { $this->uri = $this->getMetadata('uri') ?? \false; } return $this->uri; } public function getSize(): ?int { if (null !== $this->size) { return $this->size; } if (!isset($this->stream)) { return null; } // Clear the stat cache if the stream has a URI if ($uri = $this->getUri()) { \clearstatcache(\true, $uri); } $stats = \fstat($this->stream); if (isset($stats['size'])) { $this->size = $stats['size']; return $this->size; } return null; } public function tell(): int { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (\false === $result = @\ftell($this->stream)) { throw new \RuntimeException('Unable to determine stream position: ' . (\error_get_last()['message'] ?? '')); } return $result; } public function eof(): bool { return !isset($this->stream) || \feof($this->stream); } public function isSeekable(): bool { return $this->seekable; } public function seek($offset, $whence = \SEEK_SET): void { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->seekable) { throw new \RuntimeException('Stream is not seekable'); } if (-1 === \fseek($this->stream, $offset, $whence)) { throw new \RuntimeException('Unable to seek to stream position "' . $offset . '" with whence ' . \var_export($whence, \true)); } } public function rewind(): void { $this->seek(0); } public function isWritable(): bool { return $this->writable; } public function write($string): int { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->writable) { throw new \RuntimeException('Cannot write to a non-writable stream'); } // We can't know the size after writing anything $this->size = null; if (\false === $result = @\fwrite($this->stream, $string)) { throw new \RuntimeException('Unable to write to stream: ' . (\error_get_last()['message'] ?? '')); } return $result; } public function isReadable(): bool { return $this->readable; } public function read($length): string { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } if (!$this->readable) { throw new \RuntimeException('Cannot read from non-readable stream'); } if (\false === $result = @\fread($this->stream, $length)) { throw new \RuntimeException('Unable to read from stream: ' . (\error_get_last()['message'] ?? '')); } return $result; } public function getContents(): string { if (!isset($this->stream)) { throw new \RuntimeException('Stream is detached'); } $exception = null; \set_error_handler(static function ($type, $message) use (&$exception) { throw $exception = new \RuntimeException('Unable to read stream contents: ' . $message); }); try { return \stream_get_contents($this->stream); } catch (\Throwable $e) { throw $e === $exception ? $e : new \RuntimeException('Unable to read stream contents: ' . $e->getMessage(), 0, $e); } finally { \restore_error_handler(); } } /** * @return mixed */ public function getMetadata($key = null) { if (null !== $key && !\is_string($key)) { throw new \InvalidArgumentException('Metadata key must be a string'); } if (!isset($this->stream)) { return $key ? null : []; } $meta = \stream_get_meta_data($this->stream); if (null === $key) { return $meta; } return $meta[$key] ?? null; } private static function openZvalStream(string $body) { static $wrapper; $wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class { public $context; private $data; private $position = 0; public function stream_open(): bool { $this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data']; \stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null); return \true; } public function stream_read(int $count): string { $result = \substr($this->data, $this->position, $count); $this->position += \strlen($result); return $result; } public function stream_write(string $data): int { $this->data = \substr_replace($this->data, $data, $this->position, \strlen($data)); $this->position += \strlen($data); return \strlen($data); } public function stream_tell(): int { return $this->position; } public function stream_eof(): bool { return \strlen($this->data) <= $this->position; } public function stream_stat(): array { return [ 'mode' => 33206, // POSIX_S_IFREG | 0666 'nlink' => 1, 'rdev' => -1, 'size' => \strlen($this->data), 'blksize' => -1, 'blocks' => -1, ]; } public function stream_seek(int $offset, int $whence): bool { if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) { $this->position = $offset; } elseif (\SEEK_CUR === $whence && 0 <= $offset) { $this->position += $offset; } elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) { $this->position = $offset; } else { return \false; } return \true; } public function stream_set_option(): bool { return \true; } public function stream_truncate(int $new_size): bool { if ($new_size) { $this->data = \substr($this->data, 0, $new_size); $this->position = \min($this->position, $new_size); } else { $this->data = ''; $this->position = 0; } return \true; } })); $context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]); if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', \false, $context)) { \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper); $stream = \fopen('Nyholm-Psr7-Zval://', 'r+', \false, $context); } return $stream; } } third-party/Nyholm/Psr7/RequestTrait.php 0000644 00000005741 15212525755 0014301 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise */ trait RequestTrait { /** @var string */ private $method; /** @var string|null */ private $requestTarget; /** @var UriInterface|null */ private $uri; public function getRequestTarget(): string { if (null !== $this->requestTarget) { return $this->requestTarget; } if ('' === $target = $this->uri->getPath()) { $target = '/'; } if ('' !== $this->uri->getQuery()) { $target .= '?' . $this->uri->getQuery(); } return $target; } /** * @return static */ public function withRequestTarget($requestTarget): RequestInterface { if (!\is_string($requestTarget)) { throw new \InvalidArgumentException('Request target must be a string'); } if (\preg_match('#\s#', $requestTarget)) { throw new \InvalidArgumentException('Invalid request target provided; cannot contain whitespace'); } $new = clone $this; $new->requestTarget = $requestTarget; return $new; } public function getMethod(): string { return $this->method; } /** * @return static */ public function withMethod($method): RequestInterface { if (!\is_string($method)) { throw new \InvalidArgumentException('Method must be a string'); } $new = clone $this; $new->method = $method; return $new; } public function getUri(): UriInterface { return $this->uri; } /** * @return static */ public function withUri(UriInterface $uri, $preserveHost = \false): RequestInterface { if ($uri === $this->uri) { return $this; } $new = clone $this; $new->uri = $uri; if (!$preserveHost || !$this->hasHeader('Host')) { $new->updateHostFromUri(); } return $new; } private function updateHostFromUri(): void { if ('' === $host = $this->uri->getHost()) { return; } if (null !== $port = $this->uri->getPort()) { $host .= ':' . $port; } if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else { $this->headerNames['host'] = $header = 'Host'; } // Ensure Host is the first header. // See: http://tools.ietf.org/html/rfc7230#section-5.4 $this->headers = [$header => [$host]] + $this->headers; } } third-party/Nyholm/Psr7/ServerRequest.php 0000644 00000011715 15212525755 0014462 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class ServerRequest implements ServerRequestInterface { use MessageTrait; use RequestTrait; /** @var array */ private $attributes = []; /** @var array */ private $cookieParams = []; /** @var array|object|null */ private $parsedBody; /** @var array */ private $queryParams = []; /** @var array */ private $serverParams; /** @var UploadedFileInterface[] */ private $uploadedFiles = []; /** * @param string $method HTTP method * @param string|UriInterface $uri URI * @param array $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version * @param array $serverParams Typically the $_SERVER superglobal */ public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1', array $serverParams = []) { $this->serverParams = $serverParams; if (!$uri instanceof UriInterface) { $uri = new Uri($uri); } $this->method = $method; $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; \parse_str($uri->getQuery(), $this->queryParams); if (!$this->hasHeader('Host')) { $this->updateHostFromUri(); } // If we got no body, defer initialization of the stream until ServerRequest::getBody() if ('' !== $body && null !== $body) { $this->stream = Stream::create($body); } } public function getServerParams(): array { return $this->serverParams; } public function getUploadedFiles(): array { return $this->uploadedFiles; } /** * @return static */ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface { $new = clone $this; $new->uploadedFiles = $uploadedFiles; return $new; } public function getCookieParams(): array { return $this->cookieParams; } /** * @return static */ public function withCookieParams(array $cookies): ServerRequestInterface { $new = clone $this; $new->cookieParams = $cookies; return $new; } public function getQueryParams(): array { return $this->queryParams; } /** * @return static */ public function withQueryParams(array $query): ServerRequestInterface { $new = clone $this; $new->queryParams = $query; return $new; } /** * @return array|object|null */ public function getParsedBody() { return $this->parsedBody; } /** * @return static */ public function withParsedBody($data): ServerRequestInterface { if (!\is_array($data) && !\is_object($data) && null !== $data) { throw new \InvalidArgumentException('First parameter to withParsedBody MUST be object, array or null'); } $new = clone $this; $new->parsedBody = $data; return $new; } public function getAttributes(): array { return $this->attributes; } /** * @return mixed */ public function getAttribute($attribute, $default = null) { if (!\is_string($attribute)) { throw new \InvalidArgumentException('Attribute name must be a string'); } if (\false === \array_key_exists($attribute, $this->attributes)) { return $default; } return $this->attributes[$attribute]; } /** * @return static */ public function withAttribute($attribute, $value): ServerRequestInterface { if (!\is_string($attribute)) { throw new \InvalidArgumentException('Attribute name must be a string'); } $new = clone $this; $new->attributes[$attribute] = $value; return $new; } /** * @return static */ public function withoutAttribute($attribute): ServerRequestInterface { if (!\is_string($attribute)) { throw new \InvalidArgumentException('Attribute name must be a string'); } if (\false === \array_key_exists($attribute, $this->attributes)) { return $this; } $new = clone $this; unset($new->attributes[$attribute]); return $new; } } third-party/Nyholm/Psr7/Uri.php 0000644 00000023011 15212525755 0012372 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; /** * PSR-7 URI implementation. * * @author Michael Dowling * @author Tobias Schultze * @author Matthew Weier O'Phinney * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class Uri implements UriInterface { private const SCHEMES = ['http' => 80, 'https' => 443]; private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; private const CHAR_GEN_DELIMS = ':\/\?#\[\]@'; /** @var string Uri scheme. */ private $scheme = ''; /** @var string Uri user info. */ private $userInfo = ''; /** @var string Uri host. */ private $host = ''; /** @var int|null Uri port. */ private $port; /** @var string Uri path. */ private $path = ''; /** @var string Uri query string. */ private $query = ''; /** @var string Uri fragment. */ private $fragment = ''; public function __construct(string $uri = '') { if ('' !== $uri) { if (\false === $parts = \parse_url($uri)) { throw new \InvalidArgumentException(\sprintf('Unable to parse URI: "%s"', $uri)); } // Apply parse_url parts to a URI. $this->scheme = isset($parts['scheme']) ? \strtr($parts['scheme'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; $this->userInfo = $parts['user'] ?? ''; $this->host = isset($parts['host']) ? \strtr($parts['host'], 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') : ''; $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null; $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : ''; $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : ''; if (isset($parts['pass'])) { $this->userInfo .= ':' . $parts['pass']; } } } public function __toString(): string { return self::createUriString($this->scheme, $this->getAuthority(), $this->path, $this->query, $this->fragment); } public function getScheme(): string { return $this->scheme; } public function getAuthority(): string { if ('' === $this->host) { return ''; } $authority = $this->host; if ('' !== $this->userInfo) { $authority = $this->userInfo . '@' . $authority; } if (null !== $this->port) { $authority .= ':' . $this->port; } return $authority; } public function getUserInfo(): string { return $this->userInfo; } public function getHost(): string { return $this->host; } public function getPort(): ?int { return $this->port; } public function getPath(): string { $path = $this->path; if ('' !== $path && '/' !== $path[0]) { if ('' !== $this->host) { // If the path is rootless and an authority is present, the path MUST be prefixed by "/" $path = '/' . $path; } } elseif (isset($path[1]) && '/' === $path[1]) { // If the path is starting with more than one "/", the // starting slashes MUST be reduced to one. $path = '/' . \ltrim($path, '/'); } return $path; } public function getQuery(): string { return $this->query; } public function getFragment(): string { return $this->fragment; } /** * @return static */ public function withScheme($scheme): UriInterface { if (!\is_string($scheme)) { throw new \InvalidArgumentException('Scheme must be a string'); } if ($this->scheme === $scheme = \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { return $this; } $new = clone $this; $new->scheme = $scheme; $new->port = $new->filterPort($new->port); return $new; } /** * @return static */ public function withUserInfo($user, $password = null): UriInterface { if (!\is_string($user)) { throw new \InvalidArgumentException('User must be a string'); } $info = \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $user); if (null !== $password && '' !== $password) { if (!\is_string($password)) { throw new \InvalidArgumentException('Password must be a string'); } $info .= ':' . \preg_replace_callback('/[' . self::CHAR_GEN_DELIMS . self::CHAR_SUB_DELIMS . ']++/', [__CLASS__, 'rawurlencodeMatchZero'], $password); } if ($this->userInfo === $info) { return $this; } $new = clone $this; $new->userInfo = $info; return $new; } /** * @return static */ public function withHost($host): UriInterface { if (!\is_string($host)) { throw new \InvalidArgumentException('Host must be a string'); } if ($this->host === $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')) { return $this; } $new = clone $this; $new->host = $host; return $new; } /** * @return static */ public function withPort($port): UriInterface { if ($this->port === $port = $this->filterPort($port)) { return $this; } $new = clone $this; $new->port = $port; return $new; } /** * @return static */ public function withPath($path): UriInterface { if ($this->path === $path = $this->filterPath($path)) { return $this; } $new = clone $this; $new->path = $path; return $new; } /** * @return static */ public function withQuery($query): UriInterface { if ($this->query === $query = $this->filterQueryAndFragment($query)) { return $this; } $new = clone $this; $new->query = $query; return $new; } /** * @return static */ public function withFragment($fragment): UriInterface { if ($this->fragment === $fragment = $this->filterQueryAndFragment($fragment)) { return $this; } $new = clone $this; $new->fragment = $fragment; return $new; } /** * Create a URI string from its various parts. */ private static function createUriString(string $scheme, string $authority, string $path, string $query, string $fragment): string { $uri = ''; if ('' !== $scheme) { $uri .= $scheme . ':'; } if ('' !== $authority) { $uri .= '//' . $authority; } if ('' !== $path) { if ('/' !== $path[0]) { if ('' !== $authority) { // If the path is rootless and an authority is present, the path MUST be prefixed by "/" $path = '/' . $path; } } elseif (isset($path[1]) && '/' === $path[1]) { if ('' === $authority) { // If the path is starting with more than one "/" and no authority is present, the // starting slashes MUST be reduced to one. $path = '/' . \ltrim($path, '/'); } } $uri .= $path; } if ('' !== $query) { $uri .= '?' . $query; } if ('' !== $fragment) { $uri .= '#' . $fragment; } return $uri; } /** * Is a given port non-standard for the current scheme? */ private static function isNonStandardPort(string $scheme, int $port): bool { return !isset(self::SCHEMES[$scheme]) || $port !== self::SCHEMES[$scheme]; } private function filterPort($port): ?int { if (null === $port) { return null; } $port = (int) $port; if (0 > $port || 0xffff < $port) { throw new \InvalidArgumentException(\sprintf('Invalid port: %d. Must be between 0 and 65535', $port)); } return self::isNonStandardPort($this->scheme, $port) ? $port : null; } private function filterPath($path): string { if (!\is_string($path)) { throw new \InvalidArgumentException('Path must be a string'); } return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $path); } private function filterQueryAndFragment($str): string { if (!\is_string($str)) { throw new \InvalidArgumentException('Query and fragment must be a string'); } return \preg_replace_callback('/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [__CLASS__, 'rawurlencodeMatchZero'], $str); } private static function rawurlencodeMatchZero(array $match): string { return \rawurlencode($match[0]); } } third-party/Nyholm/Psr7/StreamTrait.php 0000644 00000003074 15212525755 0014101 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Symfony\Component\Debug\ErrorHandler as SymfonyLegacyErrorHandler; use WordPress\AiClientDependencies\Symfony\Component\ErrorHandler\ErrorHandler as SymfonyErrorHandler; if (\PHP_VERSION_ID >= 70400 || (new \ReflectionMethod(StreamInterface::class, '__toString'))->hasReturnType()) { /** * @internal */ trait StreamTrait { public function __toString(): string { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } } } else { /** * @internal */ trait StreamTrait { /** * @return string */ public function __toString() { try { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } catch (\Throwable $e) { if (\is_array($errorHandler = \set_error_handler('var_dump'))) { $errorHandler = $errorHandler[0] ?? null; } \restore_error_handler(); if ($e instanceof \Error || $errorHandler instanceof SymfonyErrorHandler || $errorHandler instanceof SymfonyLegacyErrorHandler) { return \trigger_error((string) $e, \E_USER_ERROR); } return ''; } } } } third-party/Nyholm/Psr7/error_log 0000644 00000006530 15212525755 0013046 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/ServerRequest.php on line 17 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\UriInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Uri.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Uri.php on line 18 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/UploadedFile.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/UploadedFile.php on line 15 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\StreamTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Stream.php on line 14 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Response.php on line 15 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Request.php on line 15 [11-Jun-2026 08:15:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/UploadedFile.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/UploadedFile.php on line 15 [11-Jun-2026 08:15:46 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/ServerRequest.php on line 17 [11-Jun-2026 08:16:34 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\StreamTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Stream.php on line 14 [11-Jun-2026 08:18:26 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\UriInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Uri.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Uri.php on line 18 [11-Jun-2026 09:35:08 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Response.php on line 15 [11-Jun-2026 10:37:03 UTC] PHP Fatal error: Trait "WordPress\AiClientDependencies\Nyholm\Psr7\MessageTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Request.php on line 15 third-party/Nyholm/Psr7/MessageTrait.php 0000644 00000016452 15212525755 0014236 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\MessageInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; /** * Trait implementing functionality common to requests and responses. * * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @internal should not be used outside of Nyholm/Psr7 as it does not fall under our BC promise */ trait MessageTrait { /** @var array Map of all registered headers, as original name => array of values */ private $headers = []; /** @var array Map of lowercase header name => original name at registration */ private $headerNames = []; /** @var string */ private $protocol = '1.1'; /** @var StreamInterface|null */ private $stream; public function getProtocolVersion(): string { return $this->protocol; } /** * @return static */ public function withProtocolVersion($version): MessageInterface { if (!\is_scalar($version)) { throw new \InvalidArgumentException('Protocol version must be a string'); } if ($this->protocol === $version) { return $this; } $new = clone $this; $new->protocol = (string) $version; return $new; } public function getHeaders(): array { return $this->headers; } public function hasHeader($header): bool { return isset($this->headerNames[\strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')]); } public function getHeader($header): array { if (!\is_string($header)) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); } $header = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); if (!isset($this->headerNames[$header])) { return []; } $header = $this->headerNames[$header]; return $this->headers[$header]; } public function getHeaderLine($header): string { return \implode(', ', $this->getHeader($header)); } /** * @return static */ public function withHeader($header, $value): MessageInterface { $value = $this->validateAndTrimHeader($header, $value); $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); $new = clone $this; if (isset($new->headerNames[$normalized])) { unset($new->headers[$new->headerNames[$normalized]]); } $new->headerNames[$normalized] = $header; $new->headers[$header] = $value; return $new; } /** * @return static */ public function withAddedHeader($header, $value): MessageInterface { if (!\is_string($header) || '' === $header) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); } $new = clone $this; $new->setHeaders([$header => $value]); return $new; } /** * @return static */ public function withoutHeader($header): MessageInterface { if (!\is_string($header)) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); } $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); if (!isset($this->headerNames[$normalized])) { return $this; } $header = $this->headerNames[$normalized]; $new = clone $this; unset($new->headers[$header], $new->headerNames[$normalized]); return $new; } public function getBody(): StreamInterface { if (null === $this->stream) { $this->stream = Stream::create(''); } return $this->stream; } /** * @return static */ public function withBody(StreamInterface $body): MessageInterface { if ($body === $this->stream) { return $this; } $new = clone $this; $new->stream = $body; return $new; } private function setHeaders(array $headers): void { foreach ($headers as $header => $value) { if (\is_int($header)) { // If a header name was set to a numeric string, PHP will cast the key to an int. // We must cast it back to a string in order to comply with validation. $header = (string) $header; } $value = $this->validateAndTrimHeader($header, $value); $normalized = \strtr($header, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); if (isset($this->headerNames[$normalized])) { $header = $this->headerNames[$normalized]; $this->headers[$header] = \array_merge($this->headers[$header], $value); } else { $this->headerNames[$normalized] = $header; $this->headers[$header] = $value; } } } /** * Make sure the header complies with RFC 7230. * * Header names must be a non-empty string consisting of token characters. * * Header values must be strings consisting of visible characters with all optional * leading and trailing whitespace stripped. This method will always strip such * optional whitespace. Note that the method does not allow folding whitespace within * the values as this was deprecated for almost all instances by the RFC. * * header-field = field-name ":" OWS field-value OWS * field-name = 1*( "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" * / "_" / "`" / "|" / "~" / %x30-39 / ( %x41-5A / %x61-7A ) ) * OWS = *( SP / HTAB ) * field-value = *( ( %x21-7E / %x80-FF ) [ 1*( SP / HTAB ) ( %x21-7E / %x80-FF ) ] ) * * @see https://tools.ietf.org/html/rfc7230#section-3.2.4 */ private function validateAndTrimHeader($header, $values): array { if (!\is_string($header) || 1 !== \preg_match("@^[!#\$%&'*+.^_`|~0-9A-Za-z-]+\$@D", $header)) { throw new \InvalidArgumentException('Header name must be an RFC 7230 compatible string'); } if (!\is_array($values)) { // This is simple, just one value. if (!\is_numeric($values) && !\is_string($values) || 1 !== \preg_match("@^[ \t!-~\x80-\xff]*\$@", (string) $values)) { throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); } return [\trim((string) $values, " \t")]; } if (empty($values)) { throw new \InvalidArgumentException('Header values must be a string or an array of strings, empty array given'); } // Assert Non empty array $returnValues = []; foreach ($values as $v) { if (!\is_numeric($v) && !\is_string($v) || 1 !== \preg_match("@^[ \t!-~\x80-\xff]*\$@D", (string) $v)) { throw new \InvalidArgumentException('Header values must be RFC 7230 compatible strings'); } $returnValues[] = \trim((string) $v, " \t"); } return $returnValues; } } third-party/Nyholm/Psr7/Request.php 0000644 00000002740 15212525755 0013271 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; /** * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class Request implements RequestInterface { use MessageTrait; use RequestTrait; /** * @param string $method HTTP method * @param string|UriInterface $uri URI * @param array $headers Request headers * @param string|resource|StreamInterface|null $body Request body * @param string $version Protocol version */ public function __construct(string $method, $uri, array $headers = [], $body = null, string $version = '1.1') { if (!$uri instanceof UriInterface) { $uri = new Uri($uri); } $this->method = $method; $this->uri = $uri; $this->setHeaders($headers); $this->protocol = $version; if (!$this->hasHeader('Host')) { $this->updateHostFromUri(); } // If we got no body, defer initialization of the stream until Request::getBody() if ('' !== $body && null !== $body) { $this->stream = Stream::create($body); } } } third-party/Nyholm/Psr7/Response.php 0000644 00000010357 15212525755 0013442 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class Response implements ResponseInterface { use MessageTrait; /** @var array Map of standard HTTP status code/reason phrases */ private const PHRASES = [100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-status', 208 => 'Already Reported', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 306 => 'Switch Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', 418 => 'I\'m a teapot', 422 => 'Unprocessable Entity', 423 => 'Locked', 424 => 'Failed Dependency', 425 => 'Unordered Collection', 426 => 'Upgrade Required', 428 => 'Precondition Required', 429 => 'Too Many Requests', 431 => 'Request Header Fields Too Large', 451 => 'Unavailable For Legal Reasons', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', 508 => 'Loop Detected', 511 => 'Network Authentication Required']; /** @var string */ private $reasonPhrase = ''; /** @var int */ private $statusCode; /** * @param int $status Status code * @param array $headers Response headers * @param string|resource|StreamInterface|null $body Response body * @param string $version Protocol version * @param string|null $reason Reason phrase (when empty a default will be used based on the status code) */ public function __construct(int $status = 200, array $headers = [], $body = null, string $version = '1.1', ?string $reason = null) { // If we got no body, defer initialization of the stream until Response::getBody() if ('' !== $body && null !== $body) { $this->stream = Stream::create($body); } $this->statusCode = $status; $this->setHeaders($headers); if (null === $reason && isset(self::PHRASES[$this->statusCode])) { $this->reasonPhrase = self::PHRASES[$status]; } else { $this->reasonPhrase = $reason ?? ''; } $this->protocol = $version; } public function getStatusCode(): int { return $this->statusCode; } public function getReasonPhrase(): string { return $this->reasonPhrase; } /** * @return static */ public function withStatus($code, $reasonPhrase = ''): ResponseInterface { if (!\is_int($code) && !\is_string($code)) { throw new \InvalidArgumentException('Status code has to be an integer'); } $code = (int) $code; if ($code < 100 || $code > 599) { throw new \InvalidArgumentException(\sprintf('Status code has to be an integer between 100 and 599. A status code of %d was given', $code)); } $new = clone $this; $new->statusCode = $code; if ((null === $reasonPhrase || '' === $reasonPhrase) && isset(self::PHRASES[$new->statusCode])) { $reasonPhrase = self::PHRASES[$new->statusCode]; } $new->reasonPhrase = $reasonPhrase; return $new; } } third-party/Nyholm/Psr7/Factory/Psr17Factory.php 0000644 00000007376 15212525755 0015526 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7\Factory; use WordPress\AiClientDependencies\Nyholm\Psr7\Request; use WordPress\AiClientDependencies\Nyholm\Psr7\Response; use WordPress\AiClientDependencies\Nyholm\Psr7\ServerRequest; use WordPress\AiClientDependencies\Nyholm\Psr7\Stream; use WordPress\AiClientDependencies\Nyholm\Psr7\UploadedFile; use WordPress\AiClientDependencies\Nyholm\Psr7\Uri; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; /** * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface { public function createRequest(string $method, $uri): RequestInterface { return new Request($method, $uri); } public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface { if (2 > \func_num_args()) { // This will make the Response class to use a custom reasonPhrase $reasonPhrase = null; } return new Response($code, [], null, '1.1', $reasonPhrase); } public function createStream(string $content = ''): StreamInterface { return Stream::create($content); } public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface { if ('' === $filename) { throw new \RuntimeException('Path cannot be empty'); } if (\false === $resource = @\fopen($filename, $mode)) { if ('' === $mode || \false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], \true)) { throw new \InvalidArgumentException(\sprintf('The mode "%s" is invalid.', $mode)); } throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $filename, \error_get_last()['message'] ?? '')); } return Stream::create($resource); } public function createStreamFromResource($resource): StreamInterface { return Stream::create($resource); } public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface { if (null === $size) { $size = $stream->getSize(); } return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType); } public function createUri(string $uri = ''): UriInterface { return new Uri($uri); } public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface { return new ServerRequest($method, $uri, [], null, '1.1', $serverParams); } } third-party/Nyholm/Psr7/Factory/HttplugFactory.php 0000644 00000004531 15212525755 0016227 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7\Factory; use WordPress\AiClientDependencies\Http\Message\MessageFactory; use WordPress\AiClientDependencies\Http\Message\StreamFactory; use WordPress\AiClientDependencies\Http\Message\UriFactory; use WordPress\AiClientDependencies\Nyholm\Psr7\Request; use WordPress\AiClientDependencies\Nyholm\Psr7\Response; use WordPress\AiClientDependencies\Nyholm\Psr7\Stream; use WordPress\AiClientDependencies\Nyholm\Psr7\Uri; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriInterface; if (!\interface_exists(MessageFactory::class)) { throw new \LogicException('You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead'); } @\trigger_error('Class "Nyholm\Psr7\Factory\HttplugFactory" is deprecated since version 1.8, use "Nyholm\Psr7\Factory\Psr17Factory" instead.', \E_USER_DEPRECATED); /** * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md * * @deprecated since version 1.8, use Psr17Factory instead */ class HttplugFactory implements MessageFactory, StreamFactory, UriFactory { public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface { return new Request($method, $uri, $headers, $body, $protocolVersion); } public function createResponse($statusCode = 200, $reasonPhrase = null, array $headers = [], $body = null, $version = '1.1'): ResponseInterface { return new Response((int) $statusCode, $headers, $body, $version, $reasonPhrase); } public function createStream($body = null): StreamInterface { return Stream::create($body ?? ''); } public function createUri($uri = ''): UriInterface { if ($uri instanceof UriInterface) { return $uri; } return new Uri($uri); } } third-party/Nyholm/Psr7/Factory/error_log 0000644 00000003736 15212525755 0014462 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/Psr17Factory.php:30 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/Psr17Factory.php on line 30 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught LogicException: You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/HttplugFactory.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/HttplugFactory.php on line 18 [11-Jun-2026 10:36:44 UTC] PHP Fatal error: Uncaught LogicException: You cannot use "Nyholm\Psr7\Factory\HttplugFactory" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory". Note that this package is deprecated, use "psr/http-factory" instead in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/HttplugFactory.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/HttplugFactory.php on line 18 [11-Jun-2026 11:47:05 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/Psr17Factory.php:30 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Nyholm/Psr7/Factory/Psr17Factory.php on line 30 third-party/Nyholm/Psr7/UploadedFile.php 0000644 00000012650 15212525755 0014177 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Nyholm\Psr7; use WordPress\AiClientDependencies\Psr\Http\Message\StreamInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileInterface; /** * @author Michael Dowling and contributors to guzzlehttp/psr7 * @author Tobias Nyholm <tobias.nyholm@gmail.com> * @author Martijn van der Ven <martijn@vanderven.se> * * @final This class should never be extended. See https://github.com/Nyholm/psr7/blob/master/doc/final.md */ class UploadedFile implements UploadedFileInterface { /** @var array */ private const ERRORS = [\UPLOAD_ERR_OK => 1, \UPLOAD_ERR_INI_SIZE => 1, \UPLOAD_ERR_FORM_SIZE => 1, \UPLOAD_ERR_PARTIAL => 1, \UPLOAD_ERR_NO_FILE => 1, \UPLOAD_ERR_NO_TMP_DIR => 1, \UPLOAD_ERR_CANT_WRITE => 1, \UPLOAD_ERR_EXTENSION => 1]; /** @var string */ private $clientFilename; /** @var string */ private $clientMediaType; /** @var int */ private $error; /** @var string|null */ private $file; /** @var bool */ private $moved = \false; /** @var int */ private $size; /** @var StreamInterface|null */ private $stream; /** * @param StreamInterface|string|resource $streamOrFile * @param int $size * @param int $errorStatus * @param string|null $clientFilename * @param string|null $clientMediaType */ public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null) { if (\false === \is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) { throw new \InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants'); } if (\false === \is_int($size)) { throw new \InvalidArgumentException('Upload file size must be an integer'); } if (null !== $clientFilename && !\is_string($clientFilename)) { throw new \InvalidArgumentException('Upload file client filename must be a string or null'); } if (null !== $clientMediaType && !\is_string($clientMediaType)) { throw new \InvalidArgumentException('Upload file client media type must be a string or null'); } $this->error = $errorStatus; $this->size = $size; $this->clientFilename = $clientFilename; $this->clientMediaType = $clientMediaType; if (\UPLOAD_ERR_OK === $this->error) { // Depending on the value set file or stream variable. if (\is_string($streamOrFile) && '' !== $streamOrFile) { $this->file = $streamOrFile; } elseif (\is_resource($streamOrFile)) { $this->stream = Stream::create($streamOrFile); } elseif ($streamOrFile instanceof StreamInterface) { $this->stream = $streamOrFile; } else { throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile'); } } } /** * @throws \RuntimeException if is moved or not ok */ private function validateActive(): void { if (\UPLOAD_ERR_OK !== $this->error) { throw new \RuntimeException('Cannot retrieve stream due to upload error'); } if ($this->moved) { throw new \RuntimeException('Cannot retrieve stream after it has already been moved'); } } public function getStream(): StreamInterface { $this->validateActive(); if ($this->stream instanceof StreamInterface) { return $this->stream; } if (\false === $resource = @\fopen($this->file, 'r')) { throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $this->file, \error_get_last()['message'] ?? '')); } return Stream::create($resource); } public function moveTo($targetPath): void { $this->validateActive(); if (!\is_string($targetPath) || '' === $targetPath) { throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string'); } if (null !== $this->file) { $this->moved = 'cli' === \PHP_SAPI ? @\rename($this->file, $targetPath) : @\move_uploaded_file($this->file, $targetPath); if (\false === $this->moved) { throw new \RuntimeException(\sprintf('Uploaded file could not be moved to "%s": %s', $targetPath, \error_get_last()['message'] ?? '')); } } else { $stream = $this->getStream(); if ($stream->isSeekable()) { $stream->rewind(); } if (\false === $resource = @\fopen($targetPath, 'w')) { throw new \RuntimeException(\sprintf('The file "%s" cannot be opened: %s', $targetPath, \error_get_last()['message'] ?? '')); } $dest = Stream::create($resource); while (!$stream->eof()) { if (!$dest->write($stream->read(1048576))) { break; } } $this->moved = \true; } } public function getSize(): int { return $this->size; } public function getError(): int { return $this->error; } public function getClientFilename(): ?string { return $this->clientFilename; } public function getClientMediaType(): ?string { return $this->clientMediaType; } } third-party/Http/Discovery/Strategy/CommonClassesStrategy.php 0000644 00000020522 15212525755 0020517 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Strategy; use WordPress\AiClientDependencies\GuzzleHttp\Client as GuzzleHttp; use WordPress\AiClientDependencies\GuzzleHttp\Promise\Promise; use WordPress\AiClientDependencies\GuzzleHttp\Psr7\Request as GuzzleRequest; use WordPress\AiClientDependencies\Http\Adapter\Artax\Client as Artax; use WordPress\AiClientDependencies\Http\Adapter\Buzz\Client as Buzz; use WordPress\AiClientDependencies\Http\Adapter\Cake\Client as Cake; use WordPress\AiClientDependencies\Http\Adapter\Guzzle5\Client as Guzzle5; use WordPress\AiClientDependencies\Http\Adapter\Guzzle6\Client as Guzzle6; use WordPress\AiClientDependencies\Http\Adapter\Guzzle7\Client as Guzzle7; use WordPress\AiClientDependencies\Http\Adapter\React\Client as React; use WordPress\AiClientDependencies\Http\Client\Curl\Client as Curl; use WordPress\AiClientDependencies\Http\Client\HttpAsyncClient; use WordPress\AiClientDependencies\Http\Client\HttpClient; use WordPress\AiClientDependencies\Http\Client\Socket\Client as Socket; use WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException; use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery; use WordPress\AiClientDependencies\Http\Message\MessageFactory; use WordPress\AiClientDependencies\Http\Message\MessageFactory\DiactorosMessageFactory; use WordPress\AiClientDependencies\Http\Message\MessageFactory\GuzzleMessageFactory; use WordPress\AiClientDependencies\Http\Message\MessageFactory\SlimMessageFactory; use WordPress\AiClientDependencies\Http\Message\StreamFactory; use WordPress\AiClientDependencies\Http\Message\StreamFactory\DiactorosStreamFactory; use WordPress\AiClientDependencies\Http\Message\StreamFactory\GuzzleStreamFactory; use WordPress\AiClientDependencies\Http\Message\StreamFactory\SlimStreamFactory; use WordPress\AiClientDependencies\Http\Message\UriFactory; use WordPress\AiClientDependencies\Http\Message\UriFactory\DiactorosUriFactory; use WordPress\AiClientDependencies\Http\Message\UriFactory\GuzzleUriFactory; use WordPress\AiClientDependencies\Http\Message\UriFactory\SlimUriFactory; use WordPress\AiClientDependencies\Laminas\Diactoros\Request as DiactorosRequest; use WordPress\AiClientDependencies\Nyholm\Psr7\Factory\HttplugFactory as NyholmHttplugFactory; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface as Psr18Client; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface as Psr17RequestFactory; use WordPress\AiClientDependencies\Slim\Http\Request as SlimRequest; use WordPress\AiClientDependencies\Symfony\Component\HttpClient\HttplugClient as SymfonyHttplug; use WordPress\AiClientDependencies\Symfony\Component\HttpClient\Psr18Client as SymfonyPsr18; /** * @internal * * @author Tobias Nyholm <tobias.nyholm@gmail.com> * * Don't miss updating src/Composer/Plugin.php when adding a new supported class. */ final class CommonClassesStrategy implements DiscoveryStrategy { /** * @var array */ private static $classes = [MessageFactory::class => [['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], ['class' => GuzzleMessageFactory::class, 'condition' => [GuzzleRequest::class, GuzzleMessageFactory::class]], ['class' => DiactorosMessageFactory::class, 'condition' => [DiactorosRequest::class, DiactorosMessageFactory::class]], ['class' => SlimMessageFactory::class, 'condition' => [SlimRequest::class, SlimMessageFactory::class]]], StreamFactory::class => [['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], ['class' => GuzzleStreamFactory::class, 'condition' => [GuzzleRequest::class, GuzzleStreamFactory::class]], ['class' => DiactorosStreamFactory::class, 'condition' => [DiactorosRequest::class, DiactorosStreamFactory::class]], ['class' => SlimStreamFactory::class, 'condition' => [SlimRequest::class, SlimStreamFactory::class]]], UriFactory::class => [['class' => NyholmHttplugFactory::class, 'condition' => [NyholmHttplugFactory::class]], ['class' => GuzzleUriFactory::class, 'condition' => [GuzzleRequest::class, GuzzleUriFactory::class]], ['class' => DiactorosUriFactory::class, 'condition' => [DiactorosRequest::class, DiactorosUriFactory::class]], ['class' => SlimUriFactory::class, 'condition' => [SlimRequest::class, SlimUriFactory::class]]], HttpAsyncClient::class => [['class' => SymfonyHttplug::class, 'condition' => [SymfonyHttplug::class, Promise::class, [self::class, 'isPsr17FactoryInstalled']]], ['class' => Guzzle7::class, 'condition' => Guzzle7::class], ['class' => Guzzle6::class, 'condition' => Guzzle6::class], ['class' => Curl::class, 'condition' => Curl::class], ['class' => React::class, 'condition' => React::class]], HttpClient::class => [['class' => SymfonyHttplug::class, 'condition' => [SymfonyHttplug::class, [self::class, 'isPsr17FactoryInstalled'], [self::class, 'isSymfonyImplementingHttpClient']]], ['class' => Guzzle7::class, 'condition' => Guzzle7::class], ['class' => Guzzle6::class, 'condition' => Guzzle6::class], ['class' => Guzzle5::class, 'condition' => Guzzle5::class], ['class' => Curl::class, 'condition' => Curl::class], ['class' => Socket::class, 'condition' => Socket::class], ['class' => Buzz::class, 'condition' => Buzz::class], ['class' => React::class, 'condition' => React::class], ['class' => Cake::class, 'condition' => Cake::class], ['class' => Artax::class, 'condition' => Artax::class], ['class' => [self::class, 'buzzInstantiate'], 'condition' => [\WordPress\AiClientDependencies\Buzz\Client\FileGetContents::class, \WordPress\AiClientDependencies\Buzz\Message\ResponseBuilder::class]]], Psr18Client::class => [['class' => [self::class, 'symfonyPsr18Instantiate'], 'condition' => [SymfonyPsr18::class, Psr17RequestFactory::class]], ['class' => GuzzleHttp::class, 'condition' => [self::class, 'isGuzzleImplementingPsr18']], ['class' => [self::class, 'buzzInstantiate'], 'condition' => [\WordPress\AiClientDependencies\Buzz\Client\FileGetContents::class, \WordPress\AiClientDependencies\Buzz\Message\ResponseBuilder::class]]]]; public static function getCandidates($type) { if (Psr18Client::class === $type) { return self::getPsr18Candidates(); } return self::$classes[$type] ?? []; } /** * @return array The return value is always an array with zero or more elements. Each * element is an array with two keys ['class' => string, 'condition' => mixed]. */ private static function getPsr18Candidates() { $candidates = self::$classes[Psr18Client::class]; // HTTPlug 2.0 clients implements PSR18Client too. foreach (self::$classes[HttpClient::class] as $c) { if (!is_string($c['class'])) { continue; } try { if (ClassDiscovery::safeClassExists($c['class']) && is_subclass_of($c['class'], Psr18Client::class)) { $candidates[] = $c; } } catch (\Throwable $e) { trigger_error(sprintf('Got exception "%s (%s)" while checking if a PSR-18 Client is available', get_class($e), $e->getMessage()), \E_USER_WARNING); } } return $candidates; } public static function buzzInstantiate() { return new \WordPress\AiClientDependencies\Buzz\Client\FileGetContents(Psr17FactoryDiscovery::findResponseFactory()); } public static function symfonyPsr18Instantiate() { return new SymfonyPsr18(null, Psr17FactoryDiscovery::findResponseFactory(), Psr17FactoryDiscovery::findStreamFactory()); } public static function isGuzzleImplementingPsr18() { return defined('GuzzleHttp\ClientInterface::MAJOR_VERSION'); } public static function isSymfonyImplementingHttpClient() { return is_subclass_of(SymfonyHttplug::class, HttpClient::class); } /** * Can be used as a condition. * * @return bool */ public static function isPsr17FactoryInstalled() { try { Psr17FactoryDiscovery::findResponseFactory(); } catch (NotFoundException $e) { return \false; } catch (\Throwable $e) { trigger_error(sprintf('Got exception "%s (%s)" while checking if a PSR-17 ResponseFactory is available', get_class($e), $e->getMessage()), \E_USER_WARNING); return \false; } return \true; } } third-party/Http/Discovery/Strategy/DiscoveryStrategy.php 0000644 00000001242 15212525755 0017716 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Strategy; use WordPress\AiClientDependencies\Http\Discovery\Exception\StrategyUnavailableException; /** * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ interface DiscoveryStrategy { /** * Find a resource of a specific type. * * @param string $type * * @return array The return value is always an array with zero or more elements. Each * element is an array with two keys ['class' => string, 'condition' => mixed]. * * @throws StrategyUnavailableException if we cannot use this strategy */ public static function getCandidates($type); } third-party/Http/Discovery/Strategy/error_log 0000644 00000002514 15212525755 0015433 0 ustar 00 [11-Jun-2026 09:33:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/CommonClassesStrategy.php:48 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/CommonClassesStrategy.php on line 48 [11-Jun-2026 09:33:24 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/PuliBetaStrategy.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/PuliBetaStrategy.php on line 19 [11-Jun-2026 09:34:09 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/CommonPsr17ClassesStrategy.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Strategy/CommonPsr17ClassesStrategy.php on line 18 third-party/Http/Discovery/Strategy/CommonPsr17ClassesStrategy.php 0000644 00000010055 15212525755 0021354 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Strategy; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface; /** * @internal * * @author Tobias Nyholm <tobias.nyholm@gmail.com> * * Don't miss updating src/Composer/Plugin.php when adding a new supported class. */ final class CommonPsr17ClassesStrategy implements DiscoveryStrategy { /** * @var array */ private static $classes = [RequestFactoryInterface::class => ['Phalcon\Http\Message\RequestFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\RequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\RequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\RequestFactory', 'Laminas\Diactoros\RequestFactory', 'Slim\Psr7\Factory\RequestFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\RequestFactory'], ResponseFactoryInterface::class => ['Phalcon\Http\Message\ResponseFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\ResponseFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\ResponseFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\ResponseFactory', 'Laminas\Diactoros\ResponseFactory', 'Slim\Psr7\Factory\ResponseFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\ResponseFactory'], ServerRequestFactoryInterface::class => ['Phalcon\Http\Message\ServerRequestFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\ServerRequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\ServerRequestFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\ServerRequestFactory', 'Laminas\Diactoros\ServerRequestFactory', 'Slim\Psr7\Factory\ServerRequestFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\ServerRequestFactory'], StreamFactoryInterface::class => ['Phalcon\Http\Message\StreamFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\StreamFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\StreamFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\StreamFactory', 'Laminas\Diactoros\StreamFactory', 'Slim\Psr7\Factory\StreamFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\StreamFactory'], UploadedFileFactoryInterface::class => ['Phalcon\Http\Message\UploadedFileFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\UploadedFileFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\UploadedFileFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\UploadedFileFactory', 'Laminas\Diactoros\UploadedFileFactory', 'Slim\Psr7\Factory\UploadedFileFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\UploadedFileFactory'], UriFactoryInterface::class => ['Phalcon\Http\Message\UriFactory', 'Nyholm\Psr7\Factory\Psr17Factory', 'GuzzleHttp\Psr7\HttpFactory', 'WordPress\AiClientDependencies\Http\Factory\Diactoros\UriFactory', 'WordPress\AiClientDependencies\Http\Factory\Guzzle\UriFactory', 'WordPress\AiClientDependencies\Http\Factory\Slim\UriFactory', 'Laminas\Diactoros\UriFactory', 'Slim\Psr7\Factory\UriFactory', 'WordPress\AiClientDependencies\HttpSoft\Message\UriFactory']]; public static function getCandidates($type) { $candidates = []; if (isset(self::$classes[$type])) { foreach (self::$classes[$type] as $class) { $candidates[] = ['class' => $class, 'condition' => [$class]]; } } return $candidates; } } third-party/Http/Discovery/Strategy/PuliBetaStrategy.php 0000644 00000004515 15212525755 0017462 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Strategy; use WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Exception\PuliUnavailableException; use WordPress\AiClientDependencies\Puli\Discovery\Api\Discovery; use WordPress\AiClientDependencies\Puli\GeneratedPuliFactory; /** * Find candidates using Puli. * * @internal * * @final * * @author David de Boer <david@ddeboer.nl> * @author Márk Sági-Kazár <mark.sagikazar@gmail.com> */ class PuliBetaStrategy implements DiscoveryStrategy { /** * @var GeneratedPuliFactory */ protected static $puliFactory; /** * @var Discovery */ protected static $puliDiscovery; /** * @return GeneratedPuliFactory * * @throws PuliUnavailableException */ private static function getPuliFactory() { if (null === self::$puliFactory) { if (!defined('PULI_FACTORY_CLASS')) { throw new PuliUnavailableException('Puli Factory is not available'); } $puliFactoryClass = PULI_FACTORY_CLASS; if (!ClassDiscovery::safeClassExists($puliFactoryClass)) { throw new PuliUnavailableException('Puli Factory class does not exist'); } self::$puliFactory = new $puliFactoryClass(); } return self::$puliFactory; } /** * Returns the Puli discovery layer. * * @return Discovery * * @throws PuliUnavailableException */ private static function getPuliDiscovery() { if (!isset(self::$puliDiscovery)) { $factory = self::getPuliFactory(); $repository = $factory->createRepository(); self::$puliDiscovery = $factory->createDiscovery($repository); } return self::$puliDiscovery; } public static function getCandidates($type) { $returnData = []; $bindings = self::getPuliDiscovery()->findBindings($type); foreach ($bindings as $binding) { $condition = \true; if ($binding->hasParameterValue('depends')) { $condition = $binding->getParameterValue('depends'); } $returnData[] = ['class' => $binding->getClassName(), 'condition' => $condition]; } return $returnData; } } third-party/Http/Discovery/Exception.php 0000644 00000000353 15212525755 0014362 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery; /** * An interface implemented by all discovery related exceptions. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ interface Exception extends \Throwable { } third-party/Http/Discovery/error_log 0000644 00000003204 15212525755 0013626 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php:13 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php on line 13 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php on line 18 [11-Jun-2026 08:26:23 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr17FactoryDiscovery.php on line 18 [11-Jun-2026 08:27:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\ClassDiscovery" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php:13 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Psr18ClientDiscovery.php on line 13 third-party/Http/Discovery/Psr17FactoryDiscovery.php 0000644 00000007761 15212525755 0016572 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery; use WordPress\AiClientDependencies\Http\Discovery\Exception\DiscoveryFailedException; use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException as RealNotFoundException; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface; /** * Finds PSR-17 factories. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class Psr17FactoryDiscovery extends ClassDiscovery { private static function createException($type, Exception $e) { return new RealNotFoundException('No PSR-17 ' . $type . ' found. Install a package from this list: https://packagist.org/providers/psr/http-factory-implementation', 0, $e); } /** * @return RequestFactoryInterface * * @throws RealNotFoundException */ public static function findRequestFactory() { try { $messageFactory = static::findOneByType(RequestFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('request factory', $e); } return static::instantiateClass($messageFactory); } /** * @return ResponseFactoryInterface * * @throws RealNotFoundException */ public static function findResponseFactory() { try { $messageFactory = static::findOneByType(ResponseFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('response factory', $e); } return static::instantiateClass($messageFactory); } /** * @return ServerRequestFactoryInterface * * @throws RealNotFoundException */ public static function findServerRequestFactory() { try { $messageFactory = static::findOneByType(ServerRequestFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('server request factory', $e); } return static::instantiateClass($messageFactory); } /** * @return StreamFactoryInterface * * @throws RealNotFoundException */ public static function findStreamFactory() { try { $messageFactory = static::findOneByType(StreamFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('stream factory', $e); } return static::instantiateClass($messageFactory); } /** * @return UploadedFileFactoryInterface * * @throws RealNotFoundException */ public static function findUploadedFileFactory() { try { $messageFactory = static::findOneByType(UploadedFileFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('uploaded file factory', $e); } return static::instantiateClass($messageFactory); } /** * @return UriFactoryInterface * * @throws RealNotFoundException */ public static function findUriFactory() { try { $messageFactory = static::findOneByType(UriFactoryInterface::class); } catch (DiscoveryFailedException $e) { throw self::createException('url factory', $e); } return static::instantiateClass($messageFactory); } /** * @return UriFactoryInterface * * @throws RealNotFoundException * * @deprecated This will be removed in 2.0. Consider using the findUriFactory() method. */ public static function findUrlFactory() { return static::findUriFactory(); } } third-party/Http/Discovery/ClassDiscovery.php 0000644 00000015652 15212525755 0015371 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery; use WordPress\AiClientDependencies\Http\Discovery\Exception\ClassInstantiationFailedException; use WordPress\AiClientDependencies\Http\Discovery\Exception\DiscoveryFailedException; use WordPress\AiClientDependencies\Http\Discovery\Exception\NoCandidateFoundException; use WordPress\AiClientDependencies\Http\Discovery\Exception\StrategyUnavailableException; use WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy; /** * Registry that based find results on class existence. * * @author David de Boer <david@ddeboer.nl> * @author Márk Sági-Kazár <mark.sagikazar@gmail.com> * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ abstract class ClassDiscovery { /** * A list of strategies to find classes. * * @var DiscoveryStrategy[] */ private static $strategies = [Strategy\GeneratedDiscoveryStrategy::class, Strategy\CommonClassesStrategy::class, Strategy\CommonPsr17ClassesStrategy::class, Strategy\PuliBetaStrategy::class]; private static $deprecatedStrategies = [Strategy\PuliBetaStrategy::class => \true]; /** * Discovery cache to make the second time we use discovery faster. * * @var array */ private static $cache = []; /** * Finds a class. * * @param string $type * * @return string|\Closure * * @throws DiscoveryFailedException */ protected static function findOneByType($type) { // Look in the cache if (null !== $class = self::getFromCache($type)) { return $class; } static $skipStrategy; $skipStrategy ?? $skipStrategy = self::safeClassExists(Strategy\GeneratedDiscoveryStrategy::class) ? \false : Strategy\GeneratedDiscoveryStrategy::class; $exceptions = []; foreach (self::$strategies as $strategy) { if ($skipStrategy === $strategy) { continue; } try { $candidates = $strategy::getCandidates($type); } catch (StrategyUnavailableException $e) { if (!isset(self::$deprecatedStrategies[$strategy])) { $exceptions[] = $e; } continue; } foreach ($candidates as $candidate) { if (isset($candidate['condition'])) { if (!self::evaluateCondition($candidate['condition'])) { continue; } } // save the result for later use self::storeInCache($type, $candidate); return $candidate['class']; } $exceptions[] = new NoCandidateFoundException($strategy, $candidates); } throw DiscoveryFailedException::create($exceptions); } /** * Get a value from cache. * * @param string $type * * @return string|null */ private static function getFromCache($type) { if (!isset(self::$cache[$type])) { return; } $candidate = self::$cache[$type]; if (isset($candidate['condition'])) { if (!self::evaluateCondition($candidate['condition'])) { return; } } return $candidate['class']; } /** * Store a value in cache. * * @param string $type * @param string $class */ private static function storeInCache($type, $class) { self::$cache[$type] = $class; } /** * Set new strategies and clear the cache. * * @param string[] $strategies list of fully qualified class names that implement DiscoveryStrategy */ public static function setStrategies(array $strategies) { self::$strategies = $strategies; self::clearCache(); } /** * Returns the currently configured discovery strategies as fully qualified class names. * * @return string[] */ public static function getStrategies(): iterable { return self::$strategies; } /** * Append a strategy at the end of the strategy queue. * * @param string $strategy Fully qualified class name of a DiscoveryStrategy */ public static function appendStrategy($strategy) { self::$strategies[] = $strategy; self::clearCache(); } /** * Prepend a strategy at the beginning of the strategy queue. * * @param string $strategy Fully qualified class name to a DiscoveryStrategy */ public static function prependStrategy($strategy) { array_unshift(self::$strategies, $strategy); self::clearCache(); } public static function clearCache() { self::$cache = []; } /** * Evaluates conditions to boolean. * * @return bool */ protected static function evaluateCondition($condition) { if (is_string($condition)) { // Should be extended for functions, extensions??? return self::safeClassExists($condition); } if (is_callable($condition)) { return (bool) $condition(); } if (is_bool($condition)) { return $condition; } if (is_array($condition)) { foreach ($condition as $c) { if (\false === static::evaluateCondition($c)) { // Immediately stop execution if the condition is false return \false; } } return \true; } return \false; } /** * Get an instance of the $class. * * @param string|\Closure $class a FQCN of a class or a closure that instantiate the class * * @return object * * @throws ClassInstantiationFailedException */ protected static function instantiateClass($class) { try { if (is_string($class)) { return new $class(); } if (is_callable($class)) { return $class(); } } catch (\Exception $e) { throw new ClassInstantiationFailedException('Unexpected exception when instantiating class.', 0, $e); } throw new ClassInstantiationFailedException('Could not instantiate class because parameter is neither a callable nor a string'); } /** * We need a "safe" version of PHP's "class_exists" because Magento has a bug * (or they call it a "feature"). Magento is throwing an exception if you do class_exists() * on a class that ends with "Factory" and if that file does not exits. * * This function catches all potential exceptions and makes sure to always return a boolean. * * @param string $class * * @return bool */ public static function safeClassExists($class) { try { return class_exists($class) || interface_exists($class); } catch (\Exception $e) { return \false; } } } third-party/Http/Discovery/Psr18ClientDiscovery.php 0000644 00000002016 15212525755 0016366 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery; use WordPress\AiClientDependencies\Http\Discovery\Exception\DiscoveryFailedException; use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException as RealNotFoundException; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface; /** * Finds a PSR-18 HTTP Client. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class Psr18ClientDiscovery extends ClassDiscovery { /** * Finds a PSR-18 HTTP Client. * * @return ClientInterface * * @throws RealNotFoundException */ public static function find() { try { $client = static::findOneByType(ClientInterface::class); } catch (DiscoveryFailedException $e) { throw new RealNotFoundException('No PSR-18 clients found. Make sure to install a package providing "psr/http-client-implementation". Example: "php-http/guzzle7-adapter".', 0, $e); } return static::instantiateClass($client); } } third-party/Http/Discovery/Exception/StrategyUnavailableException.php 0000644 00000000643 15212525755 0022211 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; use WordPress\AiClientDependencies\Http\Discovery\Exception; /** * This exception is thrown when we cannot use a discovery strategy. This is *not* thrown when * the discovery fails to find a class. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ class StrategyUnavailableException extends \RuntimeException implements Exception { } third-party/Http/Discovery/Exception/NoCandidateFoundException.php 0000644 00000002203 15212525755 0021402 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; use WordPress\AiClientDependencies\Http\Discovery\Exception; /** * When we have used a strategy but no candidates provided by that strategy could be used. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class NoCandidateFoundException extends \Exception implements Exception { /** * @param string $strategy */ public function __construct($strategy, array $candidates) { $classes = array_map(function ($a) { return $a['class']; }, $candidates); $message = sprintf('No valid candidate found using strategy "%s". We tested the following candidates: %s.', $strategy, implode(', ', array_map([$this, 'stringify'], $classes))); parent::__construct($message); } private function stringify($mixed) { if (is_string($mixed)) { return $mixed; } if (is_array($mixed) && 2 === count($mixed)) { return sprintf('%s::%s', $this->stringify($mixed[0]), $mixed[1]); } return is_object($mixed) ? get_class($mixed) : gettype($mixed); } } third-party/Http/Discovery/Exception/NotFoundException.php 0000644 00000000631 15212525755 0017774 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; use WordPress\AiClientDependencies\Http\Discovery\Exception; /** * Thrown when a discovery does not find any matches. * * @final do NOT extend this class, not final for BC reasons * * @author Márk Sági-Kazár <mark.sagikazar@gmail.com> */ /* final */ class NotFoundException extends \RuntimeException implements Exception { } third-party/Http/Discovery/Exception/ClassInstantiationFailedException.php 0000644 00000000524 15212525755 0023160 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; use WordPress\AiClientDependencies\Http\Discovery\Exception; /** * Thrown when a class fails to instantiate. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class ClassInstantiationFailedException extends \RuntimeException implements Exception { } third-party/Http/Discovery/Exception/error_log 0000644 00000004306 15212525755 0015570 0 ustar 00 [11-Jun-2026 09:33:14 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Exception" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/NotFoundException.php:14 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/NotFoundException.php on line 14 [11-Jun-2026 09:33:54 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Exception" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/NoCandidateFoundException.php:11 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/NoCandidateFoundException.php on line 11 [11-Jun-2026 09:34:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClientDependencies\Http\Discovery\Exception\StrategyUnavailableException" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/PuliUnavailableException.php:10 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/PuliUnavailableException.php on line 10 [11-Jun-2026 09:34:18 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Exception" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/DiscoveryFailedException.php:11 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/DiscoveryFailedException.php on line 11 [11-Jun-2026 09:34:22 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Exception" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/ClassInstantiationFailedException.php:11 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Http/Discovery/Exception/ClassInstantiationFailedException.php on line 11 third-party/Http/Discovery/Exception/DiscoveryFailedException.php 0000644 00000002331 15212525755 0021313 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; use WordPress\AiClientDependencies\Http\Discovery\Exception; /** * Thrown when all discovery strategies fails to find a resource. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class DiscoveryFailedException extends \Exception implements Exception { /** * @var \Exception[] */ private $exceptions; /** * @param string $message * @param \Exception[] $exceptions */ public function __construct($message, array $exceptions = []) { $this->exceptions = $exceptions; parent::__construct($message); } /** * @param \Exception[] $exceptions */ public static function create($exceptions) { $message = 'Could not find resource using any discovery strategy. Find more information at http://docs.php-http.org/en/latest/discovery.html#common-errors'; foreach ($exceptions as $e) { $message .= "\n - " . $e->getMessage(); } $message .= "\n\n"; return new self($message, $exceptions); } /** * @return \Exception[] */ public function getExceptions() { return $this->exceptions; } } third-party/Http/Discovery/Exception/PuliUnavailableException.php 0000644 00000000407 15212525755 0021316 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Http\Discovery\Exception; /** * Thrown when we can't use Puli for discovery. * * @author Tobias Nyholm <tobias.nyholm@gmail.com> */ final class PuliUnavailableException extends StrategyUnavailableException { } third-party/Psr/SimpleCache/CacheInterface.php 0000644 00000011030 15212525755 0015275 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\SimpleCache; interface CacheInterface { /** * Fetches a value from the cache. * * @param string $key The unique key of this item in the cache. * @param mixed $default Default value to return if the key does not exist. * * @return mixed The value of the item from the cache, or $default in case of cache miss. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function get($key, $default = null); /** * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time. * * @param string $key The key of the item to store. * @param mixed $value The value of the item to store, must be serializable. * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function set($key, $value, $ttl = null); /** * Delete an item from the cache by its unique key. * * @param string $key The unique cache key of the item to delete. * * @return bool True if the item was successfully removed. False if there was an error. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function delete($key); /** * Wipes clean the entire cache's keys. * * @return bool True on success and false on failure. */ public function clear(); /** * Obtains multiple cache items by their unique keys. * * @param iterable $keys A list of keys that can obtained in a single operation. * @param mixed $default Default value to return for keys that do not exist. * * @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ public function getMultiple($keys, $default = null); /** * Persists a set of key => value pairs in the cache, with an optional TTL. * * @param iterable $values A list of key => value pairs for a multiple-set operation. * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. * * @return bool True on success and false on failure. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $values is neither an array nor a Traversable, * or if any of the $values are not a legal value. */ public function setMultiple($values, $ttl = null); /** * Deletes multiple cache items in a single operation. * * @param iterable $keys A list of string-based keys to be deleted. * * @return bool True if the items were successfully removed. False if there was an error. * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if $keys is neither an array nor a Traversable, * or if any of the $keys are not a legal value. */ public function deleteMultiple($keys); /** * Determines whether an item is present in the cache. * * NOTE: It is recommended that has() is only to be used for cache warming type purposes * and not to be used within your live applications operations for get/set, as this method * is subject to a race condition where your has() will return true and immediately after, * another script can remove it making the state of your app out of date. * * @param string $key The cache item key. * * @return bool * * @throws \Psr\SimpleCache\InvalidArgumentException * MUST be thrown if the $key string is not a legal value. */ public function has($key); } third-party/Psr/Http/Message/ResponseFactoryInterface.php 0000644 00000001101 15212525755 0017544 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface ResponseFactoryInterface { /** * Create a new response. * * @param int $code HTTP status code; defaults to 200 * @param string $reasonPhrase Reason phrase to associate with status code * in generated response; if none is provided implementations MAY use * the defaults as suggested in the HTTP specification. * * @return ResponseInterface */ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface; } third-party/Psr/Http/Message/RequestInterface.php 0000644 00000011521 15212525755 0016055 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Representation of an outgoing, client-side request. * * Per the HTTP specification, this interface includes properties for * each of the following: * * - Protocol version * - HTTP method * - URI * - Headers * - Message body * * During construction, implementations MUST attempt to set the Host header from * a provided URI if no Host header is provided. * * Requests are considered immutable; all methods that might change state MUST * be implemented such that they retain the internal state of the current * message and return an instance that contains the changed state. */ interface RequestInterface extends MessageInterface { /** * Retrieves the message's request target. * * Retrieves the message's request-target either as it will appear (for * clients), as it appeared at request (for servers), or as it was * specified for the instance (see withRequestTarget()). * * In most cases, this will be the origin-form of the composed URI, * unless a value was provided to the concrete implementation (see * withRequestTarget() below). * * If no URI is available, and no request-target has been specifically * provided, this method MUST return the string "/". * * @return string */ public function getRequestTarget(): string; /** * Return an instance with the specific request-target. * * If the request needs a non-origin-form request-target — e.g., for * specifying an absolute-form, authority-form, or asterisk-form — * this method may be used to create an instance with the specified * request-target, verbatim. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * changed request target. * * @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various * request-target forms allowed in request messages) * @param string $requestTarget * @return static */ public function withRequestTarget(string $requestTarget): RequestInterface; /** * Retrieves the HTTP method of the request. * * @return string Returns the request method. */ public function getMethod(): string; /** * Return an instance with the provided HTTP method. * * While HTTP method names are typically all uppercase characters, HTTP * method names are case-sensitive and thus implementations SHOULD NOT * modify the given string. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * changed request method. * * @param string $method Case-sensitive method. * @return static * @throws \InvalidArgumentException for invalid HTTP methods. */ public function withMethod(string $method): RequestInterface; /** * Retrieves the URI instance. * * This method MUST return a UriInterface instance. * * @link http://tools.ietf.org/html/rfc3986#section-4.3 * @return UriInterface Returns a UriInterface instance * representing the URI of the request. */ public function getUri(): UriInterface; /** * Returns an instance with the provided URI. * * This method MUST update the Host header of the returned request by * default if the URI contains a host component. If the URI does not * contain a host component, any pre-existing Host header MUST be carried * over to the returned request. * * You can opt-in to preserving the original state of the Host header by * setting `$preserveHost` to `true`. When `$preserveHost` is set to * `true`, this method interacts with the Host header in the following ways: * * - If the Host header is missing or empty, and the new URI contains * a host component, this method MUST update the Host header in the returned * request. * - If the Host header is missing or empty, and the new URI does not contain a * host component, this method MUST NOT update the Host header in the returned * request. * - If a Host header is present and non-empty, this method MUST NOT update * the Host header in the returned request. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new UriInterface instance. * * @link http://tools.ietf.org/html/rfc3986#section-4.3 * @param UriInterface $uri New request URI to use. * @param bool $preserveHost Preserve the original state of the Host header. * @return static */ public function withUri(UriInterface $uri, bool $preserveHost = \false): RequestInterface; } third-party/Psr/Http/Message/UriInterface.php 0000644 00000031057 15212525755 0015172 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Value object representing a URI. * * This interface is meant to represent URIs according to RFC 3986 and to * provide methods for most common operations. Additional functionality for * working with URIs can be provided on top of the interface or externally. * Its primary use is for HTTP requests, but may also be used in other * contexts. * * Instances of this interface are considered immutable; all methods that * might change state MUST be implemented such that they retain the internal * state of the current instance and return an instance that contains the * changed state. * * Typically the Host header will be also be present in the request message. * For server-side requests, the scheme will typically be discoverable in the * server parameters. * * @link http://tools.ietf.org/html/rfc3986 (the URI specification) */ interface UriInterface { /** * Retrieve the scheme component of the URI. * * If no scheme is present, this method MUST return an empty string. * * The value returned MUST be normalized to lowercase, per RFC 3986 * Section 3.1. * * The trailing ":" character is not part of the scheme and MUST NOT be * added. * * @see https://tools.ietf.org/html/rfc3986#section-3.1 * @return string The URI scheme. */ public function getScheme(): string; /** * Retrieve the authority component of the URI. * * If no authority information is present, this method MUST return an empty * string. * * The authority syntax of the URI is: * * <pre> * [user-info@]host[:port] * </pre> * * If the port component is not set or is the standard port for the current * scheme, it SHOULD NOT be included. * * @see https://tools.ietf.org/html/rfc3986#section-3.2 * @return string The URI authority, in "[user-info@]host[:port]" format. */ public function getAuthority(): string; /** * Retrieve the user information component of the URI. * * If no user information is present, this method MUST return an empty * string. * * If a user is present in the URI, this will return that value; * additionally, if the password is also present, it will be appended to the * user value, with a colon (":") separating the values. * * The trailing "@" character is not part of the user information and MUST * NOT be added. * * @return string The URI user information, in "username[:password]" format. */ public function getUserInfo(): string; /** * Retrieve the host component of the URI. * * If no host is present, this method MUST return an empty string. * * The value returned MUST be normalized to lowercase, per RFC 3986 * Section 3.2.2. * * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 * @return string The URI host. */ public function getHost(): string; /** * Retrieve the port component of the URI. * * If a port is present, and it is non-standard for the current scheme, * this method MUST return it as an integer. If the port is the standard port * used with the current scheme, this method SHOULD return null. * * If no port is present, and no scheme is present, this method MUST return * a null value. * * If no port is present, but a scheme is present, this method MAY return * the standard port for that scheme, but SHOULD return null. * * @return null|int The URI port. */ public function getPort(): ?int; /** * Retrieve the path component of the URI. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * Normally, the empty path "" and absolute path "/" are considered equal as * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically * do this normalization because in contexts with a trimmed base path, e.g. * the front controller, this difference becomes significant. It's the task * of the user to handle both "" and "/". * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.3. * * As an example, if the value should include a slash ("/") not intended as * delimiter between path segments, that value MUST be passed in encoded * form (e.g., "%2F") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.3 * @return string The URI path. */ public function getPath(): string; /** * Retrieve the query string of the URI. * * If no query string is present, this method MUST return an empty string. * * The leading "?" character is not part of the query and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.4. * * As an example, if a value in a key/value pair of the query string should * include an ampersand ("&") not intended as a delimiter between values, * that value MUST be passed in encoded form (e.g., "%26") to the instance. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.4 * @return string The URI query string. */ public function getQuery(): string; /** * Retrieve the fragment component of the URI. * * If no fragment is present, this method MUST return an empty string. * * The leading "#" character is not part of the fragment and MUST NOT be * added. * * The value returned MUST be percent-encoded, but MUST NOT double-encode * any characters. To determine what characters to encode, please refer to * RFC 3986, Sections 2 and 3.5. * * @see https://tools.ietf.org/html/rfc3986#section-2 * @see https://tools.ietf.org/html/rfc3986#section-3.5 * @return string The URI fragment. */ public function getFragment(): string; /** * Return an instance with the specified scheme. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified scheme. * * Implementations MUST support the schemes "http" and "https" case * insensitively, and MAY accommodate other schemes if required. * * An empty scheme is equivalent to removing the scheme. * * @param string $scheme The scheme to use with the new instance. * @return static A new instance with the specified scheme. * @throws \InvalidArgumentException for invalid or unsupported schemes. */ public function withScheme(string $scheme): UriInterface; /** * Return an instance with the specified user information. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified user information. * * Password is optional, but the user information MUST include the * user; an empty string for the user is equivalent to removing user * information. * * @param string $user The user name to use for authority. * @param null|string $password The password associated with $user. * @return static A new instance with the specified user information. */ public function withUserInfo(string $user, ?string $password = null): UriInterface; /** * Return an instance with the specified host. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified host. * * An empty host value is equivalent to removing the host. * * @param string $host The hostname to use with the new instance. * @return static A new instance with the specified host. * @throws \InvalidArgumentException for invalid hostnames. */ public function withHost(string $host): UriInterface; /** * Return an instance with the specified port. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified port. * * Implementations MUST raise an exception for ports outside the * established TCP and UDP port ranges. * * A null value provided for the port is equivalent to removing the port * information. * * @param null|int $port The port to use with the new instance; a null value * removes the port information. * @return static A new instance with the specified port. * @throws \InvalidArgumentException for invalid ports. */ public function withPort(?int $port): UriInterface; /** * Return an instance with the specified path. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified path. * * The path can either be empty or absolute (starting with a slash) or * rootless (not starting with a slash). Implementations MUST support all * three syntaxes. * * If the path is intended to be domain-relative rather than path relative then * it must begin with a slash ("/"). Paths not starting with a slash ("/") * are assumed to be relative to some base path known to the application or * consumer. * * Users can provide both encoded and decoded path characters. * Implementations ensure the correct encoding as outlined in getPath(). * * @param string $path The path to use with the new instance. * @return static A new instance with the specified path. * @throws \InvalidArgumentException for invalid paths. */ public function withPath(string $path): UriInterface; /** * Return an instance with the specified query string. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified query string. * * Users can provide both encoded and decoded query characters. * Implementations ensure the correct encoding as outlined in getQuery(). * * An empty query string value is equivalent to removing the query string. * * @param string $query The query string to use with the new instance. * @return static A new instance with the specified query string. * @throws \InvalidArgumentException for invalid query strings. */ public function withQuery(string $query): UriInterface; /** * Return an instance with the specified URI fragment. * * This method MUST retain the state of the current instance, and return * an instance that contains the specified URI fragment. * * Users can provide both encoded and decoded fragment characters. * Implementations ensure the correct encoding as outlined in getFragment(). * * An empty fragment value is equivalent to removing the fragment. * * @param string $fragment The fragment to use with the new instance. * @return static A new instance with the specified fragment. */ public function withFragment(string $fragment): UriInterface; /** * Return the string representation as a URI reference. * * Depending on which components of the URI are present, the resulting * string is either a full URI or relative reference according to RFC 3986, * Section 4.1. The method concatenates the various components of the URI, * using the appropriate delimiters: * * - If a scheme is present, it MUST be suffixed by ":". * - If an authority is present, it MUST be prefixed by "//". * - The path can be concatenated without delimiters. But there are two * cases where the path has to be adjusted to make the URI reference * valid as PHP does not allow to throw an exception in __toString(): * - If the path is rootless and an authority is present, the path MUST * be prefixed by "/". * - If the path is starting with more than one "/" and no authority is * present, the starting slashes MUST be reduced to one. * - If a query is present, it MUST be prefixed by "?". * - If a fragment is present, it MUST be prefixed by "#". * * @see http://tools.ietf.org/html/rfc3986#section-4.1 * @return string */ public function __toString(): string; } third-party/Psr/Http/Message/ServerRequestInterface.php 0000644 00000024115 15212525755 0017247 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Representation of an incoming, server-side HTTP request. * * Per the HTTP specification, this interface includes properties for * each of the following: * * - Protocol version * - HTTP method * - URI * - Headers * - Message body * * Additionally, it encapsulates all data as it has arrived to the * application from the CGI and/or PHP environment, including: * * - The values represented in $_SERVER. * - Any cookies provided (generally via $_COOKIE) * - Query string arguments (generally via $_GET, or as parsed via parse_str()) * - Upload files, if any (as represented by $_FILES) * - Deserialized body parameters (generally from $_POST) * * $_SERVER values MUST be treated as immutable, as they represent application * state at the time of request; as such, no methods are provided to allow * modification of those values. The other values provide such methods, as they * can be restored from $_SERVER or the request body, and may need treatment * during the application (e.g., body parameters may be deserialized based on * content type). * * Additionally, this interface recognizes the utility of introspecting a * request to derive and match additional parameters (e.g., via URI path * matching, decrypting cookie values, deserializing non-form-encoded body * content, matching authorization headers to users, etc). These parameters * are stored in an "attributes" property. * * Requests are considered immutable; all methods that might change state MUST * be implemented such that they retain the internal state of the current * message and return an instance that contains the changed state. */ interface ServerRequestInterface extends RequestInterface { /** * Retrieve server parameters. * * Retrieves data related to the incoming request environment, * typically derived from PHP's $_SERVER superglobal. The data IS NOT * REQUIRED to originate from $_SERVER. * * @return array */ public function getServerParams(): array; /** * Retrieve cookies. * * Retrieves cookies sent by the client to the server. * * The data MUST be compatible with the structure of the $_COOKIE * superglobal. * * @return array */ public function getCookieParams(): array; /** * Return an instance with the specified cookies. * * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST * be compatible with the structure of $_COOKIE. Typically, this data will * be injected at instantiation. * * This method MUST NOT update the related Cookie header of the request * instance, nor related values in the server params. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated cookie values. * * @param array $cookies Array of key/value pairs representing cookies. * @return static */ public function withCookieParams(array $cookies): ServerRequestInterface; /** * Retrieve query string arguments. * * Retrieves the deserialized query string arguments, if any. * * Note: the query params might not be in sync with the URI or server * params. If you need to ensure you are only getting the original * values, you may need to parse the query string from `getUri()->getQuery()` * or from the `QUERY_STRING` server param. * * @return array */ public function getQueryParams(): array; /** * Return an instance with the specified query string arguments. * * These values SHOULD remain immutable over the course of the incoming * request. They MAY be injected during instantiation, such as from PHP's * $_GET superglobal, or MAY be derived from some other value such as the * URI. In cases where the arguments are parsed from the URI, the data * MUST be compatible with what PHP's parse_str() would return for * purposes of how duplicate query parameters are handled, and how nested * sets are handled. * * Setting query string arguments MUST NOT change the URI stored by the * request, nor the values in the server params. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated query string arguments. * * @param array $query Array of query string arguments, typically from * $_GET. * @return static */ public function withQueryParams(array $query): ServerRequestInterface; /** * Retrieve normalized file upload data. * * This method returns upload metadata in a normalized tree, with each leaf * an instance of Psr\Http\Message\UploadedFileInterface. * * These values MAY be prepared from $_FILES or the message body during * instantiation, or MAY be injected via withUploadedFiles(). * * @return array An array tree of UploadedFileInterface instances; an empty * array MUST be returned if no data is present. */ public function getUploadedFiles(): array; /** * Create a new instance with the specified uploaded files. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param array $uploadedFiles An array tree of UploadedFileInterface instances. * @return static * @throws \InvalidArgumentException if an invalid structure is provided. */ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; /** * Retrieve any parameters provided in the request body. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, this method MUST * return the contents of $_POST. * * Otherwise, this method may return any results of deserializing * the request body content; as parsing returns structured content, the * potential types MUST be arrays or objects only. A null value indicates * the absence of body content. * * @return null|array|object The deserialized body parameters, if any. * These will typically be an array or object. */ public function getParsedBody(); /** * Return an instance with the specified body parameters. * * These MAY be injected during instantiation. * * If the request Content-Type is either application/x-www-form-urlencoded * or multipart/form-data, and the request method is POST, use this method * ONLY to inject the contents of $_POST. * * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of * deserializing the request body content. Deserialization/parsing returns * structured data, and, as such, this method ONLY accepts arrays or objects, * or a null value if nothing was available to parse. * * As an example, if content negotiation determines that the request data * is a JSON payload, this method could be used to create a request * instance with the deserialized parameters. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated body parameters. * * @param null|array|object $data The deserialized body data. This will * typically be in an array or object. * @return static * @throws \InvalidArgumentException if an unsupported argument type is * provided. */ public function withParsedBody($data): ServerRequestInterface; /** * Retrieve attributes derived from the request. * * The request "attributes" may be used to allow injection of any * parameters derived from the request: e.g., the results of path * match operations; the results of decrypting cookies; the results of * deserializing non-form-encoded message bodies; etc. Attributes * will be application and request specific, and CAN be mutable. * * @return array Attributes derived from the request. */ public function getAttributes(): array; /** * Retrieve a single derived request attribute. * * Retrieves a single derived request attribute as described in * getAttributes(). If the attribute has not been previously set, returns * the default value as provided. * * This method obviates the need for a hasAttribute() method, as it allows * specifying a default value to return if the attribute is not found. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $default Default value to return if the attribute does not exist. * @return mixed */ public function getAttribute(string $name, $default = null); /** * Return an instance with the specified derived request attribute. * * This method allows setting a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated attribute. * * @see getAttributes() * @param string $name The attribute name. * @param mixed $value The value of the attribute. * @return static */ public function withAttribute(string $name, $value): ServerRequestInterface; /** * Return an instance that removes the specified derived request attribute. * * This method allows removing a single derived request attribute as * described in getAttributes(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the attribute. * * @see getAttributes() * @param string $name The attribute name. * @return static */ public function withoutAttribute(string $name): ServerRequestInterface; } third-party/Psr/Http/Message/StreamFactoryInterface.php 0000644 00000002647 15212525755 0017221 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface StreamFactoryInterface { /** * Create a new stream from a string. * * The stream SHOULD be created with a temporary resource. * * @param string $content String content with which to populate the stream. * * @return StreamInterface */ public function createStream(string $content = ''): StreamInterface; /** * Create a stream from an existing file. * * The file MUST be opened using the given mode, which may be any mode * supported by the `fopen` function. * * The `$filename` MAY be any string supported by `fopen()`. * * @param string $filename Filename or stream URI to use as basis of stream. * @param string $mode Mode with which to open the underlying filename/stream. * * @return StreamInterface * @throws \RuntimeException If the file cannot be opened. * @throws \InvalidArgumentException If the mode is invalid. */ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface; /** * Create a new stream from an existing resource. * * The stream MUST be readable and may be writable. * * @param resource $resource PHP resource to use as basis of stream. * * @return StreamInterface */ public function createStreamFromResource($resource): StreamInterface; } third-party/Psr/Http/Message/ServerRequestFactoryInterface.php 0000644 00000001676 15212525755 0020606 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface ServerRequestFactoryInterface { /** * Create a new server request. * * Note that server-params are taken precisely as given - no parsing/processing * of the given values is performed, and, in particular, no attempt is made to * determine the HTTP method or URI, which must be provided explicitly. * * @param string $method The HTTP method associated with the request. * @param UriInterface|string $uri The URI associated with the request. If * the value is a string, the factory MUST create a UriInterface * instance based on it. * @param array $serverParams Array of SAPI parameters with which to seed * the generated request instance. * * @return ServerRequestInterface */ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface; } third-party/Psr/Http/Message/StreamInterface.php 0000644 00000011416 15212525755 0015663 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Describes a data stream. * * Typically, an instance will wrap a PHP stream; this interface provides * a wrapper around the most common operations, including serialization of * the entire stream to a string. */ interface StreamInterface { /** * Reads all data from the stream into a string, from the beginning to end. * * This method MUST attempt to seek to the beginning of the stream before * reading data and read the stream until the end is reached. * * Warning: This could attempt to load a large amount of data into memory. * * This method MUST NOT raise an exception in order to conform with PHP's * string casting operations. * * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring * @return string */ public function __toString(): string; /** * Closes the stream and any underlying resources. * * @return void */ public function close(): void; /** * Separates any underlying resources from the stream. * * After the stream has been detached, the stream is in an unusable state. * * @return resource|null Underlying PHP stream, if any */ public function detach(); /** * Get the size of the stream if known. * * @return int|null Returns the size in bytes if known, or null if unknown. */ public function getSize(): ?int; /** * Returns the current position of the file read/write pointer * * @return int Position of the file pointer * @throws \RuntimeException on error. */ public function tell(): int; /** * Returns true if the stream is at the end of the stream. * * @return bool */ public function eof(): bool; /** * Returns whether or not the stream is seekable. * * @return bool */ public function isSeekable(): bool; /** * Seek to a position in the stream. * * @link http://www.php.net/manual/en/function.fseek.php * @param int $offset Stream offset * @param int $whence Specifies how the cursor position will be calculated * based on the seek offset. Valid values are identical to the built-in * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to * offset bytes SEEK_CUR: Set position to current location plus offset * SEEK_END: Set position to end-of-stream plus offset. * @throws \RuntimeException on failure. */ public function seek(int $offset, int $whence = \SEEK_SET): void; /** * Seek to the beginning of the stream. * * If the stream is not seekable, this method will raise an exception; * otherwise, it will perform a seek(0). * * @see seek() * @link http://www.php.net/manual/en/function.fseek.php * @throws \RuntimeException on failure. */ public function rewind(): void; /** * Returns whether or not the stream is writable. * * @return bool */ public function isWritable(): bool; /** * Write data to the stream. * * @param string $string The string that is to be written. * @return int Returns the number of bytes written to the stream. * @throws \RuntimeException on failure. */ public function write(string $string): int; /** * Returns whether or not the stream is readable. * * @return bool */ public function isReadable(): bool; /** * Read data from the stream. * * @param int $length Read up to $length bytes from the object and return * them. Fewer than $length bytes may be returned if underlying stream * call returns fewer bytes. * @return string Returns the data read from the stream, or an empty string * if no bytes are available. * @throws \RuntimeException if an error occurs. */ public function read(int $length): string; /** * Returns the remaining contents in a string * * @return string * @throws \RuntimeException if unable to read or an error occurs while * reading. */ public function getContents(): string; /** * Get stream metadata as an associative array or retrieve a specific key. * * The keys returned are identical to the keys returned from PHP's * stream_get_meta_data() function. * * @link http://php.net/manual/en/function.stream-get-meta-data.php * @param string|null $key Specific metadata to retrieve. * @return array|mixed|null Returns an associative array if no key is * provided. Returns a specific key value if a key is provided and the * value is found, or null if the key is not found. */ public function getMetadata(?string $key = null); } third-party/Psr/Http/Message/UploadedFileInterface.php 0000644 00000011226 15212525755 0016764 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Value object representing a file uploaded through an HTTP request. * * Instances of this interface are considered immutable; all methods that * might change state MUST be implemented such that they retain the internal * state of the current instance and return an instance that contains the * changed state. */ interface UploadedFileInterface { /** * Retrieve a stream representing the uploaded file. * * This method MUST return a StreamInterface instance, representing the * uploaded file. The purpose of this method is to allow utilizing native PHP * stream functionality to manipulate the file upload, such as * stream_copy_to_stream() (though the result will need to be decorated in a * native PHP stream wrapper to work with such functions). * * If the moveTo() method has been called previously, this method MUST raise * an exception. * * @return StreamInterface Stream representation of the uploaded file. * @throws \RuntimeException in cases when no stream is available or can be * created. */ public function getStream(): StreamInterface; /** * Move the uploaded file to a new location. * * Use this method as an alternative to move_uploaded_file(). This method is * guaranteed to work in both SAPI and non-SAPI environments. * Implementations must determine which environment they are in, and use the * appropriate method (move_uploaded_file(), rename(), or a stream * operation) to perform the operation. * * $targetPath may be an absolute path, or a relative path. If it is a * relative path, resolution should be the same as used by PHP's rename() * function. * * The original file or stream MUST be removed on completion. * * If this method is called more than once, any subsequent calls MUST raise * an exception. * * When used in an SAPI environment where $_FILES is populated, when writing * files via moveTo(), is_uploaded_file() and move_uploaded_file() SHOULD be * used to ensure permissions and upload status are verified correctly. * * If you wish to move to a stream, use getStream(), as SAPI operations * cannot guarantee writing to stream destinations. * * @see http://php.net/is_uploaded_file * @see http://php.net/move_uploaded_file * @param string $targetPath Path to which to move the uploaded file. * @throws \InvalidArgumentException if the $targetPath specified is invalid. * @throws \RuntimeException on any error during the move operation, or on * the second or subsequent call to the method. */ public function moveTo(string $targetPath): void; /** * Retrieve the file size. * * Implementations SHOULD return the value stored in the "size" key of * the file in the $_FILES array if available, as PHP calculates this based * on the actual size transmitted. * * @return int|null The file size in bytes or null if unknown. */ public function getSize(): ?int; /** * Retrieve the error associated with the uploaded file. * * The return value MUST be one of PHP's UPLOAD_ERR_XXX constants. * * If the file was uploaded successfully, this method MUST return * UPLOAD_ERR_OK. * * Implementations SHOULD return the value stored in the "error" key of * the file in the $_FILES array. * * @see http://php.net/manual/en/features.file-upload.errors.php * @return int One of PHP's UPLOAD_ERR_XXX constants. */ public function getError(): int; /** * Retrieve the filename sent by the client. * * Do not trust the value returned by this method. A client could send * a malicious filename with the intention to corrupt or hack your * application. * * Implementations SHOULD return the value stored in the "name" key of * the file in the $_FILES array. * * @return string|null The filename sent by the client or null if none * was provided. */ public function getClientFilename(): ?string; /** * Retrieve the media type sent by the client. * * Do not trust the value returned by this method. A client could send * a malicious media type with the intention to corrupt or hack your * application. * * Implementations SHOULD return the value stored in the "type" key of * the file in the $_FILES array. * * @return string|null The media type sent by the client or null if none * was provided. */ public function getClientMediaType(): ?string; } third-party/Psr/Http/Message/ResponseInterface.php 0000644 00000005147 15212525755 0016232 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * Representation of an outgoing, server-side response. * * Per the HTTP specification, this interface includes properties for * each of the following: * * - Protocol version * - Status code and reason phrase * - Headers * - Message body * * Responses are considered immutable; all methods that might change state MUST * be implemented such that they retain the internal state of the current * message and return an instance that contains the changed state. */ interface ResponseInterface extends MessageInterface { /** * Gets the response status code. * * The status code is a 3-digit integer result code of the server's attempt * to understand and satisfy the request. * * @return int Status code. */ public function getStatusCode(): int; /** * Return an instance with the specified status code and, optionally, reason phrase. * * If no reason phrase is specified, implementations MAY choose to default * to the RFC 7231 or IANA recommended reason phrase for the response's * status code. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * updated status and reason phrase. * * @link http://tools.ietf.org/html/rfc7231#section-6 * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @param int $code The 3-digit integer result code to set. * @param string $reasonPhrase The reason phrase to use with the * provided status code; if none is provided, implementations MAY * use the defaults as suggested in the HTTP specification. * @return static * @throws \InvalidArgumentException For invalid status code arguments. */ public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface; /** * Gets the response reason phrase associated with the status code. * * Because a reason phrase is not a required element in a response * status line, the reason phrase value MAY be null. Implementations MAY * choose to return the default RFC 7231 recommended reason phrase (or those * listed in the IANA HTTP Status Code Registry) for the response's * status code. * * @link http://tools.ietf.org/html/rfc7231#section-6 * @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml * @return string Reason phrase; must return an empty string if none present. */ public function getReasonPhrase(): string; } third-party/Psr/Http/Message/UploadedFileFactoryInterface.php 0000644 00000002131 15212525755 0020307 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface UploadedFileFactoryInterface { /** * Create a new uploaded file. * * If a size is not provided it will be determined by checking the size of * the file. * * @see http://php.net/manual/features.file-upload.post-method.php * @see http://php.net/manual/features.file-upload.errors.php * * @param StreamInterface $stream Underlying stream representing the * uploaded file content. * @param int|null $size in bytes * @param int $error PHP file upload error * @param string|null $clientFilename Filename as provided by the client, if any. * @param string|null $clientMediaType Media type as provided by the client, if any. * * @return UploadedFileInterface * * @throws \InvalidArgumentException If the file resource is not readable. */ public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface; } third-party/Psr/Http/Message/UriFactoryInterface.php 0000644 00000000544 15212525755 0016517 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface UriFactoryInterface { /** * Create a new URI. * * @param string $uri * * @return UriInterface * * @throws \InvalidArgumentException If the given URI cannot be parsed. */ public function createUri(string $uri = ''): UriInterface; } third-party/Psr/Http/Message/MessageInterface.php 0000644 00000015723 15212525755 0016021 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; /** * HTTP messages consist of requests from a client to a server and responses * from a server to a client. This interface defines the methods common to * each. * * Messages are considered immutable; all methods that might change state MUST * be implemented such that they retain the internal state of the current * message and return an instance that contains the changed state. * * @link http://www.ietf.org/rfc/rfc7230.txt * @link http://www.ietf.org/rfc/rfc7231.txt */ interface MessageInterface { /** * Retrieves the HTTP protocol version as a string. * * The string MUST contain only the HTTP version number (e.g., "1.1", "1.0"). * * @return string HTTP protocol version. */ public function getProtocolVersion(): string; /** * Return an instance with the specified HTTP protocol version. * * The version string MUST contain only the HTTP version number (e.g., * "1.1", "1.0"). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new protocol version. * * @param string $version HTTP protocol version * @return static */ public function withProtocolVersion(string $version): MessageInterface; /** * Retrieves all message header values. * * The keys represent the header name as it will be sent over the wire, and * each value is an array of strings associated with the header. * * // Represent the headers as a string * foreach ($message->getHeaders() as $name => $values) { * echo $name . ": " . implode(", ", $values); * } * * // Emit headers iteratively: * foreach ($message->getHeaders() as $name => $values) { * foreach ($values as $value) { * header(sprintf('%s: %s', $name, $value), false); * } * } * * While header names are not case-sensitive, getHeaders() will preserve the * exact case in which headers were originally specified. * * @return string[][] Returns an associative array of the message's headers. Each * key MUST be a header name, and each value MUST be an array of strings * for that header. */ public function getHeaders(): array; /** * Checks if a header exists by the given case-insensitive name. * * @param string $name Case-insensitive header field name. * @return bool Returns true if any header names match the given header * name using a case-insensitive string comparison. Returns false if * no matching header name is found in the message. */ public function hasHeader(string $name): bool; /** * Retrieves a message header value by the given case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $name Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function getHeader(string $name): array; /** * Retrieves a comma-separated string of the values for a single header. * * This method returns all of the header values of the given * case-insensitive header name as a string concatenated together using * a comma. * * NOTE: Not all header values may be appropriately represented using * comma concatenation. For such headers, use getHeader() instead * and supply your own delimiter when concatenating. * * If the header does not appear in the message, this method MUST return * an empty string. * * @param string $name Case-insensitive header field name. * @return string A string of values as provided for the given header * concatenated together using a comma. If the header does not appear in * the message, this method MUST return an empty string. */ public function getHeaderLine(string $name): string; /** * Return an instance with the provided value replacing the specified header. * * While header names are case-insensitive, the casing of the header will * be preserved by this function, and returned from getHeaders(). * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new and/or updated header and value. * * @param string $name Case-insensitive header field name. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withHeader(string $name, $value): MessageInterface; /** * Return an instance with the specified header appended with the given value. * * Existing values for the specified header will be maintained. The new * value(s) will be appended to the existing list. If the header did not * exist previously, it will be added. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that has the * new header and/or value. * * @param string $name Case-insensitive header field name to add. * @param string|string[] $value Header value(s). * @return static * @throws \InvalidArgumentException for invalid header names or values. */ public function withAddedHeader(string $name, $value): MessageInterface; /** * Return an instance without the specified header. * * Header resolution MUST be done without case-sensitivity. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return an instance that removes * the named header. * * @param string $name Case-insensitive header field name to remove. * @return static */ public function withoutHeader(string $name): MessageInterface; /** * Gets the body of the message. * * @return StreamInterface Returns the body as a stream. */ public function getBody(): StreamInterface; /** * Return an instance with the specified message body. * * The body MUST be a StreamInterface object. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. * * @param StreamInterface $body Body. * @return static * @throws \InvalidArgumentException When the body is not valid. */ public function withBody(StreamInterface $body): MessageInterface; } third-party/Psr/Http/Message/RequestFactoryInterface.php 0000644 00000001022 15212525755 0017400 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Message; interface RequestFactoryInterface { /** * Create a new request. * * @param string $method The HTTP method associated with the request. * @param UriInterface|string $uri The URI associated with the request. If * the value is a string, the factory MUST create a UriInterface * instance based on it. * * @return RequestInterface */ public function createRequest(string $method, $uri): RequestInterface; } third-party/Psr/Http/Client/RequestExceptionInterface.php 0000644 00000001207 15212525755 0017566 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Client; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; /** * Exception for when a request failed. * * Examples: * - Request is invalid (e.g. method is missing) * - Runtime request errors (e.g. the body stream is not seekable) */ interface RequestExceptionInterface extends ClientExceptionInterface { /** * Returns the request. * * The request object MAY be a different object from the one passed to ClientInterface::sendRequest() * * @return RequestInterface */ public function getRequest(): RequestInterface; } third-party/Psr/Http/Client/NetworkExceptionInterface.php 0000644 00000001317 15212525755 0017571 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Client; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; /** * Thrown when the request cannot be completed because of network issues. * * There is no response object as this exception is thrown when no response has been received. * * Example: the target host name can not be resolved or the connection failed. */ interface NetworkExceptionInterface extends ClientExceptionInterface { /** * Returns the request. * * The request object MAY be a different object from the one passed to ClientInterface::sendRequest() * * @return RequestInterface */ public function getRequest(): RequestInterface; } third-party/Psr/Http/Client/error_log 0000644 00000001566 15212525755 0013652 0 ustar 00 [11-Jun-2026 10:19:42 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Psr/Http/Client/NetworkExceptionInterface.php:13 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Psr/Http/Client/NetworkExceptionInterface.php on line 13 [11-Jun-2026 11:24:22 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Psr/Http/Client/RequestExceptionInterface.php:13 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/third-party/Psr/Http/Client/RequestExceptionInterface.php on line 13 third-party/Psr/Http/Client/ClientExceptionInterface.php 0000644 00000000312 15212525755 0017350 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Client; /** * Every HTTP client related exception MUST implement this interface. */ interface ClientExceptionInterface extends \Throwable { } third-party/Psr/Http/Client/ClientInterface.php 0000644 00000001120 15212525755 0015467 0 ustar 00 <?php namespace WordPress\AiClientDependencies\Psr\Http\Client; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; interface ClientInterface { /** * Sends a PSR-7 request and returns a PSR-7 response. * * @param RequestInterface $request * * @return ResponseInterface * * @throws \Psr\Http\Client\ClientExceptionInterface If an error happens while processing the request. */ public function sendRequest(RequestInterface $request): ResponseInterface; } third-party/Psr/EventDispatcher/EventDispatcherInterface.php 0000644 00000000717 15212525755 0020307 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClientDependencies\Psr\EventDispatcher; /** * Defines a dispatcher for events. */ interface EventDispatcherInterface { /** * Provide all relevant listeners with an event to process. * * @param object $event * The object to process. * * @return object * The Event that was passed, now modified by listeners. */ public function dispatch(object $event); } src/Results/DTO/Candidate.php 0000644 00000006647 15212525755 0012035 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Results\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\Enums\FinishReasonEnum; /** * Represents a candidate response from an AI model. * * When generating content, AI models can produce multiple candidates. * Each candidate contains a message and metadata about why generation stopped. * * @since 0.1.0 * * @phpstan-import-type MessageArrayShape from Message * * @phpstan-type CandidateArrayShape array{message: MessageArrayShape, finishReason: string} * * @extends AbstractDataTransferObject<CandidateArrayShape> */ 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; } } src/Results/DTO/TokenUsage.php 0000644 00000011420 15212525755 0012207 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Results\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; /** * Represents token usage statistics for an AI operation. * * This DTO tracks the number of tokens used in prompts and completions, * which is important for monitoring usage and costs. * * Note that thought tokens are a subset of completion tokens, not additive. * In other words: completionTokens - thoughtTokens = tokens of actual output content. * * @since 0.1.0 * * @phpstan-type TokenUsageArrayShape array{ * promptTokens: int, * completionTokens: int, * totalTokens: int, * thoughtTokens?: int * } * * @extends AbstractDataTransferObject<TokenUsageArrayShape> */ 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); } } src/Results/DTO/error_log 0000644 00000004260 15212525755 0011352 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [11-Jun-2026 08:08:00 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27 [11-Jun-2026 08:09:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38 [11-Jun-2026 09:21:50 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24 src/Results/DTO/GenerativeAiResult.php 0000644 00000032757 15212525755 0013724 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Results\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\DTO\Message; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\Contracts\ResultInterface; /** * Represents the result of a generative AI operation. * * This DTO contains the generated candidates along with usage statistics * and metadata from the AI provider. * * @since 0.1.0 * * @phpstan-import-type CandidateArrayShape from Candidate * @phpstan-import-type TokenUsageArrayShape from TokenUsage * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata * * @phpstan-type GenerativeAiResultArrayShape array{ * id: string, * candidates: array<CandidateArrayShape>, * tokenUsage: TokenUsageArrayShape, * providerMetadata: ProviderMetadataArrayShape, * modelMetadata: ModelMetadataArrayShape, * additionalData?: array<string, mixed> * } * * @extends AbstractDataTransferObject<GenerativeAiResultArrayShape> */ 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<string, mixed> 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<string, mixed> $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<string> 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<File> 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<File> 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<File> 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<File> 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<Message> 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; } } src/Results/Contracts/ResultInterface.php 0000644 00000002571 15212525755 0014562 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Results\Contracts; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Results\DTO\TokenUsage; /** * Interface for AI operation results. * * Results contain the output from AI operations along with metadata * such as token usage and provider-specific information. * * @since 0.1.0 */ interface ResultInterface { /** * Gets the result ID. * * @since 0.1.0 * * @return string The unique result identifier. */ public function getId(): string; /** * Gets token usage information. * * @since 0.1.0 * * @return TokenUsage Token usage statistics. */ public function getTokenUsage(): TokenUsage; /** * Gets the provider metadata. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function getProviderMetadata(): ProviderMetadata; /** * Gets the model metadata. * * @since 0.1.0 * * @return ModelMetadata The model metadata. */ public function getModelMetadata(): ModelMetadata; /** * Gets provider-specific metadata. * * @since 0.1.0 * * @return array<string, mixed> Provider metadata. */ public function getAdditionalData(): array; } src/Results/Enums/FinishReasonEnum.php 0000644 00000002616 15212525755 0014027 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Results\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for finish reasons of AI generation. * * @since 0.1.0 * * @method static self stop() Creates an instance for STOP reason. * @method static self length() Creates an instance for LENGTH reason. * @method static self contentFilter() Creates an instance for CONTENT_FILTER reason. * @method static self toolCalls() Creates an instance for TOOL_CALLS reason. * @method static self error() Creates an instance for ERROR reason. * @method bool isStop() Checks if the reason is STOP. * @method bool isLength() Checks if the reason is LENGTH. * @method bool isContentFilter() Checks if the reason is CONTENT_FILTER. * @method bool isToolCalls() Checks if the reason is TOOL_CALLS. * @method bool isError() Checks if the reason is ERROR. */ class FinishReasonEnum extends AbstractEnum { /** * Generation stopped naturally. */ public const STOP = 'stop'; /** * Generation stopped due to max length. */ public const LENGTH = 'length'; /** * Generation stopped due to content filter. */ public const CONTENT_FILTER = 'content_filter'; /** * Generation stopped to make tool calls. */ public const TOOL_CALLS = 'tool_calls'; /** * Generation stopped due to error. */ public const ERROR = 'error'; } src/Results/Enums/error_log 0000644 00000000560 15212525755 0012012 0 ustar 00 [11-Jun-2026 08:15:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23 src/Events/BeforeGenerateResultEvent.php 0000644 00000005251 15212525755 0014402 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Events; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; /** * Event dispatched before a prompt is sent to the AI model. * * This event allows listeners to inspect and modify the messages before they * are sent to the model. The event is not stoppable, meaning the model call * will always proceed regardless of listener actions. * * @since 0.4.0 */ class BeforeGenerateResultEvent { /** * @var list<Message> The messages to be sent to the model. */ private array $messages; /** * @var ModelInterface The model that will process the prompt. */ private ModelInterface $model; /** * @var CapabilityEnum|null The capability being used for generation. */ private ?CapabilityEnum $capability; /** * Constructor. * * @since 0.4.0 * * @param list<Message> $messages The messages to be sent to the model. * @param ModelInterface $model The model that will process the prompt. * @param CapabilityEnum|null $capability The capability being used for generation. */ public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability) { $this->messages = $messages; $this->model = $model; $this->capability = $capability; } /** * Gets the messages to be sent to the model. * * @since 0.4.0 * * @return list<Message> The messages. */ public function getMessages(): array { return $this->messages; } /** * Gets the model that will process the prompt. * * @since 0.4.0 * * @return ModelInterface The model. */ public function getModel(): ModelInterface { return $this->model; } /** * Gets the capability being used for generation. * * @since 0.4.0 * * @return CapabilityEnum|null The capability, or null if not specified. */ public function getCapability(): ?CapabilityEnum { return $this->capability; } /** * Performs a deep clone of the event. * * This method ensures that message objects are cloned to prevent * modifications to the cloned event from affecting the original. * The model object is not cloned as it is a service object. * * @since 0.4.2 */ public function __clone() { $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; } } src/Events/AfterGenerateResultEvent.php 0000644 00000006353 15212525755 0014245 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Events; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Event dispatched after a prompt has been sent to the AI model and a response received. * * This event allows listeners to inspect the result of the model call for logging, * analytics, or other post-processing purposes. The result object is immutable. * * @since 0.4.0 */ class AfterGenerateResultEvent { /** * @var list<Message> The messages that were sent to the model. */ private array $messages; /** * @var ModelInterface The model that processed the prompt. */ private ModelInterface $model; /** * @var CapabilityEnum|null The capability that was used for generation. */ private ?CapabilityEnum $capability; /** * @var GenerativeAiResult The result from the model. */ private GenerativeAiResult $result; /** * Constructor. * * @since 0.4.0 * * @param list<Message> $messages The messages that were sent to the model. * @param ModelInterface $model The model that processed the prompt. * @param CapabilityEnum|null $capability The capability that was used for generation. * @param GenerativeAiResult $result The result from the model. */ public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability, GenerativeAiResult $result) { $this->messages = $messages; $this->model = $model; $this->capability = $capability; $this->result = $result; } /** * Gets the messages that were sent to the model. * * @since 0.4.0 * * @return list<Message> The messages. */ public function getMessages(): array { return $this->messages; } /** * Gets the model that processed the prompt. * * @since 0.4.0 * * @return ModelInterface The model. */ public function getModel(): ModelInterface { return $this->model; } /** * Gets the capability that was used for generation. * * @since 0.4.0 * * @return CapabilityEnum|null The capability, or null if not specified. */ public function getCapability(): ?CapabilityEnum { return $this->capability; } /** * Gets the result from the model. * * @since 0.4.0 * * @return GenerativeAiResult The result. */ public function getResult(): GenerativeAiResult { return $this->result; } /** * Performs a deep clone of the event. * * This method ensures that message and result objects are cloned to prevent * modifications to the cloned event from affecting the original. * The model object is not cloned as it is a service object. * * @since 0.4.2 */ public function __clone() { $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; $this->result = clone $this->result; } } src/Common/AbstractDataTransferObject.php 0000644 00000011175 15212525755 0014503 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common; use JsonSerializable; use stdClass; use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface; use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Abstract base class for all Data Value Objects in the AI Client. * * This abstract class consolidates the common functionality needed by all * data transfer objects: * - Array transformation for data manipulation * - JSON schema support for validation and documentation * - JSON serialization with proper empty object handling * * All DTOs in the AI Client should extend this class to ensure * consistent behavior across the codebase. * * @since 0.1.0 * * @template TArrayShape of array<string, mixed> * @implements WithArrayTransformationInterface<TArrayShape> */ abstract class AbstractDataTransferObject implements WithArrayTransformationInterface, WithJsonSchemaInterface, JsonSerializable { /** * Validates that required keys exist in the array data. * * @since 0.1.0 * * @param array<mixed> $data The array data to validate. * @param string[] $requiredKeys The keys that must be present. * @throws InvalidArgumentException If any required key is missing. */ protected static function validateFromArrayData(array $data, array $requiredKeys): void { $missingKeys = []; foreach ($requiredKeys as $key) { if (!array_key_exists($key, $data)) { $missingKeys[] = $key; } } if (!empty($missingKeys)) { throw new InvalidArgumentException(sprintf('%s::fromArray() missing required keys: %s', static::class, implode(', ', $missingKeys))); } } /** * {@inheritDoc} * * @since 0.1.0 */ public static function isArrayShape(array $array): bool { try { /** @var TArrayShape $array */ static::fromArray($array); return \true; } catch (InvalidArgumentException $e) { return \false; } } /** * Converts the object to a JSON-serializable format. * * This method uses the toArray() method and then processes the result * based on the JSON schema to ensure proper object representation for * empty arrays. * * @since 0.1.0 * * @return mixed The JSON-serializable representation. */ #[\ReturnTypeWillChange] public function jsonSerialize() { $data = $this->toArray(); $schema = static::getJsonSchema(); return $this->convertEmptyArraysToObjects($data, $schema); } /** * Recursively converts empty arrays to stdClass objects where the schema expects objects. * * @since 0.1.0 * * @param mixed $data The data to process. * @param array<mixed, mixed> $schema The JSON schema for the data. * @return mixed The processed data. */ private function convertEmptyArraysToObjects($data, array $schema) { // If data is an empty array and schema expects object, convert to stdClass if (is_array($data) && empty($data) && isset($schema['type']) && $schema['type'] === 'object') { return new stdClass(); } // If data is an array with content, recursively process nested structures if (is_array($data)) { // Handle object properties if (isset($schema['properties']) && is_array($schema['properties'])) { foreach ($data as $key => $value) { if (isset($schema['properties'][$key]) && is_array($schema['properties'][$key])) { $data[$key] = $this->convertEmptyArraysToObjects($value, $schema['properties'][$key]); } } } // Handle array items if (isset($schema['items']) && is_array($schema['items'])) { foreach ($data as $index => $item) { $data[$index] = $this->convertEmptyArraysToObjects($item, $schema['items']); } } // Handle oneOf/anyOf schemas - just use the first one foreach (['oneOf', 'anyOf'] as $keyword) { if (isset($schema[$keyword]) && is_array($schema[$keyword])) { foreach ($schema[$keyword] as $possibleSchema) { if (is_array($possibleSchema)) { return $this->convertEmptyArraysToObjects($data, $possibleSchema); } } } } } return $data; } } src/Common/Contracts/WithJsonSchemaInterface.php 0000644 00000001136 15212525755 0015755 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that can provide their JSON schema representation. * * This interface is implemented by DTOs to provide a consistent way to retrieve * their JSON schema for validation and serialization purposes. * * @since 0.1.0 */ interface WithJsonSchemaInterface { /** * Gets the JSON schema representation of the object. * * @since 0.1.0 * * @return array<string, mixed> The JSON schema as an associative array. */ public static function getJsonSchema(): array; } src/Common/Contracts/WithArrayTransformationInterface.php 0000644 00000002033 15212525755 0017725 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that support array transformation. * * @since 0.1.0 * * @template TArrayShape of array<string, mixed> */ interface WithArrayTransformationInterface { /** * Converts the object to an array representation. * * @since 0.1.0 * * @return TArrayShape The array representation. */ public function toArray(): array; /** * Creates an instance from array data. * * @since 0.1.0 * * @param TArrayShape $array The array data. * @return self<TArrayShape> The created instance. */ public static function fromArray(array $array): self; /** * Checks if the array is a valid shape for this object. * * @since 0.1.0 * * @param array<mixed> $array The array to check. * @return bool True if the array is a valid shape. * @phpstan-assert-if-true TArrayShape $array */ public static function isArrayShape(array $array): bool; } src/Common/Contracts/AiClientExceptionInterface.php 0000644 00000000527 15212525755 0016441 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; use Throwable; /** * Base interface for all AI Client exceptions. * * This interface allows callers to catch all AI Client specific exceptions * with a single catch statement. * * @since 0.2.0 */ interface AiClientExceptionInterface extends Throwable { } src/Common/Contracts/CachesDataInterface.php 0000644 00000000542 15212525755 0015047 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Contracts; /** * Interface for objects that cache data. * * @since 0.4.0 */ interface CachesDataInterface { /** * Invalidates all caches managed by this object. * * @since 0.4.0 * * @return void */ public function invalidateCaches(): void; } src/Common/AbstractEnum.php 0000644 00000026160 15212525755 0011702 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common; use BadMethodCallException; use JsonSerializable; use ReflectionClass; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; /** * Abstract base class for enum-like behavior in PHP 7.4. * * This class provides enum-like functionality for PHP versions that don't support native enums. * Child classes should define uppercase snake_case constants for enum values. * * @example * class PersonEnum extends AbstractEnum { * public const FIRST_NAME = 'first'; * public const LAST_NAME = 'last'; * } * * // Usage: * $enum = PersonEnum::from('first'); // Creates instance with value 'first' * $enum = PersonEnum::tryFrom('invalid'); // Returns null * $enum = PersonEnum::firstName(); // Creates instance with value 'first' * $enum->name; // 'FIRST_NAME' * $enum->value; // 'first' * $enum->equals('first'); // Returns true * $enum->is(PersonEnum::firstName()); // Returns true * PersonEnum::cases(); // Returns array of all enum instances * * @property-read string $value The value of the enum instance. * @property-read string $name The name of the enum constant. * * @since 0.1.0 */ abstract class AbstractEnum implements JsonSerializable { /** * @var string The value of the enum instance. */ private string $value; /** * @var string The name of the enum constant. */ private string $name; /** * @var array<string, array<string, string>> Cache for reflection data. */ private static array $cache = []; /** * @var array<string, array<string, self>> Cache for enum instances. */ private static array $instances = []; /** * Constructor is private to ensure instances are created through static methods. * * @since 0.1.0 * * @param string $value The enum value. * @param string $name The constant name. */ final private function __construct(string $value, string $name) { $this->value = $value; $this->name = $name; } /** * Provides read-only access to properties. * * @since 0.1.0 * * @param string $property The property name. * @return mixed The property value. * @throws BadMethodCallException If property doesn't exist. */ final public function __get(string $property) { if ($property === 'value' || $property === 'name') { return $this->{$property}; } throw new BadMethodCallException(sprintf('Property %s::%s does not exist', static::class, $property)); } /** * Prevents property modification. * * @since 0.1.0 * * @param string $property The property name. * @param mixed $value The value to set. * @throws BadMethodCallException Always, as enum properties are read-only. */ final public function __set(string $property, $value): void { throw new BadMethodCallException(sprintf('Cannot modify property %s::%s - enum properties are read-only', static::class, $property)); } /** * Creates an enum instance from a value, throws exception if invalid. * * @since 0.1.0 * * @param string $value The enum value. * @return static The enum instance. * @throws InvalidArgumentException If the value is not valid. */ final public static function from(string $value): self { $instance = self::tryFrom($value); if ($instance === null) { throw new InvalidArgumentException(sprintf('%s is not a valid backing value for enum %s', $value, static::class)); } return $instance; } /** * Tries to create an enum instance from a value, returns null if invalid. * * @since 0.1.0 * * @param string $value The enum value. * @return static|null The enum instance or null. */ final public static function tryFrom(string $value): ?self { $constants = static::getConstants(); foreach ($constants as $name => $constantValue) { if ($constantValue === $value) { return self::getInstance($constantValue, $name); } } return null; } /** * Gets all enum cases. * * @since 0.1.0 * * @return static[] Array of all enum instances. */ final public static function cases(): array { $cases = []; $constants = static::getConstants(); foreach ($constants as $name => $value) { $cases[] = self::getInstance($value, $name); } return $cases; } /** * Checks if this enum has the same value as the given value. * * @since 0.1.0 * * @param string|self $other The value or enum to compare. * @return bool True if values are equal. */ final public function equals($other): bool { if ($other instanceof self) { return $this->is($other); } return $this->value === $other; } /** * Checks if this enum is the same instance type and value as another enum. * * @since 0.1.0 * * @param self $other The other enum to compare. * @return bool True if enums are identical. */ final public function is(self $other): bool { return $this === $other; // Since we're using singletons, we can use identity comparison } /** * Gets all valid values for this enum. * * @since 0.1.0 * * @return string[] List of all enum values. */ final public static function getValues(): array { return array_values(static::getConstants()); } /** * Checks if a value is valid for this enum. * * @since 0.1.0 * * @param string $value The value to check. * @return bool True if value is valid. */ final public static function isValidValue(string $value): bool { return in_array($value, self::getValues(), \true); } /** * Gets or creates a singleton instance for the given value and name. * * @since 0.1.0 * * @param string $value The enum value. * @param string $name The constant name. * @return static The enum instance. */ private static function getInstance(string $value, string $name): self { $className = static::class; if (!isset(self::$instances[$className])) { self::$instances[$className] = []; } if (!isset(self::$instances[$className][$name])) { $instance = new $className($value, $name); self::$instances[$className][$name] = $instance; } /** @var static */ return self::$instances[$className][$name]; } /** * Gets all constants for this enum class. * * @since 0.1.0 * * @return array<string, string> Map of constant names to values. * @throws RuntimeException If invalid constant found. */ final protected static function getConstants(): array { $className = static::class; if (!isset(self::$cache[$className])) { self::$cache[$className] = static::determineClassEnumerations($className); } return self::$cache[$className]; } /** * Determines the class enumerations by reflecting on class constants. * * This method can be overridden by subclasses to customize how * enumerations are determined (e.g., to add dynamic constants). * * @since 0.1.0 * * @param class-string $className The fully qualified class name. * @return array<string, string> Map of constant names to values. * @throws RuntimeException If invalid constant found. */ protected static function determineClassEnumerations(string $className): array { $reflection = new ReflectionClass($className); $constants = $reflection->getConstants(); // Validate all constants $enumConstants = []; foreach ($constants as $name => $value) { // Check if constant name follows uppercase snake_case pattern if (!preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) { throw new RuntimeException(sprintf('Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.', $name, $className)); } // Check if value is valid type if (!is_string($value)) { throw new RuntimeException(sprintf('Invalid enum value type for constant %s::%s. ' . 'Only string values are allowed, %s given.', $className, $name, gettype($value))); } $enumConstants[$name] = $value; } return $enumConstants; } /** * Handles dynamic method calls for enum checking. * * @since 0.1.0 * * @param string $name The method name. * @param array<mixed> $arguments The method arguments. * @return bool True if the enum value matches. * @throws BadMethodCallException If the method doesn't exist. */ final public function __call(string $name, array $arguments): bool { // Handle is* methods if (str_starts_with($name, 'is')) { $constantName = self::camelCaseToConstant(substr($name, 2)); $constants = static::getConstants(); if (isset($constants[$constantName])) { return $this->value === $constants[$constantName]; } } throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name)); } /** * Handles static method calls for enum creation. * * @since 0.1.0 * * @param string $name The method name. * @param array<mixed> $arguments The method arguments. * @return static The enum instance. * @throws BadMethodCallException If the method doesn't exist. */ final public static function __callStatic(string $name, array $arguments): self { $constantName = self::camelCaseToConstant($name); $constants = static::getConstants(); if (isset($constants[$constantName])) { return self::getInstance($constants[$constantName], $constantName); } throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name)); } /** * Converts camelCase to CONSTANT_CASE. * * @since 0.1.0 * * @param string $camelCase The camelCase string. * @return string The CONSTANT_CASE version. */ private static function camelCaseToConstant(string $camelCase): string { $snakeCase = preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase); if ($snakeCase === null) { return strtoupper($camelCase); } return strtoupper($snakeCase); } /** * Returns string representation of the enum. * * @since 0.1.0 * * @return string The enum value. */ final public function __toString(): string { return $this->value; } /** * Converts the enum to a JSON-serializable format. * * @since 0.1.0 * * @return string The enum value. */ #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->value; } } src/Common/error_log 0000644 00000003140 15212525755 0010507 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [11-Jun-2026 06:44:45 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [11-Jun-2026 09:33:19 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 [11-Jun-2026 11:11:43 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28 src/Common/Traits/WithDataCachingTrait.php 0000644 00000011762 15212525755 0014550 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Traits; use WordPress\AiClient\AiClient; /** * Trait for objects that cache data using PSR-16 cache with in-memory fallback. * * When a PSR-16 cache is configured via AiClient::setCache(), data is stored persistently. * Otherwise, data is cached in-memory for the duration of the request. * * @since 0.4.0 */ trait WithDataCachingTrait { /** * In-memory cache used when no PSR-16 cache is configured. * * @since 0.4.0 * * @var array<string, mixed> */ private array $localCache = []; /** * Gets the cache key suffixes managed by this object. * * @since 0.4.0 * * @return list<string> The cache key suffixes. */ abstract protected function getCachedKeys(): array; /** * Gets the base cache key for this object. * * The base cache key is used as a prefix for all cache keys managed by this object. * It should be unique to the implementing class to avoid cache key collisions. * * @since 0.4.0 * * @return string The base cache key. */ abstract protected function getBaseCacheKey(): string; /** * Checks if a value exists in the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @return bool True if the value exists in cache, false otherwise. */ protected function hasCache(string $key): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->has($fullKey); } return array_key_exists($fullKey, $this->localCache); } /** * Gets a value from the cache, or computes and caches it if not present. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param callable $callback The callback to compute the value if not cached. * @param int|\DateInterval|null $ttl The TTL for the cache entry, or null for default. * Ignored for local cache. * @return mixed The cached or computed value. */ protected function cached(string $key, callable $callback, $ttl = null) { if ($this->hasCache($key)) { return $this->getCache($key); } $value = $callback(); $this->setCache($key, $value, $ttl); return $value; } /** * Gets a value from the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param mixed $default The default value to return if the key does not exist. * @return mixed The cached value or the default value if not found. */ protected function getCache(string $key, $default = null) { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->get($fullKey, $default); } return $this->localCache[$fullKey] ?? $default; } /** * Sets a value in the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @param mixed $value The value to cache. * @param int|\DateInterval|null $ttl The TTL for the cache entry, or null for default. Ignored for local cache. * @return bool True on success, false on failure. */ protected function setCache(string $key, $value, $ttl = null): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->set($fullKey, $value, $ttl); } $this->localCache[$fullKey] = $value; return \true; } /** * Invalidates all caches managed by this object. * * @since 0.4.0 * * @return void */ public function invalidateCaches(): void { foreach ($this->getCachedKeys() as $key) { $this->clearCache($key); } } /** * Clears a value from the cache. * * @since 0.4.0 * * @param string $key The cache key suffix (will be appended to the base key). * @return bool True on success, false on failure. */ protected function clearCache(string $key): bool { $fullKey = $this->buildCacheKey($key); $cache = AiClient::getCache(); if ($cache !== null) { return $cache->delete($fullKey); } unset($this->localCache[$fullKey]); return \true; } /** * Builds the full cache key by combining the base key with the suffix. * * @since 0.4.0 * * @param string $key The cache key suffix. * @return string The full cache key. */ private function buildCacheKey(string $key): string { return $this->getBaseCacheKey() . '_' . $key; } } src/Common/Exception/TokenLimitReachedException.php 0000644 00000002616 15212525755 0016462 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; /** * Exception thrown when a token limit is reached during prompt fulfillment. * * Providers should throw this exception when the token usage for a request * exceeds the allowed limit, whether that is the model's context window * or a configured maximum. * * @since 1.0.0 */ class TokenLimitReachedException extends \WordPress\AiClient\Common\Exception\RuntimeException { /** * The token limit that was reached, if known. * * @since 1.0.0 * * @var int|null */ private $maxTokens; /** * Creates a new TokenLimitReachedException. * * @since 1.0.0 * * @param string $message The exception message. * @param int|null $maxTokens The token limit that was reached, if known. * @param \Throwable|null $previous The previous throwable used for exception chaining. */ public function __construct(string $message = '', ?int $maxTokens = null, ?\Throwable $previous = null) { parent::__construct($message, 0, $previous); $this->maxTokens = $maxTokens; } /** * Returns the token limit that was reached, if known. * * @since 1.0.0 * * @return int|null The token limit, or null if not provided. */ public function getMaxTokens(): ?int { return $this->maxTokens; } } src/Common/Exception/RuntimeException.php 0000644 00000000675 15212525755 0014555 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface; /** * Exception thrown for runtime errors. * * This extends PHP's built-in RuntimeException while implementing * the AI Client exception interface for consistent catch handling. * * @since 0.2.0 */ class RuntimeException extends \RuntimeException implements AiClientExceptionInterface { } src/Common/Exception/error_log 0000644 00000004630 15212525755 0012452 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [11-Jun-2026 07:57:55 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15 [11-Jun-2026 07:58:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15 [11-Jun-2026 09:09:38 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15 src/Common/Exception/InvalidArgumentException.php 0000644 00000000747 15212525755 0016223 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Common\Exception; use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface; /** * Exception thrown when an invalid argument is provided. * * This extends PHP's built-in InvalidArgumentException while implementing * the AI Client exception interface for consistent catch handling. * * @since 0.2.0 */ class InvalidArgumentException extends \InvalidArgumentException implements AiClientExceptionInterface { } src/Operations/DTO/GenerativeAiOperation.php 0000644 00000012132 15212525755 0015051 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Operations\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Operations\Contracts\OperationInterface; use WordPress\AiClient\Operations\Enums\OperationStateEnum; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Represents a long-running generative AI operation. * * This DTO tracks the progress of generative AI tasks that may not complete * immediately, providing access to the result once available. * * @since 0.1.0 * * @phpstan-import-type GenerativeAiResultArrayShape from GenerativeAiResult * * @phpstan-type GenerativeAiOperationArrayShape array{id: string, state: string, result?: GenerativeAiResultArrayShape} * * @extends AbstractDataTransferObject<GenerativeAiOperationArrayShape> */ 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); } } src/Operations/DTO/error_log 0000644 00000001424 15212525755 0012033 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 [11-Jun-2026 09:01:48 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24 src/Operations/Contracts/OperationInterface.php 0000644 00000001413 15212525755 0015720 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Operations\Contracts; use WordPress\AiClient\Operations\Enums\OperationStateEnum; /** * Interface for AI operations. * * Operations represent long-running AI tasks that may not complete immediately. * They provide a way to track the progress and retrieve results asynchronously. * * @since 0.1.0 */ interface OperationInterface { /** * Gets the operation ID. * * @since 0.1.0 * * @return string The unique operation identifier. */ public function getId(): string; /** * Gets the current state of the operation. * * @since 0.1.0 * * @return OperationStateEnum The operation state. */ public function getState(): OperationStateEnum; } src/Operations/Enums/OperationStateEnum.php 0000644 00000002503 15212525755 0015055 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Operations\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for operation states. * * @since 0.1.0 * * @method static self starting() Creates an instance for STARTING state. * @method static self processing() Creates an instance for PROCESSING state. * @method static self succeeded() Creates an instance for SUCCEEDED state. * @method static self failed() Creates an instance for FAILED state. * @method static self canceled() Creates an instance for CANCELED state. * @method bool isStarting() Checks if the state is STARTING. * @method bool isProcessing() Checks if the state is PROCESSING. * @method bool isSucceeded() Checks if the state is SUCCEEDED. * @method bool isFailed() Checks if the state is FAILED. * @method bool isCanceled() Checks if the state is CANCELED. */ class OperationStateEnum extends AbstractEnum { /** * Operation is starting. */ public const STARTING = 'starting'; /** * Operation is processing. */ public const PROCESSING = 'processing'; /** * Operation succeeded. */ public const SUCCEEDED = 'succeeded'; /** * Operation failed. */ public const FAILED = 'failed'; /** * Operation was canceled. */ public const CANCELED = 'canceled'; } src/Operations/Enums/error_log 0000644 00000001364 15212525755 0012477 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 [11-Jun-2026 08:00:49 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23 src/Builders/PromptBuilder.php 0000644 00000153503 15212525755 0012425 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Builders; use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Events\AfterGenerateResultEvent; use WordPress\AiClient\Events\BeforeGenerateResultEvent; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\DTO\UserMessage; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface; use WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationModelInterface; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; use WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionModelInterface; use WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationModelInterface; use WordPress\AiClient\Providers\ProviderRegistry; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\FunctionResponse; use WordPress\AiClient\Tools\DTO\WebSearch; /** * Fluent builder for constructing AI prompts. * * This class provides a fluent interface for building prompts with various * content types and model configurations. It automatically infers model * requirements based on the features used in the prompt. * * @since 0.1.0 * * @phpstan-import-type MessageArrayShape from Message * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type Prompt string|MessagePart|Message|MessageArrayShape|list<string|MessagePart|MessagePartArrayShape>|list<Message>|null */ class PromptBuilder { /** * @var ProviderRegistry The provider registry for finding suitable models. */ private ProviderRegistry $registry; /** * @var list<Message> The messages in the conversation. */ protected array $messages = []; /** * @var ModelInterface|null The model to use for generation. */ protected ?ModelInterface $model = null; /** * @var list<string> Ordered list of preference keys to check when selecting a model. */ protected array $modelPreferenceKeys = []; /** * @var string|null The provider ID or class name. */ protected ?string $providerIdOrClassName = null; /** * @var ModelConfig The model configuration. */ protected ModelConfig $modelConfig; /** * @var RequestOptions|null The request options for HTTP transport. */ protected ?RequestOptions $requestOptions = null; /** * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events. */ private ?EventDispatcherInterface $eventDispatcher = null; // phpcs:disable Generic.Files.LineLength.TooLong /** * Constructor. * * @since 0.1.0 * * @param ProviderRegistry $registry The provider registry for finding suitable models. * @param Prompt $prompt Optional initial prompt content. * @param EventDispatcherInterface|null $eventDispatcher Optional event dispatcher for lifecycle events. */ // phpcs:enable Generic.Files.LineLength.TooLong public function __construct(ProviderRegistry $registry, $prompt = null, ?EventDispatcherInterface $eventDispatcher = null) { $this->registry = $registry; $this->modelConfig = new ModelConfig(); $this->eventDispatcher = $eventDispatcher; if ($prompt === null) { return; } // Check if it's a list of Messages - set as messages if ($this->isMessagesList($prompt)) { $this->messages = $prompt; return; } // Parse it as a user message $userMessage = $this->parseMessage($prompt, MessageRoleEnum::user()); $this->messages[] = $userMessage; } /** * Creates a deep clone of this builder. * * Clones all mutable state including messages, model configuration, and request options. * Service objects (registry, model, event dispatcher) are intentionally NOT cloned * as they are shared dependencies. * * @since 0.4.2 */ public function __clone() { // Deep clone messages array (Message has __clone) $clonedMessages = []; foreach ($this->messages as $message) { $clonedMessages[] = clone $message; } $this->messages = $clonedMessages; // Clone model config (ModelConfig has __clone) $this->modelConfig = clone $this->modelConfig; // Clone request options if set (contains only primitives) if ($this->requestOptions !== null) { $this->requestOptions = clone $this->requestOptions; } // Note: $registry, $model, and $eventDispatcher are service objects // and are intentionally NOT cloned - they should be shared references. } /** * Adds text to the current message. * * @since 0.1.0 * * @param string $text The text to add. * @return self */ public function withText(string $text): self { $part = new MessagePart($text); $this->appendPartToMessages($part); return $this; } /** * Adds a file to the current message. * * Accepts: * - File object * - URL string (remote file) * - Base64-encoded data string * - Data URI string (data:mime/type;base64,data) * - Local file path string * * @since 0.1.0 * * @param string|File $file The file (File object or string representation). * @param string|null $mimeType The MIME type (optional, ignored if File object provided). * @return self * @throws InvalidArgumentException If the file is invalid or MIME type cannot be determined. */ public function withFile($file, ?string $mimeType = null): self { $file = $file instanceof File ? $file : new File($file, $mimeType); $part = new MessagePart($file); $this->appendPartToMessages($part); return $this; } /** * Adds a function response to the current message. * * @since 0.1.0 * * @param FunctionResponse $functionResponse The function response. * @return self */ public function withFunctionResponse(FunctionResponse $functionResponse): self { $part = new MessagePart($functionResponse); $this->appendPartToMessages($part); return $this; } /** * Adds message parts to the current message. * * @since 0.1.0 * * @param MessagePart ...$parts The message parts to add. * @return self */ public function withMessageParts(MessagePart ...$parts): self { foreach ($parts as $part) { $this->appendPartToMessages($part); } return $this; } /** * Adds conversation history messages. * * Historical messages are prepended to the beginning of the message list, * before the current message being built. * * @since 0.1.0 * * @param Message ...$messages The messages to add to history. * @return self */ public function withHistory(Message ...$messages): self { // Prepend the history messages to the beginning of the messages array $this->messages = array_merge($messages, $this->messages); return $this; } /** * Sets the model to use for generation. * * The model's configuration will be merged with the builder's configuration, * with the builder's configuration taking precedence for any overlapping settings. * * @since 0.1.0 * * @param ModelInterface $model The model to use. * @return self */ public function usingModel(ModelInterface $model): self { $this->model = $model; // Merge model's config with builder's config, with builder's config taking precedence $modelConfigArray = $model->getConfig()->toArray(); $builderConfigArray = $this->modelConfig->toArray(); $mergedConfigArray = array_merge($modelConfigArray, $builderConfigArray); $this->modelConfig = ModelConfig::fromArray($mergedConfigArray); return $this; } /** * Sets preferred models to evaluate in order. * * @since 0.2.0 * * @param string|ModelInterface|array{0:string,1:string} ...$preferredModels The preferred models as model IDs, * model instances, or [provider ID, model ID] tuples. For broader compatibility, it is recommended you specify * only model IDs or model instances, as that will allow for different providers that expose the same model to be * considered. * @return self * * @throws InvalidArgumentException When a preferred model has an invalid type or identifier. */ public function usingModelPreference(...$preferredModels): self { if ($preferredModels === []) { throw new InvalidArgumentException('At least one model preference must be provided.'); } $preferenceKeys = []; foreach ($preferredModels as $preferredModel) { if (is_array($preferredModel)) { // [model identifier, provider ID] tuple if (!array_is_list($preferredModel) || count($preferredModel) !== 2) { throw new InvalidArgumentException('Model preference tuple must contain model identifier and provider ID.'); } [$providerId, $modelId] = $preferredModel; $modelId = $this->normalizePreferenceIdentifier($modelId); $providerId = $this->normalizePreferenceIdentifier($providerId, 'Model preference provider identifiers cannot be empty.'); $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId); } elseif ($preferredModel instanceof ModelInterface) { // Model instance $modelId = $preferredModel->metadata()->getId(); $providerId = $preferredModel->providerMetadata()->getId(); $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId); } elseif (is_string($preferredModel)) { // Model ID $modelId = $this->normalizePreferenceIdentifier($preferredModel); $preferenceKey = $this->createModelPreferenceKey($modelId); } else { // Invalid type throw new InvalidArgumentException('Model preferences must be model identifiers, instances of ModelInterface, ' . 'or provider/model tuples.'); } $preferenceKeys[] = $preferenceKey; } $this->modelPreferenceKeys = $preferenceKeys; return $this; } /** * Sets the model configuration. * * Merges the provided configuration with the builder's configuration, * with builder configuration taking precedence. * * @since 0.1.0 * * @param ModelConfig $config The model configuration to merge. * @return self */ public function usingModelConfig(ModelConfig $config): self { // Convert both configs to arrays $builderConfigArray = $this->modelConfig->toArray(); $providedConfigArray = $config->toArray(); // Merge arrays with builder config taking precedence $mergedArray = array_merge($providedConfigArray, $builderConfigArray); // Create new config from merged array $this->modelConfig = ModelConfig::fromArray($mergedArray); return $this; } /** * Sets the provider to use for generation. * * @since 0.1.0 * * @param string $providerIdOrClassName The provider ID or class name. * @return self */ public function usingProvider(string $providerIdOrClassName): self { $this->providerIdOrClassName = $providerIdOrClassName; return $this; } /** * Sets the system instruction. * * System instructions are stored in the model configuration and guide * the AI model's behavior throughout the conversation. * * @since 0.1.0 * * @param string $systemInstruction The system instruction text. * @return self */ public function usingSystemInstruction(string $systemInstruction): self { $this->modelConfig->setSystemInstruction($systemInstruction); return $this; } /** * Sets the maximum number of tokens to generate. * * @since 0.1.0 * * @param int $maxTokens The maximum number of tokens. * @return self */ public function usingMaxTokens(int $maxTokens): self { $this->modelConfig->setMaxTokens($maxTokens); return $this; } /** * Sets the temperature for generation. * * @since 0.1.0 * * @param float $temperature The temperature value. * @return self */ public function usingTemperature(float $temperature): self { $this->modelConfig->setTemperature($temperature); return $this; } /** * Sets the top-p value for generation. * * @since 0.1.0 * * @param float $topP The top-p value. * @return self */ public function usingTopP(float $topP): self { $this->modelConfig->setTopP($topP); return $this; } /** * Sets the top-k value for generation. * * @since 0.1.0 * * @param int $topK The top-k value. * @return self */ public function usingTopK(int $topK): self { $this->modelConfig->setTopK($topK); return $this; } /** * Sets stop sequences for generation. * * @since 0.1.0 * * @param string ...$stopSequences The stop sequences. * @return self */ public function usingStopSequences(string ...$stopSequences): self { $this->modelConfig->setStopSequences($stopSequences); return $this; } /** * Sets the number of candidates to generate. * * @since 0.1.0 * * @param int $candidateCount The number of candidates. * @return self */ public function usingCandidateCount(int $candidateCount): self { $this->modelConfig->setCandidateCount($candidateCount); return $this; } /** * Sets the function declarations available to the model. * * @since 0.1.0 * * @param FunctionDeclaration ...$functionDeclarations The function declarations. * @return self */ public function usingFunctionDeclarations(FunctionDeclaration ...$functionDeclarations): self { $this->modelConfig->setFunctionDeclarations($functionDeclarations); return $this; } /** * Sets the presence penalty for generation. * * @since 0.1.0 * * @param float $presencePenalty The presence penalty value. * @return self */ public function usingPresencePenalty(float $presencePenalty): self { $this->modelConfig->setPresencePenalty($presencePenalty); return $this; } /** * Sets the frequency penalty for generation. * * @since 0.1.0 * * @param float $frequencyPenalty The frequency penalty value. * @return self */ public function usingFrequencyPenalty(float $frequencyPenalty): self { $this->modelConfig->setFrequencyPenalty($frequencyPenalty); return $this; } /** * Sets the web search configuration. * * @since 0.1.0 * * @param WebSearch $webSearch The web search configuration. * @return self */ public function usingWebSearch(WebSearch $webSearch): self { $this->modelConfig->setWebSearch($webSearch); return $this; } /** * Sets the request options for HTTP transport. * * @since 0.3.0 * * @param RequestOptions $requestOptions The request options. * @return self */ public function usingRequestOptions(RequestOptions $requestOptions): self { $this->requestOptions = $requestOptions; return $this; } /** * Sets the top log probabilities configuration. * * If $topLogprobs is null, enables log probabilities. * If $topLogprobs has a value, enables log probabilities and sets the number of top log probabilities to return. * * @since 0.1.0 * * @param int|null $topLogprobs The number of top log probabilities to return, or null to enable log probabilities. * @return self */ public function usingTopLogprobs(?int $topLogprobs = null): self { // Always enable log probabilities $this->modelConfig->setLogprobs(\true); // If a specific number is provided, set it if ($topLogprobs !== null) { $this->modelConfig->setTopLogprobs($topLogprobs); } return $this; } /** * Sets the output MIME type. * * @since 0.1.0 * * @param string $mimeType The MIME type. * @return self */ public function asOutputMimeType(string $mimeType): self { $this->modelConfig->setOutputMimeType($mimeType); return $this; } /** * Sets the output schema. * * @since 0.1.0 * * @param array<string, mixed> $schema The output schema. * @return self */ public function asOutputSchema(array $schema): self { $this->modelConfig->setOutputSchema($schema); return $this; } /** * Sets the output modalities. * * @since 0.1.0 * * @param ModalityEnum ...$modalities The output modalities. * @return self */ public function asOutputModalities(ModalityEnum ...$modalities): self { $this->modelConfig->setOutputModalities($modalities); return $this; } /** * Sets the output file type. * * @since 0.1.0 * * @param FileTypeEnum $fileType The output file type. * @return self */ public function asOutputFileType(FileTypeEnum $fileType): self { $this->modelConfig->setOutputFileType($fileType); return $this; } /** * Sets the output media orientation. * * @since 1.3.0 * * @param MediaOrientationEnum $orientation The output media orientation. * @return self */ public function asOutputMediaOrientation(MediaOrientationEnum $orientation): self { $this->modelConfig->setOutputMediaOrientation($orientation); return $this; } /** * Sets the output media aspect ratio. * * If set, this supersedes the output media orientation, as it is a more * specific configuration. * * @since 1.3.0 * * @param string $aspectRatio The aspect ratio (e.g. "16:9", "3:2"). * @return self */ public function asOutputMediaAspectRatio(string $aspectRatio): self { $this->modelConfig->setOutputMediaAspectRatio($aspectRatio); return $this; } /** * Sets the output speech voice. * * @since 1.3.0 * * @param string $voice The output speech voice. * @return self */ public function asOutputSpeechVoice(string $voice): self { $this->modelConfig->setOutputSpeechVoice($voice); return $this; } /** * Configures the prompt for JSON response output. * * @since 0.1.0 * * @param array<string, mixed>|null $schema Optional JSON schema. * @return self */ public function asJsonResponse(?array $schema = null): self { $this->asOutputMimeType('application/json'); if ($schema !== null) { $this->asOutputSchema($schema); } return $this; } /** * Infers the capability from configured output modalities. * * @since 0.1.0 * * @return CapabilityEnum The inferred capability. * @throws RuntimeException If the output modality is not supported. */ private function inferCapabilityFromOutputModalities(): CapabilityEnum { // Get the configured output modalities $outputModalities = $this->modelConfig->getOutputModalities(); // Default to text if no output modality is specified if ($outputModalities === null || empty($outputModalities)) { return CapabilityEnum::textGeneration(); } // Multi-modal output (multiple modalities) defaults to text generation. This is temporary // as a multi-modal interface will be implemented in the future. if (count($outputModalities) > 1) { return CapabilityEnum::textGeneration(); } // Infer capability from single output modality $outputModality = $outputModalities[0]; if ($outputModality->isText()) { return CapabilityEnum::textGeneration(); } elseif ($outputModality->isImage()) { return CapabilityEnum::imageGeneration(); } elseif ($outputModality->isAudio()) { return CapabilityEnum::speechGeneration(); } elseif ($outputModality->isVideo()) { return CapabilityEnum::videoGeneration(); } else { // For unsupported modalities, provide a clear error message throw new RuntimeException(sprintf('Output modality "%s" is not yet supported.', $outputModality->value)); } } /** * Infers the capability from a model's implemented interfaces. * * @since 0.1.0 * * @param ModelInterface $model The model to infer capability from. * @return CapabilityEnum|null The inferred capability, or null if none can be inferred. */ private function inferCapabilityFromModelInterfaces(ModelInterface $model): ?CapabilityEnum { // Check model interfaces in order of preference if ($model instanceof TextGenerationModelInterface) { return CapabilityEnum::textGeneration(); } if ($model instanceof ImageGenerationModelInterface) { return CapabilityEnum::imageGeneration(); } if ($model instanceof TextToSpeechConversionModelInterface) { return CapabilityEnum::textToSpeechConversion(); } if ($model instanceof SpeechGenerationModelInterface) { return CapabilityEnum::speechGeneration(); } if ($model instanceof VideoGenerationModelInterface) { return CapabilityEnum::videoGeneration(); } // No supported interface found return null; } /** * Checks if the current prompt is supported by the selected model. * * @since 0.1.0 * @since 0.3.0 Method visibility changed to public. * * @param CapabilityEnum|null $capability Optional capability to check support for. * @return bool True if supported, false otherwise. */ public function isSupported(?CapabilityEnum $capability = null): bool { // If no intended capability provided, infer from output modalities if ($capability === null) { // First try to infer from a specific model if one is set if ($this->model !== null) { $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model); if ($inferredCapability !== null) { $capability = $inferredCapability; } } // If still no capability, infer from output modalities if ($capability === null) { $capability = $this->inferCapabilityFromOutputModalities(); } } // Build requirements with the specified capability $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig); // If the model has been set, check if it meets the requirements if ($this->model !== null) { return $requirements->areMetBy($this->model->metadata()); } try { // Check if any models support these requirements $models = $this->registry->findModelsMetadataForSupport($requirements); return !empty($models); } catch (InvalidArgumentException $e) { // No models support the requirements return \false; } } /** * Checks if the prompt is supported for text generation. * * @since 0.1.0 * * @return bool True if text generation is supported. */ public function isSupportedForTextGeneration(): bool { return $this->isSupported(CapabilityEnum::textGeneration()); } /** * Checks if the prompt is supported for image generation. * * @since 0.1.0 * * @return bool True if image generation is supported. */ public function isSupportedForImageGeneration(): bool { return $this->isSupported(CapabilityEnum::imageGeneration()); } /** * Checks if the prompt is supported for text to speech conversion. * * @since 0.1.0 * * @return bool True if text to speech conversion is supported. */ public function isSupportedForTextToSpeechConversion(): bool { return $this->isSupported(CapabilityEnum::textToSpeechConversion()); } /** * Checks if the prompt is supported for video generation. * * @since 0.1.0 * * @return bool True if video generation is supported. */ public function isSupportedForVideoGeneration(): bool { return $this->isSupported(CapabilityEnum::videoGeneration()); } /** * Checks if the prompt is supported for speech generation. * * @since 0.1.0 * * @return bool True if speech generation is supported. */ public function isSupportedForSpeechGeneration(): bool { return $this->isSupported(CapabilityEnum::speechGeneration()); } /** * Checks if the prompt is supported for music generation. * * @since 0.1.0 * * @return bool True if music generation is supported. */ public function isSupportedForMusicGeneration(): bool { return $this->isSupported(CapabilityEnum::musicGeneration()); } /** * Checks if the prompt is supported for embedding generation. * * @since 0.1.0 * * @return bool True if embedding generation is supported. */ public function isSupportedForEmbeddingGeneration(): bool { return $this->isSupported(CapabilityEnum::embeddingGeneration()); } /** * Generates a result from the prompt. * * This is the primary execution method that generates a result (containing * potentially multiple candidates) based on the specified capability or * the configured output modality. * * @since 0.1.0 * * @param CapabilityEnum|null $capability Optional capability to use for generation. * If null, capability is inferred from output modality. * @return GenerativeAiResult The generated result containing candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support the required capability. */ public function generateResult(?CapabilityEnum $capability = null): GenerativeAiResult { $this->validateMessages(); // If capability is not provided, infer it if ($capability === null) { // First try to infer from a specific model if one is set if ($this->model !== null) { $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model); if ($inferredCapability !== null) { $capability = $inferredCapability; } } // If still no capability, infer from output modalities if ($capability === null) { $capability = $this->inferCapabilityFromOutputModalities(); } } $model = $this->getConfiguredModel($capability); // Dispatch BeforeGenerateResultEvent $this->dispatchEvent(new BeforeGenerateResultEvent($this->messages, $model, $capability)); // Route to the appropriate generation method based on capability $result = $this->executeModelGeneration($model, $capability, $this->messages); // Dispatch AfterGenerateResultEvent $this->dispatchEvent(new AfterGenerateResultEvent($this->messages, $model, $capability, $result)); return $result; } /** * Executes the model generation based on capability. * * @since 0.4.0 * * @param ModelInterface $model The model to use for generation. * @param CapabilityEnum $capability The capability to use. * @param list<Message> $messages The messages to send. * @return GenerativeAiResult The generated result. * @throws RuntimeException If the model doesn't support the required capability. */ private function executeModelGeneration(ModelInterface $model, CapabilityEnum $capability, array $messages): GenerativeAiResult { if ($capability->isTextGeneration()) { if (!$model instanceof TextGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support text generation.', $model->metadata()->getId())); } return $model->generateTextResult($messages); } if ($capability->isImageGeneration()) { if (!$model instanceof ImageGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support image generation.', $model->metadata()->getId())); } return $model->generateImageResult($messages); } if ($capability->isTextToSpeechConversion()) { if (!$model instanceof TextToSpeechConversionModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support text-to-speech conversion.', $model->metadata()->getId())); } return $model->convertTextToSpeechResult($messages); } if ($capability->isSpeechGeneration()) { if (!$model instanceof SpeechGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support speech generation.', $model->metadata()->getId())); } return $model->generateSpeechResult($messages); } if ($capability->isVideoGeneration()) { if (!$model instanceof VideoGenerationModelInterface) { throw new RuntimeException(sprintf('Model "%s" does not support video generation.', $model->metadata()->getId())); } return $model->generateVideoResult($messages); } // TODO: Add support for other capabilities when interfaces are available throw new RuntimeException(sprintf('Capability "%s" is not yet supported for generation.', $capability->value)); } /** * Generates a text result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing text candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support text generation. */ public function generateTextResult(): GenerativeAiResult { // Include text in output modalities $this->includeOutputModalities(ModalityEnum::text()); // Generate and return the result with text generation capability return $this->generateResult(CapabilityEnum::textGeneration()); } /** * Generates an image result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing image candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support image generation. */ public function generateImageResult(): GenerativeAiResult { // Include image in output modalities $this->includeOutputModalities(ModalityEnum::image()); // Generate and return the result with image generation capability return $this->generateResult(CapabilityEnum::imageGeneration()); } /** * Generates a speech result from the prompt. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing speech audio candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support speech generation. */ public function generateSpeechResult(): GenerativeAiResult { // Include audio in output modalities $this->includeOutputModalities(ModalityEnum::audio()); // Generate and return the result with speech generation capability return $this->generateResult(CapabilityEnum::speechGeneration()); } /** * Converts text to speech and returns the result. * * @since 0.1.0 * * @return GenerativeAiResult The generated result containing speech audio candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support text-to-speech conversion. */ public function convertTextToSpeechResult(): GenerativeAiResult { // Include audio in output modalities $this->includeOutputModalities(ModalityEnum::audio()); // Generate and return the result with text-to-speech conversion capability return $this->generateResult(CapabilityEnum::textToSpeechConversion()); } /** * Generates a video result from the prompt. * * @since 1.3.0 * * @return GenerativeAiResult The generated result containing video candidates. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If the model doesn't support video generation. */ public function generateVideoResult(): GenerativeAiResult { // Include video in output modalities $this->includeOutputModalities(ModalityEnum::video()); // Generate and return the result with video generation capability return $this->generateResult(CapabilityEnum::videoGeneration()); } /** * Generates text from the prompt. * * @since 0.1.0 * * @return string The generated text. * @throws InvalidArgumentException If the prompt or model validation fails. */ public function generateText(): string { return $this->generateTextResult()->toText(); } /** * Generates multiple text candidates from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of candidates to generate. * @return list<string> The generated texts. * @throws InvalidArgumentException If the prompt or model validation fails. */ public function generateTexts(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } // Generate text result return $this->generateTextResult()->toTexts(); } /** * Generates an image from the prompt. * * @since 0.1.0 * * @return File The generated image file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no image is generated. */ public function generateImage(): File { return $this->generateImageResult()->toFile(); } /** * Generates multiple images from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of images to generate. * @return list<File> The generated image files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no images are generated. */ public function generateImages(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateImageResult()->toFiles(); } /** * Converts text to speech. * * @since 0.1.0 * * @return File The generated speech audio file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function convertTextToSpeech(): File { return $this->convertTextToSpeechResult()->toFile(); } /** * Converts text to multiple speech outputs. * * @since 0.1.0 * * @param int|null $candidateCount The number of speech outputs to generate. * @return list<File> The generated speech audio files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function convertTextToSpeeches(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->convertTextToSpeechResult()->toFiles(); } /** * Generates speech from the prompt. * * @since 0.1.0 * * @return File The generated speech audio file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function generateSpeech(): File { return $this->generateSpeechResult()->toFile(); } /** * Generates multiple speech outputs from the prompt. * * @since 0.1.0 * * @param int|null $candidateCount The number of speech outputs to generate. * @return list<File> The generated speech audio files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no audio is generated. */ public function generateSpeeches(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateSpeechResult()->toFiles(); } /** * Generates a video from the prompt. * * @since 1.3.0 * * @return File The generated video file. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no video is generated. */ public function generateVideo(): File { return $this->generateVideoResult()->toFile(); } /** * Generates multiple videos from the prompt. * * @since 1.3.0 * * @param int|null $candidateCount The number of videos to generate. * @return list<File> The generated video files. * @throws InvalidArgumentException If the prompt or model validation fails. * @throws RuntimeException If no videos are generated. */ public function generateVideos(?int $candidateCount = null): array { if ($candidateCount !== null) { $this->usingCandidateCount($candidateCount); } return $this->generateVideoResult()->toFiles(); } /** * Appends a MessagePart to the messages array. * * If the last message has a user role, the part is added to it. * Otherwise, a new UserMessage is created with the part. * * @since 0.1.0 * * @param MessagePart $part The part to append. * @return void */ protected function appendPartToMessages(MessagePart $part): void { $lastMessage = end($this->messages); if ($lastMessage instanceof Message && $lastMessage->getRole()->isUser()) { // Replace the last message with a new one containing the appended part array_pop($this->messages); $this->messages[] = $lastMessage->withPart($part); return; } // Create new UserMessage with the part $this->messages[] = new UserMessage([$part]); } /** * Gets the model to use for generation. * * If a model has been explicitly set, validates it meets requirements and returns it. * Otherwise, finds a suitable model based on the prompt requirements. * * @since 0.1.0 * * @param CapabilityEnum $capability The capability the model will be using. * @return ModelInterface The model to use. * @throws InvalidArgumentException If no suitable model is found or set model doesn't meet requirements. */ private function getConfiguredModel(CapabilityEnum $capability): ModelInterface { $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig); if ($this->model !== null) { // Explicit model was provided via usingModel(); just update config and bind dependencies. $model = $this->model; $model->setConfig($this->modelConfig); $this->registry->bindModelDependencies($model); $this->bindModelRequestOptions($model); return $model; } // Retrieve the candidate models map which satisfies the requirements. $candidateMap = $this->getCandidateModelsMap($requirements); if (empty($candidateMap)) { $message = sprintf('No models found that support %s for this prompt.', $capability->value); if ($this->providerIdOrClassName !== null) { $message = sprintf('No models found for provider "%s" that support %s for this prompt.', $this->providerIdOrClassName, $capability->value); } throw new InvalidArgumentException($message); } // Check if any preferred models match the candidates, in priority order. if (!empty($this->modelPreferenceKeys)) { // Find preferences that match available candidates, preserving preference order. $matchingPreferences = array_intersect_key(array_flip($this->modelPreferenceKeys), $candidateMap); if (!empty($matchingPreferences)) { // Get the first matching preference key $firstMatchKey = key($matchingPreferences); [$providerId, $modelId] = $candidateMap[$firstMatchKey]; $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig); $this->bindModelRequestOptions($model); return $model; } } // No preference matched; fall back to the first candidate discovered. [$providerId, $modelId] = reset($candidateMap); $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig); $this->bindModelRequestOptions($model); return $model; } /** * Binds configured request options to the model if present and supported. * * Request options are only applicable to API-based models that make HTTP requests. * * @since 0.3.0 * * @param ModelInterface $model The model to bind request options to. * @return void */ private function bindModelRequestOptions(ModelInterface $model): void { if ($this->requestOptions !== null && $model instanceof ApiBasedModelInterface) { $model->setRequestOptions($this->requestOptions); } } /** * Builds a map of candidate models that satisfy the requirements for efficient lookup. * * @since 0.2.0 * * @param ModelRequirements $requirements The requirements derived from the prompt. * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples. */ private function getCandidateModelsMap(ModelRequirements $requirements): array { if ($this->providerIdOrClassName === null) { // No provider locked in, gather all models across providers that meet requirements. $providerModelsMetadata = $this->registry->findModelsMetadataForSupport($requirements); $candidateMap = []; foreach ($providerModelsMetadata as $providerModels) { $providerId = $providerModels->getProvider()->getId(); $providerMap = $this->generateMapFromCandidates($providerId, $providerModels->getModels()); // Use + operator to merge, preserving keys from $candidateMap (first provider wins for model-only keys) $candidateMap = $candidateMap + $providerMap; } return $candidateMap; } // Provider set, only consider models from that provider. $modelsMetadata = $this->registry->findProviderModelsMetadataForSupport($this->providerIdOrClassName, $requirements); // Ensure we pass the provider ID, not the class name $providerId = $this->registry->getProviderId($this->providerIdOrClassName); return $this->generateMapFromCandidates($providerId, $modelsMetadata); } /** * Generates a candidate map from model metadata with both provider-specific and model-only keys. * * @since 0.2.0 * * @param string $providerId The provider ID. * @param list<ModelMetadata> $modelsMetadata The models metadata to map. * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples. */ private function generateMapFromCandidates(string $providerId, array $modelsMetadata): array { $map = []; foreach ($modelsMetadata as $modelMetadata) { $modelId = $modelMetadata->getId(); // Add provider-specific key $providerModelKey = $this->createProviderModelPreferenceKey($providerId, $modelId); $map[$providerModelKey] = [$providerId, $modelId]; // Add model-only key $modelKey = $this->createModelPreferenceKey($modelId); $map[$modelKey] = [$providerId, $modelId]; } return $map; } /** * Normalizes and validates a preference identifier string. * * @since 0.2.0 * * @param mixed $value The value to normalize. * @param string $emptyMessage The message for empty or invalid values. * @return string The normalized identifier. * * @throws InvalidArgumentException If the value is not a non-empty string. */ private function normalizePreferenceIdentifier($value, string $emptyMessage = 'Model preference identifiers cannot be empty.'): string { if (!is_string($value)) { throw new InvalidArgumentException($emptyMessage); } $trimmed = trim($value); if ($trimmed === '') { throw new InvalidArgumentException($emptyMessage); } return $trimmed; } /** * Creates a preference key for a provider/model combination. * * @since 0.2.0 * * @param string $providerId The provider identifier. * @param string $modelId The model identifier. * @return string The generated preference key. */ private function createProviderModelPreferenceKey(string $providerId, string $modelId): string { return 'providerModel::' . $providerId . '::' . $modelId; } /** * Creates a preference key for a model identifier. * * @since 0.2.0 * * @param string $modelId The model identifier. * @return string The generated preference key. */ private function createModelPreferenceKey(string $modelId): string { return 'model::' . $modelId; } /** * Parses various input types into a Message with the given role. * * @since 0.1.0 * * @param mixed $input The input to parse. * @param MessageRoleEnum $defaultRole The role for the message if not specified by input. * @return Message The parsed message. * @throws InvalidArgumentException If the input type is not supported or results in empty message. */ private function parseMessage($input, MessageRoleEnum $defaultRole): Message { // Handle Message input directly if ($input instanceof Message) { return $input; } // Handle single MessagePart if ($input instanceof MessagePart) { return new Message($defaultRole, [$input]); } // Handle string input if (is_string($input)) { if (trim($input) === '') { throw new InvalidArgumentException('Cannot create a message from an empty string.'); } return new Message($defaultRole, [new MessagePart($input)]); } // Handle array input if (!is_array($input)) { throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, ' . 'a list of string|MessagePart|MessagePartArrayShape, or a Message instance.'); } // Handle MessageArrayShape input if (Message::isArrayShape($input)) { return Message::fromArray($input); } // Check if it's a MessagePartArrayShape if (MessagePart::isArrayShape($input)) { return new Message($defaultRole, [MessagePart::fromArray($input)]); } // It should be a list of string|MessagePart|MessagePartArrayShape if (!array_is_list($input)) { throw new InvalidArgumentException('Array input must be a list array.'); } // Empty array check if (empty($input)) { throw new InvalidArgumentException('Cannot create a message from an empty array.'); } $parts = []; foreach ($input as $item) { if (is_string($item)) { $parts[] = new MessagePart($item); } elseif ($item instanceof MessagePart) { $parts[] = $item; } elseif (is_array($item) && MessagePart::isArrayShape($item)) { $parts[] = MessagePart::fromArray($item); } else { throw new InvalidArgumentException('Array items must be strings, MessagePart instances, or MessagePartArrayShape.'); } } return new Message($defaultRole, $parts); } /** * Validates the messages array for prompt generation. * * Ensures that: * - The first message is a user message * - The last message is a user message * - The last message has parts * * @since 0.1.0 * * @return void * @throws InvalidArgumentException If validation fails. */ private function validateMessages(): void { if (empty($this->messages)) { throw new InvalidArgumentException('Cannot generate from an empty prompt. Add content using withText() or similar methods.'); } $firstMessage = reset($this->messages); if (!$firstMessage->getRole()->isUser()) { throw new InvalidArgumentException('The first message must be from a user role, not from ' . $firstMessage->getRole()->value); } $lastMessage = end($this->messages); if (!$lastMessage->getRole()->isUser()) { throw new InvalidArgumentException('The last message must be from a user role, not from ' . $lastMessage->getRole()->value); } if (empty($lastMessage->getParts())) { throw new InvalidArgumentException('The last message must have content parts. Add content using withText() or similar methods.'); } } /** * Checks if the value is a list of Message objects. * * @since 0.1.0 * * @param mixed $value The value to check. * @return bool True if the value is a list of Message objects. * * @phpstan-assert-if-true list<Message> $value */ private function isMessagesList($value): bool { if (!is_array($value) || empty($value) || !array_is_list($value)) { return \false; } // Check if all items are Messages foreach ($value as $item) { if (!$item instanceof Message) { return \false; } } return \true; } /** * Includes output modalities if not already present. * * Adds the given modalities to the output modalities list if they're not * already included. If output modalities is null, initializes it with * the given modalities. * * @since 0.1.0 * * @param ModalityEnum ...$modalities The modalities to include. * @return void */ private function includeOutputModalities(ModalityEnum ...$modalities): void { $existing = $this->modelConfig->getOutputModalities(); // Initialize if null if ($existing === null) { $this->modelConfig->setOutputModalities($modalities); return; } // Build a set of existing modality values for O(1) lookup $existingValues = []; foreach ($existing as $existingModality) { $existingValues[$existingModality->value] = \true; } // Add new modalities that don't exist $toAdd = []; foreach ($modalities as $modality) { if (!isset($existingValues[$modality->value])) { $toAdd[] = $modality; } } // Update if we have new modalities to add if (!empty($toAdd)) { $this->modelConfig->setOutputModalities(array_merge($existing, $toAdd)); } } /** * Dispatches an event if an event dispatcher is registered. * * @since 0.4.0 * * @param object $event The event to dispatch. * @return void */ private function dispatchEvent(object $event): void { if ($this->eventDispatcher !== null) { $this->eventDispatcher->dispatch($event); } } } src/Builders/MessageBuilder.php 0000644 00000014672 15212525755 0012533 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Builders; use InvalidArgumentException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionResponse; /** * Fluent builder for constructing AI messages. * * This class provides a fluent interface for building messages with various * content types including text, files, function calls, and function responses. * * @since 0.2.0 * * @phpstan-import-type MessagePartArrayShape from MessagePart * * @phpstan-type Input string|MessagePart|MessagePartArrayShape|File|FunctionCall|FunctionResponse|null */ class MessageBuilder { /** * @var MessageRoleEnum|null The role of the message sender. */ protected ?MessageRoleEnum $role = null; /** * @var list<MessagePart> The parts that make up the message. */ protected array $parts = []; /** * Constructor. * * @since 0.2.0 * * @param Input $input Optional initial content. * @param MessageRoleEnum|null $role Optional role. */ public function __construct($input = null, ?MessageRoleEnum $role = null) { $this->role = $role; if ($input === null) { return; } // Handle different input types if ($input instanceof MessagePart) { $this->parts[] = $input; } elseif (is_string($input)) { $this->withText($input); } elseif ($input instanceof File) { $this->withFile($input); } elseif ($input instanceof FunctionCall) { $this->withFunctionCall($input); } elseif ($input instanceof FunctionResponse) { $this->withFunctionResponse($input); } elseif (is_array($input) && MessagePart::isArrayShape($input)) { $this->parts[] = MessagePart::fromArray($input); } else { throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, File, FunctionCall, or FunctionResponse.'); } } /** * Creates a deep clone of this builder. * * Clones all MessagePart objects in the parts array to ensure * the cloned builder is independent of the original. * * @since 0.4.2 */ public function __clone() { // Deep clone parts array (MessagePart has __clone) $clonedParts = []; foreach ($this->parts as $part) { $clonedParts[] = clone $part; } $this->parts = $clonedParts; // Note: $role is an enum value object and can be safely shared } /** * Sets the role of the message sender. * * @since 0.2.0 * * @param MessageRoleEnum $role The role to set. * @return self */ public function usingRole(MessageRoleEnum $role): self { $this->role = $role; return $this; } /** * Sets the role to user. * * @since 0.2.0 * * @return self */ public function usingUserRole(): self { return $this->usingRole(MessageRoleEnum::user()); } /** * Sets the role to model. * * @since 0.2.0 * * @return self */ public function usingModelRole(): self { return $this->usingRole(MessageRoleEnum::model()); } /** * Adds text content to the message. * * @since 0.2.0 * * @param string $text The text to add. * @return self * @throws InvalidArgumentException If the text is empty. */ public function withText(string $text): self { if (trim($text) === '') { throw new InvalidArgumentException('Text content cannot be empty.'); } $this->parts[] = new MessagePart($text); return $this; } /** * Adds a file to the message. * * Accepts: * - File object * - URL string (remote file) * - Base64-encoded data string * - Data URI string (data:mime/type;base64,data) * - Local file path string * * @since 0.2.0 * * @param string|File $file The file to add. * @param string|null $mimeType Optional MIME type (ignored if File object provided). * @return self * @throws InvalidArgumentException If the file is invalid. */ public function withFile($file, ?string $mimeType = null): self { $file = $file instanceof File ? $file : new File($file, $mimeType); $this->parts[] = new MessagePart($file); return $this; } /** * Adds a function call to the message. * * @since 0.2.0 * * @param FunctionCall $functionCall The function call to add. * @return self */ public function withFunctionCall(FunctionCall $functionCall): self { $this->parts[] = new MessagePart($functionCall); return $this; } /** * Adds a function response to the message. * * @since 0.2.0 * * @param FunctionResponse $functionResponse The function response to add. * @return self */ public function withFunctionResponse(FunctionResponse $functionResponse): self { $this->parts[] = new MessagePart($functionResponse); return $this; } /** * Adds multiple message parts to the message. * * @since 0.2.0 * * @param MessagePart ...$parts The message parts to add. * @return self */ public function withMessageParts(MessagePart ...$parts): self { foreach ($parts as $part) { $this->parts[] = $part; } return $this; } /** * Builds and returns the Message object. * * @since 0.2.0 * * @return Message The built message. * @throws InvalidArgumentException If the message validation fails. */ public function get(): Message { if (empty($this->parts)) { throw new InvalidArgumentException('Cannot build an empty message. Add content using withText() or similar methods.'); } if ($this->role === null) { throw new InvalidArgumentException('Cannot build a message with no role. Set a role using usingRole() or similar methods.'); } // At this point, we've validated that $this->role is not null /** @var MessageRoleEnum $role */ $role = $this->role; return new Message($role, $this->parts); } } src/AiClient.php 0000644 00000041720 15212525755 0007551 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient; use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface; use WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface; use WordPress\AiClient\Builders\PromptBuilder; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\ProviderRegistry; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Main AI Client class providing both fluent and traditional APIs for AI operations. * * This class serves as the primary entry point for AI operations, offering: * - Fluent API for easy-to-read chained method calls * - Traditional API for array-based configuration (WordPress style) * - Integration with provider registry for model discovery * - Support for three model specification approaches * * All model requirements analysis and capability matching is handled * automatically by the PromptBuilder, which provides intelligent model * discovery based on prompt content and configuration. * * ## Model Specification Approaches * * ### 1. Specific Model Instance * Use a specific ModelInterface instance when you know exactly which model to use: * ```php * $model = $registry->getProvider('openai')->getModel('gpt-4'); * $result = AiClient::generateTextResult('What is PHP?', $model); * ``` * * ### 2. ModelConfig for Auto-Discovery * Use ModelConfig to specify requirements and let the system discover the best model: * ```php * $config = new ModelConfig(); * $config->setTemperature(0.7); * $config->setMaxTokens(150); * * $result = AiClient::generateTextResult('What is PHP?', $config); * ``` * * ### 3. Automatic Discovery (Default) * Pass null or omit the parameter for intelligent model discovery based on prompt content: * ```php * // System analyzes prompt and selects appropriate model automatically * $result = AiClient::generateTextResult('What is PHP?'); * $imageResult = AiClient::generateImageResult('A sunset over mountains'); * ``` * * ## Fluent API Examples * ```php * // Fluent API with automatic model discovery * $result = AiClient::prompt('Generate an image of a sunset') * ->usingTemperature(0.7) * ->generateImageResult(); * * // Fluent API with specific model * $result = AiClient::prompt('What is PHP?') * ->usingModel($specificModel) * ->usingTemperature(0.5) * ->generateTextResult(); * * // Fluent API with model configuration * $result = AiClient::prompt('Explain quantum physics') * ->usingModelConfig($config) * ->generateTextResult(); * ``` * * @since 0.1.0 * * @phpstan-import-type Prompt from PromptBuilder * * phpcs:ignore Generic.Files.LineLength.TooLong */ class AiClient { /** * @var string The version of the AI Client. */ public const VERSION = '1.3.1'; /** * @var ProviderRegistry|null The default provider registry instance. */ private static ?ProviderRegistry $defaultRegistry = null; /** * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events. */ private static ?EventDispatcherInterface $eventDispatcher = null; /** * @var CacheInterface|null The PSR-16 cache for storing and retrieving cached data. */ private static ?CacheInterface $cache = null; /** * Gets the default provider registry instance. * * @since 0.1.0 * * @return ProviderRegistry The default provider registry. */ public static function defaultRegistry(): ProviderRegistry { if (self::$defaultRegistry === null) { self::$defaultRegistry = new ProviderRegistry(); } return self::$defaultRegistry; } /** * Sets the event dispatcher for prompt lifecycle events. * * The event dispatcher will be used to dispatch BeforeGenerateResultEvent and * AfterGenerateResultEvent during prompt generation. * * @since 0.4.0 * * @param EventDispatcherInterface|null $dispatcher The event dispatcher, or null to disable. * @return void */ public static function setEventDispatcher(?EventDispatcherInterface $dispatcher): void { self::$eventDispatcher = $dispatcher; } /** * Gets the event dispatcher for prompt lifecycle events. * * @since 0.4.0 * * @return EventDispatcherInterface|null The event dispatcher, or null if not set. */ public static function getEventDispatcher(): ?EventDispatcherInterface { return self::$eventDispatcher; } /** * Sets the PSR-16 cache for storing and retrieving cached data. * * The cache can be used to store AI responses and other data to avoid * redundant API calls and improve performance. * * @since 0.4.0 * * @param CacheInterface|null $cache The PSR-16 cache instance, or null to disable caching. * @return void */ public static function setCache(?CacheInterface $cache): void { self::$cache = $cache; } /** * Gets the PSR-16 cache instance. * * @since 0.4.0 * * @return CacheInterface|null The cache instance, or null if not set. */ public static function getCache(): ?CacheInterface { return self::$cache; } /** * Checks if a provider is configured and available for use. * * Supports multiple input formats for developer convenience: * - ProviderAvailabilityInterface: Direct availability check * - string (provider ID): e.g., AiClient::isConfigured('openai') * - string (class name): e.g., AiClient::isConfigured(OpenAiProvider::class) * * When using string input, this method leverages the ProviderRegistry's centralized * dependency management, ensuring HttpTransporter and authentication are properly * injected into availability instances. * * @since 0.1.0 * @since 0.2.0 Now supports being passed a provider ID or class name. * * @param ProviderAvailabilityInterface|string|class-string<ProviderInterface> $availabilityOrIdOrClassName * The provider availability instance, provider ID, or provider class name. * @return bool True if the provider is configured and available, false otherwise. */ public static function isConfigured($availabilityOrIdOrClassName): bool { // Handle direct ProviderAvailabilityInterface (backward compatibility) if ($availabilityOrIdOrClassName instanceof ProviderAvailabilityInterface) { return $availabilityOrIdOrClassName->isConfigured(); } // Handle string input (provider ID or class name) via registry if (is_string($availabilityOrIdOrClassName)) { return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName); } throw new \InvalidArgumentException('Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' . sprintf('Received: %s', is_object($availabilityOrIdOrClassName) ? get_class($availabilityOrIdOrClassName) : gettype($availabilityOrIdOrClassName))); } /** * Creates a new prompt builder for fluent API usage. * * Returns a PromptBuilder instance configured with the specified or default registry. * The traditional API methods in this class delegate to PromptBuilder * for all generation logic. * * @since 0.1.0 * * @param Prompt $prompt Optional initial prompt content. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return PromptBuilder The prompt builder instance. */ public static function prompt($prompt = null, ?ProviderRegistry $registry = null): PromptBuilder { return new PromptBuilder($registry ?? self::defaultRegistry(), $prompt, self::$eventDispatcher); } /** * Generates content using a unified API that automatically detects model capabilities. * * When no model is provided, this method delegates to PromptBuilder for intelligent * model discovery based on prompt content and configuration. When a model is provided, * it infers the capability from the model's interfaces and delegates to the capability-based method. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig $modelOrConfig Specific model to use, or model configuration * for auto-discovery. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the provided model doesn't support any known generation type. * @throws \RuntimeException If no suitable model can be found for the prompt. */ public static function generateResult($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateResult(); } /** * Generates text using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateTextResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateTextResult(); } /** * Generates an image using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateImageResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateImageResult(); } /** * Converts text to speech using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function convertTextToSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->convertTextToSpeechResult(); } /** * Generates speech using the traditional API approach. * * @since 0.1.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult(); } /** * Generates a video using the traditional API approach. * * @since 1.3.0 * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use, * or model configuration for auto-discovery, * or null for defaults. * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default. * @return GenerativeAiResult The generation result. * * @throws \InvalidArgumentException If the prompt format is invalid. * @throws \RuntimeException If no suitable model is found. */ public static function generateVideoResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult { self::validateModelOrConfigParameter($modelOrConfig); return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateVideoResult(); } /** * Creates a new message builder for fluent API usage. * * This method will be implemented once MessageBuilder is available. * MessageBuilder will provide a fluent interface for constructing complex * messages with multiple parts, attachments, and metadata. * * @since 0.1.0 * * @param string|null $text Optional initial message text. * @return object MessageBuilder instance (type will be updated when MessageBuilder is available). * * @throws \RuntimeException When MessageBuilder is not yet available. */ public static function message(?string $text = null) { throw new RuntimeException('MessageBuilder is not yet available. This method depends on builder infrastructure. ' . 'Use direct generation methods (generateTextResult, generateImageResult, etc.) for now.'); } /** * Validates that parameter is ModelInterface, ModelConfig, or null. * * @param mixed $modelOrConfig The parameter to validate. * @return void * @throws \InvalidArgumentException If parameter is invalid type. */ private static function validateModelOrConfigParameter($modelOrConfig): void { if ($modelOrConfig !== null && !$modelOrConfig instanceof ModelInterface && !$modelOrConfig instanceof ModelConfig) { throw new InvalidArgumentException('Parameter must be a ModelInterface instance (specific model), ' . 'ModelConfig instance (for auto-discovery), or null (default auto-discovery). ' . sprintf('Received: %s', is_object($modelOrConfig) ? get_class($modelOrConfig) : gettype($modelOrConfig))); } } /** * Configures PromptBuilder based on model/config parameter type. * * @param Prompt $prompt The prompt content. * @param ModelInterface|ModelConfig|null $modelOrConfig The model or config parameter. * @param ProviderRegistry|null $registry Optional custom registry to use. * @return PromptBuilder Configured prompt builder. */ private static function getConfiguredPromptBuilder($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): PromptBuilder { $builder = self::prompt($prompt, $registry); if ($modelOrConfig instanceof ModelInterface) { $builder->usingModel($modelOrConfig); } elseif ($modelOrConfig instanceof ModelConfig) { $builder->usingModelConfig($modelOrConfig); } // null case: use default model discovery return $builder; } } src/Tools/DTO/FunctionResponse.php 0000644 00000007313 15212525755 0013113 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]); } } src/Tools/DTO/FunctionCall.php 0000644 00000007133 15212525755 0012170 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); } } src/Tools/DTO/error_log 0000644 00000007760 15212525755 0011021 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [11-Jun-2026 11:11:19 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19 [11-Jun-2026 11:11:23 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [11-Jun-2026 11:11:28 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 [11-Jun-2026 11:11:33 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [11-Jun-2026 12:07:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20 [11-Jun-2026 12:07:02 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20 [11-Jun-2026 12:07:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23 src/Tools/DTO/FunctionDeclaration.php 0000644 00000007005 15212525755 0013540 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); } } src/Tools/DTO/WebSearch.php 0000644 00000005502 15212525755 0011450 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] ?? []); } } src/Files/DTO/File.php 0000644 00000032326 15212525755 0010432 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; } } src/Files/DTO/error_log 0000644 00000002032 15212525755 0010746 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [11-Jun-2026 11:11:38 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 [11-Jun-2026 12:07:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28 src/Files/Enums/MediaOrientationEnum.php 0000644 00000001730 15212525755 0014267 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Files\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents the type of file storage. * * @method static self square() Returns the square orientation * @method static self landscape() Returns the landscape orientation. * @method static self portrait() Returns the portrait orientation. * @method bool isSquare() Checks if this is an square orientation * @method bool isLandscape() Checks if this is a landscape orientation. * @method bool isPortrait() Checks if this is a portrait orientation. * * @since 0.1.0 */ class MediaOrientationEnum extends AbstractEnum { /** * Square orientation. * * @var string */ public const SQUARE = 'square'; /** * Landscape orientation. * * @var string */ public const LANDSCAPE = 'landscape'; /** * Portrait orientation. * * @var string */ public const PORTRAIT = 'portrait'; } src/Files/Enums/error_log 0000644 00000002114 15212525755 0011410 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17 [11-Jun-2026 11:14:09 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19 src/Files/Enums/FileTypeEnum.php 0000644 00000001330 15212525755 0012551 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Files\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents the type of file storage. * * @method static self inline() Returns the inline file type. * @method static self remote() Returns the remote file type. * @method bool isInline() Checks if this is an inline file type. * @method bool isRemote() Checks if this is a remote file type. * * @since 0.1.0 */ class FileTypeEnum extends AbstractEnum { /** * Inline file with base64-encoded data. * * @var string */ public const INLINE = 'inline'; /** * Remote file referenced by URL. * * @var string */ public const REMOTE = 'remote'; } src/Files/ValueObjects/MimeType.php 0000644 00000017554 15212525755 0013252 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Files\ValueObjects; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Value object representing a MIME type. * * This immutable value object encapsulates MIME type validation and * provides convenient methods for checking MIME type categories. * * @since 0.1.0 */ final class MimeType { /** * @var string The MIME type value. */ private string $value; /** * Common MIME type mappings for file extensions. * * @var array<string, string> */ private static array $extensionMap = [ // Text 'txt' => 'text/plain', 'html' => 'text/html', 'htm' => 'text/html', 'css' => 'text/css', 'js' => 'application/javascript', 'json' => 'application/json', 'xml' => 'application/xml', 'csv' => 'text/csv', 'md' => 'text/markdown', // Images 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'bmp' => 'image/bmp', 'webp' => 'image/webp', 'svg' => 'image/svg+xml', 'ico' => 'image/x-icon', // Documents 'pdf' => 'application/pdf', 'doc' => 'application/msword', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'xls' => 'application/vnd.ms-excel', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ppt' => 'application/vnd.ms-powerpoint', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'odt' => 'application/vnd.oasis.opendocument.text', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', // Archives 'zip' => 'application/zip', 'tar' => 'application/x-tar', 'gz' => 'application/gzip', 'rar' => 'application/x-rar-compressed', '7z' => 'application/x-7z-compressed', // Audio 'mp3' => 'audio/mpeg', 'wav' => 'audio/wav', 'ogg' => 'audio/ogg', 'flac' => 'audio/flac', 'm4a' => 'audio/m4a', 'aac' => 'audio/aac', // Video 'mp4' => 'video/mp4', 'avi' => 'video/x-msvideo', 'mov' => 'video/quicktime', 'wmv' => 'video/x-ms-wmv', 'flv' => 'video/x-flv', 'webm' => 'video/webm', 'mkv' => 'video/x-matroska', // Fonts 'ttf' => 'font/ttf', 'otf' => 'font/otf', 'woff' => 'font/woff', 'woff2' => 'font/woff2', // Other 'php' => 'application/x-httpd-php', 'sh' => 'application/x-sh', 'exe' => 'application/x-msdownload', ]; /** * Document MIME types. * * @var array<string> */ private static array $documentTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet']; /** * Constructor. * * @since 0.1.0 * * @param string $value The MIME type value. * @throws InvalidArgumentException If the MIME type is invalid. */ public function __construct(string $value) { if (!self::isValid($value)) { throw new InvalidArgumentException(sprintf('Invalid MIME type: %s', $value)); } $this->value = strtolower($value); } /** * Gets the primary known file extension for this MIME type. * * @since 0.1.0 * * @return string The file extension (without the dot). * @throws InvalidArgumentException If no known extension exists for this MIME type. */ public function toExtension(): string { // Reverse lookup for the MIME type to find the extension. $extension = array_search($this->value, self::$extensionMap, \true); if ($extension === \false) { throw new InvalidArgumentException(sprintf('No known extension for MIME type: %s', $this->value)); } return $extension; } /** * Creates a MimeType from a file extension. * * @since 0.1.0 * * @param string $extension The file extension (without the dot). * @return self The MimeType instance. * @throws InvalidArgumentException If the extension is not recognized. */ public static function fromExtension(string $extension): self { $extension = strtolower($extension); if (!isset(self::$extensionMap[$extension])) { throw new InvalidArgumentException(sprintf('Unknown file extension: %s', $extension)); } return new self(self::$extensionMap[$extension]); } /** * Checks if a MIME type string is valid. * * @since 0.1.0 * * @param string $mimeType The MIME type to validate. * @return bool True if valid. */ public static function isValid(string $mimeType): bool { // Basic MIME type validation: type/subtype return (bool) preg_match('/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*$/', $mimeType); } /** * Checks if this MIME type is a specific type. * * This method returns true when the stored MIME type begins with the * given prefix. For example, `"audio"` matches `"audio/mpeg"`. * * @since 0.1.0 * * @param string $mimeType The MIME type prefix to check (e.g., "audio", "image"). * @return bool True if this MIME type is of the specified type. */ public function isType(string $mimeType): bool { return str_starts_with($this->value, strtolower($mimeType) . '/'); } /** * Checks if this is an image MIME type. * * @since 0.1.0 * * @return bool True if this is an image type. */ public function isImage(): bool { return $this->isType('image'); } /** * Checks if this is an audio MIME type. * * @since 0.1.0 * * @return bool True if this is an audio type. */ public function isAudio(): bool { return $this->isType('audio'); } /** * Checks if this is a video MIME type. * * @since 0.1.0 * * @return bool True if this is a video type. */ public function isVideo(): bool { return $this->isType('video'); } /** * Checks if this is a text MIME type. * * @since 0.1.0 * * @return bool True if this is a text type. */ public function isText(): bool { return $this->isType('text'); } /** * Checks if this is a document MIME type. * * @since 0.1.0 * * @return bool True if this is a document type. */ public function isDocument(): bool { return in_array($this->value, self::$documentTypes, \true); } /** * Checks if this MIME type equals another. * * @since 0.1.0 * * @param self|string $other The other MIME type to compare. * @return bool True if equal. * @throws InvalidArgumentException If the other MIME type is invalid. */ public function equals($other): bool { if ($other instanceof self) { return $this->value === $other->value; } if (is_string($other)) { return $this->value === strtolower($other); } throw new InvalidArgumentException(sprintf('Invalid MIME type comparison: %s', gettype($other))); } /** * Gets the string representation of the MIME type. * * @since 0.1.0 * * @return string The MIME type value. */ public function __toString(): string { return $this->value; } } src/Messages/DTO/Message.php 0000644 00000013044 15212525755 0011640 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; } } src/Messages/DTO/MessagePart.php 0000644 00000025055 15212525755 0012474 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; } } } src/Messages/DTO/UserMessage.php 0000644 00000001454 15212525755 0012501 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); } } src/Messages/DTO/error_log 0000644 00000005520 15212525755 0011460 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [11-Jun-2026 10:19:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19 [11-Jun-2026 11:23:41 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26 [11-Jun-2026 11:23:54 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18 [11-Jun-2026 11:24:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38 src/Messages/DTO/ModelMessage.php 0000644 00000001544 15212525755 0012623 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); } } src/Messages/Enums/ModalityEnum.php 0000644 00000002407 15212525755 0013325 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for input/output modalities. * * @since 0.1.0 * * @method static self text() Creates an instance for TEXT modality. * @method static self document() Creates an instance for DOCUMENT modality. * @method static self image() Creates an instance for IMAGE modality. * @method static self audio() Creates an instance for AUDIO modality. * @method static self video() Creates an instance for VIDEO modality. * @method bool isText() Checks if the modality is TEXT. * @method bool isDocument() Checks if the modality is DOCUMENT. * @method bool isImage() Checks if the modality is IMAGE. * @method bool isAudio() Checks if the modality is AUDIO. * @method bool isVideo() Checks if the modality is VIDEO. */ class ModalityEnum extends AbstractEnum { /** * Text modality. */ public const TEXT = 'text'; /** * Document modality (PDFs, Word docs, etc.). */ public const DOCUMENT = 'document'; /** * Image modality. */ public const IMAGE = 'image'; /** * Audio modality. */ public const AUDIO = 'audio'; /** * Video modality. */ public const VIDEO = 'video'; } src/Messages/Enums/MessageRoleEnum.php 0000644 00000001244 15212525755 0013747 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message roles in AI conversations. * * @since 0.1.0 * * @method static self user() Creates an instance for USER role. * @method static self model() Creates an instance for MODEL role. * @method bool isUser() Checks if the role is USER. * @method bool isModel() Checks if the role is MODEL. */ class MessageRoleEnum extends AbstractEnum { /** * User role - messages from the user. */ public const USER = 'user'; /** * Model role - messages from the AI model. */ public const MODEL = 'model'; } src/Messages/Enums/error_log 0000644 00000002720 15212525755 0012120 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23 src/Messages/Enums/MessagePartTypeEnum.php 0000644 00000002171 15212525755 0014616 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message part types. * * @since 0.1.0 * * @method static self text() Creates an instance for TEXT type. * @method static self file() Creates an instance for FILE type. * @method static self functionCall() Creates an instance for FUNCTION_CALL type. * @method static self functionResponse() Creates an instance for FUNCTION_RESPONSE type. * @method bool isText() Checks if the type is TEXT. * @method bool isFile() Checks if the type is FILE. * @method bool isFunctionCall() Checks if the type is FUNCTION_CALL. * @method bool isFunctionResponse() Checks if the type is FUNCTION_RESPONSE. */ class MessagePartTypeEnum extends AbstractEnum { /** * Text content. */ public const TEXT = 'text'; /** * File content (inline or remote). */ public const FILE = 'file'; /** * Function call request. */ public const FUNCTION_CALL = 'function_call'; /** * Function response. */ public const FUNCTION_RESPONSE = 'function_response'; } src/Messages/Enums/MessagePartChannelEnum.php 0000644 00000001264 15212525755 0015247 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Messages\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for message part channels. * * @since 0.1.0 * * @method static self content() Creates an instance for CONTENT channel. * @method static self thought() Creates an instance for THOUGHT channel. * @method bool isContent() Checks if the channel is CONTENT. * @method bool isThought() Checks if the channel is THOUGHT. */ class MessagePartChannelEnum extends AbstractEnum { /** * Regular (primary) content. */ public const CONTENT = 'content'; /** * Model thinking or reasoning. */ public const THOUGHT = 'thought'; } src/Providers/DTO/ProviderModelsMetadata.php 0000644 00000010144 15212525755 0015057 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Represents metadata about a provider and its available models. * * This class combines provider information with the models that * the provider offers, facilitating model discovery and selection. * * @since 0.1.0 * * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata * * @phpstan-type ProviderModelsMetadataArrayShape array{ * provider: ProviderMetadataArrayShape, * models: list<ModelMetadataArrayShape> * } * * @extends AbstractDataTransferObject<ProviderModelsMetadataArrayShape> */ 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<ModelMetadata> The available models. */ protected array $models; /** * Constructor. * * @since 0.1.0 * * @param ProviderMetadata $provider The provider metadata. * @param list<ModelMetadata> $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<ModelMetadata> 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])); } } src/Providers/DTO/ProviderMetadata.php 0000644 00000017364 15212525755 0013726 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Enums\ProviderTypeEnum; use WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod; /** * Represents metadata about an AI provider. * * This class contains information about an AI provider, including its * unique identifier, display name, and type (cloud, server, or client). * * @since 0.1.0 * @since 1.2.0 Added optional description property. * @since 1.3.0 Added optional logoPath property. * * @phpstan-type ProviderMetadataArrayShape array{ * id: string, * name: string, * description?: ?string, * type: string, * credentialsUrl?: ?string, * authenticationMethod?: ?string, * logoPath?: ?string * } * * @extends AbstractDataTransferObject<ProviderMetadataArrayShape> */ 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); } } src/Providers/DTO/error_log 0000644 00000003020 15212525755 0011657 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 [11-Jun-2026 09:20:08 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27 [11-Jun-2026 09:20:16 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32 src/Providers/ProviderRegistry.php 0000644 00000057020 15212525755 0013361 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers; use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException as DiscoveryNotFoundException; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\Contracts\ProviderWithOperationsHandlerInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\DTO\ProviderModelsMetadata; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\HttpTransporterFactory; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; /** * Registry for managing AI providers and their models. * * This class provides a centralized way to register AI providers, discover * their capabilities, and find suitable models based on requirements. * * @since 0.1.0 */ class ProviderRegistry implements WithHttpTransporterInterface { use WithHttpTransporterTrait { setHttpTransporter as setHttpTransporterOriginal; } /** * @var array<string, class-string<ProviderInterface>> Mapping of provider IDs to class names. */ private array $registeredIdsToClassNames = []; /** * @var array<class-string<ProviderInterface>, string> Mapping of provider class names to IDs. */ private array $registeredClassNamesToIds = []; /** * @var array<class-string<ProviderInterface>, RequestAuthenticationInterface> Mapping of provider class names to * authentication instances. */ private array $providerAuthenticationInstances = []; /** * Registers a provider class with the registry. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The fully qualified provider class name implementing the * ProviderInterface * @throws InvalidArgumentException If the class doesn't exist or implement the required interface. */ public function registerProvider(string $className): void { if (!class_exists($className)) { throw new InvalidArgumentException(sprintf('Provider class does not exist: %s', $className)); } // Validate that class implements ProviderInterface if (!is_subclass_of($className, ProviderInterface::class)) { throw new InvalidArgumentException(sprintf('Provider class must implement %s: %s', ProviderInterface::class, $className)); } $metadata = $className::metadata(); if (!$metadata instanceof ProviderMetadata) { throw new InvalidArgumentException(sprintf('Provider must return ProviderMetadata from metadata() method: %s', $className)); } // If there is already a HTTP transporter instance set, hook it up to the provider as needed. try { $httpTransporter = $this->getHttpTransporter(); } catch (RuntimeException $e) { /* * If this fails, it's okay. There is no defined sequence between setting the HTTP transporter in the * registry and registering providers in it, so it might be that the transporter is set later. It will be * hooked up then. * But for now we can ignore this exception and attempt to set the default HTTP transporter, if possible. */ try { $this->setHttpTransporter(HttpTransporterFactory::createTransporter()); $httpTransporter = $this->getHttpTransporter(); } catch (DiscoveryNotFoundException $e) { /* * If no HTTP client implementation can be discovered yet, we can ignore this for now. * It might be set later, so it's not a hard error at this point. * We'll try again the next time a provider is registered, or maybe by that time an explicit * HTTP transporter will have been set. */ } } if (isset($httpTransporter)) { $this->setHttpTransporterForProvider($className, $httpTransporter); } // Hook up the request authentication instance, using a default if not set. if (!isset($this->providerAuthenticationInstances[$className])) { $defaultProviderAuthentication = $this->createDefaultProviderRequestAuthentication($className); if ($defaultProviderAuthentication !== null) { $this->providerAuthenticationInstances[$className] = $defaultProviderAuthentication; } } if (isset($this->providerAuthenticationInstances[$className])) { $this->setRequestAuthenticationForProvider($className, $this->providerAuthenticationInstances[$className]); } $this->registeredIdsToClassNames[$metadata->getId()] = $className; $this->registeredClassNamesToIds[$className] = $metadata->getId(); } /** * Gets a list of all registered provider IDs. * * @since 0.1.0 * * @return list<string> List of registered provider IDs. */ public function getRegisteredProviderIds(): array { return array_keys($this->registeredIdsToClassNames); } /** * Checks if a provider is registered. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name to check. * @return bool True if the provider is registered. */ public function hasProvider(string $idOrClassName): bool { return $this->isRegisteredId($idOrClassName) || $this->isRegisteredClassName($idOrClassName); } /** * Gets the class name for a registered provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return class-string<ProviderInterface> The provider class name. * @throws InvalidArgumentException If the provider is not registered. */ public function getProviderClassName(string $idOrClassName): string { // If it's already a class name, return it if ($this->isRegisteredClassName($idOrClassName)) { return $idOrClassName; } // If it's a registered ID, return its class name if ($this->isRegisteredId($idOrClassName)) { return $this->registeredIdsToClassNames[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * Gets the provider ID for a registered provider. * * @since 0.2.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return string The provider ID. * @throws InvalidArgumentException If the provider is not registered. */ public function getProviderId(string $idOrClassName): string { // If it's already an ID, return it if ($this->isRegisteredId($idOrClassName)) { return $idOrClassName; } // If it's a registered class name, return its ID if ($this->isRegisteredClassName($idOrClassName)) { return $this->registeredClassNamesToIds[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * Checks if a provider is properly configured. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return bool True if the provider is configured and ready to use. */ public function isProviderConfigured(string $idOrClassName): bool { try { $className = $this->resolveProviderClassName($idOrClassName); // Use static method from ProviderInterface /** @var class-string<ProviderInterface> $className */ $availability = $className::availability(); return $availability->isConfigured(); } catch (InvalidArgumentException $e) { return \false; } } /** * Finds models across all available providers that support the given requirements. * * @since 0.1.0 * * @param ModelRequirements $modelRequirements The requirements to match against. * @return list<ProviderModelsMetadata> List of provider models metadata that match requirements. */ public function findModelsMetadataForSupport(ModelRequirements $modelRequirements): array { $results = []; foreach ($this->registeredIdsToClassNames as $providerId => $className) { $providerResults = $this->findProviderModelsMetadataForSupport($providerId, $modelRequirements); if (!empty($providerResults)) { // Use static method from ProviderInterface /** @var class-string<ProviderInterface> $className */ $providerMetadata = $className::metadata(); $results[] = new ProviderModelsMetadata($providerMetadata, $providerResults); } } return $results; } /** * Finds models within a specific available provider that support the given requirements. * * @since 0.1.0 * * @param string $idOrClassName The provider ID or class name. * @param ModelRequirements $modelRequirements The requirements to match against. * @return list<ModelMetadata> List of model metadata that match requirements. */ public function findProviderModelsMetadataForSupport(string $idOrClassName, ModelRequirements $modelRequirements): array { $className = $this->resolveProviderClassName($idOrClassName); // If the provider is not configured, there is no way to use it, so it is considered unavailable. if (!$this->isProviderConfigured($className)) { return []; } $modelMetadataDirectory = $className::modelMetadataDirectory(); // Filter models that meet requirements $matchingModels = []; foreach ($modelMetadataDirectory->listModelMetadata() as $modelMetadata) { if ($modelRequirements->areMetBy($modelMetadata)) { $matchingModels[] = $modelMetadata; } } return $matchingModels; } /** * Gets a configured model instance from a provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @param string $modelId The model identifier. * @param ModelConfig|null $modelConfig The model configuration. * @return ModelInterface The configured model instance. * @throws InvalidArgumentException If provider or model is not found. */ public function getProviderModel(string $idOrClassName, string $modelId, ?ModelConfig $modelConfig = null): ModelInterface { $className = $this->resolveProviderClassName($idOrClassName); $modelInstance = $className::model($modelId, $modelConfig); $this->bindModelDependencies($modelInstance); return $modelInstance; } /** * Binds dependencies to a model instance. * * This method injects required dependencies such as HTTP transporter * and authentication into model instances that need them. * * @since 0.1.0 * * @param ModelInterface $modelInstance The model instance to bind dependencies to. * @return void */ public function bindModelDependencies(ModelInterface $modelInstance): void { $className = $this->resolveProviderClassName($modelInstance->providerMetadata()->getId()); if ($modelInstance instanceof WithHttpTransporterInterface) { $modelInstance->setHttpTransporter($this->getHttpTransporter()); } if ($modelInstance instanceof WithRequestAuthenticationInterface) { $requestAuthentication = $this->getProviderRequestAuthentication($className); if ($requestAuthentication !== null) { $modelInstance->setRequestAuthentication($requestAuthentication); } } } /** * Gets the class name for a registered provider (handles both ID and class name input). * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return class-string<ProviderInterface> The provider class name. * @throws InvalidArgumentException If provider is not registered. */ private function resolveProviderClassName(string $idOrClassName): string { // If it's already a class name, return it if ($this->isRegisteredClassName($idOrClassName)) { return $idOrClassName; } // If it's a registered ID, return its class name if ($this->isRegisteredId($idOrClassName)) { return $this->registeredIdsToClassNames[$idOrClassName]; } // Not found throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName)); } /** * {@inheritDoc} * * @since 0.1.0 */ public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void { $this->setHttpTransporterOriginal($httpTransporter); // Make sure all registered providers have the HTTP transporter hooked up as needed. foreach ($this->registeredIdsToClassNames as $className) { $this->setHttpTransporterForProvider($className, $httpTransporter); } } /** * Sets the request authentication instance for the given provider. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @param RequestAuthenticationInterface $requestAuthentication The request authentication instance. */ public function setProviderRequestAuthentication(string $idOrClassName, RequestAuthenticationInterface $requestAuthentication): void { $className = $this->resolveProviderClassName($idOrClassName); $this->providerAuthenticationInstances[$className] = $requestAuthentication; $this->setRequestAuthenticationForProvider($className, $requestAuthentication); } /** * Gets the request authentication instance for the given provider, if set. * * @since 0.1.0 * * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name. * @return ?RequestAuthenticationInterface The request authentication instance, or null if not set. */ public function getProviderRequestAuthentication(string $idOrClassName): ?RequestAuthenticationInterface { $className = $this->resolveProviderClassName($idOrClassName); if (!isset($this->providerAuthenticationInstances[$className])) { return null; } return $this->providerAuthenticationInstances[$className]; } /** * Sets the HTTP transporter for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @param HttpTransporterInterface $httpTransporter The HTTP transporter instance. */ private function setHttpTransporterForProvider(string $className, HttpTransporterInterface $httpTransporter): void { $availability = $className::availability(); if ($availability instanceof WithHttpTransporterInterface) { $availability->setHttpTransporter($httpTransporter); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithHttpTransporterInterface) { $modelMetadataDirectory->setHttpTransporter($httpTransporter); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithHttpTransporterInterface) { $operationsHandler->setHttpTransporter($httpTransporter); } } } /** * Sets the request authentication for a specific provider, hooking up its class instances. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @param RequestAuthenticationInterface $requestAuthentication The authentication instance. * * @throws InvalidArgumentException If the authentication instance is not of the expected type. */ private function setRequestAuthenticationForProvider(string $className, RequestAuthenticationInterface $requestAuthentication): void { $authenticationMethod = $className::metadata()->getAuthenticationMethod(); if ($authenticationMethod === null) { throw new InvalidArgumentException(sprintf('Provider %s does not expect any authentication, but got %s.', $className, get_class($requestAuthentication))); } $expectedClass = $authenticationMethod->getImplementationClass(); if (!$requestAuthentication instanceof $expectedClass) { throw new InvalidArgumentException(sprintf('Provider %s expects authentication of type %s, but got %s.', $className, $expectedClass, get_class($requestAuthentication))); } $availability = $className::availability(); if ($availability instanceof WithRequestAuthenticationInterface) { $availability->setRequestAuthentication($requestAuthentication); } $modelMetadataDirectory = $className::modelMetadataDirectory(); if ($modelMetadataDirectory instanceof WithRequestAuthenticationInterface) { $modelMetadataDirectory->setRequestAuthentication($requestAuthentication); } if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) { $operationsHandler = $className::operationsHandler(); if ($operationsHandler instanceof WithRequestAuthenticationInterface) { $operationsHandler->setRequestAuthentication($requestAuthentication); } } } /** * Creates a default request authentication instance for a provider. * * @since 0.1.0 * * @param class-string<ProviderInterface> $className The provider class name. * @return ?RequestAuthenticationInterface The default request authentication instance, or null if not required or * if no credential data can be found. */ private function createDefaultProviderRequestAuthentication(string $className): ?RequestAuthenticationInterface { $providerMetadata = $className::metadata(); $providerId = $providerMetadata->getId(); $authenticationMethod = $providerMetadata->getAuthenticationMethod(); if ($authenticationMethod === null) { return null; } $authenticationClass = $authenticationMethod->getImplementationClass(); if ($authenticationClass === null) { return null; } $authenticationSchema = $authenticationClass::getJsonSchema(); // Iterate over all JSON schema object properties to try to determine the necessary authentication data. $authenticationData = []; if (isset($authenticationSchema['properties']) && is_array($authenticationSchema['properties'])) { /** @var array<string, mixed> $details */ foreach ($authenticationSchema['properties'] as $property => $details) { $envVarName = $this->getEnvVarName($providerId, $property); // Try to get the value from environment variable or constant. $envValue = getenv($envVarName); if ($envValue === \false) { if (!defined($envVarName)) { continue; // Skip if neither environment variable nor constant is defined. } $envValue = constant($envVarName); if (!is_scalar($envValue)) { continue; } } if (isset($details['type'])) { switch ($details['type']) { case 'boolean': $authenticationData[$property] = filter_var($envValue, \FILTER_VALIDATE_BOOLEAN); break; case 'number': $authenticationData[$property] = (int) $envValue; break; case 'string': default: $authenticationData[$property] = (string) $envValue; } } else { // Default to string if no type is specified. $authenticationData[$property] = (string) $envValue; } } // If any required fields are missing, return null to avoid immediate errors. if (isset($authenticationSchema['required']) && is_array($authenticationSchema['required'])) { /** @var list<string> $requiredProperties */ $requiredProperties = $authenticationSchema['required']; if (array_diff_key(array_flip($requiredProperties), $authenticationData)) { return null; } } } /** @var RequestAuthenticationInterface */ /** @var array<string, mixed> $authenticationData */ return $authenticationClass::fromArray($authenticationData); } /** * Checks if the given value is a registered provider class name. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered class name. * @phpstan-assert-if-true class-string<ProviderInterface> $idOrClassName */ private function isRegisteredClassName(string $idOrClassName): bool { return isset($this->registeredClassNamesToIds[$idOrClassName]); } /** * Checks if the given value is a registered provider ID. * * @since 0.4.0 * * @param string $idOrClassName The value to check. * @return bool True if it's a registered provider ID. */ private function isRegisteredId(string $idOrClassName): bool { return isset($this->registeredIdsToClassNames[$idOrClassName]); } /** * Converts a provider ID and field name to a constant case environment variable name. * * @since 0.1.0 * * @param string $providerId The provider ID. * @param string $field The field name. * @return string The environment variable name in CONSTANT_CASE. */ private function getEnvVarName(string $providerId, string $field): string { // Convert camelCase or kebab-case or snake_case to CONSTANT_CASE. $constantCaseProviderId = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $providerId))); $constantCaseField = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $field))); return "{$constantCaseProviderId}_{$constantCaseField}"; } } src/Providers/Contracts/ProviderOperationsHandlerInterface.php 0000644 00000001522 15212525755 0020747 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Operations\Contracts\OperationInterface; /** * Interface for handling provider-level operations. * * Provides methods to retrieve and manage long-running operations * across all models within a provider. Operations are tracked at the * provider level rather than per-model. * * @since 0.1.0 */ interface ProviderOperationsHandlerInterface { /** * Gets an operation by ID. * * @since 0.1.0 * * @param string $operationId Operation identifier. * @return OperationInterface The operation. * @throws InvalidArgumentException If operation not found. */ public function getOperation(string $operationId): OperationInterface; } src/Providers/Contracts/ProviderInterface.php 0000644 00000003322 15212525755 0015405 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; /** * Interface for AI providers. * * Providers represent AI services (Google, OpenAI, Anthropic, etc.) * and provide access to models, metadata, and availability information. * * @since 0.1.0 */ interface ProviderInterface { /** * Gets provider metadata. * * @since 0.1.0 * * @return ProviderMetadata Provider metadata. */ public static function metadata(): ProviderMetadata; /** * Creates a model instance. * * @since 0.1.0 * * @param string $modelId Model identifier. * @param ?ModelConfig $modelConfig Model configuration. * @return ModelInterface Model instance. * @throws InvalidArgumentException If model not found or configuration invalid. */ public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface; /** * Gets provider availability checker. * * @since 0.1.0 * * @return ProviderAvailabilityInterface Provider availability checker. */ public static function availability(): \WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; /** * Gets model metadata directory. * * @since 0.1.0 * * @return ModelMetadataDirectoryInterface Model metadata directory. */ public static function modelMetadataDirectory(): \WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; } src/Providers/Contracts/ProviderWithOperationsHandlerInterface.php 0000644 00000001235 15212525755 0021604 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; /** * Interface for providers that support operations handlers. * * Providers implementing this interface can return an operations handler * for managing long-running operations across all their models. * * @since 0.1.0 */ interface ProviderWithOperationsHandlerInterface { /** * Gets the operations handler for this provider. * * @since 0.1.0 * * @return ProviderOperationsHandlerInterface The operations handler. */ public static function operationsHandler(): \WordPress\AiClient\Providers\Contracts\ProviderOperationsHandlerInterface; } src/Providers/Contracts/ProviderAvailabilityInterface.php 0000644 00000001056 15212525755 0017742 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; /** * Interface for checking provider availability. * * Determines whether a provider is configured and available * for use based on API keys, credentials, or other requirements. * * @since 0.1.0 */ interface ProviderAvailabilityInterface { /** * Checks if the provider is configured. * * @since 0.1.0 * * @return bool True if the provider is configured and available, false otherwise. */ public function isConfigured(): bool; } src/Providers/Contracts/ModelMetadataDirectoryInterface.php 0000644 00000002347 15212525755 0020207 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Contracts; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Interface for accessing model metadata within a provider. * * Provides methods to list, check, and retrieve model metadata * for all models supported by a provider. * * @since 0.1.0 */ interface ModelMetadataDirectoryInterface { /** * Lists all available model metadata. * * @since 0.1.0 * * @return list<ModelMetadata> Array of model metadata. */ public function listModelMetadata(): array; /** * Checks if metadata exists for a specific model. * * @since 0.1.0 * * @param string $modelId Model identifier. * @return bool True if metadata exists, false otherwise. */ public function hasModelMetadata(string $modelId): bool; /** * Gets metadata for a specific model. * * @since 0.1.0 * * @param string $modelId Model identifier. * @return ModelMetadata Model metadata. * @throws InvalidArgumentException If model metadata not found. */ public function getModelMetadata(string $modelId): ModelMetadata; } src/Providers/AbstractProvider.php 0000644 00000010014 15212525755 0013304 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Contracts\ProviderInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for a provider. * * @since 0.1.0 */ abstract class AbstractProvider implements ProviderInterface { /** * @var array<string, ProviderMetadata> Cache for provider metadata per class. */ private static array $metadataCache = []; /** * @var array<string, ProviderAvailabilityInterface> Cache for provider availability per class. */ private static array $availabilityCache = []; /** * @var array<string, ModelMetadataDirectoryInterface> Cache for model metadata directory per class. */ private static array $modelMetadataDirectoryCache = []; /** * {@inheritDoc} * * @since 0.1.0 */ final public static function metadata(): ProviderMetadata { $className = static::class; if (!isset(self::$metadataCache[$className])) { self::$metadataCache[$className] = static::createProviderMetadata(); } return self::$metadataCache[$className]; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface { $providerMetadata = static::metadata(); $modelMetadata = static::modelMetadataDirectory()->getModelMetadata($modelId); $model = static::createModel($modelMetadata, $providerMetadata); if ($modelConfig) { $model->setConfig($modelConfig); } return $model; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function availability(): ProviderAvailabilityInterface { $className = static::class; if (!isset(self::$availabilityCache[$className])) { self::$availabilityCache[$className] = static::createProviderAvailability(); } return self::$availabilityCache[$className]; } /** * {@inheritDoc} * * @since 0.1.0 */ final public static function modelMetadataDirectory(): ModelMetadataDirectoryInterface { $className = static::class; if (!isset(self::$modelMetadataDirectoryCache[$className])) { self::$modelMetadataDirectoryCache[$className] = static::createModelMetadataDirectory(); } return self::$modelMetadataDirectoryCache[$className]; } /** * Creates a model instance based on the given model metadata and provider metadata. * * @since 0.1.0 * * @param ModelMetadata $modelMetadata The model metadata. * @param ProviderMetadata $providerMetadata The provider metadata. * @return ModelInterface The new model instance. */ abstract protected static function createModel(ModelMetadata $modelMetadata, ProviderMetadata $providerMetadata): ModelInterface; /** * Creates the provider metadata instance. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ abstract protected static function createProviderMetadata(): ProviderMetadata; /** * Creates the provider availability instance. * * @since 0.1.0 * * @return ProviderAvailabilityInterface The provider availability. */ abstract protected static function createProviderAvailability(): ProviderAvailabilityInterface; /** * Creates the model metadata directory instance. * * @since 0.1.0 * * @return ModelMetadataDirectoryInterface The model metadata directory. */ abstract protected static function createModelMetadataDirectory(): ModelMetadataDirectoryInterface; } src/Providers/Http/DTO/RequestOptions.php 0000644 00000014523 15212525755 0014410 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; /** * Represents optional HTTP transport configuration for a single request. * * Provides mutable setters for working with timeouts and redirect handling. * * @since 0.2.0 * * @phpstan-type RequestOptionsArrayShape array{ * timeout?: float|null, * connectTimeout?: float|null, * maxRedirects?: int|null * } * * @extends AbstractDataTransferObject<RequestOptionsArrayShape> */ class RequestOptions extends AbstractDataTransferObject { public const KEY_TIMEOUT = 'timeout'; public const KEY_CONNECT_TIMEOUT = 'connectTimeout'; public const KEY_MAX_REDIRECTS = 'maxRedirects'; /** * @var float|null Maximum duration in seconds to wait for the full response. */ protected ?float $timeout = null; /** * @var float|null Maximum duration in seconds to wait for the initial connection. */ protected ?float $connectTimeout = null; /** * @var int|null Maximum number of redirects to follow. 0 disables redirects, null is unspecified. */ protected ?int $maxRedirects = null; /** * Sets the request timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_TIMEOUT); $this->timeout = $timeout; } /** * Sets the connection timeout in seconds. * * @since 0.2.0 * * @param float|null $timeout Connection timeout in seconds. * @return void * * @throws InvalidArgumentException When timeout is negative. */ public function setConnectTimeout(?float $timeout): void { $this->validateTimeout($timeout, self::KEY_CONNECT_TIMEOUT); $this->connectTimeout = $timeout; } /** * Sets the maximum number of redirects to follow. * * Set to 0 to disable redirects, null for unspecified, or a positive integer * to enable redirects with a maximum count. * * @since 0.2.0 * * @param int|null $maxRedirects Maximum redirects to follow, or 0 to disable, or null for unspecified. * @return void * * @throws InvalidArgumentException When redirect count is negative. */ public function setMaxRedirects(?int $maxRedirects): void { if ($maxRedirects !== null && $maxRedirects < 0) { throw new InvalidArgumentException('Request option "maxRedirects" must be greater than or equal to 0.'); } $this->maxRedirects = $maxRedirects; } /** * Gets the request timeout in seconds. * * @since 0.2.0 * * @return float|null Timeout in seconds. */ public function getTimeout(): ?float { return $this->timeout; } /** * Gets the connection timeout in seconds. * * @since 0.2.0 * * @return float|null Connection timeout in seconds. */ public function getConnectTimeout(): ?float { return $this->connectTimeout; } /** * Checks whether redirects are allowed. * * @since 0.2.0 * * @return bool|null True when redirects are allowed (maxRedirects > 0), * false when disabled (maxRedirects = 0), * null when unspecified (maxRedirects = null). */ public function allowsRedirects(): ?bool { if ($this->maxRedirects === null) { return null; } return $this->maxRedirects > 0; } /** * Gets the maximum number of redirects to follow. * * @since 0.2.0 * * @return int|null Maximum redirects or null when not specified. */ public function getMaxRedirects(): ?int { return $this->maxRedirects; } /** * {@inheritDoc} * * @since 0.2.0 * * @return RequestOptionsArrayShape */ public function toArray(): array { $data = []; if ($this->timeout !== null) { $data[self::KEY_TIMEOUT] = $this->timeout; } if ($this->connectTimeout !== null) { $data[self::KEY_CONNECT_TIMEOUT] = $this->connectTimeout; } if ($this->maxRedirects !== null) { $data[self::KEY_MAX_REDIRECTS] = $this->maxRedirects; } return $data; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function fromArray(array $array): self { $instance = new self(); if (isset($array[self::KEY_TIMEOUT])) { $instance->setTimeout((float) $array[self::KEY_TIMEOUT]); } if (isset($array[self::KEY_CONNECT_TIMEOUT])) { $instance->setConnectTimeout((float) $array[self::KEY_CONNECT_TIMEOUT]); } if (isset($array[self::KEY_MAX_REDIRECTS])) { $instance->setMaxRedirects((int) $array[self::KEY_MAX_REDIRECTS]); } return $instance; } /** * {@inheritDoc} * * @since 0.2.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the full response.'], self::KEY_CONNECT_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the initial connection.'], self::KEY_MAX_REDIRECTS => ['type' => ['integer', 'null'], 'minimum' => 0, 'description' => 'Maximum redirects to follow. 0 disables, null is unspecified.']], 'additionalProperties' => \false]; } /** * Validates timeout values. * * @since 0.2.0 * * @param float|null $value Timeout to validate. * @param string $fieldName Field name for the error message. * * @throws InvalidArgumentException When timeout is negative. */ private function validateTimeout(?float $value, string $fieldName): void { if ($value !== null && $value < 0) { throw new InvalidArgumentException(sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName)); } } } src/Providers/Http/DTO/ApiKeyRequestAuthentication.php 0000644 00000004517 15212525755 0017041 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; /** * Class for HTTP request authentication using an API key. * * @since 0.1.0 * * @phpstan-type ApiKeyRequestAuthenticationArrayShape array{ * apiKey: string * } * * @extends AbstractDataTransferObject<ApiKeyRequestAuthenticationArrayShape> */ class ApiKeyRequestAuthentication extends AbstractDataTransferObject implements RequestAuthenticationInterface { public const KEY_API_KEY = 'apiKey'; /** * @var string The API key used for authentication. */ protected string $apiKey; /** * Constructor. * * @since 0.1.0 * * @param string $apiKey The API key used for authentication. */ public function __construct(string $apiKey) { $this->apiKey = $apiKey; } /** * {@inheritDoc} * * @since 0.1.0 */ public function authenticateRequest(\WordPress\AiClient\Providers\Http\DTO\Request $request): \WordPress\AiClient\Providers\Http\DTO\Request { // Add the API key to the request headers. return $request->withHeader('Authorization', 'Bearer ' . $this->apiKey); } /** * Gets the API key. * * @since 0.1.0 * * @return string The API key. */ public function getApiKey(): string { return $this->apiKey; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 * * @return ApiKeyRequestAuthenticationArrayShape */ public function toArray(): array { return [self::KEY_API_KEY => $this->apiKey]; } /** * {@inheritDoc} * * @since 0.1.0 * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_API_KEY]); return new self($array[self::KEY_API_KEY]); } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_API_KEY => ['type' => 'string', 'title' => 'API Key', 'description' => 'The API key used for authentication.']], 'required' => [self::KEY_API_KEY]]; } } src/Providers/Http/DTO/Request.php 0000644 00000030047 15212525755 0013033 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use JsonException; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; /** * Represents an HTTP request. * * This class encapsulates HTTP request data that can be converted * to PSR-7 requests by the HTTP transporter. * * @since 0.1.0 * * @phpstan-import-type RequestOptionsArrayShape from RequestOptions * @phpstan-type RequestArrayShape array{ * method: string, * uri: string, * headers: array<string, list<string>>, * body?: string|null, * options?: RequestOptionsArrayShape * } * * @extends AbstractDataTransferObject<RequestArrayShape> */ class Request extends AbstractDataTransferObject { public const KEY_METHOD = 'method'; public const KEY_URI = 'uri'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; public const KEY_OPTIONS = 'options'; /** * @var HttpMethodEnum The HTTP method. */ protected HttpMethodEnum $method; /** * @var string The request URI. */ protected string $uri; /** * @var HeadersCollection The request headers. */ protected HeadersCollection $headers; /** * @var array<string, mixed>|null The request data (for query params or form data). */ protected ?array $data = null; /** * @var string|null The request body (raw string content). */ protected ?string $body = null; /** * @var RequestOptions|null Request transport options. */ protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null; /** * Constructor. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $uri The request URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @param RequestOptions|null $options The request transport options. * * @throws InvalidArgumentException If the URI is empty. */ public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null) { if (empty($uri)) { throw new InvalidArgumentException('URI cannot be empty.'); } $this->method = $method; $this->uri = $uri; $this->headers = new HeadersCollection($headers); // Separate data and body based on type if (is_string($data)) { $this->body = $data; } elseif (is_array($data)) { $this->data = $data; } $this->options = $options; } /** * Creates a deep clone of this request. * * Clones the headers collection and request options to ensure * the cloned request is independent of the original. * The HTTP method enum is immutable and can be safely shared. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; // Clone request options if present (contains only primitives) if ($this->options !== null) { $this->options = clone $this->options; } // Note: $method is an immutable enum and can be safely shared } /** * Gets the HTTP method. * * @since 0.1.0 * * @return HttpMethodEnum The HTTP method. */ public function getMethod(): HttpMethodEnum { return $this->method; } /** * Gets the request URI. * * For GET requests with array data, appends the data as query parameters. * * @since 0.1.0 * * @return string The URI. */ public function getUri(): string { // If GET request with data, append as query parameters if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) { $separator = str_contains($this->uri, '?') ? '&' : '?'; return $this->uri . $separator . http_build_query($this->data); } return $this->uri; } /** * Gets the request headers. * * @since 0.1.0 * * @return array<string, list<string>> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string, or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Gets the request body. * * For GET requests, returns null. * For POST/PUT/PATCH requests: * - If body is set, returns it as-is * - If data is set and Content-Type is JSON, returns JSON-encoded data * - If data is set and Content-Type is form, returns URL-encoded data * * @since 0.1.0 * * @return string|null The body. * @throws JsonException If the data cannot be encoded to JSON. */ public function getBody(): ?string { // GET requests don't have a body if (!$this->method->hasBody()) { return null; } // If body is set, return it as-is if ($this->body !== null) { return $this->body; } // If data is set, encode based on content type if ($this->data !== null) { $contentType = $this->getContentType(); // JSON encoding if ($contentType !== null && stripos($contentType, 'application/json') !== \false) { return json_encode($this->data, \JSON_THROW_ON_ERROR); } // Default to URL encoding for forms return http_build_query($this->data); } return null; } /** * Gets the Content-Type header value. * * @since 0.1.0 * * @return string|null The Content-Type header value or null if not set. */ private function getContentType(): ?string { $values = $this->getHeader('Content-Type'); return $values !== null ? $values[0] : null; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $newHeaders = $this->headers->withHeader($name, $value); $new = clone $this; $new->headers = $newHeaders; return $new; } /** * Returns a new instance with the specified data. * * @since 0.1.0 * * @param string|array<string, mixed> $data The request data. * @return self A new instance with the data. */ public function withData($data): self { $new = clone $this; if (is_string($data)) { $new->body = $data; $new->data = null; } elseif (is_array($data)) { $new->data = $data; $new->body = null; } else { $new->data = null; $new->body = null; } return $new; } /** * Gets the request data array. * * @since 0.1.0 * * @return array<string, mixed>|null The request data array. */ public function getData(): ?array { return $this->data; } /** * Gets the request options. * * @since 0.2.0 * * @return RequestOptions|null Request transport options when configured. */ public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions { return $this->options; } /** * Returns a new instance with the specified request options. * * @since 0.2.0 * * @param RequestOptions|null $options The request options to apply. * @return self A new instance with the options. */ public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self { $new = clone $this; $new->options = $options; return $new; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return RequestArrayShape */ public function toArray(): array { $array = [ self::KEY_METHOD => $this->method->value, self::KEY_URI => $this->getUri(), // Include query params if GET with data self::KEY_HEADERS => $this->headers->getAll(), ]; // Include body if present (getBody() handles the conversion) $body = $this->getBody(); if ($body !== null) { $array[self::KEY_BODY] = $body; } if ($this->options !== null) { $optionsArray = $this->options->toArray(); if (!empty($optionsArray)) { $array[self::KEY_OPTIONS] = $optionsArray; } } return $array; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]); return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null); } /** * Creates a Request instance from a PSR-7 RequestInterface. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request to convert. * @return self A new Request instance. * @throws InvalidArgumentException If the HTTP method is not supported. */ public static function fromPsrRequest(RequestInterface $psrRequest): self { $method = HttpMethodEnum::from($psrRequest->getMethod()); $uri = (string) $psrRequest->getUri(); // Convert PSR-7 headers to array format expected by our constructor /** @var array<string, list<string>> $headers */ $headers = $psrRequest->getHeaders(); // Get body content $body = $psrRequest->getBody()->getContents(); $bodyOrData = !empty($body) ? $body : null; return new self($method, $uri, $headers, $bodyOrData); } } src/Providers/Http/DTO/Response.php 0000644 00000013734 15212525755 0013205 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\Collections\HeadersCollection; /** * Represents an HTTP response. * * This class encapsulates HTTP response data that has been converted * from PSR-7 responses by the HTTP transporter. * * @since 0.1.0 * * @phpstan-type ResponseArrayShape array{ * statusCode: int, * headers: array<string, list<string>>, * body?: string|null * } * * @extends AbstractDataTransferObject<ResponseArrayShape> */ class Response extends AbstractDataTransferObject { public const KEY_STATUS_CODE = 'statusCode'; public const KEY_HEADERS = 'headers'; public const KEY_BODY = 'body'; /** * @var int The HTTP status code. */ protected int $statusCode; /** * @var HeadersCollection The response headers. */ protected HeadersCollection $headers; /** * @var string|null The response body. */ protected ?string $body; /** * Constructor. * * @since 0.1.0 * * @param int $statusCode The HTTP status code. * @param array<string, string|list<string>> $headers The response headers. * @param string|null $body The response body. * * @throws InvalidArgumentException If the status code is invalid. */ public function __construct(int $statusCode, array $headers, ?string $body = null) { if ($statusCode < 100 || $statusCode >= 600) { throw new InvalidArgumentException('Invalid HTTP status code: ' . $statusCode); } $this->statusCode = $statusCode; $this->headers = new HeadersCollection($headers); $this->body = $body; } /** * Creates a deep clone of this response. * * Clones the headers collection to ensure the cloned * response is independent of the original. * * @since 0.4.2 */ public function __clone() { // Clone headers collection $this->headers = clone $this->headers; } /** * Gets the HTTP status code. * * @since 0.1.0 * * @return int The status code. */ public function getStatusCode(): int { return $this->statusCode; } /** * Gets the response headers. * * @since 0.1.0 * * @return array<string, list<string>> The headers. */ public function getHeaders(): array { return $this->headers->getAll(); } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function getHeader(string $name): ?array { return $this->headers->get($name); } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getHeaderAsString(string $name): ?string { return $this->headers->getAsString($name); } /** * Gets the response body. * * @since 0.1.0 * * @return string|null The body. */ public function getBody(): ?string { return $this->body; } /** * Checks if the response has a header. * * @since 0.1.0 * * @param string $name The header name. * @return bool True if the header exists, false otherwise. */ public function hasHeader(string $name): bool { return $this->headers->has($name); } /** * Checks if the response indicates success. * * @since 0.1.0 * * @return bool True if status code is 2xx, false otherwise. */ public function isSuccessful(): bool { return $this->statusCode >= 200 && $this->statusCode < 300; } /** * Gets the response data as an array. * * Attempts to decode the body as JSON. Returns null if the body * is empty or not valid JSON. * * @since 0.1.0 * * @return array<string, mixed>|null The decoded data or null. */ public function getData(): ?array { if ($this->body === null || $this->body === '') { return null; } $data = json_decode($this->body, \true); if (json_last_error() !== \JSON_ERROR_NONE) { return null; } /** @var array<string, mixed>|null $data */ return is_array($data) ? $data : null; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_STATUS_CODE => ['type' => 'integer', 'minimum' => 100, 'maximum' => 599, 'description' => 'The HTTP status code.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The response headers.'], self::KEY_BODY => ['type' => ['string', 'null'], 'description' => 'The response body.']], 'required' => [self::KEY_STATUS_CODE, self::KEY_HEADERS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ResponseArrayShape */ public function toArray(): array { $data = [self::KEY_STATUS_CODE => $this->statusCode, self::KEY_HEADERS => $this->headers->getAll()]; if ($this->body !== null) { $data[self::KEY_BODY] = $this->body; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_STATUS_CODE, self::KEY_HEADERS]); return new self($array[self::KEY_STATUS_CODE], $array[self::KEY_HEADERS], $array[self::KEY_BODY] ?? null); } } src/Providers/Http/HttpTransporter.php 0000644 00000025234 15212525755 0014142 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http; use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface; use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\ClientWithOptionsInterface; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Exception\NetworkException; /** * HTTP transporter implementation using HTTPlug. * * This class handles the conversion between custom Request/Response * objects and PSR-7 messages, using HTTPlug for client abstraction * and PSR-17 factories for message creation. * * @since 0.1.0 */ class HttpTransporter implements HttpTransporterInterface { /** * @var RequestFactoryInterface PSR-17 request factory. */ private RequestFactoryInterface $requestFactory; /** * @var StreamFactoryInterface PSR-17 stream factory. */ private StreamFactoryInterface $streamFactory; /** * @var ClientInterface PSR-18 HTTP client. */ private ClientInterface $client; /** * Constructor. * * @since 0.1.0 * * @param ClientInterface|null $client PSR-18 HTTP client. * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory. * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory. */ public function __construct(?ClientInterface $client = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null) { $this->client = $client ?: Psr18ClientDiscovery::find(); $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory(); $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(); } /** * {@inheritDoc} * * @since 0.1.0 * @since 0.2.0 Added optional RequestOptions parameter and ClientWithOptions support. */ public function send(Request $request, ?RequestOptions $options = null): Response { $psr7Request = $this->convertToPsr7Request($request); // Merge request options with parameter options, with parameter options taking precedence $mergedOptions = $this->mergeOptions($request->getOptions(), $options); try { $hasOptions = $mergedOptions !== null; if ($hasOptions && $this->client instanceof ClientWithOptionsInterface) { $psr7Response = $this->client->sendRequestWithOptions($psr7Request, $mergedOptions); } elseif ($hasOptions && $this->isGuzzleClient($this->client)) { $psr7Response = $this->sendWithGuzzle($psr7Request, $mergedOptions); } else { $psr7Response = $this->client->sendRequest($psr7Request); } } catch (\WordPress\AiClientDependencies\Psr\Http\Client\NetworkExceptionInterface $e) { throw NetworkException::fromPsr18NetworkException($psr7Request, $e); } catch (\WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface $e) { // Handle other PSR-18 client exceptions that are not network-related throw new RuntimeException(sprintf('HTTP client error occurred while sending request to %s: %s', $request->getUri(), $e->getMessage()), 0, $e); } return $this->convertFromPsr7Response($psr7Response); } /** * Merges request options with parameter options taking precedence. * * @since 0.2.0 * * @param RequestOptions|null $requestOptions Options from the Request object. * @param RequestOptions|null $parameterOptions Options passed as method parameter. * @return RequestOptions|null Merged options, or null if both are null. */ private function mergeOptions(?RequestOptions $requestOptions, ?RequestOptions $parameterOptions): ?RequestOptions { // If no options at all, return null if ($requestOptions === null && $parameterOptions === null) { return null; } // If only one set of options exists, return it if ($requestOptions === null) { return $parameterOptions; } if ($parameterOptions === null) { return $requestOptions; } // Both exist, merge them with parameter options taking precedence $merged = new RequestOptions(); // Start with request options (lower precedence) if ($requestOptions->getTimeout() !== null) { $merged->setTimeout($requestOptions->getTimeout()); } if ($requestOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($requestOptions->getConnectTimeout()); } if ($requestOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($requestOptions->getMaxRedirects()); } // Override with parameter options (higher precedence) if ($parameterOptions->getTimeout() !== null) { $merged->setTimeout($parameterOptions->getTimeout()); } if ($parameterOptions->getConnectTimeout() !== null) { $merged->setConnectTimeout($parameterOptions->getConnectTimeout()); } if ($parameterOptions->getMaxRedirects() !== null) { $merged->setMaxRedirects($parameterOptions->getMaxRedirects()); } return $merged; } /** * Determines if the underlying client matches the Guzzle client shape. * * @since 0.2.0 * * @param ClientInterface $client The HTTP client instance. * @return bool True when the client exposes Guzzle's send signature. */ private function isGuzzleClient(ClientInterface $client): bool { $reflection = new \ReflectionObject($client); if (!is_callable([$client, 'send'])) { return \false; } if (!$reflection->hasMethod('send')) { return \false; } $method = $reflection->getMethod('send'); if (!$method->isPublic() || $method->isStatic()) { return \false; } $parameters = $method->getParameters(); if (count($parameters) < 2) { return \false; } $firstParameter = $parameters[0]->getType(); if (!$firstParameter instanceof \ReflectionNamedType || $firstParameter->isBuiltin()) { return \false; } if (!is_a($firstParameter->getName(), RequestInterface::class, \true)) { return \false; } $secondParameter = $parameters[1]; $secondType = $secondParameter->getType(); if (!$secondType instanceof \ReflectionNamedType || $secondType->getName() !== 'array') { return \false; } return \true; } /** * Sends a request using a Guzzle-compatible client. * * @since 0.2.0 * * @param RequestInterface $request The PSR-7 request to send. * @param RequestOptions $options The request options. * @return ResponseInterface The PSR-7 response received. */ private function sendWithGuzzle(RequestInterface $request, RequestOptions $options): ResponseInterface { $guzzleOptions = $this->buildGuzzleOptions($options); /** @var callable $callable */ $callable = [$this->client, 'send']; /** @var ResponseInterface $response */ $response = $callable($request, $guzzleOptions); return $response; } /** * Converts request options to a Guzzle-compatible options array. * * @since 0.2.0 * * @param RequestOptions $options The request options. * @return array<string, mixed> Guzzle-compatible options. */ private function buildGuzzleOptions(RequestOptions $options): array { $guzzleOptions = []; $timeout = $options->getTimeout(); if ($timeout !== null) { $guzzleOptions['timeout'] = $timeout; } $connectTimeout = $options->getConnectTimeout(); if ($connectTimeout !== null) { $guzzleOptions['connect_timeout'] = $connectTimeout; } $allowRedirects = $options->allowsRedirects(); if ($allowRedirects !== null) { if ($allowRedirects) { $redirectOptions = []; $maxRedirects = $options->getMaxRedirects(); if ($maxRedirects !== null) { $redirectOptions['max'] = $maxRedirects; } $guzzleOptions['allow_redirects'] = !empty($redirectOptions) ? $redirectOptions : \true; } else { $guzzleOptions['allow_redirects'] = \false; } } return $guzzleOptions; } /** * Converts a custom Request to a PSR-7 request. * * @since 0.1.0 * * @param Request $request The custom request. * @return RequestInterface The PSR-7 request. */ private function convertToPsr7Request(Request $request): RequestInterface { $psr7Request = $this->requestFactory->createRequest($request->getMethod()->value, $request->getUri()); // Add headers foreach ($request->getHeaders() as $name => $values) { foreach ($values as $value) { $psr7Request = $psr7Request->withAddedHeader($name, $value); } } // Add body if present $body = $request->getBody(); if ($body !== null) { $stream = $this->streamFactory->createStream($body); $psr7Request = $psr7Request->withBody($stream); } return $psr7Request; } /** * Converts a PSR-7 response to a custom Response. * * @since 0.1.0 * * @param ResponseInterface $psr7Response The PSR-7 response. * @return Response The custom response. */ private function convertFromPsr7Response(ResponseInterface $psr7Response): Response { $body = (string) $psr7Response->getBody(); // PSR-7 always returns headers as arrays, but HeadersCollection handles this return new Response( $psr7Response->getStatusCode(), $psr7Response->getHeaders(), // @phpstan-ignore-line $body === '' ? null : $body ); } } src/Providers/Http/Contracts/ClientWithOptionsInterface.php 0000644 00000002001 15212525755 0020151 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; /** * Interface for HTTP clients that support per-request transport options. * * Extends the capabilities of PSR-18 clients by allowing custom transport * configuration such as timeouts and redirect handling on each request. * * @since 0.2.0 */ interface ClientWithOptionsInterface { /** * Sends an HTTP request with the given transport options. * * @since 0.2.0 * * @param RequestInterface $request The PSR-7 request to send. * @param RequestOptions $options The request transport options. Must not be null. * @return ResponseInterface The PSR-7 response received. */ public function sendRequestWithOptions(RequestInterface $request, RequestOptions $options): ResponseInterface; } src/Providers/Http/Contracts/WithRequestAuthenticationInterface.php 0000644 00000001540 15212525755 0021716 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; /** * Interface for models that support request authentication. * * @since 0.1.0 */ interface WithRequestAuthenticationInterface { /** * Sets the request authentication. * * @since 0.1.0 * * @param RequestAuthenticationInterface $authentication The authentication instance. * @return void */ public function setRequestAuthentication(\WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface $authentication): void; /** * Returns the request authentication. * * @since 0.1.0 * * @return RequestAuthenticationInterface The authentication instance. */ public function getRequestAuthentication(): \WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; } src/Providers/Http/Contracts/RequestAuthenticationInterface.php 0000644 00000001155 15212525755 0021064 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface; use WordPress\AiClient\Providers\Http\DTO\Request; /** * Interface for HTTP request authentication. * * @since 0.1.0 */ interface RequestAuthenticationInterface extends WithJsonSchemaInterface { /** * Authenticates an HTTP request. * * @since 0.1.0 * * @param Request $request The request to authenticate. * @return Request The authenticated request. */ public function authenticateRequest(Request $request): Request; } src/Providers/Http/Contracts/WithHttpTransporterInterface.php 0000644 00000001455 15212525755 0020556 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; /** * Interface for models that require HTTP transport capabilities. * * @since 0.1.0 */ interface WithHttpTransporterInterface { /** * Sets the HTTP transporter. * * @since 0.1.0 * * @param HttpTransporterInterface $transporter The HTTP transporter instance. * @return void */ public function setHttpTransporter(\WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface $transporter): void; /** * Returns the HTTP transporter. * * @since 0.1.0 * * @return HttpTransporterInterface The HTTP transporter instance. */ public function getHttpTransporter(): \WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; } src/Providers/Http/Contracts/HttpTransporterInterface.php 0000644 00000001534 15212525755 0017720 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Contracts; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\DTO\Response; /** * Interface for HTTP transport implementations. * * Handles sending HTTP requests and receiving responses using * PSR-7, PSR-17, and PSR-18 standards internally. * * @since 0.1.0 */ interface HttpTransporterInterface { /** * Sends an HTTP request and returns the response. * * @since 0.1.0 * * @param Request $request The request to send. * @param RequestOptions|null $options Optional transport options for the request. * @return Response The response received. */ public function send(Request $request, ?RequestOptions $options = null): Response; } src/Providers/Http/HttpTransporterFactory.php 0000644 00000002026 15212525755 0015464 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http; use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; /** * Factory for creating HTTP transporters. * * Uses HTTPlug's Discovery component to automatically find * available HTTP clients and factories. * * @since 0.1.0 */ class HttpTransporterFactory { /** * Creates an HTTP transporter. * * Uses HTTPlug Discovery to automatically find PSR-18 client * and PSR-17 factories if not provided. * * @since 0.1.0 * * @return HttpTransporterInterface The HTTP transporter. */ public static function createTransporter(): HttpTransporterInterface { return new \WordPress\AiClient\Providers\Http\HttpTransporter(Psr18ClientDiscovery::find(), Psr17FactoryDiscovery::findRequestFactory(), Psr17FactoryDiscovery::findStreamFactory()); } } src/Providers/Http/Collections/HeadersCollection.php 0000644 00000007411 15212525755 0016621 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Collections; /** * Simple collection for managing HTTP headers with case-insensitive access. * * This class stores HTTP headers while preserving their original casing * and provides efficient case-insensitive lookups. * * @since 0.1.0 */ class HeadersCollection { /** * @var array<string, list<string>> The headers with original casing. */ private array $headers = []; /** * @var array<string, string> Map of lowercase header names to actual header names. */ private array $headersMap = []; /** * Constructor. * * @since 0.1.0 * * @param array<string, string|list<string>> $headers Initial headers. */ public function __construct(array $headers = []) { foreach ($headers as $name => $value) { $this->set($name, $value); } } /** * Gets a specific header value. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return list<string>|null The header value(s) or null if not found. */ public function get(string $name): ?array { $lowerName = strtolower($name); if (!isset($this->headersMap[$lowerName])) { return null; } $actualName = $this->headersMap[$lowerName]; return $this->headers[$actualName]; } /** * Gets all headers. * * @since 0.1.0 * * @return array<string, list<string>> All headers with their original casing. */ public function getAll(): array { return $this->headers; } /** * Gets header values as a comma-separated string. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return string|null The header values as a comma-separated string or null if not found. */ public function getAsString(string $name): ?string { $values = $this->get($name); return $values !== null ? implode(', ', $values) : null; } /** * Checks if a header exists. * * @since 0.1.0 * * @param string $name The header name (case-insensitive). * @return bool True if the header exists, false otherwise. */ public function has(string $name): bool { return isset($this->headersMap[strtolower($name)]); } /** * Sets a header value, replacing any existing value. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return void */ private function set(string $name, $value): void { if (is_array($value)) { $normalizedValues = array_values($value); } else { // Split comma-separated string into array $normalizedValues = array_map('trim', explode(',', $value)); } $lowerName = strtolower($name); // If header exists with different casing, remove the old casing if (isset($this->headersMap[$lowerName])) { $oldName = $this->headersMap[$lowerName]; if ($oldName !== $name) { unset($this->headers[$oldName]); } } // Always use the new casing $this->headers[$name] = $normalizedValues; $this->headersMap[$lowerName] = $name; } /** * Returns a new instance with the specified header. * * @since 0.1.0 * * @param string $name The header name. * @param string|list<string> $value The header value(s). * @return self A new instance with the header. */ public function withHeader(string $name, $value): self { $new = clone $this; $new->set($name, $value); return $new; } } src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php 0000644 00000005557 15212525755 0021230 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Abstracts; use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery; use WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy; use WordPress\AiClientDependencies\Nyholm\Psr7\Factory\Psr17Factory; use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface; /** * Abstract discovery strategy for HTTP client implementations. * * Provides a base for registering custom HTTP client implementations * with HTTPlug's discovery mechanism. Subclasses must implement * the createClient() method to provide their specific PSR-18 * HTTP client instance using the provided Psr17Factory. * * @since 1.1.0 */ abstract class AbstractClientDiscoveryStrategy implements DiscoveryStrategy { /** * Initializes and registers the discovery strategy. * * @since 1.1.0 * * @return void */ public static function init(): void { if (!class_exists('WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery')) { return; } Psr18ClientDiscovery::prependStrategy(static::class); } /** * {@inheritDoc} * * @since 1.1.0 * * @param string $type The type of discovery. * @return array<array<string, mixed>> The discovery candidates. */ public static function getCandidates($type) { if (ClientInterface::class === $type) { return [['class' => static function () { $psr17Factory = new Psr17Factory(); return static::createClient($psr17Factory); }]]; } $psr17Factories = ['WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface']; if (in_array($type, $psr17Factories, \true)) { return [['class' => Psr17Factory::class]]; } return []; } /** * Creates an instance of the HTTP client. * * Subclasses must implement this method to return their specific * PSR-18 HTTP client instance. The provided Psr17Factory implements * all PSR-17 interfaces (RequestFactory, ResponseFactory, StreamFactory, * etc.) and can be used to satisfy client constructor dependencies. * * @since 1.1.0 * * @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages. * @return ClientInterface The PSR-18 HTTP client. */ abstract protected static function createClient(Psr17Factory $psr17Factory): ClientInterface; } src/Providers/Http/Util/ResponseUtil.php 0000644 00000004146 15212525755 0014327 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Util; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Exception\ClientException; use WordPress\AiClient\Providers\Http\Exception\RedirectException; use WordPress\AiClient\Providers\Http\Exception\ServerException; /** * Class with static utility methods to process HTTP responses. * * @since 0.1.0 */ class ResponseUtil { /** * Throws an appropriate exception if the given response is not successful. * * This method checks the HTTP status code of the response and throws * the appropriate exception type based on the status code range: * - 3xx: RedirectException (redirect responses) * - 4xx: ClientException (client errors) * - 5xx: ServerException (server errors) * - Other unsuccessful responses: RuntimeException (invalid status codes) * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws RedirectException If the response indicates a redirect (3xx). * @throws ClientException If the response indicates a client error (4xx). * @throws ServerException If the response indicates a server error (5xx). * @throws \RuntimeException If the response has an invalid status code. */ public static function throwIfNotSuccessful(Response $response): void { if ($response->isSuccessful()) { return; } $statusCode = $response->getStatusCode(); // 3xx Redirect Responses if ($statusCode >= 300 && $statusCode < 400) { throw RedirectException::fromRedirectResponse($response); } // 4xx Client Errors if ($statusCode >= 400 && $statusCode < 500) { throw ClientException::fromClientErrorResponse($response); } // 5xx Server Errors if ($statusCode >= 500 && $statusCode < 600) { throw ServerException::fromServerErrorResponse($response); } throw new \RuntimeException(sprintf('Response returned invalid status code: %s', $response->getStatusCode())); } } src/Providers/Http/Util/ErrorMessageExtractor.php 0000644 00000003545 15212525755 0016167 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Util; /** * Utility for extracting error messages from API response data. * * Centralizes the logic for parsing common API error response formats * to avoid code duplication across exception classes. * * @since 0.2.0 * @since 0.4.0 Moved from Utilities namespace to Util namespace. */ class ErrorMessageExtractor { /** * Extracts error message from API response data. * * Handles common error response formats: * - { "error": { "message": "Error text" } } * - { "error": "Error text" } * - { "message": "Error text" } * * @since 0.2.0 * * @param mixed $data The response data to extract error message from. * @return string|null The extracted error message, or null if none found. */ public static function extractFromResponseData($data): ?string { if (!is_array($data)) { return null; } // Handle [ { "error": { "message": "Error text" } } ] if (isset($data[0]) && is_array($data[0]) && isset($data[0]['error']) && is_array($data[0]['error']) && isset($data[0]['error']['message']) && is_string($data[0]['error']['message'])) { return $data[0]['error']['message']; } // Handle { "error": { "message": "Error text" } } if (isset($data['error']) && is_array($data['error']) && isset($data['error']['message']) && is_string($data['error']['message'])) { return $data['error']['message']; } // Handle { "error": "Error text" } if (isset($data['error']) && is_string($data['error'])) { return $data['error']; } // Handle { "message": "Error text" } if (isset($data['message']) && is_string($data['message'])) { return $data['message']; } return null; } } src/Providers/Http/error_log 0000644 00000001444 15212525755 0012160 0 ustar 00 [03-Jun-2026 16:43:59 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 [11-Jun-2026 12:28:14 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29 src/Providers/Http/Traits/WithHttpTransporterTrait.php 0000644 00000002106 15212525755 0017241 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Traits; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface; /** * Trait for a class that implements WithHttpTransporterInterface. * * @since 0.1.0 */ trait WithHttpTransporterTrait { /** * @var HttpTransporterInterface|null The HTTP transporter instance. */ private ?HttpTransporterInterface $httpTransporter = null; /** * {@inheritDoc} * * @since 0.1.0 */ public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void { $this->httpTransporter = $httpTransporter; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getHttpTransporter(): HttpTransporterInterface { if ($this->httpTransporter === null) { throw new RuntimeException('HttpTransporterInterface instance not set. Make sure you use the AiClient class for all requests.'); } return $this->httpTransporter; } } src/Providers/Http/Traits/WithRequestAuthenticationTrait.php 0000644 00000002261 15212525755 0020410 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Traits; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; /** * Trait for a class that implements WithRequestAuthenticationInterface. * * @since 0.1.0 */ trait WithRequestAuthenticationTrait { /** * @var RequestAuthenticationInterface|null The request authentication instance. */ private ?RequestAuthenticationInterface $requestAuthentication = null; /** * {@inheritDoc} * * @since 0.1.0 */ public function setRequestAuthentication(RequestAuthenticationInterface $requestAuthentication): void { $this->requestAuthentication = $requestAuthentication; } /** * {@inheritDoc} * * @since 0.1.0 */ public function getRequestAuthentication(): RequestAuthenticationInterface { if ($this->requestAuthentication === null) { throw new RuntimeException('RequestAuthenticationInterface instance not set. ' . 'Make sure you use the AiClient class for all requests.'); } return $this->requestAuthentication; } } src/Providers/Http/Exception/ResponseException.php 0000644 00000003041 15212525755 0016362 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; /** * Exception class for HTTP response errors. * * This is used when response data is unexpected or malformed, * typically indicating that a provider changed in ways our code * is not aware of or when parsing response data fails. * * @since 0.1.0 */ class ResponseException extends RuntimeException { /** * Creates a ResponseException for missing expected data. * * @since 0.2.0 * * @param string $apiName The name of the API/provider. * @param string $fieldName The field that was expected but missing. * @return self */ public static function fromMissingData(string $apiName, string $fieldName): self { $message = sprintf('Unexpected %s API response: Missing the "%s" key.', $apiName, $fieldName); return new self($message); } /** * Creates a ResponseException from invalid data in an API response. * * @since 0.2.0 * * @param string $apiName The name of the API service (e.g., 'OpenAI', 'Anthropic'). * @param string $fieldName The field that was invalid. * @param string $message The specific error message describing the invalid data. * @return self */ public static function fromInvalidData(string $apiName, string $fieldName, string $message): self { return new self(sprintf('Unexpected %s API response: Invalid "%s" key: %s', $apiName, $fieldName, $message)); } } src/Providers/Http/Exception/ServerException.php 0000644 00000003425 15212525755 0016040 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; /** * Exception thrown for 5xx HTTP server errors. * * This represents errors where the server failed to fulfill * a valid request due to internal server errors. * * @since 0.2.0 */ class ServerException extends RuntimeException { /** * Creates a ServerException from a server error response. * * This method extracts error details from common API response formats * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP response that failed. * @return self */ public static function fromServerErrorResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 507 => 'Insufficient Storage', 529 => 'Overloaded']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Server error (%d): Request was rejected due to server-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $response->getStatusCode()); } } src/Providers/Http/Exception/RedirectException.php 0000644 00000003465 15212525755 0016337 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Response; /** * Exception thrown for 3xx HTTP redirect responses. * * This represents cases where the server indicates that the request * should be retried at a different location, but automatic redirect * handling was not successful or not enabled. * * @since 0.2.0 */ class RedirectException extends RuntimeException { /** * Creates a RedirectException from a redirect response. * * This method extracts redirect information from the response headers * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP redirect response. * @return self */ public static function fromRedirectResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Redirect error (%d): Request needs to be retried at a different location', $statusCode); } // Try to extract the redirect location from headers $locationValues = $response->getHeader('Location'); if ($locationValues !== null && !empty($locationValues)) { $location = $locationValues[0]; $errorMessage .= ' - Location: ' . $location; } return new self($errorMessage, $statusCode); } } src/Providers/Http/Exception/NetworkException.php 0000644 00000003512 15212525755 0016220 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Providers\Http\DTO\Request; /** * Exception thrown for network-related errors. * * This includes HTTP transport errors, connection failures, * timeouts, and other network-related issues. * * @since 0.2.0 */ class NetworkException extends RuntimeException { /** * The request that failed. * * @var Request|null */ protected ?Request $request = null; /** * Returns the request that failed as our Request DTO. * * @since 0.2.0 * * @return Request * @throws \RuntimeException If no request is available */ public function getRequest(): Request { if ($this->request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a NetworkException from a PSR-18 network exception. * * @since 0.2.0 * * @param RequestInterface $psrRequest The PSR-7 request that failed. * @param \Throwable $networkException The PSR-18 network exception. * @return self */ public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self { $request = Request::fromPsrRequest($psrRequest); $message = sprintf('Network error occurred while sending request to %s: %s', $request->getUri(), $networkException->getMessage()); $exception = new self($message, 0, $networkException); $exception->request = $request; return $exception; } } src/Providers/Http/Exception/ClientException.php 0000644 00000004655 15212525755 0016016 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Exception; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor; /** * Exception thrown for 4xx HTTP client errors. * * This represents errors where the client request was malformed, * unauthorized, forbidden, or otherwise invalid. * * @since 0.2.0 */ class ClientException extends InvalidArgumentException { /** * The request that failed. * * @var Request|null */ protected ?Request $request = null; /** * Returns the request that failed as our Request DTO. * * @since 0.2.0 * * @return Request * @throws \RuntimeException If no request is available */ public function getRequest(): Request { if ($this->request === null) { throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.'); } return $this->request; } /** * Creates a ClientException from a client error response (4xx). * * This method extracts error details from common API response formats * and creates an exception with a descriptive message and status code. * * @since 0.2.0 * * @param Response $response The HTTP response that failed. * @return self */ public static function fromClientErrorResponse(Response $response): self { $statusCode = $response->getStatusCode(); $statusTexts = [400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests']; if (isset($statusTexts[$statusCode])) { $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode); } else { $errorMessage = sprintf('Client error (%d): Request was rejected due to client-side issue', $statusCode); } // Extract error message from response data using centralized utility $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData()); if ($extractedError !== null) { $errorMessage .= ' - ' . $extractedError; } return new self($errorMessage, $statusCode); } } src/Providers/Http/Enums/HttpMethodEnum.php 0000644 00000004750 15212525755 0014753 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Represents HTTP request methods. * * @since 0.1.0 * * @method static self GET() * @method static self POST() * @method static self PUT() * @method static self PATCH() * @method static self DELETE() * @method static self HEAD() * @method static self OPTIONS() * @method static self CONNECT() * @method static self TRACE() * * @method bool isGet() * @method bool isPost() * @method bool isPut() * @method bool isPatch() * @method bool isDelete() * @method bool isHead() * @method bool isOptions() * @method bool isConnect() * @method bool isTrace() */ final class HttpMethodEnum extends AbstractEnum { /** * GET method for retrieving resources. * * @var string */ public const GET = 'GET'; /** * POST method for creating resources. * * @var string */ public const POST = 'POST'; /** * PUT method for updating/replacing resources. * * @var string */ public const PUT = 'PUT'; /** * PATCH method for partially updating resources. * * @var string */ public const PATCH = 'PATCH'; /** * DELETE method for removing resources. * * @var string */ public const DELETE = 'DELETE'; /** * HEAD method for retrieving headers only. * * @var string */ public const HEAD = 'HEAD'; /** * OPTIONS method for retrieving allowed methods. * * @var string */ public const OPTIONS = 'OPTIONS'; /** * CONNECT method for establishing tunnel. * * @var string */ public const CONNECT = 'CONNECT'; /** * TRACE method for diagnostic purposes. * * @var string */ public const TRACE = 'TRACE'; /** * Checks if this method is idempotent. * * @since 0.1.0 * * @return bool True if the method is idempotent, false otherwise. */ public function isIdempotent(): bool { return in_array($this->value, [self::GET, self::HEAD, self::OPTIONS, self::TRACE, self::PUT, self::DELETE], \true); } /** * Checks if this method typically has a request body. * * @since 0.1.0 * * @return bool True if the method typically has a body, false otherwise. */ public function hasBody(): bool { return in_array($this->value, [self::POST, self::PUT, self::PATCH], \true); } } src/Providers/Http/Enums/RequestAuthenticationMethod.php 0000644 00000002344 15212525755 0017534 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Http\Enums; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface; use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication; /** * Enum for request authentication methods. * * @since 0.4.0 * * @method static self apiKey() Creates an instance for API_KEY method. * @method bool isApiKey() Checks if the method is API_KEY. */ class RequestAuthenticationMethod extends AbstractEnum { /** * API key authentication. */ public const API_KEY = 'api_key'; /** * Gets the implementation class for the authentication method. * * @since 0.4.0 * * @return class-string<RequestAuthenticationInterface&WithArrayTransformationInterface> The implementation class. * * @phpstan-ignore missingType.generics */ public function getImplementationClass(): string { // At the moment, this is the only supported method. // Once more methods are available, add conditionals here for each method. return ApiKeyRequestAuthentication::class; } } src/Providers/Http/Enums/error_log 0000644 00000000624 15212525755 0013246 0 ustar 00 [11-Jun-2026 11:33:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18 src/Providers/Models/DTO/RequiredOption.php 0000644 00000005513 15212525755 0014660 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents an option that the implementing code requires the model to support. * * This class defines an option that the model must support with a specific value * for it to be considered suitable for the implementing code's requirements. * * @since 0.1.0 * * @phpstan-type RequiredOptionArrayShape array{ * name: string, * value: mixed * } * * @extends AbstractDataTransferObject<RequiredOptionArrayShape> */ class RequiredOption extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_VALUE = 'value'; /** * @var OptionEnum The option name. */ protected OptionEnum $name; /** * @var mixed The value that the model must support for this option. */ protected $value; /** * Constructor. * * @since 0.1.0 * * @param OptionEnum $name The option name. * @param mixed $value The value that the model must support for this option. */ public function __construct(OptionEnum $name, $value) { $this->name = $name; $this->value = $value; } /** * Gets the option name. * * @since 0.1.0 * * @return OptionEnum The option name. */ public function getName(): OptionEnum { return $this->name; } /** * Gets the value that the model must support for this option. * * @since 0.1.0 * * @return mixed The value that the model must support. */ public function getValue() { return $this->value; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_VALUE => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']], 'description' => 'The value that the model must support for this option.']], 'required' => [self::KEY_NAME, self::KEY_VALUE]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return RequiredOptionArrayShape */ public function toArray(): array { return [self::KEY_NAME => $this->name->value, self::KEY_VALUE => $this->value]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_VALUE]); return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_VALUE]); } } src/Providers/Models/DTO/SupportedOption.php 0000644 00000014011 15212525755 0015056 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents a supported configuration option for an AI model. * * This class defines an option that a model supports, including its name * and the values that are valid for that option. * * @since 0.1.0 * * @phpstan-type SupportedOptionArrayShape array{ * name: string, * supportedValues?: list<mixed> * } * * @extends AbstractDataTransferObject<SupportedOptionArrayShape> */ class SupportedOption extends AbstractDataTransferObject { public const KEY_NAME = 'name'; public const KEY_SUPPORTED_VALUES = 'supportedValues'; /** * @var OptionEnum The option name. */ protected OptionEnum $name; /** * @var list<mixed>|null The supported values for this option. */ protected ?array $supportedValues; /** * Constructor. * * @since 0.1.0 * * @param OptionEnum $name The option name. * @param list<mixed>|null $supportedValues The supported values for this option, or null if any value is supported. * * @throws InvalidArgumentException If supportedValues is not null and not a list. */ public function __construct(OptionEnum $name, ?array $supportedValues = null) { if ($supportedValues !== null && !array_is_list($supportedValues)) { throw new InvalidArgumentException('Supported values must be a list array.'); } $this->name = $name; $this->supportedValues = $supportedValues; } /** * Gets the option name. * * @since 0.1.0 * * @return OptionEnum The option name. */ public function getName(): OptionEnum { return $this->name; } /** * Checks if a value is supported for this option. * * @since 0.1.0 * * @param mixed $value The value to check. * @return bool True if the value is supported, false otherwise. */ public function isSupportedValue($value): bool { // If supportedValues is null, any value is supported if ($this->supportedValues === null) { return \true; } // If the value is an array, consider it a set (i.e. order doesn't matter). if (is_array($value)) { $normalizedValue = self::normalizeArrayForComparison($value); foreach ($this->supportedValues as $supportedValue) { if (!is_array($supportedValue)) { continue; } $normalizedSupported = self::normalizeArrayForComparison($supportedValue); if ($normalizedValue === $normalizedSupported) { return \true; } } return \false; } $normalizedValue = self::normalizeValue($value); foreach ($this->supportedValues as $supportedValue) { if (self::normalizeValue($supportedValue) === $normalizedValue) { return \true; } } return \false; } /** * Normalizes an AbstractEnum instance to its string value. * * This ensures comparisons work correctly even after deserialization * (e.g. Redis/Memcached object cache), where AbstractEnum singletons * are reconstructed as separate instances. * * @since 1.2.1 * * @param mixed $value The value to normalize. * @return mixed The normalized value. */ private static function normalizeValue($value) { if ($value instanceof AbstractEnum) { return $value->value; } return $value; } /** * Normalizes and sorts an array for comparison. * * Maps each element through normalizeValue() and sorts the result, * ensuring consistent comparison regardless of element order or * AbstractEnum instance identity. * * @since 1.2.1 * * @param array<mixed> $items The array to normalize. * @return array<mixed> The normalized, sorted array. */ private static function normalizeArrayForComparison(array $items): array { $normalized = array_map([self::class, 'normalizeValue'], $items); sort($normalized); return $normalized; } /** * Gets the supported values for this option. * * @since 0.1.0 * * @return list<mixed>|null The supported values, or null if any value is supported. */ public function getSupportedValues(): ?array { return $this->supportedValues; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_SUPPORTED_VALUES => ['type' => 'array', 'items' => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']]], 'description' => 'The supported values for this option.']], 'required' => [self::KEY_NAME]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return SupportedOptionArrayShape */ public function toArray(): array { $data = [self::KEY_NAME => $this->name->value]; if ($this->supportedValues !== null) { /** @var list<mixed> $supportedValues */ $supportedValues = $this->supportedValues; $data[self::KEY_SUPPORTED_VALUES] = $supportedValues; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_NAME]); return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_SUPPORTED_VALUES] ?? null); } } src/Providers/Models/DTO/ModelConfig.php 0000644 00000073244 15212525755 0014103 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Files\Enums\FileTypeEnum; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; use WordPress\AiClient\Tools\DTO\WebSearch; /** * Represents configuration for an AI model. * * This class allows configuring various parameters for model behavior, * including output modalities, system instructions, generation parameters, * and tool integrations. * * @since 0.1.0 * * @phpstan-import-type FunctionDeclarationArrayShape from FunctionDeclaration * @phpstan-import-type WebSearchArrayShape from WebSearch * * @phpstan-type ModelConfigArrayShape array{ * outputModalities?: list<string>, * systemInstruction?: string, * candidateCount?: int, * maxTokens?: int, * temperature?: float, * topP?: float, * topK?: int, * stopSequences?: list<string>, * presencePenalty?: float, * frequencyPenalty?: float, * logprobs?: bool, * topLogprobs?: int, * functionDeclarations?: list<FunctionDeclarationArrayShape>, * webSearch?: WebSearchArrayShape, * outputFileType?: string, * outputMimeType?: string, * outputSchema?: array<string, mixed>, * outputMediaOrientation?: string, * outputMediaAspectRatio?: string, * outputSpeechVoice?: string, * customOptions?: array<string, mixed> * } * * @extends AbstractDataTransferObject<ModelConfigArrayShape> */ class ModelConfig extends AbstractDataTransferObject { public const KEY_OUTPUT_MODALITIES = 'outputModalities'; public const KEY_SYSTEM_INSTRUCTION = 'systemInstruction'; public const KEY_CANDIDATE_COUNT = 'candidateCount'; public const KEY_MAX_TOKENS = 'maxTokens'; public const KEY_TEMPERATURE = 'temperature'; public const KEY_TOP_P = 'topP'; public const KEY_TOP_K = 'topK'; public const KEY_STOP_SEQUENCES = 'stopSequences'; public const KEY_PRESENCE_PENALTY = 'presencePenalty'; public const KEY_FREQUENCY_PENALTY = 'frequencyPenalty'; public const KEY_LOGPROBS = 'logprobs'; public const KEY_TOP_LOGPROBS = 'topLogprobs'; public const KEY_FUNCTION_DECLARATIONS = 'functionDeclarations'; public const KEY_WEB_SEARCH = 'webSearch'; public const KEY_OUTPUT_FILE_TYPE = 'outputFileType'; public const KEY_OUTPUT_MIME_TYPE = 'outputMimeType'; public const KEY_OUTPUT_SCHEMA = 'outputSchema'; public const KEY_OUTPUT_MEDIA_ORIENTATION = 'outputMediaOrientation'; public const KEY_OUTPUT_MEDIA_ASPECT_RATIO = 'outputMediaAspectRatio'; public const KEY_OUTPUT_SPEECH_VOICE = 'outputSpeechVoice'; public const KEY_CUSTOM_OPTIONS = 'customOptions'; /* * Note: This key is not an actual model config key, but specified here for convenience. * It is relevant for model discovery, to determine which models support which input modalities. * The actual input modalities are part of the message sent to the model, not the model config. */ public const KEY_INPUT_MODALITIES = 'inputModalities'; /** * @var list<ModalityEnum>|null Output modalities for the model. */ protected ?array $outputModalities = null; /** * @var string|null System instruction for the model. */ protected ?string $systemInstruction = null; /** * @var int|null Number of response candidates to generate. */ protected ?int $candidateCount = null; /** * @var int|null Maximum number of tokens to generate. */ protected ?int $maxTokens = null; /** * @var float|null Temperature for randomness (0.0 to 2.0). */ protected ?float $temperature = null; /** * @var float|null Top-p nucleus sampling parameter. */ protected ?float $topP = null; /** * @var int|null Top-k sampling parameter. */ protected ?int $topK = null; /** * @var list<string>|null Stop sequences. */ protected ?array $stopSequences = null; /** * @var float|null Presence penalty for reducing repetition. */ protected ?float $presencePenalty = null; /** * @var float|null Frequency penalty for reducing repetition. */ protected ?float $frequencyPenalty = null; /** * @var bool|null Whether to return log probabilities. */ protected ?bool $logprobs = null; /** * @var int|null Number of top log probabilities to return. */ protected ?int $topLogprobs = null; /** * @var list<FunctionDeclaration>|null Function declarations available to the model. */ protected ?array $functionDeclarations = null; /** * @var WebSearch|null Web search configuration for the model. */ protected ?WebSearch $webSearch = null; /** * @var FileTypeEnum|null Output file type. */ protected ?FileTypeEnum $outputFileType = null; /** * @var string|null Output MIME type. */ protected ?string $outputMimeType = null; /** * @var array<string, mixed>|null Output schema (JSON schema). */ protected ?array $outputSchema = null; /** * @var MediaOrientationEnum|null Output media orientation. */ protected ?MediaOrientationEnum $outputMediaOrientation = null; /** * @var string|null Output media aspect ratio (e.g. 3:2, 16:9). */ protected ?string $outputMediaAspectRatio = null; /** * @var string|null Output speech voice. */ protected ?string $outputSpeechVoice = null; /** * @var array<string, mixed> Custom provider-specific options. */ protected array $customOptions = []; /** * Creates a deep clone of this configuration. * * Clones nested objects (functionDeclarations, webSearch) to ensure * the cloned configuration is independent of the original. * Enum value objects (outputModalities, outputFileType, outputMediaOrientation) * are intentionally shared as they are immutable. * * @since 0.4.2 */ public function __clone() { // Deep clone function declarations if set if ($this->functionDeclarations !== null) { $clonedDeclarations = []; foreach ($this->functionDeclarations as $declaration) { $clonedDeclarations[] = clone $declaration; } $this->functionDeclarations = $clonedDeclarations; } // Clone web search if set if ($this->webSearch !== null) { $this->webSearch = clone $this->webSearch; } // Note: Enum value objects (outputModalities, outputFileType, outputMediaOrientation) // are immutable and can be safely shared. } /** * Sets the output modalities. * * @since 0.1.0 * * @param list<ModalityEnum> $outputModalities The output modalities. * * @throws InvalidArgumentException If the array is not a list. */ public function setOutputModalities(array $outputModalities): void { if (!array_is_list($outputModalities)) { throw new InvalidArgumentException('Output modalities must be a list array.'); } $this->outputModalities = $outputModalities; } /** * Gets the output modalities. * * @since 0.1.0 * * @return list<ModalityEnum>|null The output modalities. */ public function getOutputModalities(): ?array { return $this->outputModalities; } /** * Sets the system instruction. * * @since 0.1.0 * * @param string $systemInstruction The system instruction. */ public function setSystemInstruction(string $systemInstruction): void { $this->systemInstruction = $systemInstruction; } /** * Gets the system instruction. * * @since 0.1.0 * * @return string|null The system instruction. */ public function getSystemInstruction(): ?string { return $this->systemInstruction; } /** * Sets the candidate count. * * @since 0.1.0 * * @param int $candidateCount The candidate count. */ public function setCandidateCount(int $candidateCount): void { $this->candidateCount = $candidateCount; } /** * Gets the candidate count. * * @since 0.1.0 * * @return int|null The candidate count. */ public function getCandidateCount(): ?int { return $this->candidateCount; } /** * Sets the maximum tokens. * * @since 0.1.0 * * @param int $maxTokens The maximum tokens. */ public function setMaxTokens(int $maxTokens): void { $this->maxTokens = $maxTokens; } /** * Gets the maximum tokens. * * @since 0.1.0 * * @return int|null The maximum tokens. */ public function getMaxTokens(): ?int { return $this->maxTokens; } /** * Sets the temperature. * * @since 0.1.0 * * @param float $temperature The temperature. */ public function setTemperature(float $temperature): void { $this->temperature = $temperature; } /** * Gets the temperature. * * @since 0.1.0 * * @return float|null The temperature. */ public function getTemperature(): ?float { return $this->temperature; } /** * Sets the top-p parameter. * * @since 0.1.0 * * @param float $topP The top-p parameter. */ public function setTopP(float $topP): void { $this->topP = $topP; } /** * Gets the top-p parameter. * * @since 0.1.0 * * @return float|null The top-p parameter. */ public function getTopP(): ?float { return $this->topP; } /** * Sets the top-k parameter. * * @since 0.1.0 * * @param int $topK The top-k parameter. */ public function setTopK(int $topK): void { $this->topK = $topK; } /** * Gets the top-k parameter. * * @since 0.1.0 * * @return int|null The top-k parameter. */ public function getTopK(): ?int { return $this->topK; } /** * Sets the stop sequences. * * @since 0.1.0 * * @param list<string> $stopSequences The stop sequences. * * @throws InvalidArgumentException If the array is not a list. */ public function setStopSequences(array $stopSequences): void { if (!array_is_list($stopSequences)) { throw new InvalidArgumentException('Stop sequences must be a list array.'); } $this->stopSequences = $stopSequences; } /** * Gets the stop sequences. * * @since 0.1.0 * * @return list<string>|null The stop sequences. */ public function getStopSequences(): ?array { return $this->stopSequences; } /** * Sets the presence penalty. * * @since 0.1.0 * * @param float $presencePenalty The presence penalty. */ public function setPresencePenalty(float $presencePenalty): void { $this->presencePenalty = $presencePenalty; } /** * Gets the presence penalty. * * @since 0.1.0 * * @return float|null The presence penalty. */ public function getPresencePenalty(): ?float { return $this->presencePenalty; } /** * Sets the frequency penalty. * * @since 0.1.0 * * @param float $frequencyPenalty The frequency penalty. */ public function setFrequencyPenalty(float $frequencyPenalty): void { $this->frequencyPenalty = $frequencyPenalty; } /** * Gets the frequency penalty. * * @since 0.1.0 * * @return float|null The frequency penalty. */ public function getFrequencyPenalty(): ?float { return $this->frequencyPenalty; } /** * Sets whether to return log probabilities. * * @since 0.1.0 * * @param bool $logprobs Whether to return log probabilities. */ public function setLogprobs(bool $logprobs): void { $this->logprobs = $logprobs; } /** * Gets whether to return log probabilities. * * @since 0.1.0 * * @return bool|null Whether to return log probabilities. */ public function getLogprobs(): ?bool { return $this->logprobs; } /** * Sets the number of top log probabilities to return. * * @since 0.1.0 * * @param int $topLogprobs The number of top log probabilities. */ public function setTopLogprobs(int $topLogprobs): void { $this->topLogprobs = $topLogprobs; } /** * Gets the number of top log probabilities to return. * * @since 0.1.0 * * @return int|null The number of top log probabilities. */ public function getTopLogprobs(): ?int { return $this->topLogprobs; } /** * Sets the function declarations. * * @since 0.1.0 * * @param list<FunctionDeclaration> $functionDeclarations The function declarations. * * @throws InvalidArgumentException If the array is not a list. */ public function setFunctionDeclarations(array $functionDeclarations): void { if (!array_is_list($functionDeclarations)) { throw new InvalidArgumentException('Function declarations must be a list array.'); } $this->functionDeclarations = $functionDeclarations; } /** * Gets the function declarations. * * @since 0.1.0 * * @return list<FunctionDeclaration>|null The function declarations. */ public function getFunctionDeclarations(): ?array { return $this->functionDeclarations; } /** * Sets the web search configuration. * * @since 0.1.0 * * @param WebSearch $webSearch The web search configuration. */ public function setWebSearch(WebSearch $webSearch): void { $this->webSearch = $webSearch; } /** * Gets the web search configuration. * * @since 0.1.0 * * @return WebSearch|null The web search configuration. */ public function getWebSearch(): ?WebSearch { return $this->webSearch; } /** * Sets the output file type. * * @since 0.1.0 * * @param FileTypeEnum $outputFileType The output file type. */ public function setOutputFileType(FileTypeEnum $outputFileType): void { $this->outputFileType = $outputFileType; } /** * Gets the output file type. * * @since 0.1.0 * * @return FileTypeEnum|null The output file type. */ public function getOutputFileType(): ?FileTypeEnum { return $this->outputFileType; } /** * Sets the output MIME type. * * @since 0.1.0 * * @param string $outputMimeType The output MIME type. */ public function setOutputMimeType(string $outputMimeType): void { $this->outputMimeType = $outputMimeType; } /** * Gets the output MIME type. * * @since 0.1.0 * * @return string|null The output MIME type. */ public function getOutputMimeType(): ?string { return $this->outputMimeType; } /** * Sets the output schema. * * When setting an output schema, this method automatically sets * the output MIME type to "application/json" if not already set. * * @since 0.1.0 * * @param array<string, mixed> $outputSchema The output schema (JSON schema). */ public function setOutputSchema(array $outputSchema): void { $this->outputSchema = $outputSchema; // Automatically set outputMimeType to application/json when schema is provided if ($this->outputMimeType === null) { $this->outputMimeType = 'application/json'; } } /** * Gets the output schema. * * @since 0.1.0 * * @return array<string, mixed>|null The output schema. */ public function getOutputSchema(): ?array { return $this->outputSchema; } /** * Sets the output media orientation. * * @since 0.1.0 * * @param MediaOrientationEnum $outputMediaOrientation The output media orientation. */ public function setOutputMediaOrientation(MediaOrientationEnum $outputMediaOrientation): void { if ($this->outputMediaAspectRatio) { $this->validateMediaOrientationAspectRatioCompatibility($outputMediaOrientation, $this->outputMediaAspectRatio); } $this->outputMediaOrientation = $outputMediaOrientation; } /** * Gets the output media orientation. * * @since 0.1.0 * * @return MediaOrientationEnum|null The output media orientation. */ public function getOutputMediaOrientation(): ?MediaOrientationEnum { return $this->outputMediaOrientation; } /** * Sets the output media aspect ratio. * * If set, this supersedes the output media orientation, as it is a more specific configuration. * * @since 0.1.0 * * @param string $outputMediaAspectRatio The output media aspect ratio (e.g. 3:2, 16:9). */ public function setOutputMediaAspectRatio(string $outputMediaAspectRatio): void { if (!preg_match('/^\d+:\d+$/', $outputMediaAspectRatio)) { throw new InvalidArgumentException('Output media aspect ratio must be in the format "width:height" (e.g. 3:2, 16:9).'); } if ($this->outputMediaOrientation) { $this->validateMediaOrientationAspectRatioCompatibility($this->outputMediaOrientation, $outputMediaAspectRatio); } $this->outputMediaAspectRatio = $outputMediaAspectRatio; } /** * Gets the output media aspect ratio. * * @since 0.1.0 * * @return string|null The output media aspect ratio (e.g. 3:2, 16:9). */ public function getOutputMediaAspectRatio(): ?string { return $this->outputMediaAspectRatio; } /** * Validates that the given media orientation and aspect ratio values do not conflict with each other. * * @since 0.4.0 * * @param MediaOrientationEnum $orientation The desired media orientation. * @param string $aspectRatio The desired media aspect ratio. */ protected function validateMediaOrientationAspectRatioCompatibility(MediaOrientationEnum $orientation, string $aspectRatio): void { $aspectRatioParts = explode(':', $aspectRatio); if ($orientation->isSquare() && $aspectRatioParts[0] !== $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the square orientation.'); } if ($orientation->isLandscape() && $aspectRatioParts[0] <= $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the landscape orientation.'); } if ($orientation->isPortrait() && $aspectRatioParts[0] >= $aspectRatioParts[1]) { throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the portrait orientation.'); } } /** * Sets the output speech voice. * * @since 0.1.0 * * @param string $outputSpeechVoice The output speech voice. */ public function setOutputSpeechVoice(string $outputSpeechVoice): void { $this->outputSpeechVoice = $outputSpeechVoice; } /** * Gets the output speech voice. * * @since 0.1.0 * * @return string|null The output speech voice. */ public function getOutputSpeechVoice(): ?string { return $this->outputSpeechVoice; } /** * Sets a single custom option. * * @since 0.1.0 * * @param string $key The option key. * @param mixed $value The option value. */ public function setCustomOption(string $key, $value): void { $this->customOptions[$key] = $value; } /** * Sets the custom options. * * @since 0.1.0 * * @param array<string, mixed> $customOptions The custom options. */ public function setCustomOptions(array $customOptions): void { $this->customOptions = $customOptions; } /** * Gets the custom options. * * @since 0.1.0 * * @return array<string, mixed> The custom options. */ public function getCustomOptions(): array { return $this->customOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_OUTPUT_MODALITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ModalityEnum::getValues()], 'description' => 'Output modalities for the model.'], self::KEY_SYSTEM_INSTRUCTION => ['type' => 'string', 'description' => 'System instruction for the model.'], self::KEY_CANDIDATE_COUNT => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of response candidates to generate.'], self::KEY_MAX_TOKENS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Maximum number of tokens to generate.'], self::KEY_TEMPERATURE => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 2.0, 'description' => 'Temperature for randomness.'], self::KEY_TOP_P => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 1.0, 'description' => 'Top-p nucleus sampling parameter.'], self::KEY_TOP_K => ['type' => 'integer', 'minimum' => 1, 'description' => 'Top-k sampling parameter.'], self::KEY_STOP_SEQUENCES => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Stop sequences.'], self::KEY_PRESENCE_PENALTY => ['type' => 'number', 'description' => 'Presence penalty for reducing repetition.'], self::KEY_FREQUENCY_PENALTY => ['type' => 'number', 'description' => 'Frequency penalty for reducing repetition.'], self::KEY_LOGPROBS => ['type' => 'boolean', 'description' => 'Whether to return log probabilities.'], self::KEY_TOP_LOGPROBS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of top log probabilities to return.'], self::KEY_FUNCTION_DECLARATIONS => ['type' => 'array', 'items' => FunctionDeclaration::getJsonSchema(), 'description' => 'Function declarations available to the model.'], self::KEY_WEB_SEARCH => WebSearch::getJsonSchema(), self::KEY_OUTPUT_FILE_TYPE => ['type' => 'string', 'enum' => FileTypeEnum::getValues(), 'description' => 'Output file type.'], self::KEY_OUTPUT_MIME_TYPE => ['type' => 'string', 'description' => 'Output MIME type.'], self::KEY_OUTPUT_SCHEMA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Output schema (JSON schema).'], self::KEY_OUTPUT_MEDIA_ORIENTATION => ['type' => 'string', 'enum' => MediaOrientationEnum::getValues(), 'description' => 'Output media orientation.'], self::KEY_OUTPUT_MEDIA_ASPECT_RATIO => ['type' => 'string', 'pattern' => '^\d+:\d+$', 'description' => 'Output media aspect ratio.'], self::KEY_OUTPUT_SPEECH_VOICE => ['type' => 'string', 'description' => 'Output speech voice.'], self::KEY_CUSTOM_OPTIONS => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Custom provider-specific options.']], 'additionalProperties' => \false]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelConfigArrayShape */ public function toArray(): array { $data = []; if ($this->outputModalities !== null) { $data[self::KEY_OUTPUT_MODALITIES] = array_map(static function (ModalityEnum $modality): string { return $modality->value; }, $this->outputModalities); } if ($this->systemInstruction !== null) { $data[self::KEY_SYSTEM_INSTRUCTION] = $this->systemInstruction; } if ($this->candidateCount !== null) { $data[self::KEY_CANDIDATE_COUNT] = $this->candidateCount; } if ($this->maxTokens !== null) { $data[self::KEY_MAX_TOKENS] = $this->maxTokens; } if ($this->temperature !== null) { $data[self::KEY_TEMPERATURE] = $this->temperature; } if ($this->topP !== null) { $data[self::KEY_TOP_P] = $this->topP; } if ($this->topK !== null) { $data[self::KEY_TOP_K] = $this->topK; } if ($this->stopSequences !== null) { $data[self::KEY_STOP_SEQUENCES] = $this->stopSequences; } if ($this->presencePenalty !== null) { $data[self::KEY_PRESENCE_PENALTY] = $this->presencePenalty; } if ($this->frequencyPenalty !== null) { $data[self::KEY_FREQUENCY_PENALTY] = $this->frequencyPenalty; } if ($this->logprobs !== null) { $data[self::KEY_LOGPROBS] = $this->logprobs; } if ($this->topLogprobs !== null) { $data[self::KEY_TOP_LOGPROBS] = $this->topLogprobs; } if ($this->functionDeclarations !== null) { $data[self::KEY_FUNCTION_DECLARATIONS] = array_map(static function (FunctionDeclaration $functionDeclaration): array { return $functionDeclaration->toArray(); }, $this->functionDeclarations); } if ($this->webSearch !== null) { $data[self::KEY_WEB_SEARCH] = $this->webSearch->toArray(); } if ($this->outputFileType !== null) { $data[self::KEY_OUTPUT_FILE_TYPE] = $this->outputFileType->value; } if ($this->outputMimeType !== null) { $data[self::KEY_OUTPUT_MIME_TYPE] = $this->outputMimeType; } if ($this->outputSchema !== null) { $data[self::KEY_OUTPUT_SCHEMA] = $this->outputSchema; } if ($this->outputMediaOrientation !== null) { $data[self::KEY_OUTPUT_MEDIA_ORIENTATION] = $this->outputMediaOrientation->value; } if ($this->outputMediaAspectRatio !== null) { $data[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO] = $this->outputMediaAspectRatio; } if ($this->outputSpeechVoice !== null) { $data[self::KEY_OUTPUT_SPEECH_VOICE] = $this->outputSpeechVoice; } if (!empty($this->customOptions)) { $data[self::KEY_CUSTOM_OPTIONS] = $this->customOptions; } return $data; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { $config = new self(); if (isset($array[self::KEY_OUTPUT_MODALITIES])) { $config->setOutputModalities(array_map(static fn(string $modality): ModalityEnum => ModalityEnum::from($modality), $array[self::KEY_OUTPUT_MODALITIES])); } if (isset($array[self::KEY_SYSTEM_INSTRUCTION])) { $config->setSystemInstruction($array[self::KEY_SYSTEM_INSTRUCTION]); } if (isset($array[self::KEY_CANDIDATE_COUNT])) { $config->setCandidateCount($array[self::KEY_CANDIDATE_COUNT]); } if (isset($array[self::KEY_MAX_TOKENS])) { $config->setMaxTokens($array[self::KEY_MAX_TOKENS]); } if (isset($array[self::KEY_TEMPERATURE])) { $config->setTemperature($array[self::KEY_TEMPERATURE]); } if (isset($array[self::KEY_TOP_P])) { $config->setTopP($array[self::KEY_TOP_P]); } if (isset($array[self::KEY_TOP_K])) { $config->setTopK($array[self::KEY_TOP_K]); } if (isset($array[self::KEY_STOP_SEQUENCES])) { $config->setStopSequences($array[self::KEY_STOP_SEQUENCES]); } if (isset($array[self::KEY_PRESENCE_PENALTY])) { $config->setPresencePenalty($array[self::KEY_PRESENCE_PENALTY]); } if (isset($array[self::KEY_FREQUENCY_PENALTY])) { $config->setFrequencyPenalty($array[self::KEY_FREQUENCY_PENALTY]); } if (isset($array[self::KEY_LOGPROBS])) { $config->setLogprobs($array[self::KEY_LOGPROBS]); } if (isset($array[self::KEY_TOP_LOGPROBS])) { $config->setTopLogprobs($array[self::KEY_TOP_LOGPROBS]); } if (isset($array[self::KEY_FUNCTION_DECLARATIONS])) { $config->setFunctionDeclarations(array_map(static function (array $functionDeclarationData): FunctionDeclaration { return FunctionDeclaration::fromArray($functionDeclarationData); }, $array[self::KEY_FUNCTION_DECLARATIONS])); } if (isset($array[self::KEY_WEB_SEARCH])) { $config->setWebSearch(WebSearch::fromArray($array[self::KEY_WEB_SEARCH])); } if (isset($array[self::KEY_OUTPUT_FILE_TYPE])) { $config->setOutputFileType(FileTypeEnum::from($array[self::KEY_OUTPUT_FILE_TYPE])); } if (isset($array[self::KEY_OUTPUT_MIME_TYPE])) { $config->setOutputMimeType($array[self::KEY_OUTPUT_MIME_TYPE]); } if (isset($array[self::KEY_OUTPUT_SCHEMA])) { $config->setOutputSchema($array[self::KEY_OUTPUT_SCHEMA]); } if (isset($array[self::KEY_OUTPUT_MEDIA_ORIENTATION])) { $config->setOutputMediaOrientation(MediaOrientationEnum::from($array[self::KEY_OUTPUT_MEDIA_ORIENTATION])); } if (isset($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO])) { $config->setOutputMediaAspectRatio($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO]); } if (isset($array[self::KEY_OUTPUT_SPEECH_VOICE])) { $config->setOutputSpeechVoice($array[self::KEY_OUTPUT_SPEECH_VOICE]); } if (isset($array[self::KEY_CUSTOM_OPTIONS])) { $config->setCustomOptions($array[self::KEY_CUSTOM_OPTIONS]); } return $config; } } src/Providers/Models/DTO/ModelRequirements.php 0000644 00000036511 15212525755 0015355 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\Models\Enums\OptionEnum; /** * Represents requirements that implementing code has for AI model selection. * * This class defines the capabilities and options that a model must support * in order to be considered suitable for the implementing code's needs. * * @since 0.1.0 * * @phpstan-import-type RequiredOptionArrayShape from RequiredOption * * @phpstan-type ModelRequirementsArrayShape array{ * requiredCapabilities: list<string>, * requiredOptions: list<RequiredOptionArrayShape> * } * * @extends AbstractDataTransferObject<ModelRequirementsArrayShape> */ class ModelRequirements extends AbstractDataTransferObject { public const KEY_REQUIRED_CAPABILITIES = 'requiredCapabilities'; public const KEY_REQUIRED_OPTIONS = 'requiredOptions'; /** * @var list<CapabilityEnum> The capabilities that the model must support. */ protected array $requiredCapabilities; /** * @var list<RequiredOption> The options that the model must support with specific values. */ protected array $requiredOptions; /** * Constructor. * * @since 0.1.0 * * @param list<CapabilityEnum> $requiredCapabilities The capabilities that the model must support. * @param list<RequiredOption> $requiredOptions The options that the model must support with specific values. * * @throws InvalidArgumentException If arrays are not lists. */ public function __construct(array $requiredCapabilities, array $requiredOptions) { if (!array_is_list($requiredCapabilities)) { throw new InvalidArgumentException('Required capabilities must be a list array.'); } if (!array_is_list($requiredOptions)) { throw new InvalidArgumentException('Required options must be a list array.'); } $this->requiredCapabilities = $requiredCapabilities; $this->requiredOptions = $requiredOptions; } /** * Gets the capabilities that the model must support. * * @since 0.1.0 * * @return list<CapabilityEnum> The required capabilities. */ public function getRequiredCapabilities(): array { return $this->requiredCapabilities; } /** * Gets the options that the model must support with specific values. * * @since 0.1.0 * * @return list<RequiredOption> The required options. */ public function getRequiredOptions(): array { return $this->requiredOptions; } /** * Checks whether the given model metadata meets these requirements. * * @since 0.2.0 * * @param ModelMetadata $metadata The model metadata to check against. * @return bool True if the model meets all requirements, false otherwise. */ public function areMetBy(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $metadata): bool { // Create lookup maps for better performance (instead of nested foreach loops) $capabilitiesMap = []; foreach ($metadata->getSupportedCapabilities() as $capability) { $capabilitiesMap[$capability->value] = $capability; } $optionsMap = []; foreach ($metadata->getSupportedOptions() as $option) { $optionsMap[$option->getName()->value] = $option; } // Check if all required capabilities are supported using map lookup foreach ($this->requiredCapabilities as $requiredCapability) { if (!isset($capabilitiesMap[$requiredCapability->value])) { return \false; } } // Check if all required options are supported with the specified values foreach ($this->requiredOptions as $requiredOption) { // Use map lookup instead of linear search if (!isset($optionsMap[$requiredOption->getName()->value])) { return \false; } $supportedOption = $optionsMap[$requiredOption->getName()->value]; // Check if the required value is supported by this option if (!$supportedOption->isSupportedValue($requiredOption->getValue())) { return \false; } } return \true; } /** * Creates ModelRequirements from prompt data and model configuration. * * @since 0.2.0 * * @param CapabilityEnum $capability The capability the model must support. * @param list<Message> $messages The messages in the conversation. * @param ModelConfig $modelConfig The model configuration. * @return self The created requirements. */ public static function fromPromptData(CapabilityEnum $capability, array $messages, \WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): self { // Start with base capability $capabilities = [$capability]; $inputModalities = []; // Check if we have chat history (multiple messages) if (count($messages) > 1) { $capabilities[] = CapabilityEnum::chatHistory(); } // Analyze all messages to determine required input modalities $hasFunctionMessageParts = \false; foreach ($messages as $message) { foreach ($message->getParts() as $part) { // Check for text input if ($part->getType()->isText()) { $inputModalities[] = ModalityEnum::text(); } // Check for file inputs if ($part->getType()->isFile()) { $file = $part->getFile(); if ($file !== null) { if ($file->isImage()) { $inputModalities[] = ModalityEnum::image(); } elseif ($file->isAudio()) { $inputModalities[] = ModalityEnum::audio(); } elseif ($file->isVideo()) { $inputModalities[] = ModalityEnum::video(); } elseif ($file->isDocument() || $file->isText()) { $inputModalities[] = ModalityEnum::document(); } } } // Check for function calls/responses (these might require special capabilities) if ($part->getType()->isFunctionCall() || $part->getType()->isFunctionResponse()) { $hasFunctionMessageParts = \true; } } } // Convert ModelConfig to RequiredOptions $requiredOptions = self::toRequiredOptions($modelConfig); // Add additional options based on message analysis if ($hasFunctionMessageParts) { $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true)); } // Add input modalities if we have any inputs if (!empty($inputModalities)) { // Remove duplicates $inputModalities = array_unique($inputModalities, \SORT_REGULAR); $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::inputModalities(), array_values($inputModalities))); } // Step 6: Return new ModelRequirements return new self($capabilities, $requiredOptions); } /** * Converts ModelConfig to an array of RequiredOptions. * * @since 0.2.0 * * @param ModelConfig $modelConfig The model configuration. * @return list<RequiredOption> The required options. */ private static function toRequiredOptions(\WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): array { $requiredOptions = []; // Map properties that have corresponding OptionEnum values if ($modelConfig->getOutputModalities() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputModalities(), $modelConfig->getOutputModalities()); } if ($modelConfig->getSystemInstruction() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::systemInstruction(), $modelConfig->getSystemInstruction()); } if ($modelConfig->getCandidateCount() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::candidateCount(), $modelConfig->getCandidateCount()); } if ($modelConfig->getMaxTokens() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::maxTokens(), $modelConfig->getMaxTokens()); } if ($modelConfig->getTemperature() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::temperature(), $modelConfig->getTemperature()); } if ($modelConfig->getTopP() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topP(), $modelConfig->getTopP()); } if ($modelConfig->getTopK() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topK(), $modelConfig->getTopK()); } if ($modelConfig->getOutputMimeType() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMimeType(), $modelConfig->getOutputMimeType()); } if ($modelConfig->getOutputSchema() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputSchema(), $modelConfig->getOutputSchema()); } // Handle properties without OptionEnum values as custom options if ($modelConfig->getStopSequences() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::stopSequences(), $modelConfig->getStopSequences()); } if ($modelConfig->getPresencePenalty() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::presencePenalty(), $modelConfig->getPresencePenalty()); } if ($modelConfig->getFrequencyPenalty() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::frequencyPenalty(), $modelConfig->getFrequencyPenalty()); } if ($modelConfig->getLogprobs() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::logprobs(), $modelConfig->getLogprobs()); } if ($modelConfig->getTopLogprobs() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topLogprobs(), $modelConfig->getTopLogprobs()); } if ($modelConfig->getFunctionDeclarations() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true); } if ($modelConfig->getWebSearch() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::webSearch(), \true); } if ($modelConfig->getOutputFileType() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputFileType(), $modelConfig->getOutputFileType()); } if ($modelConfig->getOutputMediaOrientation() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaOrientation(), $modelConfig->getOutputMediaOrientation()); } if ($modelConfig->getOutputMediaAspectRatio() !== null) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaAspectRatio(), $modelConfig->getOutputMediaAspectRatio()); } // Add custom options as individual RequiredOptions foreach ($modelConfig->getCustomOptions() as $key => $value) { $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::customOptions(), [$key => $value]); } return $requiredOptions; } /** * Includes a RequiredOption in the array, ensuring no duplicates based on option name. * * @since 0.2.0 * * @param list<RequiredOption> $requiredOptions The existing required options. * @param RequiredOption $newOption The new option to include. * @return list<RequiredOption> The updated required options array. */ private static function includeInRequiredOptions(array $requiredOptions, \WordPress\AiClient\Providers\Models\DTO\RequiredOption $newOption): array { // Check if we already have this option name foreach ($requiredOptions as $index => $existingOption) { if ($existingOption->getName()->equals($newOption->getName())) { // Replace existing option with new one $requiredOptions[$index] = $newOption; return $requiredOptions; } } // Option not found, add it $requiredOptions[] = $newOption; return $requiredOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_REQUIRED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The capabilities that the model must support.'], self::KEY_REQUIRED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::getJsonSchema(), 'description' => 'The options that the model must support with specific values.']], 'required' => [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelRequirementsArrayShape */ public function toArray(): array { return [self::KEY_REQUIRED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->requiredCapabilities), self::KEY_REQUIRED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\RequiredOption $option): array => $option->toArray(), $this->requiredOptions)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]); return new self(array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_REQUIRED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\RequiredOption => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::fromArray($optionData), $array[self::KEY_REQUIRED_OPTIONS])); } } src/Providers/Models/DTO/error_log 0000644 00000003650 15212525755 0013113 0 ustar 00 [11-Jun-2026 10:19:29 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23 [11-Jun-2026 11:23:37 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28 [11-Jun-2026 11:24:45 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25 [11-Jun-2026 11:24:46 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29 [11-Jun-2026 12:28:04 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51 src/Providers/Models/DTO/ModelMetadata.php 0000644 00000013770 15212525755 0014414 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\DTO; use WordPress\AiClient\Common\AbstractDataTransferObject; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; /** * Represents metadata about an AI model. * * This class contains information about a specific AI model, including * its identifier, display name, supported capabilities, and configuration options. * * @since 0.1.0 * * @phpstan-import-type SupportedOptionArrayShape from SupportedOption * * @phpstan-type ModelMetadataArrayShape array{ * id: string, * name: string, * supportedCapabilities: list<string>, * supportedOptions: list<SupportedOptionArrayShape> * } * * @extends AbstractDataTransferObject<ModelMetadataArrayShape> */ class ModelMetadata extends AbstractDataTransferObject { public const KEY_ID = 'id'; public const KEY_NAME = 'name'; public const KEY_SUPPORTED_CAPABILITIES = 'supportedCapabilities'; public const KEY_SUPPORTED_OPTIONS = 'supportedOptions'; /** * @var string The model's unique identifier. */ protected string $id; /** * @var string The model's display name. */ protected string $name; /** * @var list<CapabilityEnum> The model's supported capabilities. */ protected array $supportedCapabilities; /** * @var list<SupportedOption> The model's supported configuration options. */ protected array $supportedOptions; /** * Constructor. * * @since 0.1.0 * * @param string $id The model's unique identifier. * @param string $name The model's display name. * @param list<CapabilityEnum> $supportedCapabilities The model's supported capabilities. * @param list<SupportedOption> $supportedOptions The model's supported configuration options. * * @throws InvalidArgumentException If arrays are not lists. */ public function __construct(string $id, string $name, array $supportedCapabilities, array $supportedOptions) { if (!array_is_list($supportedCapabilities)) { throw new InvalidArgumentException('Supported capabilities must be a list array.'); } if (!array_is_list($supportedOptions)) { throw new InvalidArgumentException('Supported options must be a list array.'); } $this->id = $id; $this->name = $name; $this->supportedCapabilities = $supportedCapabilities; $this->supportedOptions = $supportedOptions; } /** * Gets the model's unique identifier. * * @since 0.1.0 * * @return string The model ID. */ public function getId(): string { return $this->id; } /** * Gets the model's display name. * * @since 0.1.0 * * @return string The model name. */ public function getName(): string { return $this->name; } /** * Gets the model's supported capabilities. * * @since 0.1.0 * * @return list<CapabilityEnum> The supported capabilities. */ public function getSupportedCapabilities(): array { return $this->supportedCapabilities; } /** * Gets the model's supported configuration options. * * @since 0.1.0 * * @return list<SupportedOption> The supported options. */ public function getSupportedOptions(): array { return $this->supportedOptions; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function getJsonSchema(): array { return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The model\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The model\'s display name.'], self::KEY_SUPPORTED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The model\'s supported capabilities.'], self::KEY_SUPPORTED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::getJsonSchema(), 'description' => 'The model\'s supported configuration options.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]]; } /** * {@inheritDoc} * * @since 0.1.0 * * @return ModelMetadataArrayShape */ public function toArray(): array { return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_SUPPORTED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->supportedCapabilities), self::KEY_SUPPORTED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\SupportedOption $option): array => $option->toArray(), $this->supportedOptions)]; } /** * {@inheritDoc} * * @since 0.1.0 */ public static function fromArray(array $array): self { static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]); return new self($array[self::KEY_ID], $array[self::KEY_NAME], array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_SUPPORTED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\SupportedOption => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::fromArray($optionData), $array[self::KEY_SUPPORTED_OPTIONS])); } /** * Performs a deep clone of the model metadata. * * This method ensures that supported option objects are cloned to prevent * modifications to the cloned metadata from affecting the original. * * @since 0.4.2 */ public function __clone() { $clonedOptions = []; foreach ($this->supportedOptions as $option) { $clonedOptions[] = clone $option; } $this->supportedOptions = $clonedOptions; } } src/Providers/Models/TextGeneration/Contracts/TextGenerationModelInterface.php 0000644 00000001340 15212525755 0023715 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support text generation. * * Provides synchronous and streaming methods for generating text from prompts. * * @since 0.1.0 */ interface TextGenerationModelInterface { /** * Generates text from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text generation prompt. * @return GenerativeAiResult Result containing generated text. */ public function generateTextResult(array $prompt): GenerativeAiResult; } src/Providers/Models/TextGeneration/Contracts/TextGenerationOperationModelInterface.php 0000644 00000001425 15212525755 0025602 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous text generation operations. * * Provides methods for initiating long-running text generation tasks. * * @since 0.1.0 */ interface TextGenerationOperationModelInterface { /** * Creates a text generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text generation prompt. * @return GenerativeAiOperation The initiated text generation operation. */ public function generateTextOperation(array $prompt): GenerativeAiOperation; } src/Providers/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionModelInterface.php 0000644 00000001374 15212525755 0027076 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support text-to-speech conversion. * * Provides synchronous methods for converting text to speech audio. * * @since 0.1.0 */ interface TextToSpeechConversionModelInterface { /** * Converts text to speech. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text to convert to speech. * @return GenerativeAiResult Result containing generated speech audio. */ public function convertTextToSpeechResult(array $prompt): GenerativeAiResult; } Providers/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionOperationModelInterface.php 0000644 00000001527 15212525755 0030700 0 ustar 00 src <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous text-to-speech conversion operations. * * Provides methods for initiating long-running text-to-speech conversion tasks. * * @since 0.1.0 */ interface TextToSpeechConversionOperationModelInterface { /** * Creates a text-to-speech conversion operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the text to convert to speech. * @return GenerativeAiOperation The initiated text-to-speech conversion operation. */ public function convertTextToSpeechOperation(array $prompt): GenerativeAiOperation; } src/Providers/Models/Contracts/ModelInterface.php 0000644 00000002357 15212525755 0016105 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Contracts; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Interface for AI models. * * Models represent specific AI models from providers and define * their capabilities, configuration, and execution methods. * * @since 0.1.0 */ interface ModelInterface { /** * Gets model metadata. * * @since 0.1.0 * * @return ModelMetadata Model metadata. */ public function metadata(): ModelMetadata; /** * Returns the metadata for the model's provider. * * @since 0.1.0 * * @return ProviderMetadata The provider metadata. */ public function providerMetadata(): ProviderMetadata; /** * Sets model configuration. * * @since 0.1.0 * * @param ModelConfig $config Model configuration. * @return void */ public function setConfig(ModelConfig $config): void; /** * Gets model configuration. * * @since 0.1.0 * * @return ModelConfig Current model configuration. */ public function getConfig(): ModelConfig; } src/Providers/Models/ImageGeneration/Contracts/ImageGenerationOperationModelInterface.php 0000644 00000001436 15212525755 0026000 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous image generation operations. * * Provides methods for initiating long-running image generation tasks. * * @since 0.1.0 */ interface ImageGenerationOperationModelInterface { /** * Creates an image generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the image generation prompt. * @return GenerativeAiOperation The initiated image generation operation. */ public function generateImageOperation(array $prompt): GenerativeAiOperation; } src/Providers/Models/ImageGeneration/Contracts/ImageGenerationModelInterface.php 0000644 00000001342 15212525755 0024113 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support image generation. * * Provides synchronous methods for generating images from text prompts. * * @since 0.1.0 */ interface ImageGenerationModelInterface { /** * Generates images from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the image generation prompt. * @return GenerativeAiResult Result containing generated images. */ public function generateImageResult(array $prompt): GenerativeAiResult; } src/Providers/Models/VideoGeneration/Contracts/VideoGenerationOperationModelInterface.php 0000644 00000001435 15212525755 0026047 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous video generation operations. * * Provides methods for initiating long-running video generation tasks. * * @since 1.3.0 */ interface VideoGenerationOperationModelInterface { /** * Creates a video generation operation. * * @since 1.3.0 * * @param list<Message> $prompt Array of messages containing the video generation prompt. * @return GenerativeAiOperation The initiated video generation operation. */ public function generateVideoOperation(array $prompt): GenerativeAiOperation; } src/Providers/Models/VideoGeneration/Contracts/VideoGenerationModelInterface.php 0000644 00000001335 15212525755 0024165 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support video generation. * * Provides synchronous methods for generating videos from prompts. * * @since 1.3.0 */ interface VideoGenerationModelInterface { /** * Generates videos from a prompt. * * @since 1.3.0 * * @param list<Message> $prompt Array of messages containing the video generation prompt. * @return GenerativeAiResult Result containing generated videos. */ public function generateVideoResult(array $prompt): GenerativeAiResult; } src/Providers/Models/SpeechGeneration/Contracts/SpeechGenerationModelInterface.php 0000644 00000001350 15212525755 0024464 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Results\DTO\GenerativeAiResult; /** * Interface for models that support speech generation. * * Provides synchronous methods for generating speech from prompts. * * @since 0.1.0 */ interface SpeechGenerationModelInterface { /** * Generates speech from a prompt. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the speech generation prompt. * @return GenerativeAiResult Result containing generated speech audio. */ public function generateSpeechResult(array $prompt): GenerativeAiResult; } src/Providers/Models/SpeechGeneration/Contracts/SpeechGenerationOperationModelInterface.php 0000644 00000001445 15212525755 0026352 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Operations\DTO\GenerativeAiOperation; /** * Interface for models that support asynchronous speech generation operations. * * Provides methods for initiating long-running speech generation tasks. * * @since 0.1.0 */ interface SpeechGenerationOperationModelInterface { /** * Creates a speech generation operation. * * @since 0.1.0 * * @param list<Message> $prompt Array of messages containing the speech generation prompt. * @return GenerativeAiOperation The initiated speech generation operation. */ public function generateSpeechOperation(array $prompt): GenerativeAiOperation; } src/Providers/Models/Enums/CapabilityEnum.php 0000644 00000005022 15212525755 0015251 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for model capabilities. * * @since 0.1.0 * * @method static self textGeneration() Creates an instance for TEXT_GENERATION capability. * @method static self imageGeneration() Creates an instance for IMAGE_GENERATION capability. * @method static self textToSpeechConversion() Creates an instance for TEXT_TO_SPEECH_CONVERSION capability. * @method static self speechGeneration() Creates an instance for SPEECH_GENERATION capability. * @method static self musicGeneration() Creates an instance for MUSIC_GENERATION capability. * @method static self videoGeneration() Creates an instance for VIDEO_GENERATION capability. * @method static self embeddingGeneration() Creates an instance for EMBEDDING_GENERATION capability. * @method static self chatHistory() Creates an instance for CHAT_HISTORY capability. * @method bool isTextGeneration() Checks if the capability is TEXT_GENERATION. * @method bool isImageGeneration() Checks if the capability is IMAGE_GENERATION. * @method bool isTextToSpeechConversion() Checks if the capability is TEXT_TO_SPEECH_CONVERSION. * @method bool isSpeechGeneration() Checks if the capability is SPEECH_GENERATION. * @method bool isMusicGeneration() Checks if the capability is MUSIC_GENERATION. * @method bool isVideoGeneration() Checks if the capability is VIDEO_GENERATION. * @method bool isEmbeddingGeneration() Checks if the capability is EMBEDDING_GENERATION. * @method bool isChatHistory() Checks if the capability is CHAT_HISTORY. */ class CapabilityEnum extends AbstractEnum { /** * Text generation capability. */ public const TEXT_GENERATION = 'text_generation'; /** * Image generation capability. */ public const IMAGE_GENERATION = 'image_generation'; /** * Text to speech conversion capability. */ public const TEXT_TO_SPEECH_CONVERSION = 'text_to_speech_conversion'; /** * Speech generation capability. */ public const SPEECH_GENERATION = 'speech_generation'; /** * Music generation capability. */ public const MUSIC_GENERATION = 'music_generation'; /** * Video generation capability. */ public const VIDEO_GENERATION = 'video_generation'; /** * Embedding generation capability. */ public const EMBEDDING_GENERATION = 'embedding_generation'; /** * Chat history support capability. */ public const CHAT_HISTORY = 'chat_history'; } src/Providers/Models/Enums/error_log 0000644 00000001364 15212525755 0013554 0 ustar 00 [11-Jun-2026 09:22:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29 [11-Jun-2026 09:24:03 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65 src/Providers/Models/Enums/OptionEnum.php 0000644 00000013451 15212525755 0014445 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Models\Enums; use ReflectionClass; use WordPress\AiClient\Common\AbstractEnum; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; /** * Enum for model options. * * This enum dynamically includes all options from ModelConfig KEY_* constants * in addition to the explicitly defined constants below. * * Explicitly defined option (not in ModelConfig): * @method static self inputModalities() Creates an instance for INPUT_MODALITIES option. * @method bool isInputModalities() Checks if the option is INPUT_MODALITIES. * * Dynamically loaded from ModelConfig KEY_* constants: * @method static self candidateCount() Creates an instance for CANDIDATE_COUNT option. * @method static self customOptions() Creates an instance for CUSTOM_OPTIONS option. * @method static self frequencyPenalty() Creates an instance for FREQUENCY_PENALTY option. * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS option. * @method static self logprobs() Creates an instance for LOGPROBS option. * @method static self maxTokens() Creates an instance for MAX_TOKENS option. * @method static self outputFileType() Creates an instance for OUTPUT_FILE_TYPE option. * @method static self outputMediaAspectRatio() Creates an instance for OUTPUT_MEDIA_ASPECT_RATIO option. * @method static self outputMediaOrientation() Creates an instance for OUTPUT_MEDIA_ORIENTATION option. * @method static self outputMimeType() Creates an instance for OUTPUT_MIME_TYPE option. * @method static self outputModalities() Creates an instance for OUTPUT_MODALITIES option. * @method static self outputSchema() Creates an instance for OUTPUT_SCHEMA option. * @method static self outputSpeechVoice() Creates an instance for OUTPUT_SPEECH_VOICE option. * @method static self presencePenalty() Creates an instance for PRESENCE_PENALTY option. * @method static self stopSequences() Creates an instance for STOP_SEQUENCES option. * @method static self systemInstruction() Creates an instance for SYSTEM_INSTRUCTION option. * @method static self temperature() Creates an instance for TEMPERATURE option. * @method static self topK() Creates an instance for TOP_K option. * @method static self topLogprobs() Creates an instance for TOP_LOGPROBS option. * @method static self topP() Creates an instance for TOP_P option. * @method static self webSearch() Creates an instance for WEB_SEARCH option. * @method bool isCandidateCount() Checks if the option is CANDIDATE_COUNT. * @method bool isCustomOptions() Checks if the option is CUSTOM_OPTIONS. * @method bool isFrequencyPenalty() Checks if the option is FREQUENCY_PENALTY. * @method bool isFunctionDeclarations() Checks if the option is FUNCTION_DECLARATIONS. * @method bool isLogprobs() Checks if the option is LOGPROBS. * @method bool isMaxTokens() Checks if the option is MAX_TOKENS. * @method bool isOutputFileType() Checks if the option is OUTPUT_FILE_TYPE. * @method bool isOutputMediaAspectRatio() Checks if the option is OUTPUT_MEDIA_ASPECT_RATIO. * @method bool isOutputMediaOrientation() Checks if the option is OUTPUT_MEDIA_ORIENTATION. * @method bool isOutputMimeType() Checks if the option is OUTPUT_MIME_TYPE. * @method bool isOutputModalities() Checks if the option is OUTPUT_MODALITIES. * @method bool isOutputSchema() Checks if the option is OUTPUT_SCHEMA. * @method bool isOutputSpeechVoice() Checks if the option is OUTPUT_SPEECH_VOICE. * @method bool isPresencePenalty() Checks if the option is PRESENCE_PENALTY. * @method bool isStopSequences() Checks if the option is STOP_SEQUENCES. * @method bool isSystemInstruction() Checks if the option is SYSTEM_INSTRUCTION. * @method bool isTemperature() Checks if the option is TEMPERATURE. * @method bool isTopK() Checks if the option is TOP_K. * @method bool isTopLogprobs() Checks if the option is TOP_LOGPROBS. * @method bool isTopP() Checks if the option is TOP_P. * @method bool isWebSearch() Checks if the option is WEB_SEARCH. * * @since 0.1.0 */ class OptionEnum extends AbstractEnum { /** * Input modalities option. * * This constant is not in ModelConfig as it's derived from message content, * not configured directly. */ public const INPUT_MODALITIES = 'input_modalities'; /** * Determines the class enumerations by reflecting on class constants. * * Overrides the parent method to dynamically add constants from ModelConfig * that are prefixed with KEY_. These are transformed to remove the KEY_ prefix * and converted to snake_case values. * * @since 0.1.0 * * @param class-string $className The fully qualified class name. * @return array<string, string> The enum constants. */ protected static function determineClassEnumerations(string $className): array { // Start with the constants defined in this class using parent method $constants = parent::determineClassEnumerations($className); // Use reflection to get all constants from ModelConfig $modelConfigReflection = new ReflectionClass(ModelConfig::class); $modelConfigConstants = $modelConfigReflection->getConstants(); // Add ModelConfig constants that start with KEY_ foreach ($modelConfigConstants as $constantName => $constantValue) { if (str_starts_with($constantName, 'KEY_')) { // Remove KEY_ prefix to get the enum constant name $enumConstantName = substr($constantName, 4); // The value is the snake_case version stored in ModelConfig // ModelConfig already stores these as snake_case strings if (is_string($constantValue)) { $constants[$enumConstantName] = $constantValue; } } } return $constants; } } src/Providers/error_log 0000644 00000003504 15212525755 0011240 0 ustar 00 [03-Jun-2026 16:43:57 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [03-Jun-2026 16:43:57 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [11-Jun-2026 06:48:54 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [11-Jun-2026 07:58:43 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 [11-Jun-2026 11:13:20 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31 [11-Jun-2026 12:19:08 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18 src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php 0000644 00000003406 15212525755 0024131 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use Exception; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; /** * Class to check availability for an API-based provider via a test request to the endpoint to list models. * * This class should be used for cloud-based providers that offer a model listing endpoint which requires * authentication. A request to this endpoint is used to determine if the provider is properly configured * with valid credentials. * * @since 0.1.0 */ class ListModelsApiBasedProviderAvailability implements ProviderAvailabilityInterface { /** * @var ModelMetadataDirectoryInterface The model metadata directory to use for checking availability. */ private ModelMetadataDirectoryInterface $modelMetadataDirectory; /** * Constructor. * * @since 0.1.0 * * @param ModelMetadataDirectoryInterface $modelMetadataDirectory The model metadata directory to use for checking * availability. */ public function __construct(ModelMetadataDirectoryInterface $modelMetadataDirectory) { $this->modelMetadataDirectory = $modelMetadataDirectory; } /** * {@inheritDoc} * * @since 0.1.0 */ public function isConfigured(): bool { try { // Attempt to list models to check if the provider is available. $this->modelMetadataDirectory->listModelMetadata(); return \true; } catch (Exception $e) { // If an exception occurs, the provider is not available. return \false; } } } src/Providers/ApiBasedImplementation/AbstractApiProvider.php 0000644 00000003001 15212525755 0020312 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\Providers\AbstractProvider; /** * Base class for API-based providers. * * This abstract class provides URL construction utilities for providers that * communicate with REST APIs. It standardizes the pattern of combining a base * URL with endpoint paths. * * @since 0.2.0 */ abstract class AbstractApiProvider extends AbstractProvider { /** * Gets the base URL for the provider's API. * * The base URL should include the protocol and domain, and may include * the API version path (e.g., "https://api.example.com/v1"). * * @since 0.2.0 * * @return string The base URL for the provider's API. */ abstract protected static function baseUrl(): string; /** * Constructs a full URL by combining the base URL with an optional path. * * This method ensures proper URL construction by: * - Using the provider's base URL * - Trimming leading slashes from the path to prevent double-slashes * - Joining the base URL and path with a single forward slash * * @since 0.2.0 * * @param string $path Optional path to append to the base URL. Default empty string. * @return string The complete URL. */ public static function url(string $path = ''): string { if ($path === '') { return static::baseUrl(); } return static::baseUrl() . '/' . ltrim($path, '/'); } } src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php 0000644 00000002021 15212525755 0022615 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation\Contracts; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; /** * Interface for API-based AI models that support HTTP transport configuration. * * This interface extends ModelInterface to add request options support * for models that communicate with external APIs via HTTP. * * @since 0.3.0 */ interface ApiBasedModelInterface extends ModelInterface { /** * Sets the request options for HTTP transport. * * @since 0.3.0 * * @param RequestOptions $requestOptions The request options to use. * @return void */ public function setRequestOptions(RequestOptions $requestOptions): void; /** * Gets the request options for HTTP transport. * * @since 0.3.0 * * @return RequestOptions|null The request options, or null if not set. */ public function getRequestOptions(): ?RequestOptions; } src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php 0000644 00000006365 15212525755 0024065 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\AiClient; use WordPress\AiClient\Common\Contracts\CachesDataInterface; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Traits\WithDataCachingTrait; use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for an API-based model metadata directory for a provider. * * @since 0.1.0 */ abstract class AbstractApiBasedModelMetadataDirectory implements ModelMetadataDirectoryInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface, CachesDataInterface { use WithHttpTransporterTrait; use WithRequestAuthenticationTrait; use WithDataCachingTrait; /** * The cache key suffix for the models list. * * @since 0.4.0 * * @var string */ private const MODELS_CACHE_KEY = 'models'; /** * {@inheritDoc} * * @since 0.1.0 */ final public function listModelMetadata(): array { $modelsMetadata = $this->getModelMetadataMap(); return array_values($modelsMetadata); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function hasModelMetadata(string $modelId): bool { $modelsMetadata = $this->getModelMetadataMap(); return isset($modelsMetadata[$modelId]); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function getModelMetadata(string $modelId): ModelMetadata { $modelsMetadata = $this->getModelMetadataMap(); if (!isset($modelsMetadata[$modelId])) { throw new InvalidArgumentException(sprintf('No model with ID %s was found in the provider', $modelId)); } return $modelsMetadata[$modelId]; } /** * Returns the map of model ID to model metadata for all models from the provider. * * @since 0.1.0 * * @return array<string, ModelMetadata> Map of model ID to model metadata. */ private function getModelMetadataMap(): array { /** @var array<string, ModelMetadata> */ return $this->cached(self::MODELS_CACHE_KEY, fn() => $this->sendListModelsRequest(), 86400); } /** * {@inheritDoc} * * @since 0.4.0 */ protected function getCachedKeys(): array { return [self::MODELS_CACHE_KEY]; } /** * {@inheritDoc} * * @since 0.4.0 */ protected function getBaseCacheKey(): string { return 'ai_client_' . AiClient::VERSION . '_' . md5(static::class); } /** * Sends the API request to list models from the provider and returns the map of model ID to model metadata. * * @since 0.1.0 * * @return array<string, ModelMetadata> Map of model ID to model metadata. */ abstract protected function sendListModelsRequest(): array; } src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php 0000644 00000004500 15212525755 0024445 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use Exception; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface; use WordPress\AiClient\Providers\Models\Contracts\ModelInterface; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; /** * Class to check availability for an API-based provider via a test request to the endpoint to generate text. * * This class should be used for cloud-based providers that do not offer a model listing endpoint, but do offer a * text generation endpoint which requires authentication. A minimal request to this endpoint is used to determine * if the provider is properly configured with valid credentials. * * @since 0.1.0 */ class GenerateTextApiBasedProviderAvailability implements ProviderAvailabilityInterface { /** * @var ModelInterface&TextGenerationModelInterface The model to use for checking availability. */ private ModelInterface $model; /** * Constructor. * * @since 0.1.0 * * @param ModelInterface $model The model to use for checking availability. */ public function __construct(ModelInterface $model) { if (!$model instanceof TextGenerationModelInterface) { throw new Exception('The model class to check provider availability must implement TextGenerationModelInterface.'); } $this->model = $model; } /** * {@inheritDoc} * * @since 0.1.0 */ public function isConfigured(): bool { // Set config to use as few resources as possible for the test. $modelConfig = ModelConfig::fromArray([ModelConfig::KEY_MAX_TOKENS => 1]); $this->model->setConfig($modelConfig); try { // Attempt to generate text to check if the provider is available. $this->model->generateTextResult([new Message(MessageRoleEnum::user(), [new MessagePart('a')])]); return \true; } catch (Exception $e) { // If an exception occurs, the provider is not available. return \false; } } } src/Providers/ApiBasedImplementation/error_log 0000644 00000007450 15212525755 0015622 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [11-Jun-2026 08:07:58 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23 [11-Jun-2026 08:08:09 UTC] PHP Fatal error: Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21 [11-Jun-2026 09:20:14 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16 [11-Jun-2026 09:20:15 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23 [11-Jun-2026 09:20:34 UTC] PHP Fatal error: Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18 src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php 0000644 00000006231 15212525755 0020527 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\ApiBasedImplementation; use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface; use WordPress\AiClient\Providers\DTO\ProviderMetadata; use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface; use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface; use WordPress\AiClient\Providers\Http\DTO\RequestOptions; use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait; use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait; use WordPress\AiClient\Providers\Models\DTO\ModelConfig; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for an API-based model for a provider. * * While this class contains no abstract methods, it is still abstract to ensure that each model class can actually * perform generative AI tasks by implementing the corresponding interfaces. * * @since 0.1.0 */ abstract class AbstractApiBasedModel implements ApiBasedModelInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface { use WithHttpTransporterTrait; use WithRequestAuthenticationTrait; /** * @var ModelMetadata The metadata for the model. */ private ModelMetadata $metadata; /** * @var ProviderMetadata The metadata for the model's provider. */ private ProviderMetadata $providerMetadata; /** * @var ModelConfig The configuration for the model. */ private ModelConfig $config; /** * @var RequestOptions|null The request options for HTTP transport. */ private ?RequestOptions $requestOptions = null; /** * Constructor. * * @since 0.1.0 * * @param ModelMetadata $metadata The metadata for the model. * @param ProviderMetadata $providerMetadata The metadata for the model's provider. */ public function __construct(ModelMetadata $metadata, ProviderMetadata $providerMetadata) { $this->metadata = $metadata; $this->providerMetadata = $providerMetadata; $this->config = ModelConfig::fromArray([]); } /** * {@inheritDoc} * * @since 0.1.0 */ final public function metadata(): ModelMetadata { return $this->metadata; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function providerMetadata(): ProviderMetadata { return $this->providerMetadata; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function setConfig(ModelConfig $config): void { $this->config = $config; } /** * {@inheritDoc} * * @since 0.1.0 */ final public function getConfig(): ModelConfig { return $this->config; } /** * {@inheritDoc} * * @since 0.3.0 */ final public function setRequestOptions(RequestOptions $requestOptions): void { $this->requestOptions = $requestOptions; } /** * {@inheritDoc} * * @since 0.3.0 */ final public function getRequestOptions(): ?RequestOptions { return $this->requestOptions; } } src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php 0000644 00000006373 15212525755 0027272 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\DTO\ModelMetadata; /** * Base class for a model metadata directory for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * models listing endpoint, including but not limited to Anthropic, Google, and other * providers that have adopted OpenAI's models API specification as a standard interface. * * @since 0.1.0 */ abstract class AbstractOpenAiCompatibleModelMetadataDirectory extends AbstractApiBasedModelMetadataDirectory { /** * {@inheritDoc} * * @since 0.1.0 */ protected function sendListModelsRequest(): array { $httpTransporter = $this->getHttpTransporter(); $request = $this->createRequest(HttpMethodEnum::GET(), 'models'); $request = $this->getRequestAuthentication()->authenticateRequest($request); $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); $modelsMetadataList = $this->parseResponseToModelMetadataList($response); $modelMetadataMap = []; foreach ($modelsMetadataList as $modelMetadata) { $modelMetadataMap[$modelMetadata->getId()] = $modelMetadata; } return $modelMetadataMap; } /** * Creates a request object for the provider's API. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to list models into a list of model metadata objects. * * @since 0.1.0 * * @param Response $response The response from the API endpoint to list models. * @return list<ModelMetadata> List of model metadata objects. */ abstract protected function parseResponseToModelMetadataList(Response $response): array; } src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php 0000644 00000061237 15212525755 0026625 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Messages\Enums\ModalityEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface; use WordPress\AiClient\Results\DTO\Candidate; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Results\DTO\TokenUsage; use WordPress\AiClient\Results\Enums\FinishReasonEnum; use WordPress\AiClient\Tools\DTO\FunctionCall; use WordPress\AiClient\Tools\DTO\FunctionDeclaration; /** * Base class for a text generation model for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * API endpoint, including but not limited to Anthropic, Google, and other providers * that have adopted OpenAI's API specification as a standard interface. * * @since 0.1.0 * * @phpstan-type ToolCallData array{ * type?: string, * id?: string, * function?: array{ * name?: string, * arguments: string|array<string, mixed> * } * } * @phpstan-type MessageData array{ * role?: string, * reasoning_content?: string, * content?: string, * tool_calls?: list<ToolCallData> * } * @phpstan-type ChoiceData array{ * message?: MessageData, * finish_reason?: string * } * @phpstan-type UsageData array{ * prompt_tokens?: int, * completion_tokens?: int, * total_tokens?: int * } * @phpstan-type ResponseData array{ * id?: string, * choices?: list<ChoiceData>, * usage?: UsageData * } */ abstract class AbstractOpenAiCompatibleTextGenerationModel extends AbstractApiBasedModel implements TextGenerationModelInterface { /** * {@inheritDoc} * * @since 0.1.0 */ final public function generateTextResult(array $prompt): GenerativeAiResult { $httpTransporter = $this->getHttpTransporter(); $params = $this->prepareGenerateTextParams($prompt); $request = $this->createRequest(HttpMethodEnum::POST(), 'chat/completions', ['Content-Type' => 'application/json'], $params); // Add authentication credentials to the request. $request = $this->getRequestAuthentication()->authenticateRequest($request); // Send and process the request. $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); return $this->parseResponseToGenerativeAiResult($response); } /** * Prepares the given prompt and the model configuration into parameters for the API request. * * @since 0.1.0 * * @param list<Message> $prompt The prompt to generate text for. Either a single message or a list of messages * from a chat. * @return array<string, mixed> The parameters for the API request. */ protected function prepareGenerateTextParams(array $prompt): array { $config = $this->getConfig(); $params = ['model' => $this->metadata()->getId(), 'messages' => $this->prepareMessagesParam($prompt, $config->getSystemInstruction())]; $outputModalities = $config->getOutputModalities(); if (is_array($outputModalities)) { $this->validateOutputModalities($outputModalities); if (count($outputModalities) > 1) { $params['modalities'] = $this->prepareOutputModalitiesParam($outputModalities); } } $candidateCount = $config->getCandidateCount(); if ($candidateCount !== null) { $params['n'] = $candidateCount; } $maxTokens = $config->getMaxTokens(); if ($maxTokens !== null) { $params['max_tokens'] = $maxTokens; } $temperature = $config->getTemperature(); if ($temperature !== null) { $params['temperature'] = $temperature; } $topP = $config->getTopP(); if ($topP !== null) { $params['top_p'] = $topP; } $stopSequences = $config->getStopSequences(); if (is_array($stopSequences)) { $params['stop'] = $stopSequences; } $presencePenalty = $config->getPresencePenalty(); if ($presencePenalty !== null) { $params['presence_penalty'] = $presencePenalty; } $frequencyPenalty = $config->getFrequencyPenalty(); if ($frequencyPenalty !== null) { $params['frequency_penalty'] = $frequencyPenalty; } $logprobs = $config->getLogprobs(); if ($logprobs !== null) { $params['logprobs'] = $logprobs; } $topLogprobs = $config->getTopLogprobs(); if ($topLogprobs !== null) { $params['top_logprobs'] = $topLogprobs; } $functionDeclarations = $config->getFunctionDeclarations(); if (is_array($functionDeclarations)) { $params['tools'] = $this->prepareToolsParam($functionDeclarations); } $outputMimeType = $config->getOutputMimeType(); if ('application/json' === $outputMimeType) { $outputSchema = $config->getOutputSchema(); $params['response_format'] = $this->prepareResponseFormatParam($outputSchema); } /* * Any custom options are added to the parameters as well. * This allows developers to pass other options that may be more niche or not yet supported by the SDK. */ $customOptions = $config->getCustomOptions(); foreach ($customOptions as $key => $value) { if (isset($params[$key])) { throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key)); } $params[$key] = $value; } return $params; } /** * Prepares the messages parameter for the API request. * * @since 0.1.0 * * @param list<Message> $messages The messages to prepare. * @param string|null $systemInstruction An optional system instruction to prepend to the messages. * @return list<array<string, mixed>> The prepared messages parameter. */ protected function prepareMessagesParam(array $messages, ?string $systemInstruction = null): array { $messagesParam = array_map(function (Message $message): array { // Special case: Function response. $messageParts = $message->getParts(); if (count($messageParts) === 1 && $messageParts[0]->getType()->isFunctionResponse()) { $functionResponse = $messageParts[0]->getFunctionResponse(); if (!$functionResponse) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The function response typed message part must contain a function response.'); } return ['role' => 'tool', 'content' => json_encode($functionResponse->getResponse()), 'tool_call_id' => $functionResponse->getId()]; } $messageData = ['role' => $this->getMessageRoleString($message->getRole()), 'content' => array_values(array_filter(array_map([$this, 'getMessagePartContentData'], $messageParts)))]; // Only include tool_calls if there are any (OpenAI rejects empty arrays). $toolCalls = array_values(array_filter(array_map([$this, 'getMessagePartToolCallData'], $messageParts))); if (!empty($toolCalls)) { $messageData['tool_calls'] = $toolCalls; } return $messageData; }, $messages); if ($systemInstruction) { array_unshift($messagesParam, [ /* * TODO: Replace this with 'developer' in the future. * See https://platform.openai.com/docs/api-reference/chat/create#chat_create-messages */ 'role' => 'system', 'content' => [['type' => 'text', 'text' => $systemInstruction]], ]); } return $messagesParam; } /** * Returns the OpenAI API specific role string for the given message role. * * @since 0.1.0 * * @param MessageRoleEnum $role The message role. * @return string The role for the API request. */ protected function getMessageRoleString(MessageRoleEnum $role): string { if ($role === MessageRoleEnum::model()) { return 'assistant'; } return 'user'; } /** * Returns the OpenAI API specific content data for a message part. * * @since 0.1.0 * * @param MessagePart $part The message part to get the data for. * @return ?array<string, mixed> The data for the message content part, or null if not applicable. * @throws InvalidArgumentException If the message part type or data is unsupported. */ protected function getMessagePartContentData(MessagePart $part): ?array { $type = $part->getType(); if ($type->isText()) { /* * The OpenAI Chat Completions API spec does not support annotating thought parts as input, * so we instead skip them. */ if ($part->getChannel()->isThought()) { return null; } return ['type' => 'text', 'text' => $part->getText()]; } if ($type->isFile()) { $file = $part->getFile(); if (!$file) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The file typed message part must contain a file.'); } if ($file->isRemote()) { if ($file->isImage()) { return ['type' => 'image_url', 'image_url' => ['url' => $file->getUrl()]]; } throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for remote file message part.', $file->getMimeType())); } // Else, it is an inline file. if ($file->isImage()) { return ['type' => 'image_url', 'image_url' => ['url' => $file->getDataUri()]]; } if ($file->isAudio()) { return ['type' => 'input_audio', 'input_audio' => ['data' => $file->getBase64Data(), 'format' => $file->getMimeTypeObject()->toExtension()]]; } throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for inline file message part.', $file->getMimeType())); } if ($type->isFunctionCall()) { // Skip, as this is separately included. See `getMessagePartToolCallData()`. return null; } if ($type->isFunctionResponse()) { // Special case: Function response. throw new InvalidArgumentException('The API only allows a single function response, as the only content of the message.'); } throw new InvalidArgumentException(sprintf('Unsupported message part type "%s".', $type)); } /** * Returns the OpenAI API specific tool calls data for a message part. * * @since 0.1.0 * * @param MessagePart $part The message part to get the data for. * @return ?array<string, mixed> The data for the message tool call part, or null if not applicable. * @throws InvalidArgumentException If the message part type or data is unsupported. */ protected function getMessagePartToolCallData(MessagePart $part): ?array { $type = $part->getType(); if ($type->isFunctionCall()) { $functionCall = $part->getFunctionCall(); if (!$functionCall) { // This should be impossible due to class internals, but still needs to be checked. throw new RuntimeException('The function call typed message part must contain a function call.'); } $args = $functionCall->getArgs(); /* * Ensure null or empty arrays become empty objects for JSON encoding. * While in theory the JSON schema could also dictate a type of * 'array', in practice function arguments are typically of type * 'object'. More importantly, the OpenAI API specification seems * to expect that, and does not support passing arrays as the root * value. The null check handles the case where FunctionCall normalizes * empty arrays to null. */ if ($args === null || is_array($args) && count($args) === 0) { $args = new \stdClass(); } return ['type' => 'function', 'id' => $functionCall->getId(), 'function' => ['name' => $functionCall->getName(), 'arguments' => json_encode($args)]]; } // All other types are handled in `getMessagePartContentData()`. return null; } /** * Validates that the given output modalities to ensure that at least one output modality is text. * * @since 0.1.0 * * @param array<ModalityEnum> $outputModalities The output modalities to validate. * @throws InvalidArgumentException If no text output modality is present. */ protected function validateOutputModalities(array $outputModalities): void { // If no output modalities are set, it's fine, as we can assume text. if (count($outputModalities) === 0) { return; } foreach ($outputModalities as $modality) { if ($modality->isText()) { return; } } throw new InvalidArgumentException('A text output modality must be present when generating text.'); } /** * Prepares the output modalities parameter for the API request. * * @since 0.1.0 * * @param array<ModalityEnum> $modalities The modalities to prepare. * @return list<string> The prepared modalities parameter. */ protected function prepareOutputModalitiesParam(array $modalities): array { $prepared = []; foreach ($modalities as $modality) { if ($modality->isText()) { $prepared[] = 'text'; } elseif ($modality->isImage()) { $prepared[] = 'image'; } elseif ($modality->isAudio()) { $prepared[] = 'audio'; } else { throw new InvalidArgumentException(sprintf('Unsupported output modality "%s".', $modality)); } } return $prepared; } /** * Prepares the tools parameter for the API request. * * @since 0.1.0 * * @param list<FunctionDeclaration> $functionDeclarations The function declarations. * @return list<array<string, mixed>> The prepared tools parameter. */ protected function prepareToolsParam(array $functionDeclarations): array { $tools = []; foreach ($functionDeclarations as $functionDeclaration) { $tools[] = ['type' => 'function', 'function' => $functionDeclaration->toArray()]; } return $tools; } /** * Prepares the response format parameter for the API request. * * This is only called if the output MIME type is `application/json`. * * @since 0.1.0 * * @param array<string, mixed>|null $outputSchema The output schema. * @return array<string, mixed> The prepared response format parameter. */ protected function prepareResponseFormatParam(?array $outputSchema): array { if (is_array($outputSchema)) { return ['type' => 'json_schema', 'json_schema' => $outputSchema]; } return ['type' => 'json_object']; } /** * Creates a request object for the provider's API. * * Implementations should use $this->getRequestOptions() to attach any * configured request options to the Request. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to a generative AI result. * * @since 0.1.0 * * @param Response $response The response from the API endpoint. * @return GenerativeAiResult The parsed generative AI result. */ protected function parseResponseToGenerativeAiResult(Response $response): GenerativeAiResult { /** @var ResponseData $responseData */ $responseData = $response->getData(); if (!isset($responseData['choices']) || !$responseData['choices']) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'choices'); } if (!is_array($responseData['choices'])) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'choices', 'The value must be an array.'); } $candidates = []; foreach ($responseData['choices'] as $index => $choiceData) { if (!is_array($choiceData) || array_is_list($choiceData)) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must be an associative array.'); } $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index); } $id = isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : ''; if (isset($responseData['usage']) && is_array($responseData['usage'])) { $usage = $responseData['usage']; $tokenUsage = new TokenUsage($usage['prompt_tokens'] ?? 0, $usage['completion_tokens'] ?? 0, $usage['total_tokens'] ?? 0); } else { $tokenUsage = new TokenUsage(0, 0, 0); } // Use any other data from the response as provider-specific response metadata. $additionalData = $responseData; unset($additionalData['id'], $additionalData['choices'], $additionalData['usage']); return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $additionalData); } /** * Parses a single choice from the API response into a Candidate object. * * @since 0.1.0 * * @param ChoiceData $choiceData The choice data from the API response. * @param int $index The index of the choice in the choices array. * @return Candidate The parsed candidate. * @throws RuntimeException If the choice data is invalid. */ protected function parseResponseChoiceToCandidate(array $choiceData, int $index): Candidate { if (!isset($choiceData['message']) || !is_array($choiceData['message']) || array_is_list($choiceData['message'])) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].message"); } if (!isset($choiceData['finish_reason']) || !is_string($choiceData['finish_reason'])) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason"); } $messageData = $choiceData['message']; $message = $this->parseResponseChoiceMessage($messageData, $index); switch ($choiceData['finish_reason']) { case 'stop': $finishReason = FinishReasonEnum::stop(); break; case 'length': $finishReason = FinishReasonEnum::length(); break; case 'content_filter': $finishReason = FinishReasonEnum::contentFilter(); break; case 'tool_calls': $finishReason = FinishReasonEnum::toolCalls(); break; default: throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason", sprintf('Invalid finish reason "%s".', $choiceData['finish_reason'])); } return new Candidate($message, $finishReason); } /** * Parses the message from a choice in the API response. * * @since 0.1.0 * * @param MessageData $messageData The message data from the API response. * @param int $index The index of the choice in the choices array. * @return Message The parsed message. */ protected function parseResponseChoiceMessage(array $messageData, int $index): Message { $role = isset($messageData['role']) && 'user' === $messageData['role'] ? MessageRoleEnum::user() : MessageRoleEnum::model(); $parts = $this->parseResponseChoiceMessageParts($messageData, $index); return new Message($role, $parts); } /** * Parses the message parts from a choice in the API response. * * @since 0.1.0 * * @param MessageData $messageData The message data from the API response. * @param int $index The index of the choice in the choices array. * @return MessagePart[] The parsed message parts. */ protected function parseResponseChoiceMessageParts(array $messageData, int $index): array { $parts = []; if (isset($messageData['reasoning_content']) && is_string($messageData['reasoning_content'])) { $parts[] = new MessagePart($messageData['reasoning_content'], MessagePartChannelEnum::thought()); } if (isset($messageData['content']) && is_string($messageData['content'])) { $parts[] = new MessagePart($messageData['content']); } if (isset($messageData['tool_calls']) && is_array($messageData['tool_calls'])) { foreach ($messageData['tool_calls'] as $toolCallIndex => $toolCallData) { $toolCallPart = $this->parseResponseChoiceMessageToolCallPart($toolCallData); if (!$toolCallPart) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].message.tool_calls[{$toolCallIndex}]", 'The response includes a tool call of an unexpected type.'); } $parts[] = $toolCallPart; } } return $parts; } /** * Parses a tool call part from the API response. * * @since 0.1.0 * * @param ToolCallData $toolCallData The tool call data from the API response. * @return MessagePart|null The parsed message part for the tool call, or null if not applicable. */ protected function parseResponseChoiceMessageToolCallPart(array $toolCallData): ?MessagePart { /* * For now, only function calls are supported. * * Not all OpenAI compatible APIs include a 'type' key, so we only check its value if it is set. */ if (isset($toolCallData['type']) && 'function' !== $toolCallData['type'] || !isset($toolCallData['function']) || !is_array($toolCallData['function'])) { return null; } $functionArguments = is_string($toolCallData['function']['arguments']) ? json_decode($toolCallData['function']['arguments'], \true) : $toolCallData['function']['arguments']; $functionCall = new FunctionCall(isset($toolCallData['id']) && is_string($toolCallData['id']) ? $toolCallData['id'] : null, isset($toolCallData['function']['name']) && is_string($toolCallData['function']['name']) ? $toolCallData['function']['name'] : null, $functionArguments); return new MessagePart($functionCall); } } src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php 0000644 00000031733 15212525755 0026721 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation; use WordPress\AiClient\Common\Exception\InvalidArgumentException; use WordPress\AiClient\Common\Exception\RuntimeException; use WordPress\AiClient\Files\DTO\File; use WordPress\AiClient\Files\Enums\MediaOrientationEnum; use WordPress\AiClient\Messages\DTO\Message; use WordPress\AiClient\Messages\DTO\MessagePart; use WordPress\AiClient\Messages\Enums\MessageRoleEnum; use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel; use WordPress\AiClient\Providers\Http\DTO\Request; use WordPress\AiClient\Providers\Http\DTO\Response; use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum; use WordPress\AiClient\Providers\Http\Exception\ResponseException; use WordPress\AiClient\Providers\Http\Util\ResponseUtil; use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface; use WordPress\AiClient\Results\DTO\Candidate; use WordPress\AiClient\Results\DTO\GenerativeAiResult; use WordPress\AiClient\Results\DTO\TokenUsage; use WordPress\AiClient\Results\Enums\FinishReasonEnum; /** * Base class for an image generation model for providers that implement OpenAI's API format. * * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible * API endpoint for image generation, including but not limited to Anthropic, Google, and other * providers that have adopted OpenAI's image generation API specification as a standard interface. * * @since 0.1.0 * * @phpstan-type ImageGenerationParams array{ * model: string, * prompt: string, * n?: int, * response_format?: string, * output_format?: string|null, * size?: string, * ... * } * @phpstan-type ChoiceData array{ * url?: string, * b64_json?: string * } * @phpstan-type UsageData array{ * input_tokens?: int, * output_tokens?: int, * total_tokens?: int * } * @phpstan-type ResponseData array{ * id?: string, * data?: list<ChoiceData>, * usage?: UsageData * } */ abstract class AbstractOpenAiCompatibleImageGenerationModel extends AbstractApiBasedModel implements ImageGenerationModelInterface { /** * {@inheritDoc} * * @since 0.1.0 */ public function generateImageResult(array $prompt): GenerativeAiResult { $httpTransporter = $this->getHttpTransporter(); $params = $this->prepareGenerateImageParams($prompt); $request = $this->createRequest(HttpMethodEnum::POST(), 'images/generations', ['Content-Type' => 'application/json'], $params); // Add authentication credentials to the request. $request = $this->getRequestAuthentication()->authenticateRequest($request); // Send and process the request. $response = $httpTransporter->send($request); $this->throwIfNotSuccessful($response); return $this->parseResponseToGenerativeAiResult($response, isset($params['output_format']) && is_string($params['output_format']) ? "image/{$params['output_format']}" : 'image/png'); } /** * Prepares the given prompt and the model configuration into parameters for the API request. * * @since 0.1.0 * * @param list<Message> $prompt The prompt to generate an image for. Either a single message or a list of messages * from a chat. However as of today, OpenAI compatible image generation endpoints only * support a single user message. * @return ImageGenerationParams The parameters for the API request. */ protected function prepareGenerateImageParams(array $prompt): array { $config = $this->getConfig(); $params = ['model' => $this->metadata()->getId(), 'prompt' => $this->preparePromptParam($prompt)]; $candidateCount = $config->getCandidateCount(); if ($candidateCount !== null) { $params['n'] = $candidateCount; } $outputFileType = $config->getOutputFileType(); if ($outputFileType !== null) { $params['response_format'] = $outputFileType->isRemote() ? 'url' : 'b64_json'; } else { // The 'response_format' parameter is required, so we default to 'b64_json' if not set. $params['response_format'] = 'b64_json'; } $outputMimeType = $config->getOutputMimeType(); if ($outputMimeType !== null) { $params['output_format'] = preg_replace('/^image\//', '', $outputMimeType); } $outputMediaOrientation = $config->getOutputMediaOrientation(); $outputMediaAspectRatio = $config->getOutputMediaAspectRatio(); if ($outputMediaOrientation !== null || $outputMediaAspectRatio !== null) { $params['size'] = $this->prepareSizeParam($outputMediaOrientation, $outputMediaAspectRatio); } /* * Any custom options are added to the parameters as well. * This allows developers to pass other options that may be more niche or not yet supported by the SDK. */ $customOptions = $config->getCustomOptions(); foreach ($customOptions as $key => $value) { if (isset($params[$key])) { throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key)); } $params[$key] = $value; } /** @var ImageGenerationParams $params */ return $params; } /** * Prepares the prompt parameter for the API request. * * @since 0.1.0 * * @param list<Message> $messages The messages to prepare. However as of today, OpenAI compatible image generation * endpoints only support a single user message. * @return string The prepared prompt parameter. */ protected function preparePromptParam(array $messages): string { if (count($messages) !== 1) { throw new InvalidArgumentException('The API requires a single user message as prompt.'); } $message = $messages[0]; if (!$message->getRole()->isUser()) { throw new InvalidArgumentException('The API requires a user message as prompt.'); } $text = null; foreach ($message->getParts() as $part) { $text = $part->getText(); if ($text !== null) { break; } } if ($text === null) { throw new InvalidArgumentException('The API requires a single text message part as prompt.'); } return $text; } /** * Prepares the size parameter for the API request. * * @since 0.1.0 * * @param MediaOrientationEnum|null $orientation The desired media orientation. * @param string|null $aspectRatio The desired media aspect ratio. * @return string The prepared size parameter. */ protected function prepareSizeParam(?MediaOrientationEnum $orientation, ?string $aspectRatio): string { // Use aspect ratio if set, as it is more specific. if ($aspectRatio !== null) { switch ($aspectRatio) { case '1:1': return '1024x1024'; case '3:2': return '1536x1024'; case '7:4': return '1792x1024'; case '2:3': return '1024x1536'; case '4:7': return '1024x1792'; default: throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not supported.'); } } // This should always have a value, as the method is only called if at least one or the other is set. if ($orientation !== null) { if ($orientation->isLandscape()) { return '1536x1024'; } if ($orientation->isPortrait()) { return '1024x1536'; } } return '1024x1024'; } /** * Creates a request object for the provider's API. * * Implementations should use $this->getRequestOptions() to attach any * configured request options to the Request. * * @since 0.1.0 * * @param HttpMethodEnum $method The HTTP method. * @param string $path The API endpoint path, relative to the base URI. * @param array<string, string|list<string>> $headers The request headers. * @param string|array<string, mixed>|null $data The request data. * @return Request The request object. */ abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request; /** * Throws an exception if the response is not successful. * * @since 0.1.0 * * @param Response $response The HTTP response to check. * @throws ResponseException If the response is not successful. */ protected function throwIfNotSuccessful(Response $response): void { /* * While this method only calls the utility method, it's important to have it here as a protected method so * that child classes can override it if needed. */ ResponseUtil::throwIfNotSuccessful($response); } /** * Parses the response from the API endpoint to a generative AI result. * * @since 0.1.0 * * @param Response $response The response from the API endpoint. * @param string $expectedMimeType The expected MIME type the response is in. * @return GenerativeAiResult The parsed generative AI result. */ protected function parseResponseToGenerativeAiResult(Response $response, string $expectedMimeType = 'image/png'): GenerativeAiResult { /** @var ResponseData $responseData */ $responseData = $response->getData(); if (!isset($responseData['data']) || !$responseData['data']) { throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'data'); } if (!is_array($responseData['data'])) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'data', 'The value must be an array.'); } $candidates = []; foreach ($responseData['data'] as $index => $choiceData) { if (!is_array($choiceData) || array_is_list($choiceData)) { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "data[{$index}]", 'The value must be an associative array.'); } $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index, $expectedMimeType); } $id = $this->getResultId($responseData); if (isset($responseData['usage']) && is_array($responseData['usage'])) { $usage = $responseData['usage']; $tokenUsage = new TokenUsage($usage['input_tokens'] ?? 0, $usage['output_tokens'] ?? 0, $usage['total_tokens'] ?? 0); } else { $tokenUsage = new TokenUsage(0, 0, 0); } // Use any other data from the response as provider-specific response metadata. $providerMetadata = $responseData; unset($providerMetadata['id'], $providerMetadata['data'], $providerMetadata['usage']); return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $providerMetadata); } /** * Parses a single choice from the API response into a Candidate object. * * @since 0.1.0 * * @param ChoiceData $choiceData The choice data from the API response. * @param int $index The index of the choice in the choices array. * @param string $expectedMimeType The expected MIME type the response is in. * @return Candidate The parsed candidate. * @throws RuntimeException If the choice data is invalid. */ protected function parseResponseChoiceToCandidate(array $choiceData, int $index, string $expectedMimeType = 'image/png'): Candidate { if (isset($choiceData['url']) && is_string($choiceData['url'])) { $imageFile = new File($choiceData['url'], $expectedMimeType); } elseif (isset($choiceData['b64_json']) && is_string($choiceData['b64_json'])) { $imageFile = new File($choiceData['b64_json'], $expectedMimeType); } else { throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must contain either a url or b64_json key with a string value.'); } $parts = [new MessagePart($imageFile)]; $message = new Message(MessageRoleEnum::model(), $parts); return new Candidate($message, FinishReasonEnum::stop()); } /** * Extracts the result ID from the API response data. * * @since 0.4.0 * * @param array<string, mixed> $responseData The response data from the API. * @return string The result ID. */ protected function getResultId(array $responseData): string { return isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : ''; } } src/Providers/OpenAiCompatibleImplementation/error_log 0000644 00000006054 15212525755 0017324 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [11-Jun-2026 09:00:12 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57 [11-Jun-2026 09:00:57 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64 [11-Jun-2026 09:01:17 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22 src/Providers/Enums/ToolTypeEnum.php 0000644 00000001365 15212525755 0013532 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for tool types. * * @since 0.1.0 * * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS type. * @method static self webSearch() Creates an instance for WEB_SEARCH type. * @method bool isFunctionDeclarations() Checks if the type is FUNCTION_DECLARATIONS. * @method bool isWebSearch() Checks if the type is WEB_SEARCH. */ class ToolTypeEnum extends AbstractEnum { /** * Function declarations tool type. */ public const FUNCTION_DECLARATIONS = 'function_declarations'; /** * Web search tool type. */ public const WEB_SEARCH = 'web_search'; } src/Providers/Enums/ProviderTypeEnum.php 0000644 00000001673 15212525755 0014411 0 ustar 00 <?php declare (strict_types=1); namespace WordPress\AiClient\Providers\Enums; use WordPress\AiClient\Common\AbstractEnum; /** * Enum for provider types. * * @since 0.1.0 * * @method static self cloud() Creates an instance for CLOUD type. * @method static self server() Creates an instance for SERVER type. * @method static self client() Creates an instance for CLIENT type. * @method bool isCloud() Checks if the type is CLOUD. * @method bool isServer() Checks if the type is SERVER. * @method bool isClient() Checks if the type is CLIENT. */ class ProviderTypeEnum extends AbstractEnum { /** * Cloud-based AI provider (e.g. models available via external REST APIs). */ public const CLOUD = 'cloud'; /** * Server-side AI provider (e.g. self-hosted models). */ public const SERVER = 'server'; /** * Client-side AI provider (e.g. browser-based models). */ public const CLIENT = 'client'; } src/Providers/Enums/error_log 0000644 00000002700 15212525755 0012324 0 ustar 00 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [03-Jun-2026 16:43:58 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19 [11-Jun-2026 08:13:18 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17 [11-Jun-2026 09:23:21 UTC] PHP Fatal error: Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19 Stack trace: #0 {main} thrown in /home/danvrahu/kingstaroilgas.com/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
| ver. 1.4 |
Github
|
.
| PHP 8.2.31 | Generation time: 0.58 |
proxy
|
phpinfo
|
Settings