sanket3280/code-execution
0
1const axios = require('axios');2 3const CODE_EVAL_URL = process.env.CODE_EVAL_URL || 'https://api.hackerearth.com/v4/partner/code-evaluation/submissions/';4const CLIENT_SECRET = process.env.CLIENT_SECRET;5 6const STATUS_CODES = {7 'AC': 'Accepted',8 'TLE': 'Time Limit Exceeded',9 'MLE': 'Memory Limit Exceeded',10 'RE': 'Runtime Error',11 'CE': 'Compilation Error',12 'REQUEST_FAILED': 'Request Failed'13};14 15/**16 * Normalize output for comparison - handles arrays, objects, strings, numbers17 * Ensures consistent comparison regardless of data type18 */19const normalizeOutputForCompare = (text) => {20 if (text === null || text === undefined) return '';21 22 let str = String(text).trim();23 24 // Remove all whitespace for comparison25 str = str.replace(/\s+/g, '');26 27 // Remove quotes if present (to compare "321" with 321)28 str = str.replace(/^["']|["']$/g, '');29 30 // Handle boolean values31 const lower = str.toLowerCase();32 if (lower === 'true') return 'true';33 if (lower === 'false') return 'false';34 35 // Try to parse as number36 const num = Number(str);37 if (!isNaN(num) && str !== '') {38 return String(num); // Convert to string for consistent comparison39 }40 41 // Try to parse as JSON for arrays/objects42 try {43 const parsed = JSON.parse(str);44 // Stringify back to ensure consistent format45 return JSON.stringify(parsed).replace(/\s+/g, '');46 } catch (e) {47 // Not JSON, return as is (without whitespace and quotes)48 return str;49 }50};51 52/**53 * Get output content from URL or direct string54 */55const getOutputContent = async (outputUrl) => {56 try {57 // If it's already a string (Docker output), return it directly58 if (typeof outputUrl === 'string' && !outputUrl.startsWith('http')) {59 return outputUrl.trim();60 }61 62 // Otherwise fetch from URL (HackerEarth output)63 const response = await axios.get(outputUrl, { 64 responseType: 'text',65 timeout: 10000 66 });67 let output = '';68 if (typeof response.data === 'string') output = response.data;69 else if (typeof response.data === 'number') output = String(response.data);70 else if (response.data && typeof response.data === 'object') output = JSON.stringify(response.data);71 else output = String(response.data || '');72 return output.trim();73 } catch (error) {74 console.error('โ Error fetching output content:', error.message);75 return '';76 }77};78 79/**80 * Extract output from run status81 */82const extractOutput = async (runStatus) => {83 try {84 if (!runStatus) return '';85 86 if (runStatus.status === 'CODE_COMPILED' && !runStatus.output && !runStatus.stdout) {87 return '';88 }89 90 91 if (runStatus.output) {92 const output = await getOutputContent(runStatus.output);93 if (output && output.trim()) return output;94 }95 if (runStatus.output_html) return String(runStatus.output_html).replace(/<[^>]+>/g, '').trim();96 if (runStatus.stdout) return String(runStatus.stdout).trim();97 if (runStatus.compile_status && runStatus.status === 'CE') return String(runStatus.compile_status).trim();98 if (runStatus.stderr) return String(runStatus.stderr).trim();99 100 if (runStatus.result && runStatus.result.run_status) {101 const r = runStatus.result.run_status;102 if (r.output) return r.output;103 if (r.stdout) return r.stdout;104 if (r.stderr) return r.stderr;105 }106 } catch (e) {107 console.error('extractOutput error:', e);108 return '';109 }110 return '';111};112 113// In-memory store for Docker results (temporary, until proper DB storage)114const dockerResults = new Map();115 116/**117 * Store Docker result for later retrieval118 */119const storeDockerResult = (heId, result) => {120 dockerResults.set(heId, result);121 // Auto-cleanup after 5 minutes122 setTimeout(() => dockerResults.delete(heId), 5 * 60 * 1000);123};124 125/**126 * Get status of submission127 */128const getStatus = async (heId) => {129 // Check if this is a Docker result130 if (heId && heId.startsWith('docker_')) {131 const result = dockerResults.get(heId);132 if (result) {133 return result;134 }135 throw new Error('Docker result not found or expired');136 }137 138 // HackerEarth result139 if (!CLIENT_SECRET) throw new Error('HackerEarth CLIENT_SECRET not configured');140 const url = `${CODE_EVAL_URL}${heId}/`;141 const headers = { 'client-secret': CLIENT_SECRET };142 const response = await axios.get(url, { headers, timeout: 10000 });143 return response.data;144};145 146/**147 * Poll for completion with optimized polling intervals148 */149const pollForCompletion = async (heId, maxAttempts = 10, interval = 100) => {150 // Check if this is a Docker result (heId starts with "docker_")151 if (heId && heId.startsWith('docker_')) {152 console.log('\n' + '='.repeat(80));153 console.log('๐ณ DOCKER RESULT - No polling needed');154 console.log('='.repeat(80) + '\n');155 156 // Docker result is already complete, fetch it directly157 try {158 const status = await getStatus(heId);159 return status;160 } catch (error) {161 // If getStatus fails for Docker, it means result is not stored162 // This shouldn't happen, but handle gracefully163 console.error('โ Failed to get Docker result:', error.message);164 throw new Error('Docker result not found');165 }166 }167 168 let attempts = 0;169 let codeCompiledCounter = 0;170 let firstCodeCompiledAt = null;171 172 // Optimized polling for HackerEarth173 const optimizedInterval = 200;174 const optimizedMaxAttempts = 20;175 176 while (attempts < optimizedMaxAttempts) {177 try {178 const status = await getStatus(heId);179 const runStatus = status.result?.run_status;180 const requestStatus = status.request_status?.code;181 182 if (requestStatus === 'REQUEST_FAILED') {183 throw new Error('Code execution failed');184 }185 186 // Track CODE_COMPILED state - Be patient with HackerEarth187 if (runStatus?.status === 'CODE_COMPILED') {188 if (firstCodeCompiledAt === null) {189 firstCodeCompiledAt = attempts;190 }191 codeCompiledCounter++;192 193 // Allow 15 attempts (3 seconds) before considering it stuck194 // HackerEarth needs time for Java compilation + execution195 if (codeCompiledCounter >= 15) {196 // Don't throw error, just return the status and let it be handled197 console.log('โ ๏ธ Still CODE_COMPILED after 15 attempts, returning status');198 return status;199 }200 } else {201 codeCompiledCounter = 0;202 firstCodeCompiledAt = null;203 }204 205 if (requestStatus === 'REQUEST_COMPLETED' && runStatus) {206 const finalStatuses = ['AC', 'TLE', 'MLE', 'RE', 'CE', 'WA'];207 208 if (runStatus.status === 'NA' || runStatus.status === 'REQUEST_QUEUED') {209 // Continue polling210 } else if (runStatus.status === 'CODE_COMPILED') {211 // CODE_COMPILED means compilation succeeded but no execution output yet212 // Continue polling unless we've waited too long213 if (codeCompiledCounter >= 15) {214 return status; // Return what we have215 }216 } else if (finalStatuses.includes(runStatus.status)) {217 return status;218 } else {219 return status;220 }221 }222 223 await new Promise(r => setTimeout(r, optimizedInterval));224 attempts++;225 } catch (error) {226 attempts++;227 if (attempts >= optimizedMaxAttempts) throw error;228 }229 }230 231 try {232 const finalStatus = await getStatus(heId);233 return finalStatus;234 } catch (e) {235 throw new Error(`Timeout after ${optimizedMaxAttempts} attempts`);236 }237};238 239/**240 * Submit code to HackerEarth with detailed logging241 */242const submitToHackerEarth = async ({ source, language, memoryLimit = 131072, timeLimit = 3, input, usedRunner }) => {243 if (!CLIENT_SECRET) {244 throw new Error('HackerEarth CLIENT_SECRET not configured. Please set CLIENT_SECRET environment variable.');245 }246 247 const headers = { 248 'client-secret': CLIENT_SECRET, 249 'content-type': 'application/json' 250 };251 252 const data = {253 lang: language,254 source,255 memory_limit: memoryLimit,256 time_limit: timeLimit,257 context: JSON.stringify({ id: Date.now() })258 };259 260 if (!usedRunner) data.input = input;261 262 const response = await axios.post(CODE_EVAL_URL, data, { 263 headers, 264 timeout: 10000 265 });266 267 return response.data;268};269 270module.exports = {271 STATUS_CODES,272 normalizeOutputForCompare,273 getOutputContent,274 extractOutput,275 getStatus,276 storeDockerResult,277 pollForCompletion,278 submitToHackerEarth279};