Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1479dc5
feat: adds tools for importing PHP AI Client
JasonTheAdams Feb 5, 2026
2c842f1
feat: adds php-ai-client to includes
JasonTheAdams Feb 5, 2026
99efae9
feat: adds ai client
JasonTheAdams Feb 6, 2026
1c07c3e
refactor: moves prompt builder and renames directory
JasonTheAdams Feb 6, 2026
23f1af0
fix: handles support methods in an error state
JasonTheAdams Feb 6, 2026
42197b5
refactor: namespaces PSR classes and corrects versions
JasonTheAdams Feb 6, 2026
8a9d2c6
feat: adds wp_ai_client_prompt function
JasonTheAdams Feb 7, 2026
56c6873
refactor: corrects formatting issues
JasonTheAdams Feb 7, 2026
a5bd792
refactor: adds and runs third-party tree-shaking
JasonTheAdams Feb 11, 2026
242f9f9
test: corrects PHP 8.5 compatibility
JasonTheAdams Feb 11, 2026
7caa159
test: corrects formatting issues
JasonTheAdams Feb 11, 2026
0edbfef
test: adjusts reflection accessibility for PHP compatibility
JasonTheAdams Feb 11, 2026
ebbdc54
refactor: removes unnecessary polyfills
JasonTheAdams Feb 11, 2026
278f753
fix: adds missing translation functions and comments
JasonTheAdams Feb 11, 2026
85b1916
test: adds ai client util tests
JasonTheAdams Feb 11, 2026
0e78c62
Adjust path filtering for code coverage reports.
desrosj Feb 12, 2026
1e2d52c
Merge branch 'trunk' into add/wp-ai-client
felixarntz Feb 14, 2026
9626b32
chore: explicitly lays out $prompt types
JasonTheAdams Feb 16, 2026
62b33aa
feat: locks in only supported stream from file mode
JasonTheAdams Feb 19, 2026
52d4963
refactor: simplifies to using array_find
JasonTheAdams Feb 19, 2026
472f69f
test: adds Prompt_Builder snake case test
JasonTheAdams Feb 20, 2026
b4f9bd7
test: fixes using_abilities tests
JasonTheAdams Feb 20, 2026
9c3b25e
fix: correctly identifies falsey cached values
JasonTheAdams Feb 20, 2026
d708bd2
refactor: moves function resolver and renames ai client folder
JasonTheAdams Feb 20, 2026
97f8598
refactor: switches to wp_safe_remote_request
JasonTheAdams Feb 20, 2026
9e0ebcc
refactor: uses str_starts_with to simplify
JasonTheAdams Feb 20, 2026
76bc7ba
Merge branch 'trunk' into add/wp-ai-client
felixarntz Feb 20, 2026
9364aca
Move AI client initialization out of require file block.
felixarntz Feb 20, 2026
87c0400
Add correct ticket annotations for tests.
felixarntz Feb 20, 2026
d97a86a
Translate messages from failed ability lookup or execution.
felixarntz Feb 20, 2026
582d0d0
Remove unnecessary X-Stream header exclusion.
felixarntz Feb 20, 2026
9098a6e
Yoda.
felixarntz Feb 20, 2026
5f3c5be
Mark infra classes as private.
felixarntz Feb 20, 2026
00ef1b8
Clean up abilities during testing.
felixarntz Feb 20, 2026
0e8a5dc
Make function_name_to_ability_name public.
felixarntz Feb 20, 2026
e8a2a2f
Add warning if invalid ability slug is passed to using_abilities.
felixarntz Feb 20, 2026
0774f6f
feat: always clones php ai client
JasonTheAdams Feb 20, 2026
c58351d
feat: updates PHP AI Client to 1.1.0
JasonTheAdams Feb 20, 2026
7f94810
refactor: uses new PHP AI Client http discovery system
JasonTheAdams Feb 20, 2026
76af7a8
test: fixes failing test
JasonTheAdams Feb 20, 2026
b5baa00
refactor: moves all ai-client classes into ai-client directory
JasonTheAdams Feb 20, 2026
6fa12cb
chore: adds php ai client tool readme
JasonTheAdams Feb 20, 2026
c6880e1
Revert temporary change to see full test coverage.
felixarntz Feb 20, 2026
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
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<exclude-pattern>/src/wp-includes/js/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/PHPMailer/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/Requests/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/php-ai-client/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/SimplePie/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/sodium_compat/*</exclude-pattern>
<exclude-pattern>/src/wp-includes/Text/*</exclude-pattern>
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
<directory suffix=".php">src/wp-includes/IXR</directory>
<directory suffix=".php">src/wp-includes/PHPMailer</directory>
<directory suffix=".php">src/wp-includes/Requests</directory>
<directory suffix=".php">src/wp-includes/php-ai-client</directory>
<directory suffix=".php">src/wp-includes/ai-client-utils</directory>
<directory suffix=".php">src/wp-includes/SimplePie</directory>
<directory suffix=".php">src/wp-includes/sodium_compat</directory>
<directory suffix=".php">src/wp-includes/Text</directory>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<?php
/**
* WP AI Client: WP_AI_Client_Ability_Function_Resolver class
*
* @package WordPress
* @subpackage AI
* @since 7.0.0
*/

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\DTO\UserMessage;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionResponse;

/**
* Resolves and executes WordPress Abilities API function calls from AI models.
*
* @since 7.0.0
*/
class WP_AI_Client_Ability_Function_Resolver {

/**
* Prefix used to identify ability function calls.
*
* @since 7.0.0
* @var string
*/
private const ABILITY_PREFIX = 'wpab__';

/**
* Checks if a function call is an ability call.
*
* @since 7.0.0
*
* @param FunctionCall $call The function call to check.
* @return bool True if the function call is an ability call, false otherwise.
*/
public static function is_ability_call( FunctionCall $call ): bool {
$name = $call->getName();
if ( null === $name ) {
return false;
}

return str_starts_with( $name, self::ABILITY_PREFIX );
}

/**
* Executes a WordPress ability from a function call.
*
* @since 7.0.0
*
* @param FunctionCall $call The function call to execute.
* @return FunctionResponse The response from executing the ability.
*/
public static function execute_ability( FunctionCall $call ): FunctionResponse {
$function_name = $call->getName() ?? 'unknown';
$function_id = $call->getId() ?? 'unknown';

if ( ! self::is_ability_call( $call ) ) {
return new FunctionResponse(
$function_id,
$function_name,
array(
'error' => 'Not an ability function call',
'code' => 'invalid_ability_call',
)
);
}

$ability_name = self::function_name_to_ability_name( $function_name );
$ability = wp_get_ability( $ability_name );

if ( ! $ability instanceof WP_Ability ) {
return new FunctionResponse(
$function_id,
$function_name,
array(
'error' => sprintf( 'Ability "%s" not found', $ability_name ),
'code' => 'ability_not_found',
)
);
}

$args = $call->getArgs();
$result = $ability->execute( ! empty( $args ) ? $args : null );

if ( is_wp_error( $result ) ) {
return new FunctionResponse(
$function_id,
$function_name,
array(
'error' => $result->get_error_message(),
'code' => $result->get_error_code(),
'data' => $result->get_error_data(),
)
);
}

return new FunctionResponse(
$function_id,
$function_name,
$result
);
}

/**
* Checks if a message contains any ability function calls.
*
* @since 7.0.0
*
* @param Message $message The message to check.
* @return bool True if the message contains ability calls, false otherwise.
*/
public static function has_ability_calls( Message $message ): bool {
foreach ( $message->getParts() as $part ) {
if ( $part->getType()->isFunctionCall() ) {
$function_call = $part->getFunctionCall();
if ( $function_call instanceof FunctionCall && self::is_ability_call( $function_call ) ) {
return true;
}
}
}

return false;
}

/**
* Executes all ability function calls in a message.
*
* @since 7.0.0
*
* @param Message $message The message containing function calls.
* @return Message A new message with function responses.
*/
public static function execute_abilities( Message $message ): Message {
$response_parts = array();

foreach ( $message->getParts() as $part ) {
if ( $part->getType()->isFunctionCall() ) {
$function_call = $part->getFunctionCall();
if ( $function_call instanceof FunctionCall ) {
$function_response = self::execute_ability( $function_call );
$response_parts[] = new MessagePart( $function_response );
}
}
}

return new UserMessage( $response_parts );
}

/**
* Converts an ability name to a function name.
*
* Transforms "tec/create_event" to "wpab__tec__create_event".
*
* @since 7.0.0
*
* @param string $ability_name The ability name to convert.
* @return string The function name.
*/
public static function ability_name_to_function_name( string $ability_name ): string {
return self::ABILITY_PREFIX . str_replace( '/', '__', $ability_name );
}

/**
* Converts a function name to an ability name.
*
* Transforms "wpab__tec__create_event" to "tec/create_event".
*
* @since 7.0.0
*
* @param string $function_name The function name to convert.
* @return string The ability name.
*/
private static function function_name_to_ability_name( string $function_name ): string {
$without_prefix = substr( $function_name, strlen( self::ABILITY_PREFIX ) );

return str_replace( '__', '/', $without_prefix );
}
}
Loading
Loading