Skip to content
2 changes: 1 addition & 1 deletion docs/ARCHITECTURE_OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ai/
│ ├── Contracts/ # Interfaces (Feature contract)
│ ├── Experiments/ # Experiment implementations (Abilities_Explorer, etc.)
│ ├── Features/ # Feature registration and loading (Loader.php, Registry.php, Feature_Category.php)
│ ├── Services/ # External services (AI_Service)
│ ├── Services/ # Service classes (Guidelines; AI_Service, deprecated)
│ ├── Settings/ # Plugin settings and admin pages
│ ├── Asset_Loader.php # Asset loader utility class
│ ├── Deprecated.php # Backward-compatibility layer for deprecated hooks/filters
Expand Down
8 changes: 0 additions & 8 deletions docs/experiments/multi-provider-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,3 @@ add_filter( 'wpai_has_ai_credentials', function( $has_credentials, $connectors )
- Multi-provider setups can improve resilience when individual providers are unavailable.
- Keep model preference filters aligned with currently available provider model IDs.
- If no provider supports the requested capability, abilities should return explicit `WP_Error` responses.

## Related Files

- `includes/helpers.php`
- `includes/Abstracts/Abstract_Ability.php`
- `includes/Services/AI_Service.php`
- `includes/Admin/Dashboard/AI_Status_Widget.php`
- `includes/Admin/Dashboard/AI_Capabilities_Widget.php`
14 changes: 13 additions & 1 deletion includes/Services/AI_Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* Provides a centralized service layer for AI operations.
*
* @package WordPress\AI\Services
*
* @deprecated x.x.x This file will be removed in the next major release.
*/

declare( strict_types=1 );
Expand All @@ -21,7 +23,12 @@
* Manages AI provider configuration and provides a consistent interface
* for features to communicate with AI providers.
*
* Nothing in the plugin uses this class; features and abilities call
* `wp_ai_client_prompt()` directly. It is retained only so that any third-party
* code written against it keeps working for one release cycle.
*
* @since 0.2.1
* @deprecated x.x.x Use wp_ai_client_prompt() instead.
*/
class AI_Service {

Expand Down Expand Up @@ -72,9 +79,14 @@ public static function get_instance(): self {
/**
* Private constructor to enforce singleton pattern.
*
* The deprecation notice lives here rather than in `get_instance()` so that
* it is emitted once per request instead of on every call.
*
* @since 0.2.1
*/
private function __construct() {}
private function __construct() {
_deprecated_class( self::class, 'x.x.x', 'wp_ai_client_prompt()' );
}

/**
* Creates a text generation prompt builder with default configuration applied.
Expand Down
29 changes: 10 additions & 19 deletions includes/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,36 +224,27 @@ function get_preferred_models_for_text_generation(): array {
/**
* Gets the AI Service instance.
*
* Provides a convenient way to access the AI Service for performing AI operations.
* Call `wp_ai_client_prompt()` directly instead. The prompt builder it returns
* exposes the full SDK API, so the only behavior this helper added was applying
* `get_preferred_models_for_text_generation()` by default:
*
* Example usage:
* ```php
* $service = WordPress\AI\get_ai_service();
* $builder = wp_ai_client_prompt( 'Summarize this article...' );
*
* // Check if text generation is supported before generating
* $builder = $service->create_textgen_prompt( 'Summarize this article...' );
* if ( ! $builder->is_supported_for_text_generation() ) {
* return new WP_Error( 'ai_unsupported', 'No AI provider supports text generation.' );
* $models = WordPress\AI\get_preferred_models_for_text_generation();
* if ( ! empty( $models ) ) {
* $builder = $builder->using_model_preference( ...$models );
* }
* $text = $builder->generate_text();
*
* // With options array
* $text = $service->create_textgen_prompt( 'Translate to French: Hello', array(
* 'system_instruction' => 'You are a translator.',
* 'temperature' => 0.3,
* ) )->generate_text();
*
* // Chain additional SDK methods
* $titles = $service->create_textgen_prompt( 'Generate titles for: My blog post' )
* ->using_candidate_count( 5 )
* ->generate_texts();
* ```
*
* @since 0.2.1
* @deprecated x.x.x Use wp_ai_client_prompt() instead.
*
* @return \WordPress\AI\Services\AI_Service The AI Service instance.
*/
function get_ai_service(): AI_Service {
_deprecated_function( __FUNCTION__, 'x.x.x', 'wp_ai_client_prompt()' );

return AI_Service::get_instance();
}

Expand Down
70 changes: 38 additions & 32 deletions tests/Integration/Includes/Services/AI_ServiceTest.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?php
/**
* Tests for the AI_Service class.
* Tests for the deprecated AI_Service class.
*
* @package WordPress\AI\Tests\Integration\Includes\Services
*/

namespace WordPress\AI\Tests\Integration\Includes\Services;

use ReflectionProperty;
use WP_UnitTestCase;
use WordPress\AI\Services\AI_Service;

Expand All @@ -15,25 +16,24 @@
/**
* AI_Service test case.
*
* `AI_Service` and `get_ai_service()` are deprecated and scheduled for removal in
* the next major release. These tests assert that the deprecated surface still
* behaves as documented so third-party code keeps working through the deprecation
* window. Each `setExpectedDeprecated()` call asserts in both directions: the test
* fails if the notice is missing and if an unexpected notice is triggered.
*
* @since 0.2.1
*/
class AI_Service_Test extends WP_UnitTestCase {

/**
* AI service instance.
*
* @var \WordPress\AI\Services\AI_Service
*/
private AI_Service $service;

/**
* Setup test case.
*
* @since 0.2.1
*/
public function setUp(): void {
parent::setUp();
$this->service = AI_Service::get_instance();
$this->reset_instance();
}

/**
Expand All @@ -42,15 +42,34 @@ public function setUp(): void {
* @since 0.2.1
*/
public function tearDown(): void {
$this->reset_instance();
parent::tearDown();
}

/**
* Resets the singleton instance.
*
* The `_deprecated_class()` notice fires from the constructor, which the
* singleton only reaches once per process. Clearing the instance around every
* test keeps that notice deterministic instead of landing on whichever test
* happens to run first.
*
* @since x.x.x
*/
private function reset_instance(): void {
$instance = new ReflectionProperty( AI_Service::class, 'instance' );
$instance->setAccessible( true );
$instance->setValue( null, null );
}

/**
* Test singleton instance.
*
* @since 0.2.1
*/
public function test_get_instance_returns_singleton(): void {
$this->setExpectedDeprecated( AI_Service::class );

$instance1 = AI_Service::get_instance();
$instance2 = AI_Service::get_instance();

Expand All @@ -63,10 +82,13 @@ public function test_get_instance_returns_singleton(): void {
* @since 0.2.1
*/
public function test_get_ai_service_helper_returns_instance(): void {
$this->setExpectedDeprecated( 'WordPress\AI\get_ai_service' );
$this->setExpectedDeprecated( AI_Service::class );

$service = get_ai_service();

$this->assertInstanceOf( AI_Service::class, $service, 'Helper should return AI_Service instance' );
$this->assertSame( $this->service, $service, 'Helper should return singleton instance' );
$this->assertSame( AI_Service::get_instance(), $service, 'Helper should return singleton instance' );
}

/**
Expand All @@ -75,7 +97,9 @@ public function test_get_ai_service_helper_returns_instance(): void {
* @since 0.2.1
*/
public function test_create_textgen_prompt_returns_builder(): void {
$builder = $this->service->create_textgen_prompt( 'Test prompt' );
$this->setExpectedDeprecated( AI_Service::class );

$builder = AI_Service::get_instance()->create_textgen_prompt( 'Test prompt' );

$this->assertInstanceOf(
\WP_AI_Client_Prompt_Builder::class,
Expand All @@ -90,7 +114,9 @@ public function test_create_textgen_prompt_returns_builder(): void {
* @since 0.2.1
*/
public function test_create_textgen_prompt_with_options(): void {
$builder = $this->service->create_textgen_prompt(
$this->setExpectedDeprecated( AI_Service::class );

$builder = AI_Service::get_instance()->create_textgen_prompt(
'Test prompt',
array(
'system_instruction' => 'You are helpful.',
Expand All @@ -105,24 +131,4 @@ public function test_create_textgen_prompt_with_options(): void {
'Should return WP_AI_Client_Prompt_Builder instance with options applied'
);
}

/**
* Test ai_experiments_service_initialized action can be hooked.
*
* @since 0.2.1
*/
public function test_init_action_is_hookable(): void {
$callback = static function () {};

add_action( 'ai_experiments_service_initialized', $callback );

// Verify callback was registered.
$this->assertNotFalse(
has_action( 'ai_experiments_service_initialized', $callback ),
'Action should accept callbacks'
);

// Cleanup.
remove_action( 'ai_experiments_service_initialized', $callback );
}
}
Loading