var http=require('http'),fs=require('fs'),path=require('path'); var PORT=parseInt(process.env.PORT||'3090'); var IX_HOST=process.env.IX_HOST||'127.0.0.1'; var IX_PORT=parseInt(process.env.IX_PORT||'8081'); var DEFAULT_MODEL='qwen2.5-3b-instruct-q4_k_m'; var rates={}; function rateOk(k,m,w){var n=Date.now();if(!rates[k])rates[k]=[];rates[k]=rates[k].filter(function(t){return n-t=m)return false;rates[k].push(n);return true} function ixProxy(p,b,to,cb){var d=JSON.stringify(b);var o={hostname:IX_HOST,port:IX_PORT,path:p,method:'POST',timeout:to,headers:{'Content-Type':'application/json','Content-Length':Buffer.byteLength(d)}};var r=http.request(o,function(res){var c=[];res.on('data',function(x){c.push(x)});res.on('end',function(){try{cb(null,JSON.parse(Buffer.concat(c).toString()))}catch(e){cb(new Error('Parse'))}})});r.on('error',function(e){cb(e)});r.on('timeout',function(){r.destroy();cb(new Error('Timeout'))});r.write(d);r.end()} function ixGet(p,cb){http.get({hostname:IX_HOST,port:IX_PORT,path:p,timeout:5000},function(res){var c=[];res.on('data',function(x){c.push(x)});res.on('end',function(){try{cb(null,JSON.parse(Buffer.concat(c).toString()))}catch(e){cb(new Error('Parse'))}})}).on('error',function(e){cb(e)})} function json(res,d,s){res.writeHead(s||200,{'Content-Type':'application/json'});res.end(JSON.stringify(d))} function readBody(req,cb){var c=[];req.on('data',function(x){c.push(x)});req.on('end',function(){try{cb(JSON.parse(Buffer.concat(c).toString()))}catch(e){cb({})}})} var srv=http.createServer(function(req,res){ res.setHeader('Access-Control-Allow-Origin','*'); res.setHeader('Access-Control-Allow-Methods','GET,POST,OPTIONS'); res.setHeader('Access-Control-Allow-Headers','Content-Type,Authorization'); if(req.method==='OPTIONS'){res.writeHead(204);res.end();return} res.setHeader('X-Frame-Options','DENY'); res.setHeader('X-Content-Type-Options','nosniff'); var url=req.url.split('?')[0]; if(url==='/api/health')return json(res,{status:'ok',service:'echo-ix',version:'1.0',build:'935'}); if(url==='/api/echo/models'){ixGet('/v1/models',function(e,d){if(!e&&d&&d.data)return json(res,{data:d.data,source:'live'});json(res,{data:[],source:'offline'})});return} if(url==='/api/echo/hardware'){ixGet('/v1/models',function(e,d){if(e)return json(res,{status:'offline',ram_gb:64,cores:16,models:0});json(res,{status:'online',ram_gb:64,cores:16,models:d.data?d.data.length:0,active_model:DEFAULT_MODEL})});return} if(url==='/api/echo/chat'&&req.method==='POST'){var ip=req.socket.remoteAddress;if(!rateOk('e:'+ip,30,6e4))return json(res,{error:'Rate limit'},429);readBody(req,function(b){var msgs=b.messages||[];if(!msgs.length)return json(res,{error:'Messages required'},400);var t0=Date.now();var m=b.model==='auto'||!b.model?DEFAULT_MODEL:b.model;ixProxy('/v1/chat/completions',{model:m,messages:msgs,max_tokens:Math.min(b.max_tokens||512,2048),temperature:b.temperature||0.7},120000,function(e,d){if(e)return json(res,{choices:[{message:{role:'assistant',content:'Backend loading.'},finish_reason:'stop'}],ix:{backend:'offline'}});d.ix={backend:m,latency_ms:Date.now()-t0};json(res,d)})});return} if(url==='/api/echo/stream'&&req.method==='POST'){var ip=req.socket.remoteAddress;if(!rateOk('e:'+ip,30,6e4))return json(res,{error:'Rate limit'},429);readBody(req,function(b){var msgs=b.messages||[];if(!msgs.length)return json(res,{error:'Messages required'},400);res.writeHead(200,{'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive','X-Accel-Buffering':'no'});var m=b.model==='auto'||!b.model?DEFAULT_MODEL:b.model;var d=JSON.stringify({model:m,messages:msgs,max_tokens:Math.min(b.max_tokens||512,2048),temperature:b.temperature||0.7,stream:true});var o={hostname:IX_HOST,port:IX_PORT,path:'/v1/chat/completions',method:'POST',timeout:120000,headers:{'Content-Type':'application/json','Content-Length':Buffer.byteLength(d)}};var p=http.request(o,function(pR){pR.on('data',function(c){res.write(c)});pR.on('end',function(){res.end()})});p.on('error',function(){res.end()});p.write(d);p.end();req.on('close',function(){p.destroy()})});return} if(url==='/'||url==='/echo')url='/index.html'; var ext=path.extname(url);var mime={'.html':'text/html','.css':'text/css','.js':'application/javascript','.png':'image/png','.svg':'image/svg+xml'}; fs.readFile(path.join(__dirname,'public',url),function(e,d){if(e){fs.readFile(path.join(__dirname,'public','index.html'),function(e2,d2){if(e2){res.writeHead(404);res.end('Not found');return}res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'});res.end(d2)});return}res.writeHead(200,{'Content-Type':(mime[ext]||'application/octet-stream')+';charset=utf-8'});res.end(d)}); }); srv.listen(PORT,function(){console.log('Echo-IX :'+PORT+' -> '+IX_HOST+':'+IX_PORT+' | v935')});