Skip to content

Instantly share code, notes, and snippets.

@jonathanbossenger
Created March 1, 2026 10:25
Show Gist options
  • Select an option

  • Save jonathanbossenger/fd63914ee6e355171ae5ef2da2b45caa to your computer and use it in GitHub Desktop.

Select an option

Save jonathanbossenger/fd63914ee6e355171ae5ef2da2b45caa to your computer and use it in GitHub Desktop.

WordPress AI Client Developer Guide

A comprehensive guide to the WP AI Client and Abilities API for plugin and theme developers.

Table of Contents

  1. Introduction and Architecture Overview
  2. Setting Up Connectors (API Key Management)
  3. Your First AI Prompt (Quick Start)
  4. The Prompt Builder In Depth
  5. The Abilities API (Since 6.9)
  6. Connecting Abilities to AI (Function Calling)
  7. REST API Endpoints
  8. Hooks and Filters Reference
  9. Architecture Deep Dive
  10. Complete Plugin Example
  11. Best Practices and Common Patterns
  12. Troubleshooting
  13. API Quick Reference

1. Introduction and Architecture Overview

WordPress 7.0 introduces the WP AI Client, a provider-agnostic AI integration framework. It builds on the Abilities API introduced in WordPress 6.9 to let plugins expose structured functionality that AI models can invoke as tools.

Three-Layer Architecture

┌─────────────────────────────────────────────┐
│          WordPress Developer API            │
│  wp_ai_client_prompt() / Abilities API      │
├─────────────────────────────────────────────┤
│            Adapter Layer                    │
│  PSR-18 HTTP · PSR-16 Cache · PSR-14 Events │
├─────────────────────────────────────────────┤
│         PHP AI Client SDK                   │
│  Provider Registry · Models · DTOs          │
└─────────────────────────────────────────────┘
  1. WordPress Developer API — the public functions you call: wp_ai_client_prompt(), wp_register_ability(), and friends. These use WordPress conventions (snake_case, WP_Error, hooks).
  2. Adapter Layer — four small classes that bridge WordPress internals to PSR interfaces so the SDK can use wp_safe_remote_request() for HTTP, wp_cache_* for caching, and do_action() for lifecycle events.
  3. PHP AI Client SDK — the vendor library under wp-includes/php-ai-client/. It handles provider negotiation, model selection, request formatting, and response parsing.

Provider-Agnostic Design

The framework ships with support for Google (Gemini), OpenAI, and Anthropic. Your code never talks to a specific provider directly. You configure API keys, and the SDK picks a suitable model automatically—or you can request one explicitly.

Simplest Possible Example

$text = wp_ai_client_prompt( 'Summarize WordPress in one sentence.' )
    ->generate_text();

if ( is_wp_error( $text ) ) {
    error_log( $text->get_error_message() );
} else {
    echo $text; // "WordPress is an open-source content management system..."
}

2. Setting Up Connectors (API Key Management)

Before any AI call can succeed, the site needs at least one configured provider API key.

Admin UI

Navigate to Settings > Connectors in the WordPress admin. You will see fields for each supported provider where you can enter your API key.

Provider Settings Keys

Provider Option Name
Google connectors_ai_google_api_key
OpenAI connectors_ai_openai_api_key
Anthropic connectors_ai_anthropic_api_key

How Keys Are Stored and Validated

  1. Storage: Keys are saved as regular WordPress options via update_option().
  2. Sanitization: Each key passes through sanitize_text_field() and is validated against the provider before being stored. If validation fails, an empty string is saved.
  3. Masking: A filter on option_{$setting_name} replaces all but the last 4 characters with bullet characters. This means get_option( 'connectors_ai_google_api_key' ) returns a masked value in normal usage.
  4. Validation: On save and when requested via the REST Settings endpoint, the key is tested using the provider's own availability check ($registry->isProviderConfigured()).
  5. Passing to SDK: On init, WordPress reads the real (unmasked) keys and passes them to the SDK's ProviderRegistry via ApiKeyRequestAuthentication.

Programmatic Configuration

If you need to set a key programmatically (e.g., from a constant):

// In wp-config.php or a mu-plugin:
define( 'MY_OPENAI_KEY', 'sk-...' );

// In a plugin:
add_action( 'init', function () {
    if ( defined( 'MY_OPENAI_KEY' ) && ! get_option( 'connectors_ai_openai_api_key' ) ) {
        update_option( 'connectors_ai_openai_api_key', MY_OPENAI_KEY );
    }
}, 5 );

Note: The update_option() call triggers the sanitization and validation callbacks registered by the Connectors API.


3. Your First AI Prompt (Quick Start)

The Entry Point

Everything starts with wp_ai_client_prompt():

function wp_ai_client_prompt( $prompt = null ): WP_AI_Client_Prompt_Builder

It accepts an optional initial prompt (a string, MessagePart, Message, or an array/list of these) and returns a fluent builder.

Simple Text Generation

$text = wp_ai_client_prompt( 'Write a haiku about PHP.' )
    ->generate_text();

if ( is_wp_error( $text ) ) {
    wp_die( $text->get_error_message() );
}

echo esc_html( $text );

Configuration Options

$text = wp_ai_client_prompt( 'Explain caching in WordPress.' )
    ->using_temperature( 0.3 )
    ->using_max_tokens( 500 )
    ->generate_text();

Building Prompts Step by Step

You don't have to provide the prompt in the constructor. Use with_text() to add content incrementally:

$builder = wp_ai_client_prompt()
    ->using_system_instruction( 'You are a WordPress documentation assistant.' )
    ->with_text( 'What is the difference between posts and pages?' )
    ->using_temperature( 0.2 )
    ->using_max_tokens( 1000 );

$text = $builder->generate_text();

Error Handling

Every generation method returns either a result or a WP_Error:

$text = wp_ai_client_prompt( 'Hello' )->generate_text();

if ( is_wp_error( $text ) ) {
    // Common error codes:
    // 'prompt_builder_error'  - SDK or provider exception
    // 'prompt_prevented'      - Blocked by the wp_ai_client_prevent_prompt filter
    $code    = $text->get_error_code();
    $message = $text->get_error_message();
    $data    = $text->get_error_data(); // Contains 'exception_class' key

    error_log( "AI error [{$code}]: {$message}" );
}

4. The Prompt Builder In Depth

WP_AI_Client_Prompt_Builder wraps the SDK's PromptBuilder and provides a fluent, snake_case API. All configuration methods return $this for chaining. Generation methods return a result or WP_Error.

4.1 Content Methods

These methods add content to the current message being built.

with_text( string $text )

Adds a text segment to the current message.

wp_ai_client_prompt()
    ->with_text( 'Translate the following to French:' )
    ->with_text( 'Hello, how are you?' )
    ->generate_text();

with_file( $file, ?string $mimeType = null )

Adds a file (image, document, etc.) to the prompt for multi-modal input.

wp_ai_client_prompt()
    ->with_text( 'Describe this image.' )
    ->with_file( '/path/to/photo.jpg', 'image/jpeg' )
    ->generate_text();

with_function_response( FunctionResponse $functionResponse )

Adds a function response to the message. Used in tool-use loops (see Section 6).

use WordPress\AiClient\Tools\DTO\FunctionResponse;

$response = new FunctionResponse( 'call-id-123', 'wpab__my_plugin__search', $data );

wp_ai_client_prompt()
    ->with_function_response( $response )
    ->generate_text();

with_message_parts( MessagePart ...$parts )

Adds one or more MessagePart objects to the current message for advanced multi-part content.

use WordPress\AiClient\Messages\DTO\MessagePart;

$part = new MessagePart( 'Some text content' );
wp_ai_client_prompt()
    ->with_message_parts( $part )
    ->generate_text();

with_history( Message ...$messages )

Adds previous conversation messages for multi-turn conversations.

use WordPress\AiClient\Messages\DTO\UserMessage;
use WordPress\AiClient\Messages\DTO\ModelMessage;
use WordPress\AiClient\Messages\DTO\MessagePart;

$history = [
    new UserMessage( [ new MessagePart( 'What is WordPress?' ) ] ),
    new ModelMessage( [ new MessagePart( 'WordPress is an open-source CMS...' ) ] ),
];

$text = wp_ai_client_prompt()
    ->with_history( ...$history )
    ->with_text( 'How do I install plugins?' )
    ->generate_text();

4.2 Model Selection

using_model( ModelInterface $model )

Sets a specific model instance for generation.

use WordPress\AiClient\AiClient;

$registry = AiClient::defaultRegistry();
$model    = $registry->getProvider( 'google' )::model( 'gemini-2.0-flash' );

wp_ai_client_prompt( 'Hello' )
    ->using_model( $model )
    ->generate_text();

using_model_preference( ...$preferredModels )

Provides a list of preferred models. The builder evaluates them in order and selects the first available one.

wp_ai_client_prompt( 'Hello' )
    ->using_model_preference( $model_a, $model_b, $model_c )
    ->generate_text();

using_provider( string $providerIdOrClassName )

Restricts generation to a specific provider by ID or class name.

wp_ai_client_prompt( 'Hello' )
    ->using_provider( 'openai' )
    ->generate_text();

using_model_config( ModelConfig $config )

Sets a complete ModelConfig object at once (combines temperature, max tokens, etc.).

use WordPress\AiClient\Providers\Models\DTO\ModelConfig;

$config = ModelConfig::fromArray( [
    'maxTokens'   => 1000,
    'temperature' => 0.5,
] );

wp_ai_client_prompt( 'Hello' )
    ->using_model_config( $config )
    ->generate_text();

4.3 Configuration Methods

All configuration methods return $this for chaining.

Method Parameter Description
using_system_instruction() string $systemInstruction Sets the system instruction / persona
using_max_tokens() int $maxTokens Maximum tokens to generate
using_temperature() float $temperature Randomness (0.0 = deterministic, higher = more creative)
using_top_p() float $topP Nucleus sampling threshold
using_top_k() int $topK Top-k sampling
using_stop_sequences() string ...$stopSequences Sequences that stop generation
using_candidate_count() int $candidateCount Number of candidates to generate
using_presence_penalty() float $presencePenalty Penalize repeated topics
using_frequency_penalty() float $frequencyPenalty Penalize repeated tokens
using_top_logprobs() ?int $topLogprobs = null Number of top log probabilities to return
using_request_options() RequestOptions $options HTTP transport options (timeout, redirects)

Example combining several options:

$text = wp_ai_client_prompt( 'Write a product description for a blue widget.' )
    ->using_system_instruction( 'You are a concise marketing copywriter.' )
    ->using_temperature( 0.7 )
    ->using_max_tokens( 200 )
    ->using_stop_sequences( '---', '###' )
    ->using_presence_penalty( 0.3 )
    ->generate_text();

4.4 Tool / Function Calling

using_function_declarations( FunctionDeclaration ...$functionDeclarations )

Registers raw function declarations that the AI model can call. This is the low-level approach.

use WordPress\AiClient\Tools\DTO\FunctionDeclaration;

$get_weather = new FunctionDeclaration(
    'get_weather',
    'Returns the current weather for a city',
    [
        'type'       => 'object',
        'properties' => [
            'city' => [
                'type'        => 'string',
                'description' => 'The city name',
            ],
        ],
        'required' => [ 'city' ],
    ]
);

$result = wp_ai_client_prompt( 'What is the weather in Paris?' )
    ->using_function_declarations( $get_weather )
    ->generate_text_result();

using_abilities( WP_Ability|string ...$abilities )

Registers WordPress abilities as tool declarations. This is the recommended approach when working with the Abilities API. Accepts WP_Ability objects or ability name strings.

// By name (looked up from the registry):
wp_ai_client_prompt( 'Search for recent posts about AI' )
    ->using_abilities( 'my-plugin/search-posts', 'my-plugin/create-draft' )
    ->generate_text_result();

// By object:
$ability = wp_get_ability( 'my-plugin/search-posts' );
wp_ai_client_prompt( 'Find posts about caching' )
    ->using_abilities( $ability )
    ->generate_text_result();

using_web_search( WebSearch $webSearch )

Enables the model to search the web during generation (provider support varies).

use WordPress\AiClient\Tools\DTO\WebSearch;

$web_search = WebSearch::fromArray( [
    'allowedDomains' => [ 'developer.wordpress.org' ],
] );

wp_ai_client_prompt( 'What are the latest WordPress coding standards?' )
    ->using_web_search( $web_search )
    ->generate_text();

4.5 Output Format

as_json_response( ?array $schema = null )

Configures the prompt to return JSON. Optionally pass a JSON Schema to enforce structure.

$json = wp_ai_client_prompt( 'List 3 colors with hex codes.' )
    ->as_json_response( [
        'type'  => 'object',
        'properties' => [
            'colors' => [
                'type'  => 'array',
                'items' => [
                    'type'       => 'object',
                    'properties' => [
                        'name' => [ 'type' => 'string' ],
                        'hex'  => [ 'type' => 'string' ],
                    ],
                ],
            ],
        ],
    ] )
    ->generate_text();

$data = json_decode( $json, true );

as_output_schema( array $schema )

Sets the output schema without switching to JSON mode. Useful when you want schema validation but not necessarily JSON output.

as_output_modalities( ModalityEnum ...$modalities )

Sets which output types the model should produce.

use WordPress\AiClient\Messages\Enums\ModalityEnum;

wp_ai_client_prompt( 'Describe and draw a sunset.' )
    ->as_output_modalities( ModalityEnum::TEXT, ModalityEnum::IMAGE )
    ->generate_result();

as_output_mime_type( string $mimeType )

Sets the expected output MIME type.

as_output_file_type( FileTypeEnum $fileType )

Sets the output file type (inline base64 or remote URL).

use WordPress\AiClient\Files\Enums\FileTypeEnum;

wp_ai_client_prompt( 'Generate a logo' )
    ->as_output_file_type( FileTypeEnum::INLINE )
    ->generate_image();

4.6 Support Checks

Before attempting generation, you can check whether the configured provider and model support a given capability. Support check methods return bool (or bool|WP_Error for is_supported()).

Method Checks support for
is_supported( ?CapabilityEnum $capability = null ) A specific capability (returns bool|WP_Error)
is_supported_for_text_generation() Text generation
is_supported_for_image_generation() Image generation
is_supported_for_text_to_speech_conversion() Text-to-speech
is_supported_for_speech_generation() Speech generation
is_supported_for_music_generation() Music generation
is_supported_for_video_generation() Video generation
is_supported_for_embedding_generation() Embedding generation
$builder = wp_ai_client_prompt( 'Generate a landscape image.' );

if ( $builder->is_supported_for_image_generation() ) {
    $image = $builder->generate_image();
} else {
    // Fall back or show an error.
    wp_die( 'Image generation is not available with the current provider.' );
}

Note: If the builder is already in an error state or the prompt is prevented by a filter, support check methods return false.

4.7 Generation Methods

Generation methods execute the prompt and return a result or WP_Error.

Text

Method Return Type Description
generate_text() string|WP_Error Returns the generated text directly
generate_texts( ?int $candidateCount = null ) list<string>|WP_Error Returns multiple text candidates
generate_text_result() GenerativeAiResult|WP_Error Returns the full result object for text
// Single text:
$text = wp_ai_client_prompt( 'Hello' )->generate_text();

// Multiple candidates:
$texts = wp_ai_client_prompt( 'Suggest a tagline.' )
    ->generate_texts( 3 );
// $texts = ['Tagline A', 'Tagline B', 'Tagline C']

// Full result object:
$result = wp_ai_client_prompt( 'Hello' )->generate_text_result();
if ( ! is_wp_error( $result ) ) {
    $text       = $result->toText();
    $token_info = $result->getTokenUsage();
}

Images

Method Return Type Description
generate_image() File|WP_Error Returns a single generated image
generate_images( ?int $candidateCount = null ) list<File>|WP_Error Returns multiple generated images
generate_image_result() GenerativeAiResult|WP_Error Returns the full result object for images
$image = wp_ai_client_prompt( 'A cat wearing a WordPress t-shirt' )
    ->generate_image();

if ( ! is_wp_error( $image ) ) {
    // $image is a File DTO with getData(), getMimeType(), etc.
}

Speech / Text-to-Speech

Method Return Type Description
generate_speech() File|WP_Error Generates speech from audio input
generate_speeches( ?int $candidateCount = null ) list<File>|WP_Error Multiple speech outputs
generate_speech_result() GenerativeAiResult|WP_Error Full result for speech
convert_text_to_speech() File|WP_Error Converts text to speech audio
convert_text_to_speeches( ?int $candidateCount = null ) list<File>|WP_Error Multiple text-to-speech outputs
convert_text_to_speech_result() GenerativeAiResult|WP_Error Full result for text-to-speech
$audio = wp_ai_client_prompt( 'Welcome to my WordPress site.' )
    ->convert_text_to_speech();

General

Method Return Type Description
generate_result( ?CapabilityEnum $capability = null ) GenerativeAiResult|WP_Error Generates a result for any capability

The GenerativeAiResult Object

When you use *_result() methods, you get a GenerativeAiResult with these key methods:

$result = wp_ai_client_prompt( 'Hello' )->generate_text_result();

if ( ! is_wp_error( $result ) ) {
    // Text extraction:
    $text  = $result->toText();          // First candidate's text
    $texts = $result->toTexts();         // All candidates' texts

    // File extraction:
    $image = $result->toImageFile();     // First image file
    $audio = $result->toAudioFile();     // First audio file
    $video = $result->toVideoFile();     // First video file

    // Multiple files:
    $images = $result->toImageFiles();
    $audios = $result->toAudioFiles();
    $videos = $result->toVideoFiles();

    // Message access (for tool-use loops):
    $message = $result->toMessage();     // First candidate's Message object
    $messages = $result->toMessages();

    // Metadata:
    $candidates = $result->getCandidates();
    $token_usage = $result->getTokenUsage();
    $provider_meta = $result->getProviderMetadata();
    $model_meta = $result->getModelMetadata();
    $has_multiple = $result->hasMultipleCandidates();
}

4.8 Error Handling Deep Dive

Error State Propagation

The builder uses an "error state" pattern:

  1. If any method in the chain throws an exception, the builder catches it and enters an error state.
  2. Once in an error state, all subsequent configuration methods are no-ops (they return $this silently).
  3. All subsequent support check methods return false.
  4. All subsequent generation methods return the stored WP_Error.

This means you can build a long chain safely without try/catch:

// Even if using_provider() fails, generate_text() returns a WP_Error
// rather than throwing an exception.
$text = wp_ai_client_prompt( 'Hello' )
    ->using_provider( 'nonexistent-provider' )
    ->using_temperature( 0.5 )
    ->generate_text();

if ( is_wp_error( $text ) ) {
    // Handle the error from using_provider()
}

WP_Error Codes

Code Cause
prompt_builder_error An exception was thrown by the SDK (provider error, invalid config, network failure, etc.)
prompt_prevented The wp_ai_client_prevent_prompt filter returned true

The WP_Error data array includes an exception_class key:

if ( is_wp_error( $text ) ) {
    $exception_class = $text->get_error_data()['exception_class'] ?? '';
    // e.g. 'NetworkException', 'InvalidArgumentException', etc.
}

The wp_ai_client_prevent_prompt Filter

This filter fires before any support check or generation method. Return true to block the prompt:

add_filter( 'wp_ai_client_prevent_prompt', function ( bool $prevent, WP_AI_Client_Prompt_Builder $builder ): bool {
    // Example: block AI calls for subscribers.
    if ( ! current_user_can( 'edit_posts' ) ) {
        return true;
    }
    return $prevent;
}, 10, 2 );

When prevented:

  • Support check methods return false.
  • Generation methods return a WP_Error with code prompt_prevented.

5. The Abilities API (Since 6.9)

The Abilities API lets plugins register self-contained units of functionality with defined inputs, outputs, and permissions. These abilities can be executed directly, exposed via the REST API, and provided to AI models as callable tools.

5.1 Concept

An ability is a named operation with:

  • A namespace and name (e.g., my-plugin/search-posts)
  • A human-readable label and description
  • A category for grouping
  • An input schema (JSON Schema) defining expected parameters
  • An output schema (JSON Schema) defining the return structure
  • An execute callback that performs the actual work
  • A permission callback that gates access
  • Metadata including annotations (readonly, destructive, idempotent) and show_in_rest

5.2 Registering Categories

Categories must be registered before the abilities that belong to them. Use the wp_abilities_api_categories_init action:

add_action( 'wp_abilities_api_categories_init', function ( WP_Ability_Categories_Registry $registry ) {
    wp_register_ability_category( 'content-management', [
        'label'       => __( 'Content Management', 'my-plugin' ),
        'description' => __( 'Abilities for managing site content.', 'my-plugin' ),
    ] );
} );

Category registration arguments:

Key Type Required Description
label string Yes Human-readable label
description string Yes Category description
meta array No Additional metadata

5.3 Registering Abilities

Register abilities on the wp_abilities_api_init action:

add_action( 'wp_abilities_api_init', function ( WP_Abilities_Registry $registry ) {
    wp_register_ability( 'my-plugin/search-posts', [
        'label'       => __( 'Search Posts', 'my-plugin' ),
        'description' => __( 'Searches published posts by keyword and returns matching titles and excerpts.', 'my-plugin' ),
        'category'    => 'content-management',

        'input_schema' => [
            'type'       => 'object',
            'properties' => [
                'query' => [
                    'type'        => 'string',
                    'description' => 'The search keyword',
                ],
                'count' => [
                    'type'        => 'integer',
                    'description' => 'Max results to return',
                    'default'     => 5,
                ],
            ],
            'required' => [ 'query' ],
        ],

        'output_schema' => [
            'type'  => 'array',
            'items' => [
                'type'       => 'object',
                'properties' => [
                    'title'   => [ 'type' => 'string' ],
                    'excerpt' => [ 'type' => 'string' ],
                    'url'     => [ 'type' => 'string' ],
                ],
            ],
        ],

        'execute_callback' => function ( array $input ) {
            $posts = get_posts( [
                's'              => $input['query'],
                'posts_per_page' => $input['count'] ?? 5,
                'post_status'    => 'publish',
            ] );

            return array_map( function ( $post ) {
                return [
                    'title'   => get_the_title( $post ),
                    'excerpt' => get_the_excerpt( $post ),
                    'url'     => get_permalink( $post ),
                ];
            }, $posts );
        },

        'permission_callback' => function () {
            return current_user_can( 'read' );
        },

        'meta' => [
            'annotations' => [
                'readonly'    => true,
                'destructive' => false,
                'idempotent'  => true,
            ],
            'show_in_rest' => true,
        ],
    ] );
} );

Full Registration Arguments

Key Type Required Description
label string Yes Human-readable label
description string Yes Description (also used as the AI tool description)
category string Yes Category slug (must already be registered)
execute_callback callable Yes function( $input ): mixed|WP_Error
permission_callback callable Yes function( $input ): bool|WP_Error
input_schema array No JSON Schema for input validation
output_schema array No JSON Schema for output validation
ability_class string No Custom class extending WP_Ability
meta array No Metadata (see below)
Meta Structure
'meta' => [
    'annotations' => [
        'readonly'    => true|false|null,  // Does not modify data
        'destructive' => true|false|null,  // Performs destructive updates
        'idempotent'  => true|false|null,  // Repeated calls have no additional effect
    ],
    'show_in_rest' => true|false,  // Expose via REST API (default: false)
],

Ability Naming Rules

  • Must contain 2 to 4 segments separated by /
  • Only lowercase alphanumeric characters and dashes allowed within segments
  • Pattern: /^[a-z0-9-]+(?:\/[a-z0-9-]+){1,3}$/
  • Examples: my-plugin/search, my-plugin/posts/create, my-plugin/posts/comments/list

5.4 Working with Abilities Programmatically

Check if an Ability Exists

if ( wp_has_ability( 'my-plugin/search-posts' ) ) {
    // Ability is registered.
}

Get an Ability Instance

$ability = wp_get_ability( 'my-plugin/search-posts' );

if ( $ability ) {
    echo $ability->get_name();          // 'my-plugin/search-posts'
    echo $ability->get_label();         // 'Search Posts'
    echo $ability->get_description();
    echo $ability->get_category();      // 'content-management'
    $schema = $ability->get_input_schema();
    $meta   = $ability->get_meta();
}

Get All Registered Abilities

$abilities = wp_get_abilities();
// Returns WP_Ability[]

Execute an Ability Directly

$ability = wp_get_ability( 'my-plugin/search-posts' );

if ( $ability ) {
    $result = $ability->execute( [ 'query' => 'WordPress', 'count' => 3 ] );

    if ( is_wp_error( $result ) ) {
        // Handle error. Common codes:
        // 'ability_invalid_input'       - Input failed schema validation
        // 'ability_invalid_permissions'  - Permission callback denied access
        // 'ability_invalid_output'       - Output failed schema validation
        error_log( $result->get_error_message() );
    } else {
        // $result is the value returned by execute_callback.
    }
}

The execute() method performs these steps in order:

  1. Normalizes input (applies defaults from schema)
  2. Validates input against input_schema
  3. Checks permissions via permission_callback
  4. Fires wp_before_execute_ability action
  5. Calls execute_callback
  6. Validates output against output_schema
  7. Fires wp_after_execute_ability action
  8. Returns the result

5.5 Category Functions Reference

Function Description
wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category Registers a category
wp_unregister_ability_category( string $slug ): ?WP_Ability_Category Unregisters a category
wp_has_ability_category( string $slug ): bool Checks if a category exists
wp_get_ability_category( string $slug ): ?WP_Ability_Category Gets a category
wp_get_ability_categories(): array Gets all categories

6. Connecting Abilities to AI (Function Calling)

The bridge between the Abilities API and the AI Client is the using_abilities() method and the WP_AI_Client_Ability_Function_Resolver class.

6.1 The using_abilities() Method

When you call using_abilities(), the builder:

  1. Resolves each ability (by name or object).
  2. Converts the ability name to a function name using the wpab__ prefix (e.g., my-plugin/search-posts becomes wpab__my-plugin__search-posts).
  3. Creates a FunctionDeclaration using the ability's description and input schema.
  4. Registers these declarations with the underlying SDK builder.

6.2 WP_AI_Client_Ability_Function_Resolver

This class handles the conversion between ability names and function names, and executes abilities from AI function calls.

Name Conversion

Ability name:  my-plugin/search-posts
Function name: wpab__my-plugin__search-posts

The conversion replaces / with __ and prepends the wpab__ prefix.

// Ability name -> Function name:
$fn = WP_AI_Client_Ability_Function_Resolver::ability_name_to_function_name( 'my-plugin/search-posts' );
// 'wpab__my-plugin__search-posts'

// Function name -> Ability name:
$name = WP_AI_Client_Ability_Function_Resolver::function_name_to_ability_name( 'wpab__my-plugin__search-posts' );
// 'my-plugin/search-posts'

Key Static Methods

Method Description
is_ability_call( FunctionCall $call ): bool Checks if a function call uses the wpab__ prefix
execute_ability( FunctionCall $call ): FunctionResponse Executes a single ability from a function call
has_ability_calls( Message $message ): bool Checks if a message contains any ability calls
execute_abilities( Message $message ): Message Executes all ability calls in a message, returns a new message with responses

6.3 Complete Tool-Use Loop Example

When an AI model calls abilities as tools, you need a loop: generate, check for tool calls, execute them, feed results back, and generate again.

use WordPress\AiClient\Results\DTO\GenerativeAiResult;

// Step 1: Set up the prompt with abilities as tools.
$builder = wp_ai_client_prompt( 'Find recent posts about caching and summarize them.' )
    ->using_system_instruction( 'Use the available tools to search for posts, then summarize.' )
    ->using_abilities( 'my-plugin/search-posts' );

// Step 2: Generate the initial result.
$result = $builder->generate_text_result();

if ( is_wp_error( $result ) ) {
    wp_die( $result->get_error_message() );
}

// Step 3: Check if the model wants to call abilities.
$message = $result->toMessage();

if ( WP_AI_Client_Ability_Function_Resolver::has_ability_calls( $message ) ) {
    // Step 4: Execute all ability calls and get a response message.
    $response_message = WP_AI_Client_Ability_Function_Resolver::execute_abilities( $message );

    // Step 5: Continue the conversation with the function responses.
    $final_result = wp_ai_client_prompt()
        ->using_system_instruction( 'Use the available tools to search for posts, then summarize.' )
        ->using_abilities( 'my-plugin/search-posts' )
        ->with_history( $message, $response_message )
        ->generate_text();

    if ( is_wp_error( $final_result ) ) {
        wp_die( $final_result->get_error_message() );
    }

    echo esc_html( $final_result );
} else {
    // The model answered directly without tool calls.
    echo esc_html( $result->toText() );
}

Multi-Turn Tool Loop

For complex tasks, the model may need multiple rounds of tool calls:

$system = 'You are a content assistant. Use tools as needed.';
$abilities = [ 'my-plugin/search-posts', 'my-plugin/create-draft' ];
$history = [];
$max_iterations = 5;

$builder = wp_ai_client_prompt( 'Find posts about performance and draft a summary post.' )
    ->using_system_instruction( $system )
    ->using_abilities( ...$abilities );

for ( $i = 0; $i < $max_iterations; $i++ ) {
    $result = $builder->generate_text_result();

    if ( is_wp_error( $result ) ) {
        error_log( 'AI error: ' . $result->get_error_message() );
        break;
    }

    $message = $result->toMessage();

    if ( ! WP_AI_Client_Ability_Function_Resolver::has_ability_calls( $message ) ) {
        // No more tool calls, output the final text.
        echo esc_html( $result->toText() );
        break;
    }

    // Execute abilities and build next prompt with history.
    $response_message = WP_AI_Client_Ability_Function_Resolver::execute_abilities( $message );
    $history[] = $message;
    $history[] = $response_message;

    $builder = wp_ai_client_prompt()
        ->using_system_instruction( $system )
        ->using_abilities( ...$abilities )
        ->with_history( ...$history );
}

7. REST API Endpoints

The Abilities API registers REST endpoints under the wp-abilities/v1 namespace. All endpoints require the user to have the read capability.

7.1 List Abilities

GET /wp-json/wp-abilities/v1/abilities

Query Parameters:

Parameter Type Default Description
context string view view, edit, or embed
page integer 1 Page number (min: 1)
per_page integer 50 Items per page (1-100)
category string Filter by category slug

Response: Array of ability objects. Only abilities with show_in_rest: true are returned.

Response Headers:

  • X-WP-Total — total number of abilities
  • X-WP-TotalPages — total pages
// JavaScript example:
const response = await fetch( '/wp-json/wp-abilities/v1/abilities?category=content-management', {
    headers: { 'X-WP-Nonce': wpApiSettings.nonce },
} );
const abilities = await response.json();

7.2 Get Single Ability

GET /wp-json/wp-abilities/v1/abilities/{name}

Response Schema:

{
    "name": "my-plugin/search-posts",
    "label": "Search Posts",
    "description": "Searches published posts by keyword.",
    "category": "content-management",
    "input_schema": { "type": "object", "..." },
    "output_schema": { "type": "array", "..." },
    "meta": {
        "annotations": {
            "readonly": true,
            "destructive": false,
            "idempotent": true
        }
    }
}
const response = await fetch( '/wp-json/wp-abilities/v1/abilities/my-plugin/search-posts', {
    headers: { 'X-WP-Nonce': wpApiSettings.nonce },
} );
const ability = await response.json();

7.3 Execute Ability

POST /wp-json/wp-abilities/v1/abilities/{name}/run

The HTTP method is validated against the ability's annotations:

Annotations Allowed Method
readonly: true GET
destructive: true + idempotent: true DELETE
Default (none of the above) POST

Request Body:

{
    "input": {
        "query": "WordPress performance",
        "count": 3
    }
}

Response:

{
    "result": [
        {
            "title": "Speeding Up WordPress",
            "excerpt": "...",
            "url": "https://example.com/speeding-up-wordpress"
        }
    ]
}

Error Responses:

Code Status Cause
rest_ability_not_found 404 Ability doesn't exist or show_in_rest is false
rest_ability_invalid_method 405 HTTP method doesn't match annotations
rest_ability_cannot_execute 403 Permission callback denied access
// Execute a read-only ability with GET:
const response = await fetch(
    '/wp-json/wp-abilities/v1/abilities/my-plugin/search-posts/run?input[query]=WordPress',
    { headers: { 'X-WP-Nonce': wpApiSettings.nonce } }
);

// Execute a write ability with POST:
const response = await fetch(
    '/wp-json/wp-abilities/v1/abilities/my-plugin/create-draft/run',
    {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': wpApiSettings.nonce,
        },
        body: JSON.stringify( {
            input: { title: 'My Draft', content: 'Draft content...' },
        } ),
    }
);
const { result } = await response.json();

7.4 Categories Endpoints

List Categories

GET /wp-json/wp-abilities/v1/categories

Query Parameters: context, page, per_page (same defaults as abilities).

Get Single Category

GET /wp-json/wp-abilities/v1/categories/{slug}

Response Schema:

{
    "slug": "content-management",
    "label": "Content Management",
    "description": "Abilities for managing site content.",
    "meta": {}
}

Response Links: Each category includes an abilities link pointing to /wp-abilities/v1/abilities?category={slug}.

const response = await fetch( '/wp-json/wp-abilities/v1/categories', {
    headers: { 'X-WP-Nonce': wpApiSettings.nonce },
} );
const categories = await response.json();

8. Hooks and Filters Reference

8.1 Abilities API Hooks

wp_abilities_api_categories_init (Action)

Fires when the ability categories registry is initialized. Register your categories here.

add_action( 'wp_abilities_api_categories_init', function ( WP_Ability_Categories_Registry $registry ) {
    wp_register_ability_category( 'my-category', [
        'label'       => 'My Category',
        'description' => 'Custom abilities.',
    ] );
} );

Parameters:

  • $registry (WP_Ability_Categories_Registry) — the categories registry instance

wp_abilities_api_init (Action)

Fires when the abilities registry is initialized. Register your abilities here.

add_action( 'wp_abilities_api_init', function ( WP_Abilities_Registry $registry ) {
    wp_register_ability( 'my-plugin/my-ability', [ /* ... */ ] );
} );

Parameters:

  • $registry (WP_Abilities_Registry) — the abilities registry instance

wp_register_ability_args (Filter)

Filters ability arguments before validation. Useful for modifying abilities registered by other plugins.

add_filter( 'wp_register_ability_args', function ( array $args, string $name ): array {
    // Force all abilities to require edit_posts permission.
    if ( str_starts_with( $name, 'third-party/' ) ) {
        $args['permission_callback'] = function () {
            return current_user_can( 'edit_posts' );
        };
    }
    return $args;
}, 10, 2 );

Parameters:

  • $args (array) — the ability registration arguments
  • $name (string) — the ability name

wp_before_execute_ability (Action)

Fires before an ability is executed, after input validation and permission checks pass.

add_action( 'wp_before_execute_ability', function ( string $ability_name, $input ) {
    error_log( "Executing ability: {$ability_name}" );
}, 10, 2 );

Parameters:

  • $ability_name (string) — the ability name
  • $input (mixed) — the validated input

wp_after_execute_ability (Action)

Fires immediately after an ability finishes executing.

add_action( 'wp_after_execute_ability', function ( string $ability_name, $input, $result ) {
    if ( is_wp_error( $result ) ) {
        error_log( "Ability {$ability_name} failed: {$result->get_error_message()}" );
    }
}, 10, 3 );

Parameters:

  • $ability_name (string) — the ability name
  • $input (mixed) — the input that was provided
  • $result (mixed) — the execution result

8.2 AI Client Hooks

wp_ai_client_default_request_timeout (Filter)

Filters the default HTTP request timeout (in seconds) for AI Client requests. Default: 30.

add_filter( 'wp_ai_client_default_request_timeout', function ( int $timeout ): int {
    return 60; // Allow up to 60 seconds for AI requests.
} );

Parameters:

  • $timeout (int) — timeout in seconds (default: 30)

wp_ai_client_prevent_prompt (Filter)

Filters whether to prevent a prompt from being executed. Fires before any support check or generation method.

add_filter( 'wp_ai_client_prevent_prompt', function ( bool $prevent, WP_AI_Client_Prompt_Builder $builder ): bool {
    // Rate limiting example:
    $user_id = get_current_user_id();
    $count   = (int) get_transient( "ai_calls_{$user_id}" );

    if ( $count >= 50 ) {
        return true; // Block the prompt.
    }

    set_transient( "ai_calls_{$user_id}", $count + 1, HOUR_IN_SECONDS );
    return $prevent;
}, 10, 2 );

Parameters:

  • $prevent (bool) — whether to prevent the prompt (default: false)
  • $builder (WP_AI_Client_Prompt_Builder) — a read-only clone of the builder

wp_ai_client_before_generate_result (Action)

Fires before a prompt is sent to the AI model. Dispatched via the PSR-14 event system.

use WordPress\AiClient\Events\BeforeGenerateResultEvent;

add_action( 'wp_ai_client_before_generate_result', function ( BeforeGenerateResultEvent $event ) {
    $messages   = $event->getMessages();
    $model      = $event->getModel();
    $capability = $event->getCapability();

    error_log( 'Sending prompt to model: ' . $model->metadata()->getId() );
} );

Parameters:

  • $event (BeforeGenerateResultEvent) — contains messages, model, and capability

wp_ai_client_after_generate_result (Action)

Fires after a response is received from the AI model.

use WordPress\AiClient\Events\AfterGenerateResultEvent;

add_action( 'wp_ai_client_after_generate_result', function ( AfterGenerateResultEvent $event ) {
    $result      = $event->getResult();
    $token_usage = $result->getTokenUsage();

    error_log( sprintf(
        'AI response received. Tokens used: %d',
        $token_usage->getTotalTokens()
    ) );
} );

Parameters:

  • $event (AfterGenerateResultEvent) — contains messages, model, capability, and result

9. Architecture Deep Dive

9.1 Adapter Pattern

The AI Client uses four adapter classes to bridge WordPress internals to PSR interfaces expected by the SDK.

PSR-18 HTTP Client: WP_AI_Client_HTTP_Client

Implements ClientInterface (PSR-18) and ClientWithOptionsInterface.

  • Translates PSR-7 RequestInterface objects into wp_safe_remote_request() calls.
  • Converts WordPress HTTP responses back to PSR-7 ResponseInterface objects.
  • Supports timeout and redirect options via RequestOptions.
  • Throws NetworkException on WP_Error from the WordPress HTTP API.

PSR-16 Cache: WP_AI_Client_Cache

Implements CacheInterface (PSR-16).

  • Maps get(), set(), delete(), etc. to WordPress wp_cache_* functions.
  • Uses the wp_ai_client cache group.
  • Supports TTL via both int (seconds) and DateInterval.
  • Uses wp_cache_flush_group() for clear() when available.

PSR-14 Event Dispatcher: WP_AI_Client_Event_Dispatcher

Implements EventDispatcherInterface (PSR-14).

  • Converts SDK event class names to WordPress action hooks.
  • Naming convention: BeforeGenerateResultEventwp_ai_client_before_generate_result.
  • Conversion steps: extract short class name → PascalCase to snake_case → strip _event suffix → prepend wp_ai_client_.

HTTPlug Discovery: WP_AI_Client_Discovery_Strategy

Extends AbstractClientDiscoveryStrategy from the SDK.

  • Registers WP_AI_Client_HTTP_Client with the HTTPlug discovery system.
  • Uses Nyholm\Psr7\Factory\Psr17Factory for PSR-17 message factories.
  • Allows the SDK to automatically discover and use WordPress's HTTP transport.

9.2 PHP AI Client SDK Internals

The SDK lives under wp-includes/php-ai-client/src/ with these key namespaces:

Namespace Purpose
WordPress\AiClient Root: AiClient class, top-level entry
WordPress\AiClient\Builders PromptBuilder, MessageBuilder
WordPress\AiClient\Messages\DTO Message, MessagePart, UserMessage, ModelMessage
WordPress\AiClient\Messages\Enums ModalityEnum, MessageRoleEnum, MessagePartTypeEnum
WordPress\AiClient\Tools\DTO FunctionDeclaration, FunctionCall, FunctionResponse, WebSearch
WordPress\AiClient\Providers ProviderRegistry, AbstractProvider, provider contracts
WordPress\AiClient\Providers\Models\DTO ModelConfig, ModelMetadata
WordPress\AiClient\Providers\Models\Enums CapabilityEnum, OptionEnum
WordPress\AiClient\Providers\Http\DTO RequestOptions, ApiKeyRequestAuthentication
WordPress\AiClient\Results\DTO GenerativeAiResult, Candidate, TokenUsage
WordPress\AiClient\Files\DTO File
WordPress\AiClient\Events BeforeGenerateResultEvent, AfterGenerateResultEvent

Key Enums

CapabilityEnum — capabilities a model can support:

  • TEXT_GENERATION, IMAGE_GENERATION, TEXT_TO_SPEECH_CONVERSION
  • SPEECH_GENERATION, MUSIC_GENERATION, VIDEO_GENERATION
  • EMBEDDING_GENERATION, CHAT_HISTORY

ModalityEnum — input/output modalities:

  • TEXT, DOCUMENT, IMAGE, AUDIO, VIDEO

FileTypeEnum — how file data is delivered:

  • INLINE (base64-encoded), REMOTE (URL reference)

9.3 Provider System

Providers implement ProviderInterface:

interface ProviderInterface {
    public static function metadata(): ProviderMetadata;
    public static function model( string $modelId, ?ModelConfig $modelConfig = null ): ModelInterface;
    public static function availability(): ProviderAvailabilityInterface;
    public static function modelMetadataDirectory(): ModelMetadataDirectoryInterface;
}

Models implement ModelInterface and capability-specific interfaces like TextGenerationModelInterface or ImageGenerationModelInterface.

The ProviderRegistry manages all registered providers, handles authentication, discovers available models, and selects the best model for a given request.

9.4 Registering a Custom Provider (Conceptual)

While the SDK ships with Google, OpenAI, and Anthropic providers, you could conceptually register a custom provider:

// This is a simplified conceptual example.
// A real implementation requires implementing ProviderInterface
// and the associated model interfaces.

use WordPress\AiClient\AiClient;
use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication;

// Get the default registry.
$registry = AiClient::defaultRegistry();

// Register your custom provider class.
$registry->registerProvider( MyCustomProvider::class );

// Set authentication.
$registry->setProviderRequestAuthentication(
    MyCustomProvider::metadata()->getId(),
    new ApiKeyRequestAuthentication( 'my-api-key' )
);

The custom provider class would need to:

  1. Extend AbstractProvider or implement ProviderInterface.
  2. Implement model classes for each capability (text generation, image generation, etc.).
  3. Provide a model metadata directory for model discovery.

10. Complete Plugin Example

Below is a full working plugin that registers abilities and uses the AI Client with tool calling.

<?php
/**
 * Plugin Name: AI Content Assistant
 * Description: Uses WordPress AI Client with abilities to search and create content.
 * Version: 1.0.0
 * Requires at least: 7.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Register the ability category.
 */
add_action( 'wp_abilities_api_categories_init', function () {
    wp_register_ability_category( 'ai-content-assistant', [
        'label'       => __( 'AI Content Assistant', 'ai-content-assistant' ),
        'description' => __( 'Abilities for the AI Content Assistant plugin.', 'ai-content-assistant' ),
    ] );
} );

/**
 * Register abilities.
 */
add_action( 'wp_abilities_api_init', function () {
    // Ability 1: Search posts.
    wp_register_ability( 'ai-content-assistant/search-posts', [
        'label'       => __( 'Search Posts', 'ai-content-assistant' ),
        'description' => __( 'Searches published posts by keyword and returns titles, excerpts, and URLs.', 'ai-content-assistant' ),
        'category'    => 'ai-content-assistant',

        'input_schema' => [
            'type'       => 'object',
            'properties' => [
                'query' => [
                    'type'        => 'string',
                    'description' => 'Search keyword',
                ],
                'count' => [
                    'type'        => 'integer',
                    'description' => 'Maximum number of results',
                    'default'     => 5,
                ],
            ],
            'required' => [ 'query' ],
        ],

        'output_schema' => [
            'type'  => 'array',
            'items' => [
                'type'       => 'object',
                'properties' => [
                    'id'      => [ 'type' => 'integer' ],
                    'title'   => [ 'type' => 'string' ],
                    'excerpt' => [ 'type' => 'string' ],
                    'url'     => [ 'type' => 'string' ],
                ],
            ],
        ],

        'execute_callback' => function ( array $input ) {
            $posts = get_posts( [
                's'              => sanitize_text_field( $input['query'] ),
                'posts_per_page' => min( (int) ( $input['count'] ?? 5 ), 20 ),
                'post_status'    => 'publish',
            ] );

            return array_map( function ( $post ) {
                return [
                    'id'      => $post->ID,
                    'title'   => get_the_title( $post ),
                    'excerpt' => wp_trim_words( get_the_excerpt( $post ), 30 ),
                    'url'     => get_permalink( $post ),
                ];
            }, $posts );
        },

        'permission_callback' => function () {
            return current_user_can( 'read' );
        },

        'meta' => [
            'annotations'  => [
                'readonly'    => true,
                'destructive' => false,
                'idempotent'  => true,
            ],
            'show_in_rest' => true,
        ],
    ] );

    // Ability 2: Create a draft post.
    wp_register_ability( 'ai-content-assistant/create-draft', [
        'label'       => __( 'Create Draft Post', 'ai-content-assistant' ),
        'description' => __( 'Creates a new draft post with the given title and content.', 'ai-content-assistant' ),
        'category'    => 'ai-content-assistant',

        'input_schema' => [
            'type'       => 'object',
            'properties' => [
                'title' => [
                    'type'        => 'string',
                    'description' => 'Post title',
                ],
                'content' => [
                    'type'        => 'string',
                    'description' => 'Post content in HTML',
                ],
            ],
            'required' => [ 'title', 'content' ],
        ],

        'output_schema' => [
            'type'       => 'object',
            'properties' => [
                'post_id'  => [ 'type' => 'integer' ],
                'edit_url' => [ 'type' => 'string' ],
            ],
        ],

        'execute_callback' => function ( array $input ) {
            $post_id = wp_insert_post( [
                'post_title'   => sanitize_text_field( $input['title'] ),
                'post_content' => wp_kses_post( $input['content'] ),
                'post_status'  => 'draft',
                'post_author'  => get_current_user_id(),
            ], true );

            if ( is_wp_error( $post_id ) ) {
                return $post_id;
            }

            return [
                'post_id'  => $post_id,
                'edit_url' => get_edit_post_link( $post_id, 'raw' ),
            ];
        },

        'permission_callback' => function () {
            return current_user_can( 'edit_posts' );
        },

        'meta' => [
            'annotations'  => [
                'readonly'    => false,
                'destructive' => false,
                'idempotent'  => false,
            ],
            'show_in_rest' => true,
        ],
    ] );
} );

/**
 * Handles an AI content assistant request with a tool-use loop.
 *
 * @param string $user_prompt The user's request.
 * @return string|WP_Error The final AI response text or an error.
 */
function ai_content_assistant_run( string $user_prompt ) {
    $system_instruction = 'You are a WordPress content assistant. '
        . 'Use the search-posts tool to find existing content and the create-draft tool to create new posts. '
        . 'Always search before creating to avoid duplicates.';

    $abilities = [
        'ai-content-assistant/search-posts',
        'ai-content-assistant/create-draft',
    ];

    $history        = [];
    $max_iterations = 5;

    $builder = wp_ai_client_prompt( $user_prompt )
        ->using_system_instruction( $system_instruction )
        ->using_abilities( ...$abilities )
        ->using_temperature( 0.3 )
        ->using_max_tokens( 2000 );

    for ( $i = 0; $i < $max_iterations; $i++ ) {
        $result = $builder->generate_text_result();

        if ( is_wp_error( $result ) ) {
            return $result;
        }

        $message = $result->toMessage();

        // If no tool calls, we have the final answer.
        if ( ! WP_AI_Client_Ability_Function_Resolver::has_ability_calls( $message ) ) {
            return $result->toText();
        }

        // Execute all ability calls.
        $response_message = WP_AI_Client_Ability_Function_Resolver::execute_abilities( $message );

        // Add to history and rebuild the prompt.
        $history[] = $message;
        $history[] = $response_message;

        $builder = wp_ai_client_prompt()
            ->using_system_instruction( $system_instruction )
            ->using_abilities( ...$abilities )
            ->using_temperature( 0.3 )
            ->using_max_tokens( 2000 )
            ->with_history( ...$history );
    }

    return new WP_Error(
        'max_iterations_reached',
        __( 'The AI assistant reached the maximum number of tool-call iterations.', 'ai-content-assistant' )
    );
}

// Example usage (e.g., in an admin page handler or AJAX callback):
// $response = ai_content_assistant_run( 'Find posts about caching and create a summary draft.' );

11. Best Practices and Common Patterns

Error Handling

Always check for WP_Error from generation methods:

$text = wp_ai_client_prompt( $prompt )->generate_text();

if ( is_wp_error( $text ) ) {
    // Log the error internally.
    error_log( sprintf( 'AI error [%s]: %s', $text->get_error_code(), $text->get_error_message() ) );

    // Show a user-friendly message.
    wp_admin_notice( __( 'AI generation failed. Please try again later.' ), [ 'type' => 'error' ] );
    return;
}

Use is_supported_for_*() Checks

Before attempting image or speech generation, verify the provider supports it:

$builder = wp_ai_client_prompt( 'A sunset over mountains' );

if ( ! $builder->is_supported_for_image_generation() ) {
    // Offer a text description instead.
    $builder = wp_ai_client_prompt( 'Describe a sunset over mountains in vivid detail.' );
    $text = $builder->generate_text();
} else {
    $image = $builder->generate_image();
}

Ability Naming Conventions

  • Use your plugin slug as the first segment: my-plugin/action
  • Use descriptive verb-noun names: my-plugin/search-posts, my-plugin/create-draft
  • For resource-oriented abilities, use the resource as a middle segment: my-plugin/posts/search

Annotations

Set annotations accurately. They affect REST API method routing:

  • readonly: true → ability is available via GET
  • destructive: true + idempotent: true → ability is available via DELETE
  • Default → ability is available via POST

Permission Callbacks

Always implement permission callbacks. Never return true unconditionally in production:

'permission_callback' => function ( $input ) {
    // Check specific capabilities.
    if ( ! current_user_can( 'edit_posts' ) ) {
        return new WP_Error(
            'rest_forbidden',
            __( 'You do not have permission to use this ability.' ),
            [ 'status' => 403 ]
        );
    }
    return true;
},

Timeout Configuration

AI requests can be slow. Increase the timeout for complex prompts:

// Global default:
add_filter( 'wp_ai_client_default_request_timeout', function () {
    return 60;
} );

// Per-request:
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;

wp_ai_client_prompt( $long_prompt )
    ->using_request_options( RequestOptions::fromArray( [
        RequestOptions::KEY_TIMEOUT => 120,
    ] ) )
    ->generate_text();

Keep System Instructions Clear

Provide focused system instructions that guide the model's behavior:

wp_ai_client_prompt( $user_input )
    ->using_system_instruction(
        'You are a WordPress support assistant. '
        . 'Answer questions about WordPress features, plugins, and best practices. '
        . 'Keep responses concise and include code examples when helpful. '
        . 'If you are unsure, say so rather than guessing.'
    )
    ->generate_text();

12. Troubleshooting

Common Errors

Error Code / Message Cause Solution
prompt_builder_error with NetworkException HTTP request to the AI provider failed Check API key, network connectivity, and timeout settings
prompt_builder_error with InvalidArgumentException Invalid parameter passed to a builder method Check parameter types against the API reference
prompt_prevented The wp_ai_client_prevent_prompt filter returned true Check if a plugin is blocking prompts; verify user capabilities
ability_invalid_input Input failed JSON Schema validation Verify the input matches the ability's input_schema
ability_invalid_permissions Permission callback returned false or WP_Error Check the current user's capabilities
ability_invalid_output Output failed JSON Schema validation Verify the execute callback returns data matching output_schema
rest_ability_not_found (404) REST request for an ability that doesn't exist or isn't exposed Check that the ability is registered and show_in_rest is true
rest_ability_invalid_method (405) Wrong HTTP method for the ability's annotations Use GET for readonly, DELETE for destructive+idempotent, POST otherwise

Debug Strategies

1. Hook into lifecycle events:

use WordPress\AiClient\Events\BeforeGenerateResultEvent;
use WordPress\AiClient\Events\AfterGenerateResultEvent;

add_action( 'wp_ai_client_before_generate_result', function ( BeforeGenerateResultEvent $event ) {
    error_log( 'AI Request - Model: ' . $event->getModel()->metadata()->getId() );
    error_log( 'AI Request - Messages: ' . wp_json_encode( $event->getMessages() ) );
} );

add_action( 'wp_ai_client_after_generate_result', function ( AfterGenerateResultEvent $event ) {
    $usage = $event->getResult()->getTokenUsage();
    error_log( 'AI Response - Tokens: ' . wp_json_encode( $usage->toArray() ) );
} );

2. Log ability execution:

add_action( 'wp_before_execute_ability', function ( string $name, $input ) {
    error_log( "Ability executing: {$name} with input: " . wp_json_encode( $input ) );
}, 10, 2 );

add_action( 'wp_after_execute_ability', function ( string $name, $input, $result ) {
    if ( is_wp_error( $result ) ) {
        error_log( "Ability failed: {$name} - {$result->get_error_message()}" );
    } else {
        error_log( "Ability succeeded: {$name}" );
    }
}, 10, 3 );

3. Check provider configuration:

Verify that at least one API key is configured under Settings > Connectors and that the key is valid.

4. Inspect the WP_Error data:

if ( is_wp_error( $result ) ) {
    $data = $result->get_error_data();
    error_log( 'Exception class: ' . ( $data['exception_class'] ?? 'unknown' ) );
}

13. API Quick Reference

Prompt Builder Methods

Method Category Returns
with_text( string ) Content self
with_file( $file, ?string ) Content self
with_function_response( FunctionResponse ) Content self
with_message_parts( MessagePart... ) Content self
with_history( Message... ) Content self
using_model( ModelInterface ) Model self
using_model_preference( ... ) Model self
using_provider( string ) Model self
using_model_config( ModelConfig ) Config self
using_system_instruction( string ) Config self
using_max_tokens( int ) Config self
using_temperature( float ) Config self
using_top_p( float ) Config self
using_top_k( int ) Config self
using_stop_sequences( string... ) Config self
using_candidate_count( int ) Config self
using_presence_penalty( float ) Config self
using_frequency_penalty( float ) Config self
using_top_logprobs( ?int ) Config self
using_request_options( RequestOptions ) Config self
using_function_declarations( FunctionDeclaration... ) Tools self
using_abilities( WP_Ability|string... ) Tools self
using_web_search( WebSearch ) Tools self
as_json_response( ?array ) Output self
as_output_schema( array ) Output self
as_output_modalities( ModalityEnum... ) Output self
as_output_mime_type( string ) Output self
as_output_file_type( FileTypeEnum ) Output self
is_supported( ?CapabilityEnum ) Check bool|WP_Error
is_supported_for_text_generation() Check bool
is_supported_for_image_generation() Check bool
is_supported_for_text_to_speech_conversion() Check bool
is_supported_for_speech_generation() Check bool
is_supported_for_music_generation() Check bool
is_supported_for_video_generation() Check bool
is_supported_for_embedding_generation() Check bool
generate_text() Generate string|WP_Error
generate_texts( ?int ) Generate list<string>|WP_Error
generate_text_result() Generate GenerativeAiResult|WP_Error
generate_image() Generate File|WP_Error
generate_images( ?int ) Generate list<File>|WP_Error
generate_image_result() Generate GenerativeAiResult|WP_Error
generate_speech() Generate File|WP_Error
generate_speeches( ?int ) Generate list<File>|WP_Error
generate_speech_result() Generate GenerativeAiResult|WP_Error
convert_text_to_speech() Generate File|WP_Error
convert_text_to_speeches( ?int ) Generate list<File>|WP_Error
convert_text_to_speech_result() Generate GenerativeAiResult|WP_Error
generate_result( ?CapabilityEnum ) Generate GenerativeAiResult|WP_Error

Ability Registration Args

Key Type Required
label string Yes
description string Yes
category string Yes
execute_callback callable Yes
permission_callback callable Yes
input_schema array No
output_schema array No
ability_class string No
meta array No

REST Endpoints

Method Route Description
GET /wp-abilities/v1/abilities List abilities
GET /wp-abilities/v1/abilities/{name} Get ability details
GET/POST/DELETE /wp-abilities/v1/abilities/{name}/run Execute ability
GET /wp-abilities/v1/categories List categories
GET /wp-abilities/v1/categories/{slug} Get category details

Hooks

Hook Type Parameters
wp_abilities_api_categories_init Action $registry
wp_abilities_api_init Action $registry
wp_register_ability_args Filter $args, $name
wp_before_execute_ability Action $ability_name, $input
wp_after_execute_ability Action $ability_name, $input, $result
wp_ai_client_default_request_timeout Filter $timeout
wp_ai_client_prevent_prompt Filter $prevent, $builder
wp_ai_client_before_generate_result Action $event
wp_ai_client_after_generate_result Action $event

WP_Error Codes

Code Source Description
prompt_builder_error AI Client SDK exception (network, invalid args, provider error)
prompt_prevented AI Client Blocked by wp_ai_client_prevent_prompt filter
ability_invalid_input Abilities API Input failed schema validation
ability_invalid_output Abilities API Output failed schema validation
ability_invalid_permissions Abilities API Permission callback denied access
ability_invalid_execute_callback Abilities API Invalid execute callback
ability_invalid_permission_callback Abilities API Invalid permission callback
rest_ability_not_found REST API Ability not found or not exposed in REST
rest_ability_invalid_method REST API Wrong HTTP method for ability annotations
rest_ability_cannot_execute REST API REST permission check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment