Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a36f93a
feat: adds provider DTOs
JasonTheAdams Jul 28, 2025
52ef504
test: adds DTO tests
JasonTheAdams Jul 28, 2025
f5d4251
refactor: simplifies file type checks
JasonTheAdams Jul 29, 2025
0a5ed46
refactor: imports built-in PHP classes and interfaces
JasonTheAdams Jul 29, 2025
7c13ac1
refactor: removes type check from File and MessagePart fromArray
JasonTheAdams Jul 29, 2025
4342636
Merge branch 'dto-serlialization' into extender-dtos
JasonTheAdams Jul 29, 2025
3a56ff2
Merge branch 'dto-serlialization' into extender-dtos
JasonTheAdams Jul 29, 2025
1fbd566
refactor: removes useless constructor
JasonTheAdams Jul 29, 2025
22b0a11
refactor: removes model conflig fluency
JasonTheAdams Jul 29, 2025
18092d2
feat: validates ProviderMetaData::fromArray
JasonTheAdams Jul 29, 2025
0cf2617
test: fixes tests assuming config fluency
JasonTheAdams Jul 29, 2025
e1c9e5d
refactor: adds constants for scheam keys
JasonTheAdams Jul 29, 2025
5cb08bf
Merge branch 'dto-serlialization' into extender-dtos
JasonTheAdams Jul 29, 2025
e1e7312
refactor: removes final on DTOs
JasonTheAdams Jul 29, 2025
5eb9294
feat: adds validation and improves RequiredOption description
JasonTheAdams Jul 29, 2025
3db0127
Merge branch 'dto-serlialization' into extender-dtos
JasonTheAdams Jul 29, 2025
7025568
feat: adds setCustomOption method
JasonTheAdams Jul 30, 2025
8f6ff62
fix: resolved PHP 7.4 incompatibility
JasonTheAdams Jul 30, 2025
02d3433
feat: adds output schema and mime type support
JasonTheAdams Jul 30, 2025
07df1e7
feat: changes support values to be optional
JasonTheAdams Jul 30, 2025
fb76c99
refactor: removes array_values and supresses phpstan errors
JasonTheAdams Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Common/AbstractEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use BadMethodCallException;
use InvalidArgumentException;
use ReflectionClass;
use RuntimeException;

/**
* Abstract base class for enum-like behavior in PHP 7.4.
Expand Down Expand Up @@ -250,7 +251,7 @@ private static function getInstance(string $value, string $name): self
* @since n.e.x.t
*
* @return array<string, string> Map of constant names to values.
* @throws \RuntimeException If invalid constant found.
* @throws RuntimeException If invalid constant found.
*/
final protected static function getConstants(): array
{
Expand All @@ -265,7 +266,7 @@ final protected static function getConstants(): array
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(
throw new RuntimeException(
sprintf(
'Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.',
$name,
Expand All @@ -276,7 +277,7 @@ final protected static function getConstants(): array

// Check if value is valid type
if (!is_string($value)) {
throw new \RuntimeException(
throw new RuntimeException(
sprintf(
'Invalid enum value type for constant %s::%s. ' .
'Only string values are allowed, %s given.',
Expand Down
26 changes: 14 additions & 12 deletions src/Files/DTO/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace WordPress\AiClient\Files\DTO;

use InvalidArgumentException;
use RuntimeException;
use WordPress\AiClient\Common\AbstractDataValueObject;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\ValueObjects\MimeType;
Expand Down Expand Up @@ -54,7 +56,7 @@ final class File extends AbstractDataValueObject
*
* @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.
* @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
*/
public function __construct(string $file, ?string $mimeType = null)
{
Expand All @@ -69,7 +71,7 @@ public function __construct(string $file, ?string $mimeType = null)
*
* @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.
* @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
*/
private function detectAndProcessFile(string $file, ?string $providedMimeType): void
{
Expand Down Expand Up @@ -104,7 +106,7 @@ private function detectAndProcessFile(string $file, ?string $providedMimeType):
// Check if it's plain base64
if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) {
if ($providedMimeType === null) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
'MIME type is required when providing plain base64 data without data URI format.'
);
}
Expand All @@ -114,7 +116,7 @@ private function detectAndProcessFile(string $file, ?string $providedMimeType):
return;
}

throw new \InvalidArgumentException(
throw new InvalidArgumentException(
'Invalid file provided. Expected URL, base64 data, or valid local file path.'
);
}
Expand All @@ -140,14 +142,14 @@ private function isUrl(string $string): bool
*
* @param string $filePath The path to the local file.
* @return string The base64-encoded file data.
* @throws \RuntimeException If the file cannot be read.
* @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(
throw new RuntimeException(
sprintf('Unable to read file: %s', $filePath)
);
}
Expand Down Expand Up @@ -288,7 +290,7 @@ public function isText(): bool
* @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.
* @throws InvalidArgumentException If MIME type cannot be determined.
*/
private function determineMimeType(
?string $providedMimeType,
Expand Down Expand Up @@ -320,14 +322,14 @@ private function determineMimeType(
if (!empty($extension)) {
try {
return MimeType::fromExtension($extension);
} catch (\InvalidArgumentException $e) {
} catch (InvalidArgumentException $e) {
// Extension not recognized, continue to error
unset($e);
}
}
}

throw new \InvalidArgumentException(
throw new InvalidArgumentException(
'Unable to determine MIME type. Please provide it explicitly.'
);
}
Expand Down Expand Up @@ -403,7 +405,7 @@ public function toArray(): array

if ($this->fileType->isRemote() && $this->url !== null) {
$data['url'] = $this->url;
} elseif (!$this->fileType->isRemote() && $this->base64Data !== null) {
} elseif ($this->base64Data !== null) {
$data['base64Data'] = $this->base64Data;
}

Expand All @@ -421,12 +423,12 @@ public static function fromArray(array $array): File

if ($fileType->isRemote()) {
if (!isset($array['url']) || !isset($array['mimeType'])) {
throw new \InvalidArgumentException('Remote file requires url and mimeType.');
throw new InvalidArgumentException('Remote file requires url and mimeType.');
}
return new self($array['url'], $array['mimeType']);
} else {
if (!isset($array['mimeType']) || !isset($array['base64Data'])) {
throw new \InvalidArgumentException('Inline file requires mimeType and base64Data.');
throw new InvalidArgumentException('Inline file requires mimeType and base64Data.');
}
return new self($array['base64Data'], $array['mimeType']);
}
Expand Down
12 changes: 7 additions & 5 deletions src/Files/ValueObjects/MimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace WordPress\AiClient\Files\ValueObjects;

use InvalidArgumentException;

/**
* Value object representing a MIME type.
*
Expand Down Expand Up @@ -115,12 +117,12 @@ final class MimeType
* @since n.e.x.t
*
* @param string $value The MIME type value.
* @throws \InvalidArgumentException If the MIME type is invalid.
* @throws InvalidArgumentException If the MIME type is invalid.
*/
public function __construct(string $value)
{
if (!self::isValid($value)) {
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf('Invalid MIME type: %s', $value)
);
}
Expand All @@ -135,14 +137,14 @@ public function __construct(string $value)
*
* @param string $extension The file extension (without the dot).
* @return self The MimeType instance.
* @throws \InvalidArgumentException If the extension is not recognized.
* @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(
throw new InvalidArgumentException(
sprintf('Unknown file extension: %s', $extension)
);
}
Expand Down Expand Up @@ -245,7 +247,7 @@ public function equals($other): bool
return $this->value === strtolower($other);
}

throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf('Invalid MIME type comparison: %s', gettype($other))
);
}
Expand Down
13 changes: 7 additions & 6 deletions src/Messages/DTO/MessagePart.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace WordPress\AiClient\Messages\DTO;

use InvalidArgumentException;
use WordPress\AiClient\Common\AbstractDataValueObject;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum;
Expand Down Expand Up @@ -65,7 +66,7 @@ final class MessagePart extends AbstractDataValueObject
* @since n.e.x.t
*
* @param mixed $content The content of this message part.
* @throws \InvalidArgumentException If an unsupported content type is provided.
* @throws InvalidArgumentException If an unsupported content type is provided.
*/
public function __construct($content)
{
Expand All @@ -83,7 +84,7 @@ public function __construct($content)
$this->functionResponse = $content;
} else {
$type = is_object($content) ? get_class($content) : gettype($content);
throw new \InvalidArgumentException(
throw new InvalidArgumentException(
sprintf(
'Unsupported content type %s. Expected string, File, '
. 'FunctionCall, or FunctionResponse.',
Expand Down Expand Up @@ -252,25 +253,25 @@ public static function fromArray(array $array): MessagePart

if ($type->isText()) {
if (!isset($array['text'])) {
throw new \InvalidArgumentException('Text message part requires text field.');
throw new InvalidArgumentException('Text message part requires text field.');
}
return new self($array['text']);
} elseif ($type->isFile()) {
if (!isset($array['file'])) {
throw new \InvalidArgumentException('File message part requires file field.');
throw new InvalidArgumentException('File message part requires file field.');
}
$fileData = $array['file'];
return new self(File::fromArray($fileData));
} elseif ($type->isFunctionCall()) {
if (!isset($array['functionCall'])) {
throw new \InvalidArgumentException('Function call message part requires functionCall field.');
throw new InvalidArgumentException('Function call message part requires functionCall field.');
}
$functionCallData = $array['functionCall'];
return new self(FunctionCall::fromArray($functionCallData));
} else {
// Function response is the only remaining option
if (!isset($array['functionResponse'])) {
throw new \InvalidArgumentException('Function response message part requires functionResponse field.');
throw new InvalidArgumentException('Function response message part requires functionResponse field.');
}
$functionResponseData = $array['functionResponse'];
return new self(FunctionResponse::fromArray($functionResponseData));
Expand Down
Loading