true, 'message' => 'Proxy server is running!', 'timestamp' => date('c'), 'version' => '1.0.0' ]); exit; } // Rota de health check if ($_SERVER['REQUEST_METHOD'] === 'GET' && $_SERVER['REQUEST_URI'] === '/api/health') { header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); echo json_encode([ 'status' => 'ok', 'uptime' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'] ]); exit; } // Rota para verificar status da request if ($_SERVER['REQUEST_METHOD'] === 'GET' && strpos($_SERVER['REQUEST_URI'], '/api/check/') !== false) { $requestId = basename($_SERVER['REQUEST_URI']); logMessage("Checking status for request: $requestId"); header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); $responseFile = $responseDir . '/' . $requestId . '.json'; $statusFile = $responseDir . '/' . $requestId . '.status'; // Verificar e criar o diretório de respostas se necessário if (!is_dir($responseDir)) { if (!mkdir($responseDir, 0777, true)) { http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Failed to create response directory', 'requestId' => $requestId ]); exit; } chmod($responseDir, 0777); logMessage("Created response directory during status check"); } // Verificar se o arquivo de resposta existe if (file_exists($responseFile)) { // Garantir que o arquivo seja legível chmod($responseFile, 0666); if (!is_readable($responseFile)) { logMessage("ERROR: Response file exists but is not readable: $responseFile"); http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Response file exists but is not readable', 'requestId' => $requestId ]); exit; } // Verificar tamanho do arquivo $fileSize = filesize($responseFile); if ($fileSize === 0) { logMessage("ERROR: Response file exists but is empty: $responseFile"); http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Response file exists but is empty', 'requestId' => $requestId, 'fileSize' => $fileSize ]); exit; } // Tentar ler o arquivo de resposta try { $responseData = file_get_contents($responseFile); // Verificar se é JSON válido $jsonData = json_decode($responseData, true); if ($jsonData === null) { logMessage("ERROR: Response file contains invalid JSON: $responseFile"); http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Response file contains invalid JSON', 'requestId' => $requestId, 'fileSize' => $fileSize, 'preview' => substr($responseData, 0, 200) . (strlen($responseData) > 200 ? '...' : '') ]); exit; } // Se tudo estiver OK, retornar o conteúdo do arquivo echo $responseData; } catch (Exception $e) { logMessage("ERROR: Failed to read response file: " . $e->getMessage()); http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Failed to read response file: ' . $e->getMessage(), 'requestId' => $requestId ]); } } // Verificar se existe um arquivo de status temporário else if (file_exists($statusFile)) { try { $statusData = file_get_contents($statusFile); $status = json_decode($statusData, true); if ($status === null) { http_response_code(202); // Accepted, still processing echo json_encode([ 'status' => 'processing', 'requestId' => $requestId, 'message' => 'Your request is still being processed. Status file exists but is invalid.' ]); } else { http_response_code(202); // Accepted, still processing echo json_encode($status); } } catch (Exception $e) { http_response_code(202); // Accepted, still processing echo json_encode([ 'status' => 'processing', 'requestId' => $requestId, 'message' => 'Your request is still being processed. Exception reading status file: ' . $e->getMessage() ]); } } // Se não existir nem arquivo de resposta nem de status, considerar como ainda em processamento else { // Verificar Claude logs para ver se essa requisição já existe $requestLog = $responseDir . '/request_log.txt'; $foundInLogs = false; if (file_exists($requestLog) && is_readable($requestLog)) { $logContent = file_get_contents($requestLog); if (strpos($logContent, $requestId) !== false) { $foundInLogs = true; } } if ($foundInLogs) { logMessage("No response or status file, but request found in logs: $requestId"); http_response_code(202); // Accepted, still processing echo json_encode([ 'status' => 'processing', 'requestId' => $requestId, 'message' => 'Your request is being processed but no status file exists yet. This may indicate the background process is still running.' ]); } else { logMessage("No response file, status file, or log entry found for: $requestId"); http_response_code(404); // Not found echo json_encode([ 'status' => 'error', 'message' => 'Request not found. It may have been deleted or never existed.', 'requestId' => $requestId ]); } } exit; } // Lidar com preflight OPTIONS if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, x-api-key'); header('Access-Control-Max-Age: 86400'); // Cache por 24 horas http_response_code(200); exit; } // Função para fazer requisições HTTP function makeRequest($url, $method, $headers, $body = null) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); // Configurações de timeout para CURL - aumentado para 20 minutos curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60); // 1 minuto para estabelecer conexão curl_setopt($ch, CURLOPT_TIMEOUT, 1200); // 20 minutos para completar a operação // Adicionar configurações para manter a conexão ativa curl_setopt($ch, CURLOPT_TCP_KEEPALIVE, 1); curl_setopt($ch, CURLOPT_TCP_KEEPIDLE, 60); // Manter viva por 60 segundos de inatividade curl_setopt($ch, CURLOPT_TCP_KEEPINTVL, 30); // Intervalo de 30 segundos entre keep-alive // Seguir redirecionamentos se necessário curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); if ($body !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body)); } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (curl_errno($ch)) { $error = curl_error($ch); curl_close($ch); throw new Exception("CURL Error: $error (Code: " . curl_errno($ch) . ")"); } curl_close($ch); return ['code' => $httpCode, 'body' => $response]; } // Função para processar requisição em segundo plano function processClaudeRequestInBackground($requestId, $requestHeaders, $requestBody, $responseDir) { logMessage("Starting background processing for request ID: $requestId"); // Verificar novamente a existência e permissões do diretório if (!is_dir($responseDir)) { logMessage("Response directory doesn't exist. Creating: $responseDir"); if (!mkdir($responseDir, 0777, true)) { logMessage("ERROR: Failed to create response directory"); file_put_contents($responseDir . '/' . $requestId . '.json', json_encode([ 'error' => 'Failed to create response directory', 'status' => 'error' ])); return; } chmod($responseDir, 0777); // Garantir permissões adequadas } // Verificar se o diretório é gravável if (!is_writable($responseDir)) { logMessage("ERROR: Response directory is not writable: $responseDir"); chmod($responseDir, 0777); // Tentar corrigir permissões // Verificar novamente if (!is_writable($responseDir)) { logMessage("ERROR: Failed to make response directory writable"); return; } } // Registrar informações da solicitação em arquivo de log $requestLog = [ 'timestamp' => date('Y-m-d H:i:s'), 'request_id' => $requestId, 'model' => $requestBody['model'] ?? 'unknown', 'request_size' => isset($requestBody['messages'][0]['content']) ? strlen($requestBody['messages'][0]['content']) : 0 ]; file_put_contents($responseDir . '/request_log.txt', json_encode($requestLog) . "\n", FILE_APPEND); try { $startTime = microtime(true); // Construir headers para a API da Claude $anthropicVersion = $requestHeaders['anthropic-version'] ?? '2023-06-01'; $apiHeaders = [ 'Content-Type: application/json', 'x-api-key: ' . $requestHeaders['x-api-key'], 'anthropic-version: ' . $anthropicVersion ]; // Fazer a requisição à API da Claude logMessage("Sending request to Claude API for $requestId"); // Salvar um arquivo de status para informar que a requisição está em andamento file_put_contents($responseDir . '/' . $requestId . '.status', json_encode([ 'status' => 'processing', 'start_time' => date('Y-m-d H:i:s'), 'request_id' => $requestId, 'message' => 'Request is being processed by Claude API' ])); $response = makeRequest( 'https://api.anthropic.com/v1/messages', 'POST', $apiHeaders, $requestBody ); $endTime = microtime(true); $duration = round($endTime - $startTime, 2); logMessage("Claude API request $requestId completed in $duration seconds"); // Excluir o arquivo de status, pois temos a resposta completa agora if (file_exists($responseDir . '/' . $requestId . '.status')) { unlink($responseDir . '/' . $requestId . '.status'); } if ($response['code'] >= 400) { // Processar erro na resposta da API $errorData = json_decode($response['body'], true); $userMessage = 'Error in Claude API'; switch ($response['code']) { case 401: $userMessage = 'Authentication error: Please verify your Claude API key is valid.'; break; case 400: $userMessage = 'Request error: ' . ($errorData['error']['message'] ?? 'Please check the format of the data sent.'); break; case 429: $userMessage = 'Rate limit exceeded: Please wait a moment before trying again.'; break; case 500: $userMessage = 'Internal error in Claude API: Please try again later.'; break; case 504: $userMessage = 'Gateway timeout: The request to Claude API timed out.'; break; case 404: $userMessage = 'Endpoint not found: Please verify the requested model exists.'; break; } logMessage("Error response from Claude API for $requestId: $userMessage"); // Salvar erro em arquivo $errorResponse = json_encode([ 'error' => $userMessage, 'details' => $errorData, 'message' => $userMessage, 'status' => 'error', 'code' => $response['code'], 'completed_at' => date('Y-m-d H:i:s') ]); $result = file_put_contents($responseDir . '/' . $requestId . '.json', $errorResponse); if ($result === false) { logMessage("ERROR: Failed to write error response to file for $requestId"); } else { logMessage("Error response saved to file for $requestId ($result bytes)"); } } else { // Verificar se a resposta da Claude é válida $responseData = $response['body']; $jsonData = json_decode($responseData, true); if ($jsonData === null) { logMessage("WARNING: Received non-JSON response from Claude API for $requestId"); // Tentar salvar mesmo assim $result = file_put_contents($responseDir . '/' . $requestId . '.json', json_encode([ 'error' => 'Invalid JSON response from Claude API', 'raw_response' => substr($responseData, 0, 1000) . (strlen($responseData) > 1000 ? '...' : ''), 'status' => 'error', 'completed_at' => date('Y-m-d H:i:s') ])); if ($result === false) { logMessage("ERROR: Failed to write error report to file for $requestId"); } return; } // Verificar se a resposta tem o formato esperado if (!isset($jsonData['content']) || !is_array($jsonData['content']) || count($jsonData['content']) === 0) { logMessage("WARNING: Unexpected Claude API response format for $requestId"); // Salvar resposta de erro com os dados recebidos $result = file_put_contents($responseDir . '/' . $requestId . '.json', json_encode([ 'error' => 'Unexpected response format from Claude API', 'data' => $jsonData, 'status' => 'error', 'completed_at' => date('Y-m-d H:i:s') ])); if ($result === false) { logMessage("ERROR: Failed to write error report to file for $requestId"); } return; } // Resposta bem-sucedida logMessage("Successful response from Claude API for $requestId"); // Adicionar timestamp à resposta $jsonData['completed_at'] = date('Y-m-d H:i:s'); $jsonData['processing_time'] = $duration; // Salvar resposta em arquivo $outputData = json_encode($jsonData); $result = file_put_contents($responseDir . '/' . $requestId . '.json', $outputData); if ($result === false) { logMessage("ERROR: Failed to write response to file for $requestId"); } else { logMessage("Response saved to file for $requestId ($result bytes)"); logMessage("Response content length: " . (isset($jsonData['content'][0]['text']) ? strlen($jsonData['content'][0]['text']) : 'unknown')); // Verificar se o arquivo foi realmente criado e é legível if (file_exists($responseDir . '/' . $requestId . '.json')) { if (is_readable($responseDir . '/' . $requestId . '.json')) { $fileSize = filesize($responseDir . '/' . $requestId . '.json'); logMessage("Confirmed: Response file exists and is readable. Size: $fileSize bytes"); } else { logMessage("ERROR: Response file exists but is not readable"); } } else { logMessage("ERROR: Response file was not created despite successful write operation"); } } } } catch (Exception $e) { logMessage("Error processing background request $requestId: " . $e->getMessage()); // Salvar erro em arquivo $errorResponse = json_encode([ 'error' => 'Internal error processing request', 'message' => $e->getMessage(), 'status' => 'error', 'completed_at' => date('Y-m-d H:i:s') ]); $result = file_put_contents($responseDir . '/' . $requestId . '.json', $errorResponse); if ($result === false) { logMessage("ERROR: Failed to write error response to file for $requestId"); } } } // Rota para Claude API if ($_SERVER['REQUEST_METHOD'] === 'POST' && strpos($_SERVER['REQUEST_URI'], '/api/claude') !== false) { try { logMessage('Received request for Claude API'); // Definir cabeçalhos de resposta header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, x-api-key'); // Verificar API key $headers = getallheaders(); if (!isset($headers['x-api-key'])) { throw new Exception('API key is missing in request headers'); } // Obter corpo da requisição $requestBody = json_decode(file_get_contents('php://input'), true); // Log para debug if (isset($requestBody['messages'])) { logMessage("Number of messages: " . count($requestBody['messages'])); logMessage("Requested model: " . ($requestBody['model'] ?? 'not specified')); logMessage("Temperature: " . ($requestBody['temperature'] ?? 'default')); // Log de tamanho do prompt if (isset($requestBody['messages'][0]['content'])) { $promptSize = strlen($requestBody['messages'][0]['content']); logMessage("Prompt size: $promptSize characters"); } } // Gerar ID único para esta requisição $requestId = uniqid('req_'); logMessage("Generated request ID: $requestId"); // Criar diretório para armazenar respostas se não existir if (!is_dir($responseDir)) { if (!mkdir($responseDir, 0755, true)) { throw new Exception("Unable to create response directory: $responseDir"); } logMessage("Created response directory: $responseDir"); } // Verificar permissões de escrita if (!is_writable($responseDir)) { throw new Exception("Response directory is not writable: $responseDir"); } // Enviar resposta imediata ao cliente echo json_encode([ 'status' => 'processing', 'requestId' => $requestId, 'message' => 'Your request is being processed asynchronously. Use the provided requestId to check status.' ]); // Fechar a conexão com o cliente para poder continuar processando ob_end_flush(); flush(); if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); } // Agora podemos processar a requisição sem o cliente esperando processClaudeRequestInBackground($requestId, $headers, $requestBody, $responseDir); } catch (Exception $e) { logMessage('Error initiating Claude API request: ' . $e->getMessage()); http_response_code(500); echo json_encode([ 'error' => 'Error initiating request', 'message' => $e->getMessage() ]); } exit; } // Rota para OpenAI API if ($_SERVER['REQUEST_METHOD'] === 'POST' && strpos($_SERVER['REQUEST_URI'], '/api/openai') !== false) { try { logMessage('Received request for OpenAI API'); // Verificar API key $headers = getallheaders(); if (!isset($headers['Authorization'])) { throw new Exception('API key is missing in request headers'); } // Configurar headers $requestHeaders = [ 'Content-Type: application/json', 'Authorization: ' . $headers['Authorization'] ]; // Obter corpo da requisição $requestBody = json_decode(file_get_contents('php://input'), true); logMessage('Sending request to OpenAI API...'); $response = makeRequest( 'https://api.openai.com/v1/chat/completions', 'POST', $requestHeaders, $requestBody ); if ($response['code'] >= 400) { $errorData = json_decode($response['body'], true); http_response_code($response['code']); echo json_encode([ 'error' => 'Error from OpenAI API', 'details' => $errorData, 'message' => $errorData['error']['message'] ?? 'Unknown error' ]); exit; } logMessage('Response received from OpenAI API successfully'); echo $response['body']; } catch (Exception $e) { logMessage('Error in request to OpenAI API: ' . $e->getMessage()); http_response_code(500); echo json_encode([ 'error' => 'Internal error in proxy server', 'message' => $e->getMessage() ]); } exit; } // Se nenhuma rota for encontrada http_response_code(404); echo json_encode([ 'error' => 'Not Found', 'message' => 'The requested endpoint does not exist' ]);