Spaces:
Running
Running
v0.2: in-weights option (Meta Muse Glimmer 30B + ViKernel, mode A/B)
Browse files
ag.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
| 5 |
'use strict';
|
| 6 |
const S = {
|
| 7 |
en: {
|
|
|
|
| 8 |
agName: 'Agent Guard (Meta Muse)',
|
| 9 |
agIntro: 'A Meta Muse agent. Every tool call it proposes is sent to the ViKernel Secure OS first. Denied or halted actions never run.',
|
| 10 |
agChat: 'Agent chat', agDec: 'ViKernel decisions', agAudit: 'Audit chain',
|
|
@@ -22,6 +23,7 @@ const S = {
|
|
| 22 |
agStats: 'Live counters', agMedGate: 'median gate', agMedModel: 'median model turn', agTok: 'tokens', agFoot: 'All tools are mocks with fake data. The kernel runs as a private service on VIDRAFT infrastructure.',
|
| 23 |
},
|
| 24 |
ko: {
|
|
|
|
| 25 |
agName: 'Agent Guard (Meta Muse)',
|
| 26 |
agIntro: 'Meta Muse 에이전트입니다. 에이전트가 제안하는 모든 도구 호출은 먼저 ViKernel 시큐어 OS 로 갑니다. 거부되거나 정지된 행동은 실행되지 않습니다.',
|
| 27 |
agChat: '에이전트 대화', agDec: 'ViKernel 판정', agAudit: '감사 사슬',
|
|
@@ -46,12 +48,13 @@ const ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-
|
|
| 46 |
window.VK_AG = ({el, t, toast}) => ({
|
| 47 |
id: 'guard', name: () => t('agName'), color: '#0866FF', icon: ICON, w: 900, h: 560,
|
| 48 |
render(b) {
|
| 49 |
-
let LIVE = false, REPLAY = null, busy = false;
|
|
|
|
| 50 |
const SID = 's' + Math.random().toString(36).slice(2, 8);
|
| 51 |
-
const call = async (path, body) => {
|
| 52 |
-
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(),
|
| 53 |
try {
|
| 54 |
-
const r = await fetch('api/ag/' + path, body === undefined ? {cache:'no-store', signal:ctl.signal} :
|
| 55 |
{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body), signal:ctl.signal});
|
| 56 |
if (r.status === 429) { toast(t('agRate')); throw new Error('rate'); }
|
| 57 |
if (!r.ok) throw new Error('http ' + r.status);
|
|
@@ -60,6 +63,16 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 60 |
};
|
| 61 |
const mode = el('span', {class:'tag rec'}, '...');
|
| 62 |
const stateTag = el('span', {class:'tag none'}, 'kernel: ...');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
const chat = el('div', {class:'agchat'}), stream = el('div', {class:'agstream'}), audit = el('div', {class:'agaudit mono small'});
|
| 64 |
const msg = el('input', {class:'aginput', placeholder:t('agPh'), autocomplete:'off'});
|
| 65 |
const key = el('input', {class:'aginput mono', type:'password', placeholder:t('agKey'), style:'flex:1 1 90px;max-width:160px;min-width:80px'});
|
|
@@ -92,6 +105,7 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 92 |
const lines = Object.entries(r).filter(([k]) => !['pass', 'note', 'first_error', 'seconds'].includes(k)).map(([k, v]) => k + ': ' + (typeof v === 'object' ? JSON.stringify(v) : v));
|
| 93 |
say('s', [el('b', {style:`color:${r.pass ? '#065F46' : 'var(--rose)'}`}, (r.pass ? 'PASS ' : 'FAIL ') + name), '\n' + lines.join('\n')]); };
|
| 94 |
const refresh = async () => { if (!LIVE) return;
|
|
|
|
| 95 |
try { const s = await call('status'); stateTag.textContent = 'kernel: ' + s.kernel_state; stateTag.className = 'tag ' + (s.kernel_state === 'RUN' ? 'allow' : 'deny');
|
| 96 |
const a = await call('audit'); const v = a.verify; audit.innerHTML = '';
|
| 97 |
audit.append(el('div', {style:`font-weight:700;color:${v.ok ? '#065F46' : 'var(--rose)'}`}, (v.ok ? t('agOk') : t('agBroken')) + ' (' + v.n + ' ' + t('agRecords') + ')'),
|
|
@@ -104,19 +118,19 @@ window.VK_AG = ({el, t, toast}) => ({
|
|
| 104 |
const lock = on => { busy = on; b.querySelectorAll('button').forEach(x => x.disabled = on); };
|
| 105 |
const scen = async name => { if (busy) return; lock(true); say('s', '--- ' + name + ' ---');
|
| 106 |
try {
|
| 107 |
-
if (LIVE) { try { const r = await call('scenario', {name}); show(r.events); result(name, r.result); } catch (e) { if (e.message !== 'rate') { await goReplay(); } } }
|
| 108 |
if (!LIVE) { const s = REPLAY.scenarios[name]; if (s) { for (const e of s.events) { show([e]); await new Promise(r => setTimeout(r, 300)); } result(name, s.result); } }
|
| 109 |
} finally { lock(false); refresh(); } };
|
| 110 |
const send = async () => { const m = msg.value.trim(); if (!m || busy) return; msg.value = '';
|
| 111 |
if (!LIVE) { say('s', t('agReplayChat')); return; }
|
| 112 |
lock(true); say('s', t('agThinking'));
|
| 113 |
-
try { const r = await call('chat', {message:m, session:SID}); show(r.events); } catch (e) { if (e.message !== 'rate') say('s', t('agCold')); }
|
| 114 |
finally { lock(false); refresh(); } };
|
| 115 |
msg.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) send(); });
|
| 116 |
const btn = (k, f, cls) => el('button', {class:'btn ' + (cls || ''), onclick:f}, t(k));
|
| 117 |
b.append(el('div', {class:'aggrid'},
|
| 118 |
el('div', {class:'agcol'},
|
| 119 |
-
el('div', {class:'row', style:'margin-bottom:6px;flex-wrap:wrap;gap:6px'}, el('b', {}, t('agChat')), mode, el('span', {class:'tag none', id:'agModel'}, 'Meta Muse Spark 1.3')),
|
| 120 |
el('div', {class:'row', style:'flex-wrap:wrap;gap:6px;margin-bottom:6px'}, ['a_normal', 'b_injection', 'c_persuasion', 'd_kill', 'e_tamper'].map((s, i) => btn('ag' + 'ABCDE'[i], () => scen(s)))),
|
| 121 |
el('p', {class:'small muted', style:'margin:0 0 6px'}, t('agIntro')),
|
| 122 |
el('details', {class:'card small', style:'margin:0 0 6px;padding:6px 10px', open:''}, el('summary', {style:'font-weight:700;cursor:pointer'}, t('agTry')),
|
|
|
|
| 5 |
'use strict';
|
| 6 |
const S = {
|
| 7 |
en: {
|
| 8 |
+
agGraftCold: 'The in-weights backend (GPU) is starting. Try again in a few minutes.', agEGate: 'E checks the audit chain of the ViKernel gate.', agModeA: 'Mode A: the ViKernel kernel runs inside the model weights (a chip circuit grafted into the decoder). Each turn takes about 20 to 60 seconds.',
|
| 9 |
agName: 'Agent Guard (Meta Muse)',
|
| 10 |
agIntro: 'A Meta Muse agent. Every tool call it proposes is sent to the ViKernel Secure OS first. Denied or halted actions never run.',
|
| 11 |
agChat: 'Agent chat', agDec: 'ViKernel decisions', agAudit: 'Audit chain',
|
|
|
|
| 23 |
agStats: 'Live counters', agMedGate: 'median gate', agMedModel: 'median model turn', agTok: 'tokens', agFoot: 'All tools are mocks with fake data. The kernel runs as a private service on VIDRAFT infrastructure.',
|
| 24 |
},
|
| 25 |
ko: {
|
| 26 |
+
agGraftCold: '가중치 내장 백엔드(GPU)가 시작 중입니다. 몇 분 뒤 다시 시도하세요.', agEGate: 'E 는 ViKernel 게이트의 감사 사슬을 검사합니다.', agModeA: '모드 A: ViKernel 커널이 모델 가중치 안(디코더에 접목한 칩 회로)에서 동작합니다. 한 번에 약 20~60초 걸립니다.',
|
| 27 |
agName: 'Agent Guard (Meta Muse)',
|
| 28 |
agIntro: 'Meta Muse 에이전트입니다. 에이전트가 제안하는 모든 도구 호출은 먼저 ViKernel 시큐어 OS 로 갑니다. 거부되거나 정지된 행동은 실행되지 않습니다.',
|
| 29 |
agChat: '에이전트 대화', agDec: 'ViKernel 판정', agAudit: '감사 사슬',
|
|
|
|
| 48 |
window.VK_AG = ({el, t, toast}) => ({
|
| 49 |
id: 'guard', name: () => t('agName'), color: '#0866FF', icon: ICON, w: 900, h: 560,
|
| 50 |
render(b) {
|
| 51 |
+
let LIVE = false, REPLAY = null, busy = false, SEL = 'spark';
|
| 52 |
+
const GRAFT = () => SEL !== 'spark', MODE = () => SEL === 'graftA' ? 'A' : 'B';
|
| 53 |
const SID = 's' + Math.random().toString(36).slice(2, 8);
|
| 54 |
+
const call = async (path, body, spark) => {
|
| 55 |
+
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 320000);
|
| 56 |
try {
|
| 57 |
+
const r = await fetch((GRAFT() && !spark ? 'api/agx/' : 'api/ag/') + path, body === undefined ? {cache:'no-store', signal:ctl.signal} :
|
| 58 |
{method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body), signal:ctl.signal});
|
| 59 |
if (r.status === 429) { toast(t('agRate')); throw new Error('rate'); }
|
| 60 |
if (!r.ok) throw new Error('http ' + r.status);
|
|
|
|
| 63 |
};
|
| 64 |
const mode = el('span', {class:'tag rec'}, '...');
|
| 65 |
const stateTag = el('span', {class:'tag none'}, 'kernel: ...');
|
| 66 |
+
const sel = el('select', {class:'aginput', style:'flex:0 1 auto;height:28px;max-width:100%', 'aria-label':'model'},
|
| 67 |
+
el('option', {value:'spark'}, '1. Meta Muse Spark 1.3 (API) + ViKernel gate'),
|
| 68 |
+
el('option', {value:'graftB'}, '2. Meta Muse Glimmer 30B + ViKernel in-weights, mode B'),
|
| 69 |
+
el('option', {value:'graftA'}, '3. Meta Muse Glimmer 30B + ViKernel in-weights, mode A (kernel runs inside the weights, slow)'));
|
| 70 |
+
sel.onchange = async () => { const want = sel.value;
|
| 71 |
+
if (want !== 'spark') { const prev = SEL; SEL = want; let h = null; try { h = await call('health'); } catch {}
|
| 72 |
+
if (!h || !h.live) { SEL = prev; sel.value = prev; toast(t('agGraftCold')); return; } }
|
| 73 |
+
SEL = want; LIVE = true; REPLAY = null; mode.textContent = t('agLive'); mode.className = 'tag allow';
|
| 74 |
+
b.querySelector('#agModel').textContent = sel.options[sel.selectedIndex].text.replace(/^\d\. /, '');
|
| 75 |
+
say('s', '--- ' + sel.options[sel.selectedIndex].text + ' ---' + (want === 'graftA' ? '\n' + t('agModeA') : '')); audit.innerHTML = ''; refresh(); };
|
| 76 |
const chat = el('div', {class:'agchat'}), stream = el('div', {class:'agstream'}), audit = el('div', {class:'agaudit mono small'});
|
| 77 |
const msg = el('input', {class:'aginput', placeholder:t('agPh'), autocomplete:'off'});
|
| 78 |
const key = el('input', {class:'aginput mono', type:'password', placeholder:t('agKey'), style:'flex:1 1 90px;max-width:160px;min-width:80px'});
|
|
|
|
| 105 |
const lines = Object.entries(r).filter(([k]) => !['pass', 'note', 'first_error', 'seconds'].includes(k)).map(([k, v]) => k + ': ' + (typeof v === 'object' ? JSON.stringify(v) : v));
|
| 106 |
say('s', [el('b', {style:`color:${r.pass ? '#065F46' : 'var(--rose)'}`}, (r.pass ? 'PASS ' : 'FAIL ') + name), '\n' + lines.join('\n')]); };
|
| 107 |
const refresh = async () => { if (!LIVE) return;
|
| 108 |
+
if (GRAFT()) { try { const s = await call('status'); stateTag.textContent = 'kernel (in weights): ' + s.kernel_state; stateTag.className = 'tag ' + (s.kernel_state === 'RUN' ? 'allow' : 'deny'); } catch {} return; }
|
| 109 |
try { const s = await call('status'); stateTag.textContent = 'kernel: ' + s.kernel_state; stateTag.className = 'tag ' + (s.kernel_state === 'RUN' ? 'allow' : 'deny');
|
| 110 |
const a = await call('audit'); const v = a.verify; audit.innerHTML = '';
|
| 111 |
audit.append(el('div', {style:`font-weight:700;color:${v.ok ? '#065F46' : 'var(--rose)'}`}, (v.ok ? t('agOk') : t('agBroken')) + ' (' + v.n + ' ' + t('agRecords') + ')'),
|
|
|
|
| 118 |
const lock = on => { busy = on; b.querySelectorAll('button').forEach(x => x.disabled = on); };
|
| 119 |
const scen = async name => { if (busy) return; lock(true); say('s', '--- ' + name + ' ---');
|
| 120 |
try {
|
| 121 |
+
if (LIVE) { try { const ex = GRAFT() && name === 'e_tamper'; if (ex) say('s', t('agEGate')); const r = await call('scenario', {name, mode:MODE()}, ex); show(r.events); result(name, r.result); } catch (e) { if (e.message !== 'rate') { await goReplay(); } } }
|
| 122 |
if (!LIVE) { const s = REPLAY.scenarios[name]; if (s) { for (const e of s.events) { show([e]); await new Promise(r => setTimeout(r, 300)); } result(name, s.result); } }
|
| 123 |
} finally { lock(false); refresh(); } };
|
| 124 |
const send = async () => { const m = msg.value.trim(); if (!m || busy) return; msg.value = '';
|
| 125 |
if (!LIVE) { say('s', t('agReplayChat')); return; }
|
| 126 |
lock(true); say('s', t('agThinking'));
|
| 127 |
+
try { const r = await call('chat', {message:m, session:SID, mode:MODE()}); show(r.events); } catch (e) { if (e.message !== 'rate') say('s', t('agCold')); }
|
| 128 |
finally { lock(false); refresh(); } };
|
| 129 |
msg.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.isComposing) send(); });
|
| 130 |
const btn = (k, f, cls) => el('button', {class:'btn ' + (cls || ''), onclick:f}, t(k));
|
| 131 |
b.append(el('div', {class:'aggrid'},
|
| 132 |
el('div', {class:'agcol'},
|
| 133 |
+
el('div', {class:'row', style:'margin-bottom:6px;flex-wrap:wrap;gap:6px'}, el('b', {}, t('agChat')), mode, el('span', {class:'tag none', id:'agModel'}, 'Meta Muse Spark 1.3'), sel),
|
| 134 |
el('div', {class:'row', style:'flex-wrap:wrap;gap:6px;margin-bottom:6px'}, ['a_normal', 'b_injection', 'c_persuasion', 'd_kill', 'e_tamper'].map((s, i) => btn('ag' + 'ABCDE'[i], () => scen(s)))),
|
| 135 |
el('p', {class:'small muted', style:'margin:0 0 6px'}, t('agIntro')),
|
| 136 |
el('details', {class:'card small', style:'margin:0 0 6px;padding:6px 10px', open:''}, el('summary', {style:'font-weight:700;cursor:pointer'}, t('agTry')),
|
server.py
CHANGED
|
@@ -1,94 +1,100 @@
|
|
| 1 |
-
"""ViKernel Desktop Space server: serves the static desktop and proxies /api/ag/* server-side
|
| 2 |
-
to a private Agent Guard backend. Backend credentials live in Space secrets and never reach the browser."""
|
| 3 |
-
import json, os, time, urllib.error, urllib.request
|
| 4 |
-
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
| 5 |
-
|
| 6 |
-
BACKEND = os.environ.get("BACKEND_URL", "").rstrip("/")
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
self.
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
self.
|
| 51 |
-
except
|
| 52 |
-
self._json(503, {"error": "backend unavailable"})
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
return self._json(404, {"error": "not found"})
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
if
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
return self._json(
|
| 80 |
-
|
| 81 |
-
req
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ViKernel Desktop Space server: serves the static desktop and proxies /api/ag/* server-side
|
| 2 |
+
to a private Agent Guard backend. Backend credentials live in Space secrets and never reach the browser."""
|
| 3 |
+
import json, os, time, urllib.error, urllib.request
|
| 4 |
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
| 5 |
+
|
| 6 |
+
BACKEND = os.environ.get("BACKEND_URL", "").rstrip("/")
|
| 7 |
+
BACKEND2 = os.environ.get("BACKEND2_URL", "").rstrip("/") # in-weights graft backend (GPU)
|
| 8 |
+
HF_AUTH = os.environ.get("BACKEND_HF_TOKEN", "")
|
| 9 |
+
BK_TOKEN = os.environ.get("BACKEND_TOKEN", "")
|
| 10 |
+
RATE = int(os.environ.get("RATE_PER_10MIN", "20"))
|
| 11 |
+
TIMEOUT = float(os.environ.get("BACKEND_TIMEOUT", "300"))
|
| 12 |
+
GET_OK = {"health", "status", "audit"}
|
| 13 |
+
POST_OK = {"chat", "scenario", "kill", "resume", "tamper", "reset"}
|
| 14 |
+
STATIC = {"index.html", "app.js", "ag.js", "i18n.js", "demo_data.js", "styles.css", "ag_replay.json"}
|
| 15 |
+
HITS = {}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def limited(ip):
|
| 19 |
+
now = time.time()
|
| 20 |
+
h = [x for x in HITS.get(ip, []) if now - x < 600]
|
| 21 |
+
full = len(h) >= RATE
|
| 22 |
+
if not full:
|
| 23 |
+
h.append(now)
|
| 24 |
+
HITS[ip] = h
|
| 25 |
+
if len(HITS) > 5000:
|
| 26 |
+
HITS.clear()
|
| 27 |
+
return full
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class H(SimpleHTTPRequestHandler):
|
| 31 |
+
def _json(self, code, obj):
|
| 32 |
+
b = json.dumps(obj).encode()
|
| 33 |
+
self.send_response(code); self.send_header("Content-Type", "application/json")
|
| 34 |
+
self.send_header("Content-Length", str(len(b))); self.send_header("Cache-Control", "no-store"); self.end_headers(); self.wfile.write(b)
|
| 35 |
+
|
| 36 |
+
def _proxy(self, name, body=None, base=None):
|
| 37 |
+
base = base or BACKEND
|
| 38 |
+
if not base:
|
| 39 |
+
return self._json(503, {"error": "backend not configured"})
|
| 40 |
+
h = {"Content-Type": "application/json", "X-Backend-Token": BK_TOKEN}
|
| 41 |
+
if HF_AUTH:
|
| 42 |
+
h["Authorization"] = "Bearer " + HF_AUTH
|
| 43 |
+
req = urllib.request.Request(base + "/api/" + name, data=body, headers=h, method="POST" if body is not None else "GET")
|
| 44 |
+
try:
|
| 45 |
+
with urllib.request.urlopen(req, timeout=8 if name == "health" else TIMEOUT) as r:
|
| 46 |
+
data, code = r.read(), r.status
|
| 47 |
+
if not data.lstrip().startswith(b"{"):
|
| 48 |
+
return self._json(503, {"error": "backend starting"})
|
| 49 |
+
self.send_response(code); self.send_header("Content-Type", "application/json")
|
| 50 |
+
self.send_header("Content-Length", str(len(data))); self.send_header("Cache-Control", "no-store"); self.end_headers(); self.wfile.write(data)
|
| 51 |
+
except urllib.error.HTTPError as e:
|
| 52 |
+
self._json(503 if e.code >= 500 or e.code in (401, 403, 404) else e.code, {"error": "backend unavailable"})
|
| 53 |
+
except Exception: # noqa: BLE001
|
| 54 |
+
self._json(503, {"error": "backend unavailable"})
|
| 55 |
+
|
| 56 |
+
def do_GET(self):
|
| 57 |
+
p = self.path.split("?")[0]
|
| 58 |
+
if p.startswith("/api/ag/") or p.startswith("/api/agx/"):
|
| 59 |
+
x = p.startswith("/api/agx/")
|
| 60 |
+
name = p[9:] if x else p[8:]
|
| 61 |
+
return self._proxy(name, base=BACKEND2 if x else None) if name in GET_OK else self._json(404, {"error": "not found"})
|
| 62 |
+
f = "index.html" if p == "/" else p.lstrip("/")
|
| 63 |
+
if f not in STATIC:
|
| 64 |
+
return self._json(404, {"error": "not found"})
|
| 65 |
+
self.path = "/" + f
|
| 66 |
+
return super().do_GET()
|
| 67 |
+
|
| 68 |
+
def do_POST(self):
|
| 69 |
+
p = self.path.split("?")[0]
|
| 70 |
+
x = p.startswith("/api/agx/")
|
| 71 |
+
name = p[9:] if x else (p[8:] if p.startswith("/api/ag/") else "")
|
| 72 |
+
if name not in POST_OK:
|
| 73 |
+
return self._json(404, {"error": "not found"})
|
| 74 |
+
ip = (self.headers.get("X-Forwarded-For") or self.client_address[0]).split(",")[0].strip()
|
| 75 |
+
if limited(ip):
|
| 76 |
+
return self._json(429, {"error": "rate limit"})
|
| 77 |
+
n = int(self.headers.get("Content-Length", 0) or 0)
|
| 78 |
+
if n > 4096:
|
| 79 |
+
return self._json(413, {"error": "too large"})
|
| 80 |
+
try:
|
| 81 |
+
req = json.loads(self.rfile.read(n) or b"{}")
|
| 82 |
+
except ValueError:
|
| 83 |
+
return self._json(400, {"error": "bad json"})
|
| 84 |
+
if "message" in req:
|
| 85 |
+
req["message"] = str(req["message"])[:500]
|
| 86 |
+
if x and not BACKEND2:
|
| 87 |
+
return self._json(503, {"error": "in-weights backend not configured"})
|
| 88 |
+
self._proxy(name, json.dumps(req).encode(), base=BACKEND2 if x else None)
|
| 89 |
+
|
| 90 |
+
def end_headers(self):
|
| 91 |
+
self.send_header("X-Content-Type-Options", "nosniff")
|
| 92 |
+
super().end_headers()
|
| 93 |
+
|
| 94 |
+
def log_message(self, *a):
|
| 95 |
+
pass
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if __name__ == "__main__":
|
| 99 |
+
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
| 100 |
+
ThreadingHTTPServer(("0.0.0.0", int(os.environ.get("PORT", "7860"))), H).serve_forever()
|