|null */ public static function get_auth() { $user_id = (int) get_current_user_id(); if ( $user_id <= 0 ) { return null; } $stored = get_user_meta( $user_id, self::AUTH_META_KEY, true ); if ( ! WTeam_Executor_Secret_Storage::is_encrypted( $stored ) ) { return null; } return WTeam_Executor_Secret_Storage::decrypt_array( $stored, 'codex_auth:' . $user_id ); } /** @return array|WP_Error */ public static function get_usable_auth() { if ( ! self::supports_secure_storage() ) { return new WP_Error( 'wteam_executor_codex_openssl_required', __( 'ChatGPT mode requires the PHP OpenSSL extension with AES-256-GCM support so Executor can encrypt account tokens securely.', 'wteam-executor' ) ); } $auth = self::get_auth(); if ( ! is_array( $auth ) || empty( $auth['access_token'] ) || empty( $auth['refresh_token'] ) ) { return new WP_Error( 'wteam_executor_codex_not_connected', __( 'No ChatGPT account is connected for this WordPress user. Open Executor > Settings and connect ChatGPT first.', 'wteam-executor' ) ); } $expires_at = self::jwt_exp( (string) $auth['access_token'] ); if ( $expires_at > 0 && $expires_at <= time() + 300 ) { $refreshed = self::refresh_auth( $auth ); if ( is_wp_error( $refreshed ) ) { return $refreshed; } $auth = $refreshed; } return $auth; } /** * @param string $method * @param string $path * @param array|null $json_body * @param int $timeout * @param array $extra_headers * @return array|WP_Error */ public static function codex_request( $method, $path, $json_body = null, $timeout = 120, array $extra_headers = [] ) { $auth = self::get_usable_auth(); if ( is_wp_error( $auth ) ) { return $auth; } $response = self::codex_request_with_auth( $auth, $method, $path, $json_body, $timeout, $extra_headers ); if ( is_wp_error( $response ) ) { return $response; } if ( 401 === (int) $response['code'] ) { $refreshed = self::refresh_auth( $auth, true ); if ( is_wp_error( $refreshed ) ) { return $refreshed; } $response = self::codex_request_with_auth( $refreshed, $method, $path, $json_body, $timeout, $extra_headers ); } return $response; } /** * @param string $override * @return string|WP_Error */ public static function resolve_model( $override = '' ) { $override = trim( (string) $override ); $catalog = self::list_models(); if ( is_wp_error( $catalog ) ) { if ( '' !== $override ) { return $override; } return new WP_Error( 'wteam_executor_codex_models_unavailable', __( 'Executor could not load the models available to the connected ChatGPT account: ', 'wteam-executor' ) . $catalog->get_error_message() ); } $models = isset( $catalog['models'] ) && is_array( $catalog['models'] ) ? $catalog['models'] : []; if ( '' !== $override ) { foreach ( $models as $model ) { if ( is_array( $model ) && (string) ( $model['slug'] ?? '' ) === $override ) { if ( ! self::is_compatible_model( $model, true ) ) { return new WP_Error( 'wteam_executor_codex_model_unavailable', __( 'The selected ChatGPT/Codex model is not currently available to this account. Clear the model override to use automatic selection.', 'wteam-executor' ) ); } return $override; } } return $override; } usort( $models, static function ( $a, $b ) { $pa = is_array( $a ) && isset( $a['priority'] ) ? (int) $a['priority'] : 9999; $pb = is_array( $b ) && isset( $b['priority'] ) ? (int) $b['priority'] : 9999; return $pa <=> $pb; } ); foreach ( $models as $model ) { if ( self::is_compatible_model( $model, false ) ) { return (string) $model['slug']; } } foreach ( $models as $model ) { if ( self::is_compatible_model( $model, true ) ) { return (string) $model['slug']; } } return new WP_Error( 'wteam_executor_codex_no_compatible_model', __( 'The connected ChatGPT account did not advertise a usable Codex model.', 'wteam-executor' ) ); } /** * @param string $slug * @return array|null */ public static function get_model_info( $slug ) { $slug = trim( (string) $slug ); if ( '' === $slug ) { return null; } $catalog = self::list_models(); if ( is_wp_error( $catalog ) || empty( $catalog['models'] ) || ! is_array( $catalog['models'] ) ) { return null; } foreach ( $catalog['models'] as $model ) { if ( is_array( $model ) && (string) ( $model['slug'] ?? '' ) === $slug ) { return $model; } } return null; } /** @return bool */ public static function model_uses_responses_lite( $slug ) { $model = self::get_model_info( $slug ); if ( is_array( $model ) ) { return ! empty( $model['use_responses_lite'] ); } return 1 === preg_match( '/^gpt-5\.6(?:-|$)/i', (string) $slug ); } /** @return array|WP_Error */ public static function list_models() { $cache_key = 'wteam_executor_codex_models_' . (int) get_current_user_id(); $cached = get_transient( $cache_key ); if ( is_array( $cached ) && ! empty( $cached['models'] ) ) { return $cached; } $response = self::codex_request( 'GET', '/models?client_version=' . rawurlencode( self::client_version() ), null, 30 ); if ( is_wp_error( $response ) ) { return $response; } if ( (int) $response['code'] < 200 || (int) $response['code'] >= 300 ) { return new WP_Error( 'wteam_executor_codex_models_http_error', __( 'ChatGPT model discovery failed (HTTP ', 'wteam-executor' ) . (int) $response['code'] . '): ' . self::extract_error_message( (string) $response['body'] ) ); } $decoded = json_decode( (string) $response['body'], true ); if ( ! is_array( $decoded ) || ! isset( $decoded['models'] ) || ! is_array( $decoded['models'] ) ) { return new WP_Error( 'wteam_executor_codex_models_invalid', __( 'ChatGPT returned an invalid Codex model catalog.', 'wteam-executor' ) ); } set_transient( $cache_key, $decoded, 10 * MINUTE_IN_SECONDS ); return $decoded; } /** @return array */ public static function status() { if ( ! self::supports_secure_storage() ) { return [ 'available' => false, 'connected' => false, 'message' => __( 'ChatGPT mode is unavailable because the PHP OpenSSL extension with AES-256-GCM support is required to encrypt account tokens securely.', 'wteam-executor' ), 'login_error' => true, ]; } $auth = self::get_auth(); if ( is_array( $auth ) && ! empty( $auth['access_token'] ) && ! empty( $auth['refresh_token'] ) ) { $usable = self::get_usable_auth(); if ( is_wp_error( $usable ) ) { return [ 'available' => true, 'connected' => false, 'message' => $usable->get_error_message(), 'login_error' => true, ]; } $status = self::connected_status( $usable ); $usage = self::get_usage_snapshot(); if ( ! is_wp_error( $usage ) ) { $status['usage'] = $usage; } return $status; } $pending = self::get_pending(); if ( is_array( $pending ) ) { if ( empty( $pending['expires_at'] ) || (int) $pending['expires_at'] <= time() ) { self::clear_pending(); return [ 'available' => true, 'connected' => false, 'message' => __( 'The one-time ChatGPT authorization code expired. Click Connect ChatGPT to start again.', 'wteam-executor' ), 'login_error' => true, ]; } if ( empty( $pending['next_poll_at'] ) || (int) $pending['next_poll_at'] <= time() ) { $polled = self::poll_device_login( $pending ); if ( is_wp_error( $polled ) ) { return [ 'available' => true, 'connected' => false, 'message' => $polled->get_error_message(), 'verification_url' => (string) ( $pending['verification_url'] ?? '' ), 'user_code' => (string) ( $pending['user_code'] ?? '' ), 'login_error' => true, ]; } if ( ! empty( $polled['connected'] ) ) { return $polled; } $pending = self::get_pending(); } return [ 'available' => true, 'connected' => false, 'message' => __( 'Waiting for authorization on OpenAI…', 'wteam-executor' ), 'verification_url' => (string) ( $pending['verification_url'] ?? '' ), 'user_code' => (string) ( $pending['user_code'] ?? '' ), ]; } return [ 'available' => true, 'connected' => false, 'message' => __( 'Ready to connect. No server-side Codex installation is required.', 'wteam-executor' ), ]; } /** @return void */ public function ajax_start_login() { $this->assert_ajax_permission(); if ( ! self::supports_secure_storage() ) { wp_send_json_error( [ 'message' => __( 'ChatGPT mode requires the PHP OpenSSL extension with AES-256-GCM support so Executor can encrypt account tokens securely.', 'wteam-executor' ) ], 500 ); } self::clear_pending(); $response = self::auth_post_json( self::ISSUER . '/api/accounts/deviceauth/usercode', [ 'client_id' => self::CLIENT_ID ], 20 ); if ( is_wp_error( $response ) ) { wp_send_json_error( [ 'message' => $response->get_error_message() ], 502 ); } $code = (int) wp_remote_retrieve_response_code( $response ); $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code < 200 || $code >= 300 || ! is_array( $body ) || empty( $body['device_auth_id'] ) || empty( $body['user_code'] ) ) { $message = is_array( $body ) ? self::extract_error_from_array( $body ) : ''; wp_send_json_error( [ 'message' => $message ? $message : __( 'OpenAI did not start ChatGPT device authorization.', 'wteam-executor' ) ], $code >= 400 ? $code : 502 ); } $interval = isset( $body['interval'] ) ? max( 1, min( 15, (int) $body['interval'] ) ) : 5; $pending = [ 'device_auth_id' => sanitize_text_field( (string) $body['device_auth_id'] ), 'user_code' => sanitize_text_field( (string) $body['user_code'] ), 'verification_url' => self::ISSUER . '/codex/device', 'interval' => $interval, 'created_at' => time(), 'expires_at' => time() + self::DEVICE_MAX_AGE, 'next_poll_at' => time() + $interval, ]; self::save_pending( $pending ); wp_send_json_success( [ 'available' => true, 'connected' => false, 'message' => __( 'Open the OpenAI authorization page and enter the one-time code.', 'wteam-executor' ), 'verification_url' => $pending['verification_url'], 'user_code' => $pending['user_code'], ] ); } /** @return void */ public function ajax_status() { $this->assert_ajax_permission(); wp_send_json_success( self::status() ); } /** @return void */ public function ajax_logout() { $this->assert_ajax_permission(); $auth = self::get_auth(); if ( is_array( $auth ) && ! empty( $auth['refresh_token'] ) ) { wp_remote_post( self::ISSUER . '/oauth/revoke', [ 'timeout' => 10, 'headers' => [ 'Content-Type' => 'application/json' ], 'body' => wp_json_encode( [ 'token' => (string) $auth['refresh_token'], 'token_type_hint' => 'refresh_token', 'client_id' => self::CLIENT_ID, ] ), ] ); } self::clear_auth(); self::clear_pending(); delete_transient( 'wteam_executor_codex_models_' . (int) get_current_user_id() ); wp_send_json_success( self::status() ); } /** * @param array $pending * @return array|WP_Error */ private static function poll_device_login( array $pending ) { $interval = isset( $pending['interval'] ) ? max( 1, (int) $pending['interval'] ) : 5; $pending['next_poll_at'] = time() + $interval; self::save_pending( $pending ); $response = self::auth_post_json( self::ISSUER . '/api/accounts/deviceauth/token', [ 'device_auth_id' => (string) $pending['device_auth_id'], 'user_code' => (string) $pending['user_code'], ], 20 ); if ( is_wp_error( $response ) ) { return $response; } $code = (int) wp_remote_retrieve_response_code( $response ); if ( 403 === $code || 404 === $code ) { return [ 'connected' => false ]; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code < 200 || $code >= 300 || ! is_array( $body ) ) { return new WP_Error( 'wteam_executor_codex_device_poll_failed', is_array( $body ) ? self::extract_error_from_array( $body ) : __( 'ChatGPT device authorization failed.', 'wteam-executor' ) ); } foreach ( [ 'authorization_code', 'code_verifier' ] as $required ) { if ( empty( $body[ $required ] ) ) { return new WP_Error( 'wteam_executor_codex_device_invalid', __( 'OpenAI returned an incomplete device authorization response.', 'wteam-executor' ) ); } } $exchange = wp_remote_post( self::ISSUER . '/oauth/token', [ 'timeout' => 20, 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json', ], 'body' => http_build_query( [ 'grant_type' => 'authorization_code', 'code' => (string) $body['authorization_code'], 'redirect_uri' => self::ISSUER . '/deviceauth/callback', 'client_id' => self::CLIENT_ID, 'code_verifier' => (string) $body['code_verifier'], ], '', '&', PHP_QUERY_RFC3986 ), 'user-agent' => self::user_agent(), ] ); if ( is_wp_error( $exchange ) ) { return new WP_Error( 'wteam_executor_codex_token_exchange_http', __( 'ChatGPT token exchange failed: ', 'wteam-executor' ) . $exchange->get_error_message() ); } $exchange_code = (int) wp_remote_retrieve_response_code( $exchange ); $tokens = json_decode( wp_remote_retrieve_body( $exchange ), true ); if ( $exchange_code < 200 || $exchange_code >= 300 || ! is_array( $tokens ) || empty( $tokens['access_token'] ) || empty( $tokens['refresh_token'] ) ) { return new WP_Error( 'wteam_executor_codex_token_exchange_failed', is_array( $tokens ) ? self::extract_error_from_array( $tokens ) : __( 'OpenAI did not return usable ChatGPT tokens.', 'wteam-executor' ) ); } $auth = self::build_auth_record( $tokens, [] ); if ( empty( $auth['account_id'] ) ) { return new WP_Error( 'wteam_executor_codex_account_missing', __( 'The ChatGPT login succeeded but the Codex workspace/account identifier was missing from the OpenAI token.', 'wteam-executor' ) ); } $saved = self::save_auth( $auth ); if ( is_wp_error( $saved ) ) { return $saved; } self::clear_pending(); delete_transient( 'wteam_executor_codex_models_' . (int) get_current_user_id() ); return self::connected_status( $auth ); } /** * @param array $auth * @param bool $force * @return array|WP_Error */ private static function refresh_auth( array $auth, $force = false ) { if ( empty( $auth['refresh_token'] ) ) { return new WP_Error( 'wteam_executor_codex_refresh_missing', __( 'The ChatGPT session cannot be refreshed. Reconnect it under Executor > Settings.', 'wteam-executor' ) ); } if ( ! $force ) { $expires_at = self::jwt_exp( (string) ( $auth['access_token'] ?? '' ) ); if ( $expires_at <= 0 || $expires_at > time() + 300 ) { return $auth; } } $response = self::auth_post_json( self::ISSUER . '/oauth/token', [ 'client_id' => self::CLIENT_ID, 'grant_type' => 'refresh_token', 'refresh_token' => (string) $auth['refresh_token'], ], 20 ); if ( is_wp_error( $response ) ) { return new WP_Error( 'wteam_executor_codex_refresh_http', __( 'The ChatGPT session refresh failed: ', 'wteam-executor' ) . $response->get_error_message() ); } $code = (int) wp_remote_retrieve_response_code( $response ); $tokens = json_decode( wp_remote_retrieve_body( $response ), true ); if ( $code < 200 || $code >= 300 || ! is_array( $tokens ) || empty( $tokens['access_token'] ) ) { return new WP_Error( 'wteam_executor_codex_refresh_failed', is_array( $tokens ) ? self::extract_error_from_array( $tokens ) : __( 'The ChatGPT session expired. Reconnect it under Executor > Settings.', 'wteam-executor' ) ); } $merged = self::build_auth_record( $tokens, $auth ); $saved = self::save_auth( $merged ); if ( is_wp_error( $saved ) ) { return $saved; } return $merged; } /** * @param array $auth * @param string $method * @param string $path * @param array|null $json_body * @param int $timeout * @param array $extra_headers * @return array|WP_Error */ private static function codex_request_with_auth( array $auth, $method, $path, $json_body, $timeout, array $extra_headers ) { $account_id = isset( $auth['account_id'] ) ? trim( (string) $auth['account_id'] ) : ''; if ( '' === $account_id ) { return new WP_Error( 'wteam_executor_codex_account_missing', __( 'The connected ChatGPT session does not contain a Codex account/workspace id. Reconnect it under Executor > Settings.', 'wteam-executor' ) ); } $headers = array_merge( [ 'Authorization' => 'Bearer ' . (string) $auth['access_token'], 'ChatGPT-Account-ID' => $account_id, 'Originator' => self::ORIGINATOR, 'Version' => self::client_version(), 'OpenAI-Beta' => 'responses=v1', 'Accept' => 'application/json, text/event-stream', ], $extra_headers ); if ( null !== $json_body ) { $headers['Content-Type'] = 'application/json'; } $args = [ 'method' => strtoupper( (string) $method ), 'timeout' => max( 5, (int) $timeout ), 'redirection' => 3, 'headers' => $headers, 'user-agent' => self::user_agent(), ]; if ( null !== $json_body ) { $args['body'] = wp_json_encode( $json_body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); } $response = wp_remote_request( rtrim( self::CODEX_BASE_URL, '/' ) . '/' . ltrim( (string) $path, '/' ), $args ); if ( is_wp_error( $response ) ) { return new WP_Error( 'wteam_executor_codex_http_error', __( 'The ChatGPT/Codex request failed: ', 'wteam-executor' ) . $response->get_error_message() ); } return [ 'code' => (int) wp_remote_retrieve_response_code( $response ), 'body' => (string) wp_remote_retrieve_body( $response ), 'headers' => wp_remote_retrieve_headers( $response ), ]; } /** * Fetch the current Codex usage/rate-limit snapshot for the connected ChatGPT account. * * OpenAI's Codex usage response exposes rate-limit windows with reset_at (Unix seconds) * and reset_after_seconds. This method intentionally returns the raw decoded snapshot so * callers can select the actually exhausted window instead of guessing a reset time. * * @return array|WP_Error */ public static function get_usage_snapshot() { $response = self::codex_request( 'GET', '/usage', null, 20, [ 'Accept' => 'application/json' ] ); if ( is_wp_error( $response ) ) { return $response; } $code = isset( $response['code'] ) ? (int) $response['code'] : 0; if ( $code < 200 || $code >= 300 ) { return new WP_Error( 'wteam_executor_codex_usage_status_http_error', sprintf( /* translators: %d: HTTP response status code. */ __( 'Could not read the current Codex usage status (HTTP %d).', 'wteam-executor' ), $code ) ); } $decoded = json_decode( (string) ( $response['body'] ?? '' ), true ); if ( ! is_array( $decoded ) ) { return new WP_Error( 'wteam_executor_codex_usage_status_invalid', __( 'OpenAI returned an invalid Codex usage status response.', 'wteam-executor' ) ); } return $decoded; } /** @return bool */ private static function is_compatible_model( $model, $allow_hidden = false ) { if ( ! is_array( $model ) || empty( $model['slug'] ) ) { return false; } if ( isset( $model['supported_in_api'] ) && ! $model['supported_in_api'] ) { return false; } if ( ! $allow_hidden && isset( $model['visibility'] ) ) { $visibility = strtolower( (string) $model['visibility'] ); if ( in_array( $visibility, [ 'hide', 'hidden' ], true ) ) { return false; } } return true; } /** @return string */ private static function client_version() { return (string) apply_filters( 'wteam_executor_codex_client_version', self::CODEX_CLIENT_VERSION ); } /** @return string */ private static function user_agent() { return 'codex_cli_rs/' . self::client_version() . ' (Executor/' . ( defined( 'WTEAM_EXECUTOR_VERSION' ) ? WTEAM_EXECUTOR_VERSION : 'unknown' ) . '; WordPress ' . get_bloginfo( 'version' ) . ')'; } /** @return array */ private static function connected_status( array $auth ) { $parts = []; if ( ! empty( $auth['email'] ) ) { $parts[] = sanitize_email( (string) $auth['email'] ); } if ( ! empty( $auth['plan'] ) ) { $parts[] = strtoupper( sanitize_text_field( (string) $auth['plan'] ) ); } return [ 'available' => true, 'connected' => true, /* translators: %s: runtime value inserted into this message. */ 'message' => $parts ? sprintf( __( 'Connected: %s', 'wteam-executor' ), implode( ' · ', $parts ) ) : __( 'ChatGPT account connected.', 'wteam-executor' ), 'email' => isset( $auth['email'] ) ? sanitize_email( (string) $auth['email'] ) : '', 'plan' => isset( $auth['plan'] ) ? sanitize_key( (string) $auth['plan'] ) : '', ]; } /** * @param array $tokens * @param array $previous * @return array */ private static function build_auth_record( array $tokens, array $previous ) { $id_token = ! empty( $tokens['id_token'] ) ? (string) $tokens['id_token'] : (string) ( $previous['id_token'] ?? '' ); $access_token = ! empty( $tokens['access_token'] ) ? (string) $tokens['access_token'] : (string) ( $previous['access_token'] ?? '' ); $refresh = ! empty( $tokens['refresh_token'] ) ? (string) $tokens['refresh_token'] : (string) ( $previous['refresh_token'] ?? '' ); $claims = self::jwt_payload( $id_token ); // phpcs:ignore PluginCheck.CodeAnalysis.AIProvider.DirectIntegration -- OpenAI JWT claim namespace; this is not an outbound API request. $auth_claims = isset( $claims['https://api.openai.com/auth'] ) && is_array( $claims['https://api.openai.com/auth'] ) ? $claims['https://api.openai.com/auth'] : []; // phpcs:ignore PluginCheck.CodeAnalysis.AIProvider.DirectIntegration -- OpenAI JWT claim namespace; this is not an outbound API request. $profile = isset( $claims['https://api.openai.com/profile'] ) && is_array( $claims['https://api.openai.com/profile'] ) ? $claims['https://api.openai.com/profile'] : []; $account_id = isset( $auth_claims['chatgpt_account_id'] ) ? (string) $auth_claims['chatgpt_account_id'] : (string) ( $previous['account_id'] ?? '' ); $email = isset( $claims['email'] ) ? (string) $claims['email'] : ( isset( $profile['email'] ) ? (string) $profile['email'] : (string) ( $previous['email'] ?? '' ) ); $plan = isset( $auth_claims['chatgpt_plan_type'] ) ? (string) $auth_claims['chatgpt_plan_type'] : (string) ( $previous['plan'] ?? '' ); return [ 'id_token' => $id_token, 'access_token' => $access_token, 'refresh_token' => $refresh, 'account_id' => $account_id, 'email' => sanitize_email( $email ), 'plan' => sanitize_key( $plan ), 'updated_at' => time(), ]; } /** @return int */ private static function jwt_exp( $jwt ) { $payload = self::jwt_payload( (string) $jwt ); return isset( $payload['exp'] ) && is_numeric( $payload['exp'] ) ? (int) $payload['exp'] : 0; } /** @return array */ private static function jwt_payload( $jwt ) { $parts = explode( '.', (string) $jwt ); if ( count( $parts ) < 2 ) { return []; } $payload = strtr( $parts[1], '-_', '+/' ); $padding = strlen( $payload ) % 4; if ( $padding ) { $payload .= str_repeat( '=', 4 - $padding ); } $decoded = base64_decode( $payload, true ); if ( false === $decoded ) { return []; } $json = json_decode( $decoded, true ); return is_array( $json ) ? $json : []; } /** @return array|null */ private static function get_pending() { $user_id = (int) get_current_user_id(); if ( $user_id <= 0 ) { return null; } $stored = get_user_meta( $user_id, self::PENDING_META_KEY, true ); if ( ! WTeam_Executor_Secret_Storage::is_encrypted( $stored ) ) { return null; } return WTeam_Executor_Secret_Storage::decrypt_array( $stored, 'codex_pending:' . $user_id ); } /** @return void */ private static function save_pending( array $pending ) { $user_id = (int) get_current_user_id(); if ( $user_id <= 0 ) { return; } $encoded = WTeam_Executor_Secret_Storage::encrypt_array( $pending, 'codex_pending:' . $user_id ); if ( ! is_wp_error( $encoded ) ) { update_user_meta( $user_id, self::PENDING_META_KEY, $encoded ); } } /** @return void */ private static function clear_pending() { if ( get_current_user_id() ) { delete_user_meta( (int) get_current_user_id(), self::PENDING_META_KEY ); } } /** @return true|WP_Error */ private static function save_auth( array $auth ) { $user_id = (int) get_current_user_id(); $encoded = WTeam_Executor_Secret_Storage::encrypt_array( $auth, 'codex_auth:' . $user_id ); if ( is_wp_error( $encoded ) ) { return $encoded; } update_user_meta( $user_id, self::AUTH_META_KEY, $encoded ); return true; } /** @return void */ private static function clear_auth() { if ( get_current_user_id() ) { delete_user_meta( (int) get_current_user_id(), self::AUTH_META_KEY ); } } /** @return array|WP_Error */ private static function auth_post_json( $url, array $body, $timeout ) { $response = wp_remote_post( $url, [ 'timeout' => max( 5, (int) $timeout ), 'headers' => [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', ], 'body' => wp_json_encode( $body ), 'user-agent' => self::user_agent(), ] ); if ( is_wp_error( $response ) ) { return new WP_Error( 'wteam_executor_codex_auth_http', __( 'OpenAI authentication request failed: ', 'wteam-executor' ) . $response->get_error_message() ); } return $response; } /** @return string */ private static function extract_error_message( $body ) { $decoded = json_decode( (string) $body, true ); if ( is_array( $decoded ) ) { return self::extract_error_from_array( $decoded ); } $text = trim( wp_strip_all_tags( (string) $body ) ); return '' !== $text ? substr( $text, 0, 500 ) : __( 'Unknown error', 'wteam-executor' ); } /** @return string */ private static function extract_error_from_array( array $body ) { if ( isset( $body['error']['message'] ) ) { return sanitize_text_field( (string) $body['error']['message'] ); } if ( isset( $body['error']['code'] ) ) { return sanitize_text_field( (string) $body['error']['code'] ); } if ( isset( $body['message'] ) ) { return sanitize_text_field( (string) $body['message'] ); } if ( isset( $body['error'] ) && is_string( $body['error'] ) ) { return sanitize_text_field( $body['error'] ); } return __( 'OpenAI returned an authentication error.', 'wteam-executor' ); } /** @return void */ private function assert_ajax_permission() { check_ajax_referer( self::NONCE_ACTION, 'nonce' ); if ( ! WTeam_Executor_Access_Control::can_current_user() ) { wp_send_json_error( [ 'message' => __( 'You do not have permission to manage the Executor ChatGPT connection.', 'wteam-executor' ) ], 403 ); } } }