api_key = $api_key; $this->model = '' !== $model ? $model : self::DEFAULT_MODEL; $this->timeout = $timeout; } /** * Generate a completion. * * Returns the raw decoded body so Manager::request_completion() can read it * with the same `choices[0].message.content` path it uses for OpenAI. * * @param string $prompt User prompt. * @param array $options max_tokens, temperature. * @return array Decoded API response. * @throws \Exception On transport failure or a non-2xx response. */ public function generate_completion(string $prompt, array $options = []): array { $body = [ 'model' => $this->model, 'messages' => [ ['role' => 'user', 'content' => $prompt], ], 'max_tokens' => (int) ($options['max_tokens'] ?? 1000), 'temperature' => (float) ($options['temperature'] ?? 0.4), ]; $response = wp_remote_post(self::API_BASE_URL . '/chat/completions', [ 'timeout' => $this->timeout, 'headers' => [ 'Authorization' => 'Bearer ' . $this->api_key, 'Content-Type' => 'application/json', 'User-Agent' => 'ThinkRank/' . THINKRANK_VERSION, ], 'body' => wp_json_encode($body), ]); if (is_wp_error($response)) { throw new \Exception('Perplexity request failed: ' . esc_html($response->get_error_message())); } $status = wp_remote_retrieve_response_code($response); $raw = wp_remote_retrieve_body($response); if ($status >= 400) { $decoded = json_decode($raw, true); $message = $decoded['error']['message'] ?? ($decoded['detail'] ?? 'Unknown API error'); throw new \Exception(sprintf('Perplexity API error (%d): %s', (int) $status, esc_html((string) $message))); } $data = json_decode($raw, true); if (!is_array($data)) { throw new \Exception('Unexpected non-array response from Perplexity API'); } return $data; } /** * Lightweight credential check. * * @return bool True when the key answers a minimal request. */ public function test_connection(): bool { try { $this->generate_completion('Reply with OK.', ['max_tokens' => 5]); return true; } catch (\Exception $e) { return false; } } }