'application/json', 'X-OM-API-Key' => $api_key, 'X-OM-Site' => $this->proxy_site_key(), ); $source = preg_replace('/[^a-z0-9._-]/', '', strtolower((string) $source)); if ('' !== $source) { $headers['X-OM-Source'] = substr($source, 0, 64); } return $headers; } private function proxy_site_key() { static $s = null; if (null === $s) { $host = (string) wp_parse_url(home_url(), PHP_URL_HOST); $s = preg_replace('/^www\./', '', strtolower($host)); } return (string) $s; } private function get_api_key() { $key = (string) get_option('aieo_rooster_api_key', ''); return '' !== $key ? $key : false; } private function get_voyage_api_key() { return $this->get_api_key(); } public function chat_completion($messages, $options = array()) { $api_key = $this->get_api_key(); if (!$api_key) { return new WP_Error('no_api_key', __('Rooster Content Connector token not configured. Paste it in the Rooster One Pro to enable Anthropic chat.', 'ai-eshop-optimizer')); } if (!$this->check_rate_limit()) { return new WP_Error('rate_limit', __('Rate limit exceeded. Please try again later.', 'ai-eshop-optimizer')); } $defaults = array( 'model' => get_option('aieo_chat_model', 'claude-3-5-sonnet-20241022'), 'max_tokens' => (int) get_option('aieo_chat_max_tokens', 2000), 'temperature' => (float) get_option('aieo_chat_temperature', 0.5), 'system' => '', ); $options = wp_parse_args($options, $defaults); $model = $options['model']; $source = (string) ($options['source'] ?? ''); $body = array( 'model' => $model, 'max_tokens' => $options['max_tokens'], 'messages' => $messages, ); if (!empty($options['system'])) { $body['system'] = $options['system']; } $unsupported = $this->get_unsupported_params($model); if (isset($options['temperature']) && '' !== $options['temperature'] && !in_array('temperature', $unsupported, true)) { $body['temperature'] = (float) $options['temperature']; } if (isset($options['top_p']) && !in_array('top_p', $unsupported, true)) { $body['top_p'] = (float) $options['top_p']; } if (isset($options['top_k']) && !in_array('top_k', $unsupported, true)) { $body['top_k'] = (int) $options['top_k']; } $response = null; for ($pass = 0; $pass < 3; $pass++) { $response = wp_remote_post($this->get_proxy_url('anthropic/messages'), array( 'timeout' => 30, 'headers' => $this->proxy_headers($api_key, $source), 'body' => wp_json_encode($body) )); if (is_wp_error($response)) { break; } if (200 === (int) wp_remote_retrieve_response_code($response)) { break; } $err_body = json_decode(wp_remote_retrieve_body($response), true); $err_msg = isset($err_body['error']['message']) ? $err_body['error']['message'] : (isset($err_body['message']) ? $err_body['message'] : ''); if (preg_match('/`(temperature|top_p|top_k)`\s+is\s+deprecated/i', (string) $err_msg, $m) && isset($body[$m[1]])) { $bad = $m[1]; unset($body[$bad]); $this->mark_param_unsupported($model, $bad); if (defined('WP_DEBUG') && WP_DEBUG) { error_log("AIEO Chat: dropped deprecated param `{$bad}` for model {$model}, retrying"); } continue; } break; } $this->increment_rate_limit(); if (is_wp_error($response)) { return $response; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $error_body = json_decode(wp_remote_retrieve_body($response), true); $error_message = isset($error_body['error']['message']) ? $error_body['error']['message'] : (isset($error_body['message']) ? $error_body['message'] : __('Unknown API error', 'ai-eshop-optimizer')); return new WP_Error('api_error', $error_message, array('code' => $response_code)); } $result = json_decode(wp_remote_retrieve_body($response), true); return $result; } private function get_unsupported_params($model) { if (class_exists('AI_eShop_Chat_Model_Registry')) { return AI_eShop_Chat_Model_Registry::get_instance()->get_unsupported_params($model); } return array(); } private function mark_param_unsupported($model, $param) { if (class_exists('AI_eShop_Chat_Model_Registry')) { AI_eShop_Chat_Model_Registry::get_instance()->mark_param_unsupported($model, $param); } } public function raw_messages_probe(array $body) { $api_key = $this->get_api_key(); if (!$api_key) { return array('code' => 0, 'error' => __('Rooster Content Connector token not configured.', 'ai-eshop-optimizer'), 'body' => '', 'json' => null, 'latency_ms' => 0); } $start = microtime(true); $response = wp_remote_post($this->get_proxy_url('anthropic/messages'), array( 'timeout' => 40, 'headers' => $this->proxy_headers($api_key, 'aieo.diagnostic'), 'body' => wp_json_encode($body), )); $latency = (int) round((microtime(true) - $start) * 1000); if (is_wp_error($response)) { return array('code' => 0, 'error' => $response->get_error_message(), 'body' => '', 'json' => null, 'latency_ms' => $latency); } $code = (int) wp_remote_retrieve_response_code($response); $raw = (string) wp_remote_retrieve_body($response); $json = json_decode($raw, true); $err = ''; if (200 !== $code) { $err = isset($json['error']['message']) ? $json['error']['message'] : (isset($json['message']) ? $json['message'] : ''); } return array('code' => $code, 'error' => $err, 'body' => $raw, 'json' => is_array($json) ? $json : null, 'latency_ms' => $latency); } public function generate_embedding($text, $model = 'voyage-2', $source = '') { $api_key = $this->get_voyage_api_key(); if (!$api_key) { return new WP_Error('no_api_key', __('Rooster Content Connector token not configured. Paste it in the Rooster One Pro to enable Voyage embeddings.', 'ai-eshop-optimizer')); } if (!$this->check_rate_limit()) { return new WP_Error('rate_limit', __('Rate limit exceeded. Please try again later.', 'ai-eshop-optimizer')); } $cache_key = 'aieo_chat_emb_' . md5($text . $model); $cached = get_transient($cache_key); if ($cached !== false) { return $cached; } $body = array( 'input' => $text, 'model' => $model ); $response = wp_remote_post($this->get_proxy_url('voyage/embeddings'), array( 'timeout' => 30, 'headers' => $this->proxy_headers($api_key, $source), 'body' => wp_json_encode($body) )); $this->increment_rate_limit(); if (is_wp_error($response)) { return $response; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $error_body = json_decode(wp_remote_retrieve_body($response), true); $error_message = isset($error_body['error']) ? $error_body['error'] : __('Unknown API error', 'ai-eshop-optimizer'); return new WP_Error('api_error', $error_message, array('code' => $response_code)); } $result = json_decode(wp_remote_retrieve_body($response), true); if (!isset($result['data'][0]['embedding'])) { return new WP_Error('invalid_response', __('Invalid embedding response', 'ai-eshop-optimizer')); } $embedding = $result['data'][0]['embedding']; set_transient($cache_key, $embedding, HOUR_IN_SECONDS); return $embedding; } public function generate_embeddings_batch($texts, $model = 'voyage-2', $source = '') { $api_key = $this->get_voyage_api_key(); if (!$api_key) { return new WP_Error('no_api_key', __('Rooster Content Connector token not configured. Paste it in the Rooster One Pro to enable Voyage embeddings.', 'ai-eshop-optimizer')); } if (!$this->check_rate_limit()) { return new WP_Error('rate_limit', __('Rate limit exceeded. Please try again later.', 'ai-eshop-optimizer')); } $original_keys = array_keys($texts); $input_array = array_values($texts); $body = array( 'input' => $input_array, 'model' => $model ); $response = wp_remote_post($this->get_proxy_url('voyage/embeddings'), array( 'timeout' => 60, 'headers' => $this->proxy_headers($api_key, $source), 'body' => wp_json_encode($body) )); $this->increment_rate_limit(); if (is_wp_error($response)) { return $response; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $error_body = json_decode(wp_remote_retrieve_body($response), true); $error_message = isset($error_body['error']) ? $error_body['error'] : __('Unknown API error', 'ai-eshop-optimizer'); return new WP_Error('api_error', $error_message, array('code' => $response_code)); } $result = json_decode(wp_remote_retrieve_body($response), true); if (!isset($result['data']) || !is_array($result['data'])) { return new WP_Error('invalid_response', __('Invalid batch embedding response', 'ai-eshop-optimizer')); } $embeddings = array(); foreach ($result['data'] as $index => $item) { if (isset($original_keys[$index]) && isset($item['embedding'])) { $id = $original_keys[$index]; $embeddings[$id] = $item['embedding']; } } return $embeddings; } private function check_rate_limit() { $limit = (int) get_option('aieo_chat_rate_limit', 1000); $transient_key = 'aieo_chat_rate_limit_' . gmdate('Y-m-d-H'); $current = get_transient($transient_key); if ($current === false) { return true; } return (int) $current < $limit; } private function increment_rate_limit() { $transient_key = 'aieo_chat_rate_limit_' . gmdate('Y-m-d-H'); $current = get_transient($transient_key); if ($current === false) { set_transient($transient_key, 1, HOUR_IN_SECONDS); } else { set_transient($transient_key, (int) $current + 1, HOUR_IN_SECONDS); } } public function get_rate_limit_usage() { $limit = (int) get_option('aieo_chat_rate_limit', 1000); $transient_key = 'aieo_chat_rate_limit_' . gmdate('Y-m-d-H'); $current = get_transient($transient_key); if ($current === false) { $current = 0; } return array( 'limit' => $limit, 'used' => (int) $current, 'remaining' => max(0, $limit - (int) $current), 'percentage' => $limit > 0 ? round(((int) $current / $limit) * 100, 1) : 0 ); } public function reset_rate_limit() { $transient_key = 'aieo_chat_rate_limit_' . gmdate('Y-m-d-H'); delete_transient($transient_key); } public function chat_completion_stream($messages, $options = array()) { return $this->chat_completion($messages, $options); } public function get_available_models() { return array( 'claude-opus-4-5-20251101' => array( 'name' => 'Claude Opus 4.5', 'description' => __('Most intelligent model - hybrid reasoning', 'ai-eshop-optimizer'), 'max_tokens' => 16384, 'recommended' => true ), 'claude-sonnet-4-5-20250929' => array( 'name' => 'Claude Sonnet 4.5', 'description' => __('Balanced performance and speed', 'ai-eshop-optimizer'), 'max_tokens' => 16384, 'recommended' => false ), 'claude-haiku-4-5-20251001' => array( 'name' => 'Claude Haiku 4.5', 'description' => __('Fastest and most economical', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-opus-4-1-20250805' => array( 'name' => 'Claude Opus 4.1', 'description' => __('Advanced reasoning capabilities', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-opus-4-20250514' => array( 'name' => 'Claude Opus 4', 'description' => __('Original Claude 4 Opus', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-sonnet-4-20250514' => array( 'name' => 'Claude Sonnet 4', 'description' => __('Balanced Claude 4 model', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-3-7-sonnet-20250219' => array( 'name' => 'Claude 3.7 Sonnet', 'description' => __('Previous generation model', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-3-5-haiku-20241022' => array( 'name' => 'Claude 3.5 Haiku', 'description' => __('Fast and economical', 'ai-eshop-optimizer'), 'max_tokens' => 8192, 'recommended' => false ), 'claude-3-haiku-20240307' => array( 'name' => 'Claude 3 Haiku', 'description' => __('Budget-friendly fast model', 'ai-eshop-optimizer'), 'max_tokens' => 4096, 'recommended' => false ) ); } public function validate_api_key($api_key) { $response = wp_remote_get($this->get_proxy_url('anthropic/status'), array( 'timeout' => 10, 'headers' => array( 'X-OM-API-Key' => $api_key, 'Accept' => 'application/json', ), )); if (is_wp_error($response)) { return $response; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code === 200 || $response_code === 201) { return true; } if ($response_code === 401) { return new WP_Error('invalid_key', __('Invalid API key', 'ai-eshop-optimizer')); } return new WP_Error('validation_failed', __('Could not validate API key', 'ai-eshop-optimizer')); } }