/ wordpress / restai / includes / class-restai-client.php
class-restai-client.php
  1  <?php
  2  /**
  3   * Thin HTTP client for the RESTai REST API.
  4   *
  5   * @package RESTai
  6   */
  7  
  8  namespace RESTai;
  9  
 10  if ( ! defined( 'ABSPATH' ) ) {
 11  	exit;
 12  }
 13  
 14  class Client {
 15  
 16  	/**
 17  	 * Resolve the base URL from settings.
 18  	 */
 19  	public function base_url() {
 20  		$settings = get_option( 'restai_settings', array() );
 21  		return isset( $settings['url'] ) ? untrailingslashit( $settings['url'] ) : '';
 22  	}
 23  
 24  	/**
 25  	 * Resolve the API key from settings.
 26  	 */
 27  	public function api_key() {
 28  		$settings = get_option( 'restai_settings', array() );
 29  		return isset( $settings['api_key'] ) ? $settings['api_key'] : '';
 30  	}
 31  
 32  	/**
 33  	 * Build standard auth headers.
 34  	 */
 35  	private function auth_headers() {
 36  		$key = $this->api_key();
 37  		if ( empty( $key ) ) {
 38  			return array();
 39  		}
 40  		return array( 'Authorization' => 'Bearer ' . $key );
 41  	}
 42  
 43  	/**
 44  	 * Run a GET request.
 45  	 *
 46  	 * @param string $path
 47  	 * @param array  $args extra wp_remote args.
 48  	 * @return array|\WP_Error
 49  	 */
 50  	public function get( $path, $args = array() ) {
 51  		return $this->request( 'GET', $path, null, $args );
 52  	}
 53  
 54  	/**
 55  	 * Run a POST request with a JSON body.
 56  	 *
 57  	 * @param string $path
 58  	 * @param mixed  $body
 59  	 * @param array  $args
 60  	 * @return array|\WP_Error
 61  	 */
 62  	public function post( $path, $body = null, $args = array() ) {
 63  		return $this->request( 'POST', $path, $body, $args );
 64  	}
 65  
 66  	/**
 67  	 * Run a PATCH request with a JSON body.
 68  	 */
 69  	public function patch( $path, $body = null, $args = array() ) {
 70  		return $this->request( 'PATCH', $path, $body, $args );
 71  	}
 72  
 73  	/**
 74  	 * Run a DELETE request.
 75  	 */
 76  	public function delete( $path, $args = array() ) {
 77  		return $this->request( 'DELETE', $path, null, $args );
 78  	}
 79  
 80  	/**
 81  	 * Single dispatch point — handles auth, JSON encoding, error normalisation.
 82  	 *
 83  	 * @param string $method
 84  	 * @param string $path
 85  	 * @param mixed  $body
 86  	 * @param array  $extra
 87  	 * @return array|\WP_Error
 88  	 */
 89  	private function request( $method, $path, $body = null, $extra = array() ) {
 90  		$base = $this->base_url();
 91  		if ( empty( $base ) ) {
 92  			return new \WP_Error( 'restai_no_url', __( 'RESTai URL is not configured.', 'restai' ) );
 93  		}
 94  
 95  		$url = $base . '/' . ltrim( $path, '/' );
 96  
 97  		$args = wp_parse_args(
 98  			$extra,
 99  			array(
100  				'method'  => $method,
101  				'timeout' => 120,
102  				'headers' => array_merge(
103  					array( 'Content-Type' => 'application/json' ),
104  					$this->auth_headers()
105  				),
106  			)
107  		);
108  
109  		if ( null !== $body ) {
110  			$args['body'] = wp_json_encode( $body );
111  		}
112  
113  		$response = wp_remote_request( $url, $args );
114  
115  		if ( is_wp_error( $response ) ) {
116  			return $response;
117  		}
118  
119  		$status = wp_remote_retrieve_response_code( $response );
120  		$raw    = wp_remote_retrieve_body( $response );
121  		$data   = json_decode( $raw, true );
122  
123  		if ( $status >= 400 ) {
124  			$detail = is_array( $data ) && isset( $data['detail'] ) ? $data['detail'] : $raw;
125  			return new \WP_Error(
126  				'restai_http_' . $status,
127  				is_string( $detail ) ? $detail : wp_json_encode( $detail ),
128  				array( 'status' => $status )
129  			);
130  		}
131  
132  		return $data;
133  	}
134  
135  	/**
136  	 * Convenience: ask a project a one-shot question.
137  	 *
138  	 * RESTai project routes (`/projects/{projectID}/...`) are typed as `int`
139  	 * server-side, so callers MUST pass an integer project id — names will be
140  	 * rejected with HTTP 422.
141  	 *
142  	 * @param int    $project_id
143  	 * @param string $question
144  	 * @return string|\WP_Error  the answer text, or WP_Error.
145  	 */
146  	public function ask( $project_id, $question ) {
147  		$id = (int) $project_id;
148  		if ( $id <= 0 ) {
149  			return new \WP_Error( 'restai_bad_project_id', __( 'A valid integer project id is required.', 'restai' ) );
150  		}
151  		$resp = $this->post(
152  			'projects/' . $id . '/question',
153  			array( 'question' => $question )
154  		);
155  		if ( is_wp_error( $resp ) ) {
156  			return $resp;
157  		}
158  		return isset( $resp['answer'] ) ? $resp['answer'] : '';
159  	}
160  
161  	/**
162  	 * Quick health-check used by the settings page.
163  	 */
164  	public function ping() {
165  		return $this->get( 'auth/whoami' );
166  	}
167  }