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 }