proxy_base = rtrim( $base, '/' ) . '/api/v1/helpscout/'; $this->proxy_key = aisentic_get_option( 'llmProviders.aisentic.apiKey', '' ); if ( empty( $this->proxy_key ) ) { throw new \RuntimeException( 'HelpScout proxy key is not configured. Please set proper api key for aisentic.' ); } } /** * Create a new HelpScout conversation via the proxy. * * @param string $subject Conversation subject. * @param string $customer_email Customer email address. * @param string $first_name Customer first name. * @param string $body Initial message body. * @param array $threads Pre-built HelpScout thread array. If provided, this * is used directly instead of the default single-thread * created from $body. * * @throws \RuntimeException On proxy failure or missing conversation ID. * @return int HelpScout conversation ID. */ public function create_conversation( string $subject, string $customer_email, string $first_name, string $body, array $threads = [] ): int { $first_name = trim( $first_name ); if ( '' === $first_name ) { $user = wp_get_current_user(); if ( isset( $user->first_name ) && '' !== $user->first_name ) { $first_name = $user->first_name; } elseif ( isset( $user->display_name ) && '' !== $user->display_name ) { $first_name = $user->display_name; } else { $first_name = 'Customer'; } } $payload = [ 'subject' => $subject, 'customer_email' => $customer_email, 'first_name' => $first_name, 'message' => $body, ]; if ( ! empty( $threads ) ) { $payload['threads'] = $threads; } $response = $this->request( 'POST', 'conversations', $payload ); $id = (int) ( $response['data']['conversation_id'] ?? 0 ); if ( $id <= 0 ) { throw new \RuntimeException( __( 'Proxy did not return a valid conversation ID.', 'aisentic' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $id; } /** * Send a customer reply to an existing conversation via the proxy. * * @param int $conversation_id HelpScout conversation ID. * @param string $customer_email Customer email address. * @param string $body Reply message body. * * @throws \RuntimeException On proxy failure. * @return void */ public function add_reply( int $conversation_id, string $customer_email, string $body ): void { $this->request( 'POST', "conversations/{$conversation_id}/reply", [ 'customer_email' => $customer_email, 'message' => $body, ] ); } /** * Retrieve buffered agent messages for a conversation from the proxy. * Messages are consumed cursor-style on the proxy side. * * @param int $conversation_id HelpScout conversation ID. * * @throws \RuntimeException On proxy failure. * @return array */ public function get_messages( int $conversation_id ): array { $response = $this->request( 'GET', "conversations/{$conversation_id}/messages" ); return (array) ( $response['data']['messages'] ?? [] ); } /** * Make an authenticated request to the proxy. * * @param string $method HTTP method (GET, POST). * @param string $path Path relative to the proxy HelpScout base. * @param array $body Request body — omitted for GET requests. * * @throws \RuntimeException On WP_Error, or non-2xx proxy response. * @return array Parsed JSON response body. */ private function request( string $method, string $path, array $body = [] ): array { $args = [ 'method' => strtoupper( $method ), 'headers' => [ 'Authorization' => 'Bearer ' . $this->proxy_key, 'Accept' => 'application/json', ], 'timeout' => 20, ]; if ( ! empty( $body ) ) { $args['body'] = wp_json_encode( $body ); $args['headers']['Content-Type'] = 'application/json'; } $response = wp_remote_request( $this->proxy_base . $path, $args ); if ( is_wp_error( $response ) ) { throw new \RuntimeException( $response->get_error_message() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } $status = (int) wp_remote_retrieve_response_code( $response ); $parsed = json_decode( wp_remote_retrieve_body( $response ), true ) ?? []; if ( $status >= 400 ) { $message = $parsed['message'] ?? __( 'HelpScout proxy request failed.', 'aisentic' ); throw new \RuntimeException( $message ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } return $parsed; } }