PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.5
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.5
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
← All changes | _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-ai.php +179 -2 12.4.2 → 16.3-a.5 View file →
@@ -5,8 +5,14 @@
5 5 * @package automattic/jetpack
6 6 * @since 11.8
7 7 */
8 8
9 +use Automattic\Jetpack\Connection\Client;
10 +
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit( 0 );
13 +}
14 +
9 15 /**
10 16 * Class WPCOM_REST_API_V2_Endpoint_AI
11 17 */
12 18 class WPCOM_REST_API_V2_Endpoint_AI extends WP_REST_Controller {
@@ -30,21 +36,37 @@
30 36 public function __construct() {
31 37 $this->is_wpcom = true;
32 38 $this->wpcom_is_wpcom_only_endpoint = true;
33 39
40 + add_action( 'rest_api_init', array( $this, 'maybe_register_routes' ) );
41 + }
42 +
43 + /**
44 + * Register routes on `rest_api_init`, gating on the AI feature state.
45 + *
46 + * The Jetpack_AI_Helper checks (which load the helper and instantiate
47 + * Search/Connection classes) run here rather than in the constructor so that
48 + * code is only loaded when the REST API is actually in use, not on every
49 + * front-end, cron, or login request.
50 + */
51 + public function maybe_register_routes() {
34 52 if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
35 53 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
36 54 }
37 55
38 56 // Register routes that don't require Jetpack AI to be enabled.
39 - add_action( 'rest_api_init', array( $this, 'register_basic_routes' ) );
57 + $this->register_basic_routes();
40 58
59 + if ( Jetpack_AI_Helper::is_ai_chat_enabled() ) {
60 + $this->register_ai_chat_routes();
61 + }
62 +
41 63 if ( ! \Jetpack_AI_Helper::is_enabled() ) {
42 64 return;
43 65 }
44 66
45 67 // Register routes that require Jetpack AI to be enabled.
46 - add_action( 'rest_api_init', array( $this, 'register_routes' ) );
68 + $this->register_routes();
47 69 }
48 70
49 71 /**
50 72 * Register routes.
@@ -102,8 +124,67 @@
102 124 );
103 125 }
104 126
105 127 /**
128 + * Register routes for the AI Chat block.
129 + * Relies on a site connection and Jetpack Search.
130 + */
131 + public function register_ai_chat_routes() {
132 + register_rest_route(
133 + $this->namespace,
134 + '/jetpack-search/ai/search',
135 + array(
136 + array(
137 + 'methods' => WP_REST_Server::READABLE,
138 + 'callback' => array( $this, 'request_chat_with_site' ),
139 + 'permission_callback' => '__return_true',
140 + ),
141 + 'args' => array(
142 + 'query' => array(
143 + 'description' => 'Your question to the site',
144 + 'required' => true,
145 + 'sanitize_callback' => 'sanitize_text_field',
146 + ),
147 + 'answer_prompt' => array(
148 + 'description' => 'Answer prompt override',
149 + 'required' => false,
150 + 'sanitize_callback' => 'sanitize_text_field',
151 + ),
152 + ),
153 + )
154 + );
155 +
156 + register_rest_route(
157 + $this->namespace,
158 + '/jetpack-search/ai/rank',
159 + array(
160 + array(
161 + 'methods' => WP_REST_Server::CREATABLE,
162 + 'callback' => array( $this, 'rank_response' ),
163 + 'permission_callback' => '__return_true',
164 + ),
165 + 'args' => array(
166 + 'cache_key' => array(
167 + 'description' => 'Cache key of your response',
168 + 'required' => true,
169 + 'sanitize_callback' => 'sanitize_text_field',
170 + ),
171 + 'comment' => array(
172 + 'description' => 'Optional feedback',
173 + 'required' => false,
174 + 'sanitize_callback' => 'sanitize_text_field',
175 + ),
176 + 'rank' => array(
177 + 'description' => 'How do you rank this response',
178 + 'required' => false,
179 + 'sanitize_callback' => 'sanitize_text_field',
180 + ),
181 + ),
182 + )
183 + );
184 + }
185 +
186 + /**
106 187 * Register routes that don't require Jetpack AI to be enabled.
107 188 */
108 189 public function register_basic_routes() {
109 190 register_rest_route(
@@ -116,8 +197,104 @@
116 197 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
117 198 ),
118 199 )
119 200 );
201 + }
202 +
203 + /**
204 + * Get a response from chatting with the site.
205 + * Uses Jetpack Search.
206 + *
207 + * @param WP_REST_Request $request The request.
208 + * @return mixed
209 + */
210 + public function request_chat_with_site( $request ) {
211 + $question = $request->get_param( 'query' );
212 + $blog_id = \Jetpack_Options::get_option( 'id' );
213 + $response = Client::wpcom_json_api_request_as_blog(
214 + sprintf( '/sites/%d/jetpack-search/ai/search', $blog_id ) . '?force=wpcom',
215 + 2,
216 + array(
217 + 'method' => 'GET',
218 + 'headers' => array( 'content-type' => 'application/json' ),
219 + 'timeout' => MINUTE_IN_SECONDS,
220 + ),
221 + array(
222 + 'query' => $question,
223 + /**
224 + * Filter for an answer prompt override.
225 + * Example: "Talk like a cowboy."
226 + *
227 + * @param string $prompt_override The prompt override string.
228 + *
229 + * @since 12.6
230 + */
231 + 'answer_prompt' => apply_filters( 'jetpack_ai_chat_answer_prompt', false ),
232 + ),
233 + 'wpcom'
234 + );
235 +
236 + if ( is_wp_error( $response ) ) {
237 + return $response;
238 + }
239 +
240 + $status_code = wp_remote_retrieve_response_code( $response );
241 + $data = json_decode( wp_remote_retrieve_body( $response ) );
242 +
243 + // Forward the real upstream error instead of a generic 500. See SEARCH-351.
244 + if ( $status_code >= 400 || empty( $data->cache_key ) ) {
245 + $code = isset( $data->code ) && is_scalar( $data->code ) ? (string) $data->code : 'invalid_ask_response';
246 + $message = isset( $data->message ) && is_scalar( $data->message ) ? (string) $data->message : __( 'Invalid response from the server.', 'jetpack' );
247 + $status = $status_code >= 400 ? $status_code : 400;
248 + return new WP_Error( $code, $message, array( 'status' => $status ) );
249 + }
250 +
251 + return $data;
252 + }
253 +
254 + /**
255 + * Rank a response from chatting with the site.
256 + *
257 + * @param WP_REST_Request $request The request.
258 + * @return mixed
259 + */
260 + public function rank_response( $request ) {
261 + $rank = $request->get_param( 'rank' );
262 + $comment = $request->get_param( 'comment' );
263 + $cache_key = $request->get_param( 'cache_key' );
264 +
265 + if ( strpos( $cache_key, 'jp-search-ai-' ) !== 0 ) {
266 + return new WP_Error( 'invalid_cache_key', __( 'Invalid cached context for the answer feedback.', 'jetpack' ), array( 'status' => 400 ) );
267 + }
268 +
269 + $blog_id = \Jetpack_Options::get_option( 'id' );
270 + $response = Client::wpcom_json_api_request_as_blog(
271 + sprintf( '/sites/%d/jetpack-search/ai/rank', $blog_id ) . '?force=wpcom',
272 + 2,
273 + array(
274 + 'method' => 'GET',
275 + 'headers' => array( 'content-type' => 'application/json' ),
276 + 'timeout' => 30,
277 + ),
278 + array(
279 + 'rank' => $rank,
280 + 'comment' => $comment,
281 + 'cache_key' => $cache_key,
282 + ),
283 + 'wpcom'
284 + );
285 +
286 + if ( is_wp_error( $response ) ) {
287 + return $response;
288 + }
289 +
290 + $data = json_decode( wp_remote_retrieve_body( $response ) );
291 +
292 + if ( 'ok' !== $data ) {
293 + return new WP_Error( 'invalid_feedback_response', __( 'Invalid response from the server.', 'jetpack' ), array( 'status' => 400 ) );
294 + }
295 +
296 + return $data;
120 297 }
121 298
122 299 /**
123 300 * Get completions for a given text.