Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ab90d28
Implement abstract image generation model class plus implementations …
felixarntz Aug 26, 2025
0aa16cf
Fix minor incorrectness in image model metadata for Google and OpenAI.
felixarntz Aug 26, 2025
e84907e
Fix error about missing parameter in abstract OpenAI compatible image…
felixarntz Aug 26, 2025
1d4eb81
Allow generating images via the CLI testing tool.
felixarntz Aug 26, 2025
4fcb9fc
Add test coverage for OpenAI compatible image generation model class.
felixarntz Aug 26, 2025
f8daf7f
Fix PHPCS violations.
felixarntz Aug 26, 2025
350f5b5
Merge branch 'provider-base-and-implementation' into feature/image-ge…
felixarntz Aug 26, 2025
46d31ef
Fix image model class after GenerativeAiResult update.
felixarntz Aug 26, 2025
bef9232
Merge branch 'provider-base-and-implementation' into feature/image-ge…
felixarntz Aug 26, 2025
1492257
Move classes around according to updated directory/namespace structure.
felixarntz Aug 26, 2025
c318d41
Merge branch 'provider-base-and-implementation' into feature/image-ge…
felixarntz Aug 26, 2025
f8454b3
Merge branch 'provider-base-and-implementation' into feature/image-ge…
felixarntz Aug 27, 2025
c66dc0c
Merge branch 'provider-base-and-implementation' into feature/image-ge…
felixarntz Aug 29, 2025
c026b97
Add polyfills for str_starts_with and str_ends_with.
felixarntz Aug 29, 2025
97cda26
Use str_starts_with where applicable.
felixarntz Aug 29, 2025
27b3d66
Clarify throwIfNotSuccessful method.
felixarntz Aug 29, 2025
023beca
Use response array shapes for OpenAI image generation.
felixarntz Aug 29, 2025
9f39cd2
Merge branch 'trunk' into feature/image-generation
felixarntz Aug 29, 2025
11accbb
chore: updates choice data type
JasonTheAdams Aug 29, 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
14 changes: 12 additions & 2 deletions cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function logError(string $message, int $exit_code = 1): void

// Prompt input. Allow complex input as a JSON string.
$promptInput = $positional_args[0];
if (strpos($promptInput, '{') === 0 || strpos($promptInput, '[') === 0) {
if (str_starts_with($promptInput, '{') || str_starts_with($promptInput, '[')) {
$decodedInput = json_decode($promptInput, true);
if ($decodedInput) {
$promptInput = $decodedInput;
Expand Down Expand Up @@ -156,7 +156,11 @@ function logError(string $message, int $exit_code = 1): void
}

try {
$result = $promptBuilder->generateTextResult();
if ($outputFormat === 'image-json' || $outputFormat === 'image-base64') {
$result = $promptBuilder->generateImageResult();
} else {
$result = $promptBuilder->generateTextResult();
}
} catch (InvalidArgumentException $e) {
logError('Invalid arguments while trying to generate text result: ' . $e->getMessage());
} catch (ResponseException $e) {
Expand All @@ -173,6 +177,12 @@ function logError(string $message, int $exit_code = 1): void
case 'candidates-json':
$output = json_encode($result->getCandidates(), JSON_PRETTY_PRINT);
break;
case 'image-json':
$output = json_encode($result->toFile(), JSON_PRETTY_PRINT);
break;
case 'image-base64':
$output = $result->toFile()->getBase64Data();
break;
case 'message-text':
default:
$output = $result->toText();
Expand Down
2 changes: 1 addition & 1 deletion src/Common/AbstractEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ protected static function determineClassEnumerations(string $className): array
final public function __call(string $name, array $arguments): bool
{
// Handle is* methods
if (strpos($name, 'is') === 0) {
if (str_starts_with($name, 'is')) {
$constantName = self::camelCaseToConstant(substr($name, 2));
$constants = static::getConstants();

Expand Down
2 changes: 1 addition & 1 deletion src/Files/ValueObjects/MimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public static function isValid(string $mimeType): bool
*/
public function isType(string $mimeType): bool
{
return strpos($this->value, strtolower($mimeType) . '/') === 0;
return str_starts_with($this->value, strtolower($mimeType) . '/');
}

/**
Expand Down
30 changes: 30 additions & 0 deletions src/ProviderImplementations/Google/GoogleImageGenerationModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\ProviderImplementations\Google;

use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
use WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel;

/**
* Class for a Google image generation model.
*
* @since n.e.x.t
*/
class GoogleImageGenerationModel extends AbstractOpenAiCompatibleImageGenerationModel
{
/**
* @inheritDoc
*/
protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request
{
return new Request(
$method,
GoogleProvider::BASE_URI . '/openai/' . ltrim($path, '/'),
$headers,
$data
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,13 @@ protected function parseResponseToModelMetadataList(Response $response): array
new SupportedOption(OptionEnum::outputFileType(), [FileTypeEnum::inline()]),
new SupportedOption(OptionEnum::outputMediaOrientation(), [
MediaOrientationEnum::square(),
MediaOrientationEnum::landscape(),
MediaOrientationEnum::portrait(),
// The following orientations are normally supported, but not when using the OpenAI compatible endpoint.
// MediaOrientationEnum::landscape(),
// MediaOrientationEnum::portrait(),
]),
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '16:9', '4:3', '9:16', '3:4']),
// Aspect ratio is normally supported, but not when using the OpenAI compatible endpoint.
// new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '16:9', '4:3', '9:16', '3:4']),
new SupportedOption(OptionEnum::customOptions()),
];

$modelsData = (array) $responseData['models'];
Expand Down
5 changes: 1 addition & 4 deletions src/ProviderImplementations/Google/GoogleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ protected static function createModel(
return new GoogleTextGenerationModel($modelMetadata, $providerMetadata);
}
if ($capability->isImageGeneration()) {
// TODO: Implement GoogleImageGenerationModel.
throw new RuntimeException(
'Google image generation model class is not yet implemented.'
);
return new GoogleImageGenerationModel($modelMetadata, $providerMetadata);
}
}

Expand Down
50 changes: 50 additions & 0 deletions src/ProviderImplementations/OpenAi/OpenAiImageGenerationModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\ProviderImplementations\OpenAi;

use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
use WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleImageGenerationModel;

/**
* Class for an OpenAI image generation model.
*
* @since n.e.x.t
*/
class OpenAiImageGenerationModel extends AbstractOpenAiCompatibleImageGenerationModel
{
/**
* @inheritDoc
*/
protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request
{
return new Request(
$method,
OpenAiProvider::BASE_URI . '/' . ltrim($path, '/'),
$headers,
$data
);
}

/**
* @inheritDoc
*/
protected function prepareGenerateImageParams(array $prompt): array
{
$params = parent::prepareGenerateImageParams($prompt);

/*
* Only the newer 'gpt-image-' models support passing a MIME type ('output_format').
* Conversely, they do not support 'response_format', but always return a base64 encoded image.
*/
if (isset($params['model']) && is_string($params['model']) && str_starts_with($params['model'], 'gpt-image-')) {
unset($params['response_format']);
} else {
unset($params['output_format']);
}

return $params;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
MediaOrientationEnum::portrait(),
]),
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '7:4', '4:7']),
new SupportedOption(OptionEnum::customOptions()),
];
$gptImageOptions = [
new SupportedOption(OptionEnum::inputModalities(), [[ModalityEnum::text()]]),
Expand All @@ -139,6 +140,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
MediaOrientationEnum::portrait(),
]),
new SupportedOption(OptionEnum::outputMediaAspectRatio(), ['1:1', '3:2', '2:3']),
new SupportedOption(OptionEnum::customOptions()),
];
$ttsCapabilities = [
CapabilityEnum::textToSpeechConversion(),
Expand All @@ -154,6 +156,7 @@ protected function parseResponseToModelMetadataList(Response $response): array
'audio/aac',
]),
new SupportedOption(OptionEnum::outputSpeechVoice()),
new SupportedOption(OptionEnum::customOptions()),
];

$modelsData = (array) $responseData['data'];
Expand Down
5 changes: 1 addition & 4 deletions src/ProviderImplementations/OpenAi/OpenAiProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ protected static function createModel(
return new OpenAiTextGenerationModel($modelMetadata, $providerMetadata);
}
if ($capability->isImageGeneration()) {
// TODO: Implement OpenAiImageGenerationModel.
throw new RuntimeException(
'OpenAI image generation model class is not yet implemented.'
);
return new OpenAiImageGenerationModel($modelMetadata, $providerMetadata);
}
if ($capability->isTextToSpeechConversion()) {
// TODO: Implement OpenAiTextToSpeechConversionModel.
Expand Down
2 changes: 1 addition & 1 deletion src/Providers/Models/Enums/OptionEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ protected static function determineClassEnumerations(string $className): array

// Add ModelConfig constants that start with KEY_
foreach ($modelConfigConstants as $constantName => $constantValue) {
if (strpos($constantName, 'KEY_') === 0) {
if (str_starts_with($constantName, 'KEY_')) {
// Remove KEY_ prefix to get the enum constant name
$enumConstantName = substr($constantName, 4);

Expand Down
Loading