apiKey = $_ENV['OPENROUTER_API_KEY'] ?? null; if (!$this->apiKey) { throw new Exception('OpenRouter API key not configured'); } } /** * Generate installation script using OpenRouter API */ public function generateScript(string $prompt, string $model = 'openai/gpt-3.5-turbo'): array { try { $messages = [ [ 'role' => 'system', 'content' => 'You are a helpful assistant that creates bash installation scripts for VPN protocols. Always respond with valid JSON containing the script, suggestions, ubuntu compatibility, and estimated installation time.' ], [ 'role' => 'user', 'content' => $prompt ] ]; $response = $this->makeAPICall('/chat/completions', [ 'model' => $model, 'messages' => $messages, 'temperature' => 0.3, // Lower temperature for more consistent results 'max_tokens' => 4000, // Sufficient for detailed scripts 'response_format' => ['type' => 'json_object'] ]); if (!isset($response['choices'][0]['message']['content'])) { throw new Exception('Invalid response from OpenRouter API'); } $content = $response['choices'][0]['message']['content']; $parsed = json_decode($content, true); if (json_last_error() !== JSON_ERROR_NONE) { // If JSON parsing fails, try to extract script from plain text return $this->parsePlainTextResponse($content); } return $this->validateAndEnhanceResponse($parsed); } catch (Exception $e) { error_log("Error in OpenRouterService::generateScript: " . $e->getMessage()); throw new Exception('Failed to generate script: ' . $e->getMessage()); } } /** * Get available AI models from OpenRouter */ public function getAvailableModels(): array { try { $response = $this->makeAPICall('/models', [], 'GET'); if (!isset($response['data'])) { throw new Exception('Invalid response from OpenRouter API'); } // Filter models suitable for code generation $codeModels = array_filter($response['data'], function($model) { $codeModelIds = [ 'openai/gpt-3.5-turbo', 'openai/gpt-4', 'openai/gpt-4-turbo', 'anthropic/claude-3-haiku', 'anthropic/claude-3-sonnet', 'anthropic/claude-3-opus', 'google/gemini-pro', 'meta-llama/llama-2-70b-chat', 'meta-llama/llama-3-70b-instruct' ]; return in_array($model['id'], $codeModelIds) && $model['top_provider'] === true; }); return array_values(array_map(function($model) { return [ 'id' => $model['id'], 'name' => $model['name'] ?? $model['id'], 'description' => $model['description'] ?? '', 'pricing' => $model['pricing'] ?? null ]; }, $codeModels)); } catch (Exception $e) { error_log("Error in OpenRouterService::getAvailableModels: " . $e->getMessage()); // Return default models if API call fails return $this->getDefaultModels(); } } /** * Make API call to OpenRouter */ private function makeAPICall(string $endpoint, array $data = [], string $method = 'POST'): array { $ch = curl_init(); $url = $this->apiUrl . $endpoint; curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $this->apiKey, 'Content-Type: application/json', 'HTTP-Referer: ' . ($_ENV['APP_URL'] ?? 'https://localhost'), 'X-Title: Amnezia VPN Panel' ], CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2 ]); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new Exception('CURL error: ' . $error); } if ($httpCode >= 400) { $errorData = json_decode($response, true); $errorMessage = $errorData['error']['message'] ?? "HTTP $httpCode error"; throw new Exception($errorMessage); } $decoded = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception('Invalid JSON response from OpenRouter API'); } return $decoded; } /** * Parse plain text response when JSON parsing fails */ private function parsePlainTextResponse(string $content): array { // Try to extract bash script from plain text if (preg_match('/```bash\n(.*?)\n```/s', $content, $matches)) { $script = trim($matches[1]); } elseif (preg_match('/```(.*?)```/s', $content, $matches)) { $script = trim($matches[1]); } else { // If no code blocks found, treat the entire content as script $script = trim($content); } // Add bash shebang if not present if (!str_starts_with($script, '#!')) { $script = "#!/bin/bash\n\n" . $script; } return [ 'script' => $script, 'suggestions' => [ 'Check the script for syntax errors', 'Test the script in a safe environment', 'Review security implications' ], 'ubuntu_compatible' => true, 'estimated_time' => '5 minutes' ]; } /** * Validate and enhance AI response */ private function validateAndEnhanceResponse(array $response): array { $defaults = [ 'script' => '#!/bin/bash\n# Default installation script\necho "Installation script placeholder"', 'suggestions' => [], 'ubuntu_compatible' => true, 'estimated_time' => '5 minutes' ]; // Ensure all required fields are present foreach ($defaults as $key => $defaultValue) { if (!isset($response[$key])) { $response[$key] = $defaultValue; } } // Validate script format if (!str_starts_with(trim($response['script']), '#!')) { $response['script'] = "#!/bin/bash\n\n" . $response['script']; } // Ensure suggestions is an array if (!is_array($response['suggestions'])) { $response['suggestions'] = []; } // Add default suggestions if none provided if (empty($response['suggestions'])) { $response['suggestions'] = [ 'Review the generated script for security implications', 'Test the script in a development environment first', 'Ensure all dependencies are available on your system', 'Backup your system before running the script' ]; } // Validate ubuntu_compatible is boolean if (!is_bool($response['ubuntu_compatible'])) { $response['ubuntu_compatible'] = true; } return $response; } /** * Get default models when API is unavailable */ private function getDefaultModels(): array { return [ [ 'id' => 'openai/gpt-3.5-turbo', 'name' => 'GPT-3.5 Turbo', 'description' => 'Fast and cost-effective model for general purpose tasks', 'pricing' => ['prompt' => '0.001', 'completion' => '0.002'] ], [ 'id' => 'openai/gpt-4', 'name' => 'GPT-4', 'description' => 'Most capable model for complex tasks', 'pricing' => ['prompt' => '0.03', 'completion' => '0.06'] ], [ 'id' => 'anthropic/claude-3-haiku', 'name' => 'Claude 3 Haiku', 'description' => 'Fast and cost-effective model from Anthropic', 'pricing' => ['prompt' => '0.00025', 'completion' => '0.00125'] ], [ 'id' => 'anthropic/claude-3-sonnet', 'name' => 'Claude 3 Sonnet', 'description' => 'Balanced performance and cost from Anthropic', 'pricing' => ['prompt' => '0.003', 'completion' => '0.015'] ] ]; } public function testModelAvailability(string $modelId): array { if (!$this->apiKey) { return [ 'success' => false, 'http_code' => 401, 'message' => 'OpenRouter API key not configured' ]; } $payload = [ 'model' => $modelId, 'messages' => [ ['role' => 'user', 'content' => 'Reply with: OK'] ], 'max_tokens' => 5, 'temperature' => 0 ]; $ch = curl_init($this->apiUrl . '/chat/completions'); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 20); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->apiKey, 'HTTP-Referer: https://amnez.ia', 'X-Title: Amnezia VPN Panel' ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($curlError) { return [ 'success' => false, 'http_code' => null, 'message' => 'Network error: ' . $curlError ]; } $json = json_decode($response, true); $ok = $httpCode === 200 && isset($json['choices'][0]['message']['content']); return [ 'success' => $ok, 'http_code' => $httpCode, 'message' => $ok ? 'Model is available' : ($json['error']['message'] ?? 'Model test failed') ]; } }