provider = sanitize_text_field((string) $provider); $this->api_key = sanitize_text_field((string) $api_key); $this->model = sanitize_text_field((string) $model); $this->temperature = (float) $temperature; $this->max_tokens = (int) $max_tokens; $this->timeout = (int) $timeout; } public static function from_settings() { $provider = get_option('acosmaia_api_provider', 'openrouter'); if ('openai' === $provider) { $api_key = get_option('acosmaia_openai_key', ''); $model = get_option('acosmaia_openai_model', 'gpt-3.5-turbo'); } elseif ('gemini' === $provider) { $api_key = get_option('acosmaia_gemini_key', ''); $model = get_option('acosmaia_gemini_model', 'gemini-2.0-flash'); } else { $api_key = get_option('acosmaia_openrouter_key', ''); $model = get_option('acosmaia_model', 'mistralai/mistral-7b-instruct:free'); } $temperature = (float) get_option('acosmaia_temperature', 0.7); $max_tokens = (int) get_option('acosmaia_max_tokens', 600); $timeout = (int) get_option('acosmaia_timeout', 25); return new self($provider, $api_key, $model, $temperature, $max_tokens, $timeout); } public function is_configured() { return ! empty($this->api_key); } public function request($system_prompt, $message) { if (! $this->is_configured()) { return new WP_Error('api_not_configured', 'AI API key is not configured.'); } switch ($this->provider) { case 'openai': return $this->request_openai($system_prompt, $message); case 'gemini': return $this->request_gemini($system_prompt, $message); default: return $this->request_openrouter($system_prompt, $message); } } private function request_openrouter($system_prompt, $message) { // Try the configured model first $response = $this->attempt_openrouter_model($system_prompt, $message, $this->model); if (is_wp_error($response)) { $error = $response->get_error_message(); // If 404 (model not found), try fallback models if (strpos($error, 'No endpoints found') !== false || strpos($error, 'does not exist') !== false) { $fallback_models = $this->get_openrouter_fallback_models($this->model); foreach ($fallback_models as $model) { $fallback_response = $this->attempt_openrouter_model($system_prompt, $message, $model); if (! is_wp_error($fallback_response)) { // Check if it has valid content $status = (int) wp_remote_retrieve_response_code($fallback_response); $body = json_decode((string) wp_remote_retrieve_body($fallback_response), true); if ($status >= 200 && $status <= 299 && ! empty($body['choices'][0]['message']['content'])) { return $fallback_response; } } } } return $response; } return $response; } private function get_openrouter_fallback_models($exclude_model = '') { $models = ['openrouter/auto']; $dynamic_models = $this->get_openrouter_live_models(); foreach ($dynamic_models as $model_id) { if (! in_array($model_id, $models, true)) { $models[] = $model_id; } } if (empty($dynamic_models)) { $defaults = [ 'openrouter/auto', 'openai/gpt-4o-mini', 'meta-llama/llama-3.1-8b-instruct', 'google/gemma-2-9b-it', ]; foreach ($defaults as $default_model) { if (! in_array($default_model, $models, true)) { $models[] = $default_model; } } } $exclude_model = trim((string) $exclude_model); if ('' !== $exclude_model) { $models = array_values(array_filter($models, function ($model_id) use ($exclude_model) { return $model_id !== $exclude_model; })); } return $models; } private function get_openrouter_live_models() { $cache_key = 'acosmaia_openrouter_live_models'; $cached = get_transient($cache_key); if (is_array($cached) && ! empty($cached)) { return $cached; } $response = wp_remote_get('https://openrouter.ai/api/v1/models', [ 'headers' => [ 'Authorization' => 'Bearer ' . $this->api_key, 'Content-Type' => 'application/json', 'HTTP-Referer' => home_url('/'), 'X-Title' => get_bloginfo('name'), ], 'timeout' => 15, ]); if (is_wp_error($response)) { return []; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status > 299 || empty($body['data']) || ! is_array($body['data'])) { return []; } $free_models = []; $paid_models = []; foreach ($body['data'] as $model) { if (! is_array($model) || empty($model['id'])) { continue; } $model_id = sanitize_text_field((string) $model['id']); $pricing = isset($model['pricing']) && is_array($model['pricing']) ? $model['pricing'] : []; $prompt_price = isset($pricing['prompt']) ? (float) $pricing['prompt'] : 999.0; $completion_price = isset($pricing['completion']) ? (float) $pricing['completion'] : 999.0; if ($prompt_price <= 0.0 && $completion_price <= 0.0) { $free_models[] = $model_id; } else { $paid_models[] = $model_id; } } $sorted_models = array_values(array_unique(array_merge($free_models, array_slice($paid_models, 0, 8)))); if (! empty($sorted_models)) { set_transient($cache_key, $sorted_models, 10 * MINUTE_IN_SECONDS); } return $sorted_models; } private function attempt_openrouter_model($system_prompt, $message, $model) { $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [ 'headers' => [ 'Authorization' => 'Bearer ' . $this->api_key, 'Content-Type' => 'application/json', 'HTTP-Referer' => home_url('/'), 'X-Title' => get_bloginfo('name'), ], 'timeout' => max(5, min(120, $this->timeout)), 'body' => wp_json_encode([ 'model' => $model, 'temperature' => max(0, min(2, $this->temperature)), 'max_tokens' => max(100, min(4000, $this->max_tokens)), 'messages' => [ ['role' => 'system', 'content' => $system_prompt], ['role' => 'user', 'content' => $message], ], ]), ]); if (is_wp_error($response)) { return $response; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status > 299) { $error_msg = isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error'; return new WP_Error('openrouter_error', $error_msg); } if (empty($body['choices'][0]['message']['content'])) { return new WP_Error('openrouter_error', 'No response content from OpenRouter.'); } return $response; } private function request_openai($system_prompt, $message) { $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [ 'headers' => [ 'Authorization' => 'Bearer ' . $this->api_key, 'Content-Type' => 'application/json', ], 'timeout' => max(5, min(120, $this->timeout)), 'body' => wp_json_encode([ 'model' => $this->model, 'temperature' => max(0, min(2, $this->temperature)), 'max_tokens' => max(100, min(4000, $this->max_tokens)), 'messages' => [ ['role' => 'system', 'content' => $system_prompt], ['role' => 'user', 'content' => $message], ], ]), ]); if (is_wp_error($response)) { return $response; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status > 299) { $error_msg = isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error'; return new WP_Error('openai_error', $error_msg); } if (empty($body['choices'][0]['message']['content'])) { return new WP_Error('openai_error', 'No response content from OpenAI.'); } return $response; } private function request_gemini($system_prompt, $message) { $combined_message = $system_prompt . "\n\nUser: " . $message; $response = $this->attempt_gemini_model($this->model, $combined_message); if (is_wp_error($response)) { $error = $response->get_error_message(); if (strpos($error, 'not found') !== false || strpos($error, 'not supported for generateContent') !== false) { $fallback_models = $this->get_gemini_fallback_models($this->model); foreach ($fallback_models as $model) { $fallback_response = $this->attempt_gemini_model($model, $combined_message); if (! is_wp_error($fallback_response)) { return $fallback_response; } } } return $response; } return $response; } private function attempt_gemini_model($model, $combined_message) { $model = preg_replace('#^models/#', '', (string) $model); $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . rawurlencode($model) . ':generateContent?key=' . rawurlencode($this->api_key); $response = wp_remote_post($endpoint, [ 'headers' => [ 'Content-Type' => 'application/json', ], 'timeout' => max(5, min(120, $this->timeout)), 'body' => wp_json_encode([ 'contents' => [ [ 'parts' => [ ['text' => $combined_message], ], ], ], 'generationConfig' => [ 'temperature' => max(0, min(2, $this->temperature)), 'maxOutputTokens' => max(100, min(4000, $this->max_tokens)), ], ]), ]); if (is_wp_error($response)) { return $response; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status > 299) { $error_msg = isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error'; return new WP_Error('gemini_error', $error_msg); } if (empty($body['candidates'][0]['content']['parts'][0]['text'])) { return new WP_Error('gemini_error', 'No response content from Gemini.'); } return $response; } private function get_gemini_fallback_models($exclude_model = '') { $models = []; $live_models = $this->get_gemini_live_models(); foreach ($live_models as $model_id) { if (! in_array($model_id, $models, true)) { $models[] = $model_id; } } if (empty($models)) { $models = [ 'gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-flash', 'gemini-1.5-pro', ]; } $exclude_model = preg_replace('#^models/#', '', trim((string) $exclude_model)); if ('' !== $exclude_model) { $models = array_values(array_filter($models, function ($model_id) use ($exclude_model) { return $model_id !== $exclude_model; })); } return $models; } private function get_gemini_live_models() { $cache_key = 'acosmaia_gemini_live_models'; $cached = get_transient($cache_key); if (is_array($cached) && ! empty($cached)) { return $cached; } $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models?key=' . rawurlencode($this->api_key); $response = wp_remote_get($endpoint, [ 'timeout' => 15, ]); if (is_wp_error($response)) { return []; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ($status < 200 || $status > 299 || empty($body['models']) || ! is_array($body['models'])) { return []; } $models = []; foreach ($body['models'] as $model_data) { if (! is_array($model_data) || empty($model_data['name'])) { continue; } $methods = isset($model_data['supportedGenerationMethods']) && is_array($model_data['supportedGenerationMethods']) ? $model_data['supportedGenerationMethods'] : []; if (! in_array('generateContent', $methods, true)) { continue; } $name = sanitize_text_field((string) $model_data['name']); $name = preg_replace('#^models/#', '', $name); if ('' !== $name && ! in_array($name, $models, true)) { $models[] = $name; } } if (! empty($models)) { set_transient($cache_key, $models, 10 * MINUTE_IN_SECONDS); } return $models; } public function parse_response($response) { if (is_wp_error($response)) { return ['error' => $response->get_error_message()]; } $status = (int) wp_remote_retrieve_response_code($response); $body = json_decode((string) wp_remote_retrieve_body($response), true); if ('openai' === $this->provider) { if ($status >= 200 && $status <= 299 && ! empty($body['choices'][0]['message']['content'])) { return [ 'content' => wp_kses_post((string) $body['choices'][0]['message']['content']), 'usage' => isset($body['usage']) && is_array($body['usage']) ? $body['usage'] : [], ]; } return ['error' => isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error']; } if ('gemini' === $this->provider) { if ($status >= 200 && $status <= 299 && ! empty($body['candidates'][0]['content']['parts'][0]['text'])) { $usage = isset($body['usageMetadata']) && is_array($body['usageMetadata']) ? $body['usageMetadata'] : []; $prompt_tokens = isset($usage['promptTokenCount']) ? (int) $usage['promptTokenCount'] : 0; $completion_tokens = isset($usage['candidatesTokenCount']) ? (int) $usage['candidatesTokenCount'] : 0; $total_tokens = isset($usage['totalTokenCount']) ? (int) $usage['totalTokenCount'] : ($prompt_tokens + $completion_tokens); return [ 'content' => wp_kses_post((string) $body['candidates'][0]['content']['parts'][0]['text']), 'usage' => [ 'prompt_tokens' => $prompt_tokens, 'completion_tokens' => $completion_tokens, 'total_tokens' => $total_tokens, ], ]; } return ['error' => isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error']; } if ($status >= 200 && $status <= 299 && ! empty($body['choices'][0]['message']['content'])) { return [ 'content' => wp_kses_post((string) $body['choices'][0]['message']['content']), 'usage' => isset($body['usage']) && is_array($body['usage']) ? $body['usage'] : [], ]; } return ['error' => isset($body['error']['message']) ? sanitize_text_field((string) $body['error']['message']) : 'Unknown error']; } }