Add PSR HTTP Message Interfaces and Dependencies
- Implemented StreamInterface, UploadedFileInterface, and UriInterface as per PSR standards. - Added getallheaders function to retrieve HTTP headers in a compatible manner. - Included LICENSE files for ralouphie/getallheaders and symfony/deprecation-contracts. - Introduced function for triggering deprecation notices in Symfony.
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Util\AI_Capabilities
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Util;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\AI_Capability;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Model_Metadata;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generative_AI_Model;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Function_Calling;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Multimodal_Input;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Multimodal_Output;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Text_Generation;
|
||||
use ATFPP\AI_Translate\Services\Contracts\With_Web_Search;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class exposing the available AI capabilities and related static utility methods.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class AI_Capabilities {
|
||||
|
||||
/**
|
||||
* Gets the combined AI capabilities that the given model classes support.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param string[] $model_classes The model class names.
|
||||
* @return string[] The AI capabilities that the model classes support, based on the interfaces they implement.
|
||||
*/
|
||||
public static function get_model_classes_capabilities( array $model_classes ): array {
|
||||
$capabilities = array();
|
||||
foreach ( $model_classes as $model_class ) {
|
||||
$model_capabilities = self::get_model_class_capabilities( $model_class );
|
||||
foreach ( $model_capabilities as $capability ) {
|
||||
$capabilities[] = $capability;
|
||||
}
|
||||
}
|
||||
return array_unique( $capabilities );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AI capabilities that the given model class supports.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $model_class The model class name.
|
||||
* @return string[] The AI capabilities that the model class supports, based on the interfaces it implements.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*/
|
||||
public static function get_model_class_capabilities( string $model_class ): array {
|
||||
$interfaces = class_implements( $model_class );
|
||||
|
||||
$capabilities = array();
|
||||
if ( isset( $interfaces[ With_Function_Calling::class ] ) ) {
|
||||
$capabilities[] = AI_Capability::FUNCTION_CALLING;
|
||||
}
|
||||
if ( isset( $interfaces[ With_Multimodal_Input::class ] ) ) {
|
||||
$capabilities[] = AI_Capability::MULTIMODAL_INPUT;
|
||||
}
|
||||
if ( isset( $interfaces[ With_Multimodal_Output::class ] ) ) {
|
||||
$capabilities[] = AI_Capability::MULTIMODAL_OUTPUT;
|
||||
}
|
||||
if ( isset( $interfaces[ With_Text_Generation::class ] ) ) {
|
||||
$capabilities[] = AI_Capability::TEXT_GENERATION;
|
||||
}
|
||||
if ( isset( $interfaces[ With_Web_Search::class ] ) ) {
|
||||
$capabilities[] = AI_Capability::WEB_SEARCH;
|
||||
}
|
||||
return $capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the AI capabilities that the given model instance supports.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param Generative_AI_Model $model The model instance.
|
||||
* @return string[] The AI capabilities that the model instance supports, based on the interfaces it implements.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*/
|
||||
public static function get_model_instance_capabilities( Generative_AI_Model $model ): array {
|
||||
$capabilities = array();
|
||||
if ( $model instanceof With_Function_Calling ) {
|
||||
$capabilities[] = AI_Capability::FUNCTION_CALLING;
|
||||
}
|
||||
if ( $model instanceof With_Multimodal_Input ) {
|
||||
$capabilities[] = AI_Capability::MULTIMODAL_INPUT;
|
||||
}
|
||||
if ( $model instanceof With_Multimodal_Output ) {
|
||||
$capabilities[] = AI_Capability::MULTIMODAL_OUTPUT;
|
||||
}
|
||||
if ( $model instanceof With_Text_Generation ) {
|
||||
$capabilities[] = AI_Capability::TEXT_GENERATION;
|
||||
}
|
||||
if ( $model instanceof With_Web_Search ) {
|
||||
$capabilities[] = AI_Capability::WEB_SEARCH;
|
||||
}
|
||||
return $capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model slugs that satisfy the given capabilities.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.5.0 Now expects an array of model data shapes, mapped by model slug.
|
||||
* @since 0.7.0 Now expects a map of model metadata objects.
|
||||
*
|
||||
* @param array<string, Model_Metadata> $models Metadata for each model, mapped by model slug.
|
||||
* @param string[] $capabilities The required capabilities that the models should satisfy.
|
||||
* @return string[] Slugs of all models that satisfy the given capabilities.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if no model satisfies the given capabilities.
|
||||
*/
|
||||
public static function get_model_slugs_for_capabilities( array $models, array $capabilities ): array {
|
||||
$model_slugs = array();
|
||||
foreach ( $models as $model_slug => $model_metadata ) {
|
||||
$model_capabilities = $model_metadata->get_capabilities();
|
||||
if ( ! array_diff( $capabilities, $model_capabilities ) ) {
|
||||
$model_slugs[] = $model_slug;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $model_slugs ) {
|
||||
throw new InvalidArgumentException(
|
||||
'No model satisfies the given capabilities.'
|
||||
);
|
||||
}
|
||||
|
||||
return $model_slugs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model class name from the given model class names that satisfies the given capabilities.
|
||||
*
|
||||
* @since 0.7.0
|
||||
*
|
||||
* @param string[] $model_classes The model class names.
|
||||
* @param string[] $capabilities The required capabilities that the models should satisfy.
|
||||
* @return string The model class name that satisfies the given capabilities.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if no model satisfies the given capabilities.
|
||||
*/
|
||||
public static function get_model_class_for_capabilities( array $model_classes, array $capabilities ): string {
|
||||
foreach ( $model_classes as $model_class ) {
|
||||
$model_capabilities = self::get_model_class_capabilities( $model_class );
|
||||
if ( ! array_diff( $capabilities, $model_capabilities ) ) {
|
||||
return $model_class;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(
|
||||
'No model class satisfies the given capabilities.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Util\Data_Encryption
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Util;
|
||||
|
||||
/**
|
||||
* Class responsible for encrypting and decrypting data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @see https://felix-arntz.me/blog/storing-confidential-data-in-wordpress/
|
||||
*/
|
||||
final class Data_Encryption {
|
||||
|
||||
/**
|
||||
* Key to use for encryption.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var string
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* Salt to use for encryption.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @var string
|
||||
*/
|
||||
private $salt;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param ?string $key Optional. Key to use for encryption. If not passed, the default key determined by constants
|
||||
* will be used.
|
||||
* @param ?string $salt Optional. Salt to use for encryption. If not passed, the default salt determined by
|
||||
* constants will be used.
|
||||
*/
|
||||
public function __construct( ?string $key = null, ?string $salt = null ) {
|
||||
$this->key = $key ?? $this->get_default_key();
|
||||
$this->salt = $salt ?? $this->get_default_salt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a value.
|
||||
*
|
||||
* If a user-based key is set, that key is used. Otherwise the default key is used.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $value Value to encrypt.
|
||||
* @return string Encrypted value, or empty string on failure.
|
||||
*/
|
||||
public function encrypt( string $value ): string {
|
||||
if ( ! extension_loaded( 'openssl' ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$method = 'aes-256-ctr';
|
||||
$ivlen = openssl_cipher_iv_length( $method );
|
||||
$iv = openssl_random_pseudo_bytes( $ivlen );
|
||||
|
||||
$raw_value = openssl_encrypt( $value . $this->salt, $method, $this->key, 0, $iv );
|
||||
if ( ! $raw_value ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return base64_encode( $iv . $raw_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a value.
|
||||
*
|
||||
* If a user-based key is set, that key is used. Otherwise the default key is used.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string $raw_value Value to decrypt.
|
||||
* @return string Decrypted value, or empty string on failure.
|
||||
*/
|
||||
public function decrypt( string $raw_value ): string {
|
||||
if ( ! extension_loaded( 'openssl' ) ) {
|
||||
return $raw_value;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$decoded_value = base64_decode( $raw_value, true );
|
||||
|
||||
if ( false === $decoded_value ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$method = 'aes-256-ctr';
|
||||
$ivlen = openssl_cipher_iv_length( $method );
|
||||
$iv = substr( $decoded_value, 0, $ivlen );
|
||||
|
||||
$decoded_value = substr( $decoded_value, $ivlen );
|
||||
|
||||
$value = openssl_decrypt( $decoded_value, $method, $this->key, 0, $iv );
|
||||
if ( ! $value || substr( $value, - strlen( $this->salt ) ) !== $this->salt ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return substr( $value, 0, - strlen( $this->salt ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default encryption key to use.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string Default (not user-based) encryption key.
|
||||
*/
|
||||
private function get_default_key(): string {
|
||||
if ( defined( 'AI_SERVICES_ENCRYPTION_KEY' ) && '' !== AI_SERVICES_ENCRYPTION_KEY ) {
|
||||
return AI_SERVICES_ENCRYPTION_KEY;
|
||||
}
|
||||
|
||||
if ( defined( 'LOGGED_IN_KEY' ) && '' !== LOGGED_IN_KEY ) {
|
||||
return LOGGED_IN_KEY;
|
||||
}
|
||||
|
||||
// If this is reached, you're either not on a live site or have a serious security issue.
|
||||
return 'test-key';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default encryption salt to use.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @return string Encryption salt.
|
||||
*/
|
||||
private function get_default_salt(): string {
|
||||
if ( defined( 'AI_SERVICES_ENCRYPTION_SALT' ) && '' !== AI_SERVICES_ENCRYPTION_SALT ) {
|
||||
return AI_SERVICES_ENCRYPTION_SALT;
|
||||
}
|
||||
|
||||
if ( defined( 'LOGGED_IN_SALT' ) && '' !== LOGGED_IN_SALT ) {
|
||||
return LOGGED_IN_SALT;
|
||||
}
|
||||
|
||||
// If this is reached, you're either not on a live site or have a serious security issue.
|
||||
return 'test-salt';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Util\Formatter
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Util;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Enums\AI_Capability;
|
||||
use ATFPP\AI_Translate\Services\API\Enums\Content_Role;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Content;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Parts;
|
||||
use ATFPP\AI_Translate\Services\API\Types\Parts\Text_Part;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class providing static methods for formatting content.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class Formatter {
|
||||
|
||||
/**
|
||||
* Formats and validates the various supported formats of a user prompt into a consistent list of Content instances.
|
||||
*
|
||||
* This method takes into account whether the provided content is supported by the given model, based on its capabilities.
|
||||
*
|
||||
* @since 0.5.0
|
||||
*
|
||||
* @param string|Parts|Content|Content[] $content The content to format.
|
||||
* @param string[] $capabilities The AI capabilities that the model supports.
|
||||
* @return Content[] The formatted Content instances.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the content is invalid or the model does not support it.
|
||||
*/
|
||||
public static function format_and_validate_new_contents( $content, array $capabilities ): array {
|
||||
if ( is_array( $content ) ) {
|
||||
$contents = array_map(
|
||||
array( __CLASS__, 'format_new_content' ),
|
||||
$content
|
||||
);
|
||||
} else {
|
||||
$contents = array( self::format_new_content( $content ) );
|
||||
}
|
||||
|
||||
if ( count( $contents ) === 0 ) {
|
||||
throw new InvalidArgumentException(
|
||||
'No prompt was provided.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( Content_Role::USER !== $contents[0]->get_role() ) {
|
||||
throw new InvalidArgumentException(
|
||||
'The first Content instance in the conversation or prompt must be user content.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! in_array( AI_Capability::CHAT_HISTORY, $capabilities, true ) && count( $contents ) > 1 ) {
|
||||
throw new InvalidArgumentException(
|
||||
'The model does not support chat history. Only one content prompt must be provided.'
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! in_array( AI_Capability::MULTIMODAL_INPUT, $capabilities, true ) ) {
|
||||
// For performance reasons, only check the last content prompt, which likely is the only new one.
|
||||
$last_content = $contents[ count( $contents ) - 1 ];
|
||||
$last_parts = $last_content->get_parts();
|
||||
$last_parts_text_only = $last_parts->filter( array( 'class_name' => Text_Part::class ) );
|
||||
if ( count( $last_parts_text_only ) < count( $last_parts ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
'The model does not support multimodal input. Only text parts must be provided.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the various supported formats of new user content into a consistent Content instance.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string|Parts|Content $content The content to format.
|
||||
* @return Content The formatted new content.
|
||||
*/
|
||||
public static function format_new_content( $content ): Content {
|
||||
return self::format_content( $content, Content_Role::USER );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the various supported formats of a system instruction into a consistent Content instance.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string|Parts|Content $input The system instruction to format.
|
||||
* @return Content The formatted system instruction.
|
||||
*/
|
||||
public static function format_system_instruction( $input ): Content {
|
||||
return self::format_content( $input, Content_Role::SYSTEM );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the various supported formats of content into a consistent Content instance.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param string|Parts|Content $input The content to format.
|
||||
* @param string $role The role for the content.
|
||||
* @return Content The formatted content.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if the value is not a string, a Parts instance, or a Content instance.
|
||||
*/
|
||||
public static function format_content( $input, string $role ): Content {
|
||||
if ( is_string( $input ) ) {
|
||||
$parts = new Parts();
|
||||
$parts->add_text_part( $input );
|
||||
|
||||
return new Content( $role, $parts );
|
||||
}
|
||||
|
||||
if ( $input instanceof Parts ) {
|
||||
return new Content( $role, $input );
|
||||
}
|
||||
|
||||
if ( ! $input instanceof Content ) {
|
||||
throw new InvalidArgumentException(
|
||||
'The value must be a string, a Parts instance, or a Content instance.'
|
||||
);
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Util\Strings
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Util;
|
||||
|
||||
/**
|
||||
* Class providing static methods for string operations.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class Strings {
|
||||
|
||||
/**
|
||||
* Converts a snake_case string to a camelCase string.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param string $input The snake_case string.
|
||||
* @return string The camelCase string.
|
||||
*/
|
||||
public static function snake_case_to_camel_case( string $input ): string {
|
||||
return lcfirst( str_replace( '_', '', ucwords( $input, '_' ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ATFPP\AI_Translate\Services\Util\Transformer
|
||||
*
|
||||
* @since 0.2.0
|
||||
* @package ai-services
|
||||
*/
|
||||
|
||||
namespace ATFPP\AI_Translate\Services\Util;
|
||||
|
||||
use ATFPP\AI_Translate\Services\API\Types\Content;
|
||||
use ATFPP\AI_Translate\Services\Contracts\Generation_Config;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class providing static methods for transforming data.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
final class Transformer {
|
||||
|
||||
/**
|
||||
* Transforms the given content using the provided transformers.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param Content $content The content to transform.
|
||||
* @param array<string, callable> $transformers The transformers to use. Each transformer callback should accept
|
||||
* the content as its only parameter and return the transformed value
|
||||
* for its key.
|
||||
* @return array<string, mixed> The transformed content.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if a provided transformer is not callable.
|
||||
*/
|
||||
public static function transform_content( Content $content, array $transformers ): array {
|
||||
$data = array();
|
||||
|
||||
foreach ( $transformers as $key => $transformer ) {
|
||||
if ( ! is_callable( $transformer ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The transformer for key %s is invalid.',
|
||||
htmlspecialchars( $key ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Transform the value and set it if truthy.
|
||||
$value = $transformer( $content );
|
||||
if ( ! $value ) {
|
||||
continue;
|
||||
}
|
||||
$data[ $key ] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given Generation_Config instance into the given parameters using the provided transformers.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @param array<string, mixed> $params The parameters to merge the generation config into.
|
||||
* @param Generation_Config $config The generation config to use for the transformation.
|
||||
* @param array<string, callable> $transformers The transformers to use. Each transformer callback should accept
|
||||
* the generation config as its only parameter and return the
|
||||
* transformed value for its key.
|
||||
* @return array<string, mixed> The transformed parameters.
|
||||
*
|
||||
* @throws InvalidArgumentException Thrown if a provided transformer is not callable.
|
||||
*/
|
||||
public static function transform_generation_config_params( array $params, Generation_Config $config, array $transformers ): array {
|
||||
foreach ( $transformers as $key => $transformer ) {
|
||||
if ( ! is_callable( $transformer ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The transformer for key %s is invalid.',
|
||||
htmlspecialchars( $key ) // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Already set parameters take precedence.
|
||||
if ( isset( $params[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Transform the value and set it if truthy.
|
||||
$value = $transformer( $config );
|
||||
if ( ! $value ) {
|
||||
continue;
|
||||
}
|
||||
$params[ $key ] = $value;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user