PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 14.3.1
Jetpack – WP Security, Backup, Speed, & Growth v14.3.1
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 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / _inc / lib / class-jetpack-ai-helper.php
jetpack / _inc / lib Last commit date
admin-pages 1 year ago core-api 1 year ago debugger 1 year ago markdown 2 years ago class-jetpack-ai-helper.php 1 year ago class-jetpack-currencies.php 2 years ago class-jetpack-instagram-gallery-helper.php 2 years ago class-jetpack-mapbox-helper.php 3 years ago class-jetpack-podcast-feed-locator.php 1 year ago class-jetpack-podcast-helper.php 1 year ago class-jetpack-recommendations.php 2 years ago class-jetpack-top-posts-helper.php 2 years ago class.color.php 2 years ago class.core-rest-api-endpoints.php 1 year ago class.jetpack-automatic-install-skin.php 2 years ago class.jetpack-iframe-embed.php 3 years ago class.jetpack-keyring-service-helper.php 1 year ago class.jetpack-password-checker.php 2 years ago class.jetpack-search-performance-logger.php 4 years ago class.media-extractor.php 2 years ago class.media-summary.php 1 year ago class.media.php 2 years ago components.php 3 years ago debugger.php 2 years ago functions.wp-notify.php 1 year ago icalendar-reader.php 1 year ago jp-simplepie-alias-new.php 1 year ago jp-simplepie-alias-old.php 1 year ago jp-simplepie-alias.php 1 year ago markdown.php 3 years ago plans.php 4 years ago plugins.php 4 years ago tonesque.php 2 years ago widgets.php 2 years ago
class-jetpack-ai-helper.php
472 lines
1 <?php
2 /**
3 * API helper for the AI blocks.
4 *
5 * @package automattic/jetpack
6 * @since 11.8
7 */
8
9 use Automattic\Jetpack\Connection\Client;
10 use Automattic\Jetpack\Connection\Manager;
11 use Automattic\Jetpack\Search\Plan as Search_Plan;
12 use Automattic\Jetpack\Status;
13 use Automattic\Jetpack\Status\Visitor;
14
15 /**
16 * Class Jetpack_AI_Helper
17 *
18 * @since 11.8
19 */
20 class Jetpack_AI_Helper {
21 /**
22 * Allow new completion every X seconds. Will return cached result otherwise.
23 *
24 * @var int
25 */
26 public static $text_completion_cooldown_seconds = 15;
27
28 /**
29 * Cache images for a prompt for a month.
30 *
31 * @var int
32 */
33 public static $image_generation_cache_timeout = MONTH_IN_SECONDS;
34
35 /**
36 * Cache AI-assistant feature for 60 seconds.
37 *
38 * @var int
39 */
40 public static $ai_assistant_feature_cache_timeout = 60;
41
42 /**
43 * Cache AI-assistant errors for ten seconds.
44 *
45 * @var int
46 */
47 public static $ai_assistant_feature_error_cache_timeout = 10;
48
49 /**
50 * Stores the number of JetpackAI calls in case we want to mark AI-assisted posts some way.
51 *
52 * @var int
53 */
54 public static $post_meta_with_ai_generation_number = '_jetpack_ai_calls';
55
56 /**
57 * Storing the error to prevent repeated requests to WPCOM after failure.
58 *
59 * @var null|WP_Error
60 */
61 private static $ai_assistant_failed_request = null;
62
63 /**
64 * Checks if a given request is allowed to get AI data from WordPress.com.
65 *
66 * @param WP_REST_Request $request Full details about the request.
67 *
68 * @return true|WP_Error True if the request has access, WP_Error object otherwise.
69 */
70 public static function get_status_permission_check( $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
71
72 /*
73 * This may need to be updated
74 * to take into account the different ways we can make requests
75 * (from a WordPress.com site, from a Jetpack site).
76 */
77 if ( ! current_user_can( 'edit_posts' ) ) {
78 return new WP_Error(
79 'rest_forbidden',
80 __( 'Sorry, you are not allowed to access Jetpack AI help on this site.', 'jetpack' ),
81 array( 'status' => rest_authorization_required_code() )
82 );
83 }
84
85 return true;
86 }
87
88 /**
89 * Return true if these features should be active on the current site.
90 * Currently, it's limited to WPCOM Simple and Atomic.
91 */
92 public static function is_enabled() {
93 $default = false;
94
95 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
96 $default = true;
97 } elseif ( ( new Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
98 $default = true;
99 }
100
101 /**
102 * Filter whether the AI features are enabled in the Jetpack plugin.
103 *
104 * @since 11.8
105 *
106 * @param bool $default Are AI features enabled? Defaults to false.
107 */
108 return apply_filters( 'jetpack_ai_enabled', $default );
109 }
110
111 /**
112 * Return true if the AI chat feature should be active on the current site.
113 *
114 * @todo IS_WPCOM (the endpoints need to be updated too).
115 *
116 * @return bool
117 */
118 public static function is_ai_chat_enabled() {
119 $default = false;
120
121 $connection = new Manager();
122 $plan = new Search_Plan();
123 if ( $connection->is_connected() && $plan->supports_search() ) {
124 $default = true;
125 }
126
127 /**
128 * Filter whether the AI chat feature is enabled in the Jetpack plugin.
129 *
130 * @since 12.6
131 *
132 * @param bool $default Is AI chat enabled? Defaults to false.
133 */
134 return apply_filters( 'jetpack_ai_chat_enabled', $default );
135 }
136
137 /**
138 * Get the name of the transient for image generation. Unique per prompt and allows for reuse of results for the same prompt across entire WPCOM.
139 * I expext "puppy" to always be from cache.
140 *
141 * @param string $prompt - Supplied prompt.
142 */
143 public static function transient_name_for_image_generation( $prompt ) {
144 return 'jetpack_openai_image_' . md5( $prompt );
145 }
146
147 /**
148 * Get the name of the transient for text completion. Unique per user, but not per text. Serves more as a cooldown.
149 */
150 public static function transient_name_for_completion() {
151 return 'jetpack_openai_completion_' . get_current_user_id(); // Cache for each user, so that other users dont get weird cached version from somebody else.
152 }
153
154 /**
155 * Get the name of the transient for AI assistance feature. Unique per user.
156 *
157 * @param int $blog_id - Blog ID to get the transient name for.
158 * @return string
159 */
160 public static function transient_name_for_ai_assistance_feature( $blog_id ) {
161 return 'jetpack_openai_ai_assistance_feature_' . $blog_id;
162 }
163
164 /**
165 * Mark the edited post as "touched" by AI stuff.
166 *
167 * @param int $post_id Post ID for which the content is being generated.
168 * @return void
169 */
170 private static function mark_post_as_ai_assisted( $post_id ) {
171 if ( ! $post_id ) {
172 return;
173 }
174 $previous = get_post_meta( $post_id, self::$post_meta_with_ai_generation_number, true );
175 if ( ! $previous ) {
176 $previous = 0;
177 } elseif ( ! is_numeric( $previous ) ) {
178 // Data corrupted, nothing to do.
179 return;
180 }
181 $new_value = intval( $previous ) + 1;
182 update_post_meta( $post_id, self::$post_meta_with_ai_generation_number, $new_value );
183 }
184
185 /**
186 * Get text back from WordPress.com based off a starting text.
187 *
188 * @param string $content The content provided to send to the AI.
189 * @param int $post_id Post ID for which the content is being generated.
190 * @param bool $skip_cache Skip cache and force a new request.
191 * @return mixed
192 */
193 public static function get_gpt_completion( $content, $post_id, $skip_cache = false ) {
194 $content = wp_strip_all_tags( $content );
195 $cache = get_transient( self::transient_name_for_completion() );
196 if ( $cache && ! $skip_cache ) {
197 return $cache;
198 }
199
200 if ( ( new Status() )->is_offline_mode() ) {
201 return new WP_Error(
202 'dev_mode',
203 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
204 );
205 }
206
207 $site_id = Manager::get_site_id();
208 if ( is_wp_error( $site_id ) ) {
209 return $site_id;
210 }
211
212 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
213 if ( ! class_exists( 'OpenAI' ) ) {
214 \require_lib( 'openai' );
215 }
216
217 // Set the content for chatGPT endpoint
218 $data = array(
219 array(
220 'role' => 'user',
221 'content' => $content,
222 ),
223 );
224
225 $openai = new OpenAI( 'openai', array( 'post_id' => $post_id ) );
226 $moderation_result = $openai->moderate(
227 implode(
228 ' ',
229 array_map(
230 function ( $msg ) {
231 return $msg['role'] === 'user' ? $msg['content'] : '';
232 },
233 $data
234 )
235 )
236 );
237
238 if ( is_wp_error( $moderation_result ) ) {
239 return $moderation_result;
240 }
241
242 $max_tokens = 480; // Default
243 $result = $openai->request_chat_completion( $data, $max_tokens );
244
245 if ( is_wp_error( $result ) ) {
246 return $result;
247 }
248
249 $response = $result->choices[0]->message->content;
250
251 // In case of Jetpack we are setting a transient on the WPCOM and not the remote site. I think the 'get_current_user_id' may default for the connection owner at this point but we'll deal with this later.
252 set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds );
253 self::mark_post_as_ai_assisted( $post_id );
254 return $response;
255 }
256
257 $response = Client::wpcom_json_api_request_as_user(
258 sprintf( '/sites/%d/jetpack-ai/completions', $site_id ),
259 2,
260 array(
261 'method' => 'post',
262 'headers' => array( 'content-type' => 'application/json' ),
263 ),
264 wp_json_encode(
265 array(
266 'content' => $content,
267 )
268 ),
269 'wpcom'
270 );
271
272 if ( is_wp_error( $response ) ) {
273 return $response;
274 }
275
276 $data = json_decode( wp_remote_retrieve_body( $response ) );
277
278 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
279 return new WP_Error( $data->code, $data->message, $data->data );
280 }
281
282 // Do not cache if it should be skipped.
283 if ( ! $skip_cache ) {
284 set_transient( self::transient_name_for_completion(), $data, self::$text_completion_cooldown_seconds );
285 }
286 self::mark_post_as_ai_assisted( $post_id );
287
288 return $data;
289 }
290
291 /**
292 * Get an array of image objects back from WordPress.com based off a prompt.
293 *
294 * @param string $prompt The prompt to generate images for.
295 * @param int $post_id Post ID for which the content is being generated.
296 * @return mixed
297 */
298 public static function get_dalle_generation( $prompt, $post_id ) {
299 $cache = get_transient( self::transient_name_for_image_generation( $prompt ) );
300 if ( $cache ) {
301 self::mark_post_as_ai_assisted( $post_id );
302 return $cache;
303 }
304
305 if ( ( new Status() )->is_offline_mode() ) {
306 return new WP_Error(
307 'dev_mode',
308 __( 'Jetpack AI is not available in offline mode.', 'jetpack' )
309 );
310 }
311
312 $site_id = Manager::get_site_id();
313 if ( is_wp_error( $site_id ) ) {
314 return $site_id;
315 }
316
317 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
318 if ( ! class_exists( 'OpenAI' ) ) {
319 \require_lib( 'openai' );
320 }
321
322 $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_dalle_generation( $prompt );
323 if ( is_wp_error( $result ) ) {
324 return $result;
325 }
326 set_transient( self::transient_name_for_image_generation( $prompt ), $result, self::$image_generation_cache_timeout );
327 self::mark_post_as_ai_assisted( $post_id );
328 return $result;
329 }
330
331 $response = Client::wpcom_json_api_request_as_user(
332 sprintf( '/sites/%d/jetpack-ai/images/generations', $site_id ),
333 2,
334 array(
335 'method' => 'post',
336 'headers' => array( 'content-type' => 'application/json' ),
337 ),
338 wp_json_encode(
339 array(
340 'prompt' => $prompt,
341 )
342 ),
343 'wpcom'
344 );
345
346 if ( is_wp_error( $response ) ) {
347 return $response;
348 }
349
350 $data = json_decode( wp_remote_retrieve_body( $response ) );
351
352 if ( wp_remote_retrieve_response_code( $response ) >= 400 ) {
353 return new WP_Error( $data->code, $data->message, $data->data );
354 }
355 set_transient( self::transient_name_for_image_generation( $prompt ), $data, self::$image_generation_cache_timeout );
356 self::mark_post_as_ai_assisted( $post_id );
357
358 return $data;
359 }
360
361 /**
362 * Get an object with useful data about the requests made to the AI.
363 *
364 * @return mixed
365 */
366 public static function get_ai_assistance_feature() {
367 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
368 // On WPCOM, we can get the ID from the site.
369 $blog_id = get_current_blog_id();
370 $has_ai_assistant_feature = \wpcom_site_has_feature( 'ai-assistant', $blog_id );
371
372 if ( ! class_exists( 'WPCOM\Jetpack_AI\Usage\Helper' ) ) {
373 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php' ) ) {
374 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/usage/helper.php';
375 } else {
376 return new WP_Error(
377 'jetpack_ai_usage_helper_not_found',
378 __( 'WPCOM\Jetpack_AI\Usage\Helper class not found.', 'jetpack' )
379 );
380 }
381 }
382
383 if ( ! class_exists( 'WPCOM\Jetpack_AI\Feature_Control' ) ) {
384 if ( is_readable( WP_CONTENT_DIR . '/lib/jetpack-ai/feature-control.php' ) ) {
385 require_once WP_CONTENT_DIR . '/lib/jetpack-ai/feature-control.php';
386 } else {
387 return new WP_Error(
388 'jetpack_ai_feature_control_not_found',
389 __( 'WPCOM\Jetpack_AI\Feature_Control class not found.', 'jetpack' )
390 );
391 }
392 }
393
394 // Determine the upgrade type
395 $upgrade_type = wpcom_is_vip( $blog_id ) ? 'vip' : 'default';
396
397 return array(
398 'has-feature' => $has_ai_assistant_feature,
399 'is-over-limit' => WPCOM\Jetpack_AI\Usage\Helper::is_over_limit( $blog_id ),
400 'requests-count' => WPCOM\Jetpack_AI\Usage\Helper::get_all_time_requests_count( $blog_id ),
401 'requests-limit' => WPCOM\Jetpack_AI\Usage\Helper::get_free_requests_limit( $blog_id ),
402 'usage-period' => WPCOM\Jetpack_AI\Usage\Helper::get_period_data( $blog_id ),
403 'site-require-upgrade' => WPCOM\Jetpack_AI\Usage\Helper::site_requires_upgrade( $blog_id ),
404 'upgrade-type' => $upgrade_type,
405 'upgrade-url' => WPCOM\Jetpack_AI\Usage\Helper::get_upgrade_url( $blog_id ),
406 'current-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_current_tier( $blog_id ),
407 'next-tier' => WPCOM\Jetpack_AI\Usage\Helper::get_next_tier( $blog_id ),
408 'tier-plans' => WPCOM\Jetpack_AI\Usage\Helper::get_tier_plans_list(),
409 'tier-plans-enabled' => WPCOM\Jetpack_AI\Usage\Helper::ai_tier_plans_enabled(),
410 'costs' => WPCOM\Jetpack_AI\Usage\Helper::get_costs(),
411 'features-control' => WPCOM\Jetpack_AI\Feature_Control::get_features(),
412 );
413 }
414
415 // Outside of WPCOM, we need to fetch the data from the site.
416 $blog_id = Jetpack_Options::get_option( 'id' );
417
418 // Try to pick the AI Assistant feature from cache.
419 $transient_name = self::transient_name_for_ai_assistance_feature( $blog_id );
420 $cache = get_transient( $transient_name );
421 if ( $cache ) {
422 return $cache;
423 }
424
425 if ( null !== static::$ai_assistant_failed_request ) {
426 return static::$ai_assistant_failed_request;
427 }
428
429 $request_path = sprintf( '/sites/%d/jetpack-ai/ai-assistant-feature', $blog_id );
430
431 $wpcom_request = Client::wpcom_json_api_request_as_user(
432 $request_path,
433 'v2',
434 array(
435 'method' => 'GET',
436 'headers' => array(
437 'X-Forwarded-For' => ( new Visitor() )->get_ip( true ),
438 ),
439 'timeout' => 30,
440 ),
441 null,
442 'wpcom'
443 );
444
445 $response_code = wp_remote_retrieve_response_code( $wpcom_request );
446 if ( 200 === $response_code ) {
447 $ai_assistant_feature_data = json_decode( wp_remote_retrieve_body( $wpcom_request ), true );
448
449 // Cache the AI Assistant feature, for Jetpack sites.
450 set_transient( $transient_name, $ai_assistant_feature_data, self::$ai_assistant_feature_cache_timeout );
451
452 return $ai_assistant_feature_data;
453 } else {
454 $error = new WP_Error(
455 'failed_to_fetch_data',
456 esc_html__( 'Unable to fetch the requested data.', 'jetpack' ),
457 array(
458 'status' => $response_code,
459 'ts' => time(),
460 )
461 );
462
463 // Cache the AI Assistant feature error, for Jetpack sites, avoid API hammering.
464 set_transient( $transient_name, $error, self::$ai_assistant_feature_error_cache_timeout );
465
466 static::$ai_assistant_failed_request = $error;
467
468 return $error;
469 }
470 }
471 }
472