PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
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 13.9.2 14.0.1 All 504 releases
← All changes | _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-ai.php +216 -4 12.0.3 → 16.3-a.1 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,17 +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
56 + // Register routes that don't require Jetpack AI to be enabled.
57 + $this->register_basic_routes();
58 +
59 + if ( Jetpack_AI_Helper::is_ai_chat_enabled() ) {
60 + $this->register_ai_chat_routes();
61 + }
62 +
38 63 if ( ! \Jetpack_AI_Helper::is_enabled() ) {
39 64 return;
40 65 }
41 66
42 - add_action( 'rest_api_init', array( $this, 'register_routes' ) );
67 + // Register routes that require Jetpack AI to be enabled.
68 + $this->register_routes();
43 69 }
44 70
45 71 /**
46 72 * Register routes.
@@ -55,20 +81,26 @@
55 81 'callback' => array( $this, 'request_gpt_completion' ),
56 82 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
57 83 ),
58 84 'args' => array(
59 - 'content' => array(
85 + 'content' => array(
60 86 'type' => 'string',
61 87 'required' => true,
62 88 'sanitize_callback' => 'sanitize_textarea_field',
63 89 ),
64 - 'post_id' => array(
90 + 'post_id' => array(
65 91 'required' => false,
66 92 'type' => 'integer',
67 93 ),
94 + 'skip_cache' => array(
95 + 'required' => false,
96 + 'type' => 'boolean',
97 + 'description' => 'Whether to skip the cache and make a new request',
98 + ),
68 99 ),
69 100 )
70 101 );
102 +
71 103 register_rest_route(
72 104 $this->namespace,
73 105 $this->rest_base . '/images/generations',
74 106 array(
@@ -92,14 +124,186 @@
92 124 );
93 125 }
94 126
95 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 + /**
187 + * Register routes that don't require Jetpack AI to be enabled.
188 + */
189 + public function register_basic_routes() {
190 + register_rest_route(
191 + $this->namespace,
192 + $this->rest_base . '/ai-assistant-feature',
193 + array(
194 + array(
195 + 'methods' => WP_REST_Server::READABLE,
196 + 'callback' => array( $this, 'request_get_ai_assistance_feature' ),
197 + 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
198 + ),
199 + )
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;
297 + }
298 +
299 + /**
96 300 * Get completions for a given text.
97 301 *
98 302 * @param WP_REST_Request $request The request.
99 303 */
100 304 public function request_gpt_completion( $request ) {
101 - return Jetpack_AI_Helper::get_gpt_completion( $request['content'], $request['post_id'] );
305 + return Jetpack_AI_Helper::get_gpt_completion( $request['content'], $request['post_id'], $request['skip_cache'] );
102 306 }
103 307
104 308 /**
105 309 * Get image generations for a given prompt.
@@ -107,8 +311,16 @@
107 311 * @param WP_REST_Request $request The request.
108 312 */
109 313 public function request_dalle_generation( $request ) {
110 314 return Jetpack_AI_Helper::get_dalle_generation( $request['prompt'], $request['post_id'] );
315 + }
316 +
317 + /**
318 + * Collect and provide relevat data about the AI feature,
319 + * such as the number of requests made.
320 + */
321 + public function request_get_ai_assistance_feature() {
322 + return Jetpack_AI_Helper::get_ai_assistance_feature();
111 323 }
112 324 }
113 325
114 326 wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_AI' );