Skip to content

Conversation

Ref34t
Copy link
Contributor

@Ref34t Ref34t commented Oct 8, 2025

Expand HTTP Request Controls

Closes #99 – Introduces transport-level request option support and richer HTTP error reporting across the PHP AI Client SDK.

Overview

Adds a first-class RequestOptions DTO that flows from fluent builder usage down to PSR-7 conversions, so callers can specify timeouts and redirect policies per request. While wiring that through the stack, the transporter now wraps PSR-18 network failures in our exception hierarchy, and ResponseUtil translates non-2xx status codes into semantic exceptions with contextual messages. Documentation highlights the new configuration entry point to keep implementers aligned.

What's Included

Request Configuration

  • RequestOptions DTO with validation, immutability helpers, JSON schema definition, and defaults factory.
  • Request DTO gains options support, serialization, deserialization, and fromPsrRequest for transporter error wrapping.
  • Example usage in README.md so developers discover timeout and redirect controls quickly.

Tests

  • Comprehensive PHPUnit coverage for RequestOptions (constructors, validation paths, mutators, serialization, schema).
  • Updated ResponseUtilTest to assert new exception types, status-code mappings, and extracted
    error details.

Copy link

github-actions bot commented Oct 8, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @mohamed.khaled@9hdigital.com.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: mohamed.khaled@9hdigital.com.

Co-authored-by: felixarntz <flixos90@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@Ref34t Ref34t force-pushed the feature/include-request-options branch 4 times, most recently from f31c78e to deddb7d Compare October 8, 2025 23:00
@Ref34t Ref34t force-pushed the feature/include-request-options branch from deddb7d to 71306f1 Compare October 8, 2025 23:02
Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ref34t Thank you for working on this! It's a bit premature given the ongoing discussion, but a large portion of your PR is on the right track, which is great.

Comment on lines +81 to +96
### Configuring request options

You can configure HTTP transport options like timeout and maximum redirects using the `RequestOptions` DTO:

```php
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;

// Set custom timeout for long-running requests
$options = new RequestOptions(120, 10);

// Or use defaults and modify
$options = RequestOptions::defaults()->withTimeout(60);
```

For implementation ideas in different environments (WordPress, Guzzle, cURL), check the transporter-specific examples in the SDK source and tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems far too specific to document in the overall README.md. Let's not add any documentation for this yet, because we're still missing an overall structure for documentation.

Comment on lines +40 to +48
/**
* Maximum allowed timeout in seconds (1 hour).
*/
public const MAX_TIMEOUT = 3600;

/**
* Maximum allowed redirects.
*/
public const MAX_REDIRECTS = 100;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale for these values? They seem a bit arbitrary to me. I'd say let's not validate this, it's on the developer to choose reasonable values. Validating that every value is a positive integer should be sufficient.

Comment on lines +30 to +38
/**
* Default timeout in seconds.
*/
public const DEFAULT_TIMEOUT = 30;

/**
* Default maximum number of redirects.
*/
public const DEFAULT_MAX_REDIRECTS = 5;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should set any defaults. Every request options should either have a concrete value, or be null (the default), in which case it should be ignored. All of these options are optional by definition, and if not specified, whatever the default HTTP client uses will be applied.

Comment on lines +60 to +70
/**
* Constructor.
*
* @since n.e.x.t
*
* @param int|null $timeout The request timeout in seconds.
* @param int|null $max_redirects The maximum number of redirects to follow.
*
* @throws InvalidArgumentException If timeout or max_redirects is invalid.
*/
public function __construct(?int $timeout = null, ?int $max_redirects = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a constructor with named arguments, can you adjust this class to be more in line with how https://github.com/WordPress/php-ai-client/blob/trunk/src/Providers/Models/DTO/ModelConfig.php works? We have to anticipate more and more request options in the future (think e.g. user_agent, streaming, ...), and an ordered list of parameters isn't suitable for that.

Let's for now keep only supporting timeout and max_redirects like you have it, but we'll need to architect this in a way that it could easily support 20 different options too, e.g. with setter and getter methods.

Comment on lines +118 to +168
/**
* Returns a new instance with the specified timeout.
*
* @since n.e.x.t
*
* @param int|null $timeout The timeout in seconds.
* @return self A new instance with the timeout.
*
* @throws InvalidArgumentException If timeout is invalid.
*/
public function withTimeout(?int $timeout): self
{
if ($timeout !== null && ($timeout < 0 || $timeout > self::MAX_TIMEOUT)) {
throw new InvalidArgumentException(
sprintf(
'Timeout must be between 0 and %d seconds.',
self::MAX_TIMEOUT
)
);
}

$new = clone $this;
$new->timeout = $timeout;
return $new;
}

/**
* Returns a new instance with the specified max redirects.
*
* @since n.e.x.t
*
* @param int|null $maxRedirects The maximum number of redirects.
* @return self A new instance with the max redirects.
*
* @throws InvalidArgumentException If maxRedirects is invalid.
*/
public function withMaxRedirects(?int $maxRedirects): self
{
if ($maxRedirects !== null && ($maxRedirects < 0 || $maxRedirects > self::MAX_REDIRECTS)) {
throw new InvalidArgumentException(
sprintf(
'Max redirects must be between 0 and %d.',
self::MAX_REDIRECTS
)
);
}

$new = clone $this;
$new->max_redirects = $maxRedirects;
return $new;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, let's use setter and getter methods, in line with ModelConfig.

{
$psr7Request = $this->convertToPsr7Request($request);

// PSR-18 clients ignore per-request RequestOptions; custom transporters must apply them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #99 (comment) for more context on how we should support this.

* @throws RuntimeException If the response has an invalid status code.
*/
public static function throwIfNotSuccessful(Response $response): void
public static function throwIfNotSuccessful(Response $response, ?Request $request = null): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my above comment, this seems unrelated? Let's try to focus individual PRs on individual problems, otherwise code reviewing becomes difficult and it can hold up work that otherwise would already be mergeable :)

}

throw new \RuntimeException(
throw new RuntimeException(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! This one's definitely worth keeping, a simple enough fix :)

$this->expectException(ClientException::class);
$this->expectExceptionCode(400);
$this->expectExceptionMessage('Bad Request (400)');
$this->expectExceptionMessageMatches(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, the changes in this test file should be reverted, the ResponseUtil class doesn't need any updates in regards to supporting request options.

string $uri,
array $headers = [],
$data = null,
?RequestOptions $options = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some open discussion in #99 (comment) on whether this is the preferred approach, so please feel free to share your thoughts there. I'm only flagging this here - maybe what you have here is what we should be doing, but it's still undecided.

@felixarntz felixarntz added this to the 0.2.0 milestone Oct 9, 2025
@felixarntz felixarntz added the [Type] Enhancement A suggestion for improvement. label Oct 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support defining request options

2 participants