Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
485cd6a
feat: starts client adapter work
JasonTheAdams Aug 30, 2025
182e32a
Merge branch 'trunk' into http-adapter
JasonTheAdams Sep 2, 2025
51a5130
Merge branch 'trunk' into http-adapter
JasonTheAdams Sep 9, 2025
c06c468
refactor: rename file to use PSR-4 autoloading
JasonTheAdams Sep 9, 2025
845407a
chore: adds WordPress stubs to PHPStan
JasonTheAdams Sep 9, 2025
d0b9208
fix: corrects type issues
JasonTheAdams Sep 9, 2025
d7ef812
chore: cleans up phpstan file
JasonTheAdams Sep 9, 2025
f7ec38f
feat: adds nyholm/psr7 package for psr17 factory
JasonTheAdams Sep 9, 2025
8010654
feat: adds HTTPlug discovery strategy
JasonTheAdams Sep 9, 2025
4e0247f
feat: adds PSR17 discoverability
JasonTheAdams Sep 9, 2025
3998eb7
refactor: renames class
JasonTheAdams Sep 22, 2025
581de9e
refactor: removes stream args
JasonTheAdams Sep 22, 2025
5c66fde
feat: updates to PHP AI Client 0.2.0
JasonTheAdams Oct 21, 2025
4e80b89
feat: implements ClientWithOptionsInterface
JasonTheAdams Oct 21, 2025
de7de24
refactor: switches to using NetworkException
JasonTheAdams Oct 21, 2025
7943ed6
refactor: cleans up unnecessary conditions code
JasonTheAdams Nov 5, 2025
7c5d022
Merge branch 'trunk' into http-adapter
felixarntz Nov 7, 2025
990111a
Use consistent YAML formatting in PHPStan config.
felixarntz Nov 7, 2025
acd0e8b
Fix PHPStan errors after updates.
felixarntz Nov 7, 2025
a68e34c
Temp folder rename to fix incorrect casing.
felixarntz Nov 7, 2025
bf54101
Fix folder name.
felixarntz Nov 7, 2025
167aeb6
Fix PHP warning.
felixarntz Nov 7, 2025
c50d84b
Fix bug in HTTP client discovery implementation.
felixarntz Nov 7, 2025
a8aaf95
Wire up HTTP client discovery implementation.
felixarntz Nov 7, 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
187 changes: 187 additions & 0 deletions includes/http/class-wp-ai-client-client-adapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php
/**
* WordPress AI Client HTTP Client Adapter
*
* @package WordPress\AI_Client
* @since 1.0.0
*/

namespace WordPress\AI_Client\HTTP;

use Psr\Http\Client\ClientInterface;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;

/**
* PSR-18 HTTP Client adapter using WordPress HTTP API
*
* This adapter allows WordPress HTTP functions to be used
* as a PSR-18 compliant HTTP client.
*
* @since 1.0.0
*/
class WP_AI_Client_Client_Adapter implements ClientInterface {

/**
* Response factory instance.
*
* @var ResponseFactoryInterface
*/
private $response_factory;

/**
* Stream factory instance.
*
* @var StreamFactoryInterface
*/
private $stream_factory;

/**
* Constructor.
*
* @param ResponseFactoryInterface $response_factory PSR-17 Response factory.
* @param StreamFactoryInterface $stream_factory PSR-17 Stream factory.
*/
public function __construct( ResponseFactoryInterface $response_factory, StreamFactoryInterface $stream_factory ) {
$this->response_factory = $response_factory;
$this->stream_factory = $stream_factory;
}

/**
* Sends a PSR-7 request and returns a PSR-7 response.
*
* @param RequestInterface $request The PSR-7 request.
*
* @return ResponseInterface The PSR-7 response.
*
* @throws ClientExceptionInterface If an error happens while processing the request.
*/
public function sendRequest( RequestInterface $request ): ResponseInterface {
$args = $this->prepare_wp_args( $request );
$url = (string) $request->getUri();

$response = \wp_remote_request( $url, $args );

Check failure on line 66 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Function wp_remote_request not found.

if ( \is_wp_error( $response ) ) {

Check failure on line 68 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Function is_wp_error not found.
throw new WP_AI_Client_Network_Exception(
$response->get_error_message(),

Check failure on line 70 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Parameter #1 $message of class WordPress\AI_Client\HTTP\WP_AI_Client_Network_Exception constructor expects string, mixed given.

Check failure on line 70 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Cannot call method get_error_message() on mixed.
$request,
null,
$response->get_error_code() ? (int) $response->get_error_code() : 0

Check failure on line 73 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Cannot cast mixed to int.

Check failure on line 73 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Cannot call method get_error_code() on mixed.

Check failure on line 73 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Cannot call method get_error_code() on mixed.
);
}

return $this->create_psr_response( $response );

Check failure on line 77 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Parameter #1 $wp_response of method WordPress\AI_Client\HTTP\WP_AI_Client_Client_Adapter::create_psr_response() expects array, mixed given.
}

/**
* Prepare WordPress HTTP API arguments from PSR-7 request.
*
* @param RequestInterface $request The PSR-7 request.
*
* @return array WordPress HTTP API arguments.
*/
private function prepare_wp_args( RequestInterface $request ): array {

Check failure on line 87 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Method WordPress\AI_Client\HTTP\WP_AI_Client_Client_Adapter::prepare_wp_args() return type has no value type specified in iterable type array.
$args = array(
'method' => $request->getMethod(),
'headers' => $this->prepare_headers( $request ),
'body' => $this->prepare_body( $request ),
'timeout' => 30,
'redirection' => 5,
'httpversion' => $request->getProtocolVersion(),
'blocking' => true,
);

// Handle streaming requests if needed.
if ( $request->hasHeader( 'X-Stream' ) ) {
$args['stream'] = true;
$args['filename'] = $request->getHeaderLine( 'X-Stream-Filename' );
}

return $args;
}

/**
* Prepare headers for WordPress HTTP API.
*
* @param RequestInterface $request The PSR-7 request.
*
* @return array Headers array for WordPress HTTP API.
*/
private function prepare_headers( RequestInterface $request ): array {

Check failure on line 114 in includes/http/class-wp-ai-client-client-adapter.php

View workflow job for this annotation

GitHub Actions / PHP

Method WordPress\AI_Client\HTTP\WP_AI_Client_Client_Adapter::prepare_headers() return type has no value type specified in iterable type array.
$headers = array();

foreach ( $request->getHeaders() as $name => $values ) {
// Skip pseudo headers used for streaming.
if ( strpos( $name, 'X-Stream' ) === 0 ) {
continue;
}

// WordPress expects headers as name => value pairs.
$headers[ $name ] = implode( ', ', $values );
}

return $headers;
}

/**
* Prepare request body for WordPress HTTP API.
*
* @param RequestInterface $request The PSR-7 request.
*
* @return string|null The request body.
*/
private function prepare_body( RequestInterface $request ): ?string {
$body = $request->getBody();

if ( $body->getSize() === 0 ) {
return null;
}

// Rewind the stream to ensure we read from the beginning.
if ( $body->isSeekable() ) {
$body->rewind();
}

return (string) $body;
}

/**
* Create PSR-7 response from WordPress HTTP response.
*
* @param array $wp_response WordPress HTTP API response array.
*
* @return ResponseInterface PSR-7 response.
*/
private function create_psr_response( array $wp_response ): ResponseInterface {
$status_code = \wp_remote_retrieve_response_code( $wp_response );
$reason_phrase = \wp_remote_retrieve_response_message( $wp_response );
$headers = \wp_remote_retrieve_headers( $wp_response );
$body = \wp_remote_retrieve_body( $wp_response );

// Create the PSR-7 response.
$response = $this->response_factory->createResponse( $status_code, $reason_phrase );

// Add headers to response.
if ( $headers instanceof \WP_HTTP_Requests_Response ) {
$headers = $headers->get_headers();
}

if ( is_array( $headers ) || $headers instanceof \ArrayAccess ) {
foreach ( $headers as $name => $value ) {
$response = $response->withHeader( $name, $value );
}
}

// Set the response body.
if ( ! empty( $body ) ) {
$stream = $this->stream_factory->createStream( $body );
$response = $response->withBody( $stream );
}

return $response;
}
}
49 changes: 49 additions & 0 deletions includes/http/class-wp-ai-client-network-exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* WordPress AI Client Network Exception
*
* @package WordPress\AI_Client
* @since 1.0.0
*/

namespace WordPress\AI_Client\HTTP;

use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Message\RequestInterface;

/**
* Network exception for WordPress HTTP Client adapter.
*
* @since 1.0.0
*/
class WP_AI_Client_Network_Exception extends \RuntimeException implements NetworkExceptionInterface {

/**
* The request that caused the exception.
*
* @var RequestInterface
*/
private $request;

/**
* Constructor.
*
* @param string $message Exception message.
* @param RequestInterface $request The request that caused the exception.
* @param \Throwable|null $previous Previous exception.
* @param int $code Exception code.
*/
public function __construct( string $message, RequestInterface $request, ?\Throwable $previous = null, int $code = 0 ) {
parent::__construct( $message, $code, $previous );
$this->request = $request;
}

/**
* Returns the request.
*
* @return RequestInterface
*/
public function getRequest(): RequestInterface {
return $this->request;
}
}