Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3c56f68
Implement AiProviderRegistry with comprehensive test suite
Aug 5, 2025
4b09a07
Fix PHPStan type issues in AiProviderRegistry
Aug 5, 2025
a84a4ac
Remove trailing whitespace from AiProviderRegistry
Aug 5, 2025
40b926b
Integrate AiProviderRegistry with Provider interfaces from PR #35
Aug 7, 2025
7bdcb9c
Fix PHPCS style violations
Aug 7, 2025
877fc55
Fix PHPCS style after rebase
Aug 7, 2025
cacb2dc
Fix tokenCount references in test files after rebase
Aug 7, 2025
63fe203
Fix PHPCS line length warnings in test files
Aug 7, 2025
b1e63d5
Fix duplicate meetsRequirements() method after rebase
Aug 8, 2025
37d1124
Resolve merge conflict in ModelConfigTest
Aug 12, 2025
83927be
Clean up PR #38: Revert unrelated changes per Felix's feedback
Aug 14, 2025
d77b021
Fix MockModel method signature to match trunk ModelInterface
Aug 14, 2025
06411b9
refactor: renamed to ProviderRegistry
JasonTheAdams Aug 14, 2025
b44f26c
fix: removes unused imports
JasonTheAdams Aug 14, 2025
cbf1aef
Merge remote-tracking branch 'origin/trunk' into feature/provider-reg…
Aug 15, 2025
7956769
Merge remote-tracking branch 'fork/feature/provider-registry' into fe…
Aug 15, 2025
4ac777e
fix: not needed because it's used in the parameter
Aug 15, 2025
57dfe25
refractor: type fixing
Aug 15, 2025
8f96b26
refractor : type fix
Aug 15, 2025
766fcf8
refractor : type fix
Aug 15, 2025
8a64e8a
refractor : type fix
Aug 15, 2025
23a2a01
refractor : clean not needed
Aug 15, 2025
14e650b
refractor : type fix
Aug 15, 2025
2aeac0e
fix : Validate that class implements ProviderInterface
Aug 15, 2025
212bab6
refractor: Add proper PHPStan annotations for static method calls on …
Aug 15, 2025
4258145
refractor: type fix
Aug 15, 2025
da98ac1
refractor: type fix
Aug 15, 2025
8de52d2
refractor: type fix
Aug 15, 2025
2f1d300
refactor: reorganize test structure and rename ProviderRegistry test
Aug 15, 2025
db59e19
fix: line length to match phpcs
Aug 15, 2025
2c9ff36
Fix import sorting in ProviderRegistryTest
Aug 15, 2025
03310ba
fix: type
Aug 15, 2025
489a4ed
refactor: remove redundant PHPStan annotation
Aug 15, 2025
cafc630
Fix PHPCS style violations
Aug 7, 2025
df20ba4
Fix PHPCS line length warnings in test files
Aug 7, 2025
c52f1d9
test: fixes broken tests from rebase
JasonTheAdams Aug 15, 2025
3abe579
test: reverts tests back to trunk affected by rebase
JasonTheAdams Aug 15, 2025
45de1fa
test: adds missing EOL
JasonTheAdams Aug 15, 2025
6e25240
Optimize ProviderRegistry::hasProvider() performance by replacing in_…
Aug 15, 2025
f868e7b
Remove redundant interface validation in resolveProviderClassName
Aug 15, 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
229 changes: 229 additions & 0 deletions src/Providers/AiProviderRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php

declare(strict_types=1);

namespace WordPress\AiClient\Providers;

use InvalidArgumentException;
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\DTO\ProviderModelsMetadata;
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 n.e.x.t
*/
class AiProviderRegistry
{
/**
* @var array<string, string> Mapping of provider IDs to class names.
*/
private array $providerClassNames = [];


/**
* Registers a provider class with the registry.
*
* @since n.e.x.t
*
* @param string $className The fully qualified provider class name.
* @throws InvalidArgumentException If the class doesn't exist or implement 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)
);
}

// Get provider metadata to extract ID (using static method from interface)
/** @var class-string<ProviderInterface> $className */
$metadata = $className::metadata();

if (!$metadata instanceof ProviderMetadata) {
throw new InvalidArgumentException(
sprintf('Provider must return ProviderMetadata from metadata() method: %s', $className)
);
}

$this->providerClassNames[$metadata->getId()] = $className;
}

/**
* Checks if a provider is registered.
*
* @since n.e.x.t
*
* @param string $idOrClassName The provider ID or class name to check.
* @return bool True if the provider is registered.
*/
public function hasProvider(string $idOrClassName): bool
{
return isset($this->providerClassNames[$idOrClassName]) ||
in_array($idOrClassName, $this->providerClassNames, true);
}

/**
* Gets the class name for a registered provider.
*
* @since n.e.x.t
*
* @param string $id The provider ID.
* @return string The provider class name.
* @throws InvalidArgumentException If the provider is not registered.
*/
public function getProviderClassName(string $id): string
{
if (!isset($this->providerClassNames[$id])) {
throw new InvalidArgumentException(
sprintf('Provider not registered: %s', $id)
);
}

return $this->providerClassNames[$id];
}

/**
* Checks if a provider is properly configured.
*
* @since n.e.x.t
*
* @param string $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 providers that support the given requirements.
*
* @since n.e.x.t
*
* @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->providerClassNames 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 provider that support the given requirements.
*
* @since n.e.x.t
*
* @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);

// Use static method from ProviderInterface
/** @var class-string<ProviderInterface> $className */
$modelMetadataDirectory = $className::modelMetadataDirectory();

// Filter models that meet requirements
$matchingModels = [];
foreach ($modelMetadataDirectory->listModelMetadata() as $modelMetadata) {
if ($modelMetadata->meetsRequirements($modelRequirements)) {
$matchingModels[] = $modelMetadata;
}
}

return $matchingModels;
}

/**
* Gets a configured model instance from a provider.
*
* @since n.e.x.t
*
* @param string $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);

// Use static method from ProviderInterface
/** @var class-string<ProviderInterface> $className */
return $className::model($modelId, $modelConfig);
}

/**
* Gets the class name for a registered provider (handles both ID and class name input).
*
* @param string $idOrClassName The provider ID or class name.
* @return string The provider class name.
* @throws InvalidArgumentException If provider is not registered.
*/
private function resolveProviderClassName(string $idOrClassName): string
{
// Handle both ID and class name
$className = $this->providerClassNames[$idOrClassName] ?? $idOrClassName;

if (!$this->hasProvider($idOrClassName)) {
throw new InvalidArgumentException(
sprintf('Provider not registered: %s', $idOrClassName)
);
}

return $className;
}
}
Loading