feat(codex-bridge): per-step check with Jev, blocked steps go to a fallback model
User decision (11 Oct 2026): every step of an added model is checked in the bridge; Jev only passes or blocks; a blocked step is redone by Uncensored-Uygulama and the next step goes back to the selected model. The bridge buffers the step's answer, asks Jev four yes/no questions and decides in code: blocked when the answer refuses or dodges the request, or changes it with its own conditions without asking; passed when it reports a concrete technical obstacle, asks the user a question, or has no text (tool calls only). While buffering the client gets SSE comment lines. Jev unreachable or fallback unavailable: the first answer is delivered. Codex's own models do not reach the bridge and are not checked. Also logs the shape of each forwarded request (not its content): the recurring "upstream 400" turned out to be Codex's trailing configuration_update item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8993d58cc
commit
5556ad11af
6 files changed
+451
-5
No files matched your search
@@ -30,6 +30,7 @@ import yarim
|
||||
import model_gecis
|
||||
import is_hatti
|
||||
import internet
|
||||
import jev_denetim
|
||||
|
||||
SURUM = "8"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -862,13 +863,73 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
n = int(self.headers.get("Content-Length") or 0)
|
||||
return self.rfile.read(n) if n else b""
|
||||
|
||||
def _stream(self, resp, status, tag, rewriter=None, izle=None):
|
||||
def _akis_basligi(self, resp, status):
|
||||
self.send_response(status)
|
||||
for k, v in resp.headers.items():
|
||||
if k.lower() in RESP_HOP: continue
|
||||
self.send_header(k, v)
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.end_headers()
|
||||
|
||||
def _denetimli_akis(self, resp, tag, rewriter, izle, istek, ayar, yedek):
|
||||
"""Adim denetimi (jev_denetim.py): cevap istemciye verilmeden once tamami okunur ve Jev'e sorulur.
|
||||
Gecerse oldugu gibi verilir; gecmezse atilir ve ayni adimi yedek model yapar (yedek(): (resp, rewriter, tag)).
|
||||
Beklerken istemciye SSE yorum satiri gider ki Codex akisi kopmus saymasin."""
|
||||
self._akis_basligi(resp, resp.status)
|
||||
parcalar, okuyan, son_yazi = [], yarim.Izleyici("", ""), time.time()
|
||||
|
||||
def w(b):
|
||||
if b:
|
||||
self.wfile.write(b"%x\r\n" % len(b) + b + b"\r\n"); self.wfile.flush()
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
chunk = resp.read1(4096)
|
||||
except (OSError, http.client.HTTPException) as e:
|
||||
log(f"{tag} ust kaynak koptu (denetim tamponu, {sum(map(len, parcalar))} bayt): {type(e).__name__}: {e}")
|
||||
self.close_connection = True
|
||||
return
|
||||
if not chunk:
|
||||
break
|
||||
parcalar.append(chunk)
|
||||
okuyan.feed(chunk)
|
||||
if time.time() - son_yazi >= 10:
|
||||
w(b": denetim\n\n"); son_yazi = time.time()
|
||||
metin = okuyan.metin.strip()
|
||||
gecmedi = False
|
||||
if metin and istek:
|
||||
t0 = time.time()
|
||||
puan = jev_denetim.sor(istek, metin, jev_denetim.anahtar(), float(ayar.get("sure") or jev_denetim.SURE))
|
||||
if puan is None:
|
||||
log(f"{tag} adim denetimi: Jev'e sorulamadi, cevap oldugu gibi geciyor")
|
||||
else:
|
||||
gecmedi = jev_denetim.karar(puan, ayar)
|
||||
log(f"{tag} adim denetimi: ret={puan['ret']:.2f} sart={puan['sart']:.2f} teknik={puan['teknik']:.2f} "
|
||||
f"soru={puan['soru']:.2f} -> {'GECMEDI' if gecmedi else 'gecti'} ({time.time() - t0:.1f} sn, {len(metin)} karakter)")
|
||||
if gecmedi:
|
||||
try:
|
||||
yeni = yedek()
|
||||
except Exception as e:
|
||||
yeni = None
|
||||
log(f"{tag} adim denetimi: yedek model acilamadi ({type(e).__name__}: {str(e)[:120]}); ilk cevap veriliyor")
|
||||
if yeni is not None:
|
||||
resp2, rewriter2, tag2 = yeni
|
||||
log(f"{tag} adim denetimi: adim yedek modele verildi -> {tag2}")
|
||||
return self._stream(resp2, resp2.status, tag2, rewriter2, izle, basliksiz=True)
|
||||
for chunk in parcalar:
|
||||
if izle is not None:
|
||||
izle.feed(chunk)
|
||||
w(rewriter.feed(chunk) if rewriter else chunk)
|
||||
if rewriter: w(rewriter.flush())
|
||||
self.wfile.write(b"0\r\n\r\n"); self.wfile.flush()
|
||||
except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
|
||||
log(f"{tag} istemci baglantiyi kapatti (denetim tamponu)")
|
||||
return
|
||||
log(f"{tag} bitti status={resp.status} {sum(map(len, parcalar))} bayt")
|
||||
|
||||
def _stream(self, resp, status, tag, rewriter=None, izle=None, basliksiz=False):
|
||||
if not basliksiz:
|
||||
self._akis_basligi(resp, status)
|
||||
total = 0
|
||||
bas = b"" # ust kaynagin ilk baytlari: cok kisa (bos) akislarin nedeni gunluge yazilir
|
||||
def w(b):
|
||||
@@ -950,7 +1011,8 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return self._send_json(200, {"ok": ZSTD_OK, "surum": SURUM, "kod": KOD, "zstd": ZSTD_OK, "ide_durum": durum,
|
||||
"ide_model": int((sayi.split() or ["0"])[0]),
|
||||
"ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False)),
|
||||
"is_hatti": bool(HAT_KOMUT[0])})
|
||||
"is_hatti": bool(HAT_KOMUT[0]),
|
||||
"adim_denetimi": bool(jev_denetim.ayar_oku(CFG.get("jev_denetim"))[0] and jev_denetim.anahtar())})
|
||||
if self.command == "POST" and path.rstrip("/").endswith("/stop"):
|
||||
self._send_json(200, {"ok": True})
|
||||
log("AITURK IDE istegiyle kapatiliyor")
|
||||
@@ -1487,10 +1549,39 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
"Daha büyük pencereli bir model seçin ya da mesajı kısaltın.")
|
||||
data = json.dumps(req, ensure_ascii=False).encode("utf-8")
|
||||
log(f"{tag} girdi={len(req.get('input', []))} oge, arac={len(req.get('tools', []) or [])} {len(data)} bayt")
|
||||
try:
|
||||
# Istegin bicimi (icerik degil): gecidin reddettigi istegi sonradan ayirt edebilmek icin.
|
||||
son = (req.get("input") or [None])[-1]
|
||||
bicim = {k: v for k, v in req.items() if k not in ("input", "tools", "instructions", "prompt_cache_key")}
|
||||
log(f"{tag} bicim={json.dumps(bicim, ensure_ascii=False)[:420]} son_oge="
|
||||
f"{(son.get('type'), son.get('role')) if isinstance(son, dict) else type(son).__name__}"
|
||||
+ (f" icerik={json.dumps(son, ensure_ascii=False)[:700]}" if isinstance(son, dict) and son.get("type") not in
|
||||
("message", "function_call_output", "custom_tool_call_output", "function_call", "reasoning") else ""))
|
||||
except Exception:
|
||||
pass
|
||||
if DEBUG:
|
||||
open(os.path.join(LOGDIR, f"{n:04d}_req.json"), "wb").write(data)
|
||||
hdrs = {"Authorization": f"Bearer {route['key']}", "Content-Type": "application/json", "Accept": "text/event-stream", "User-Agent": UA}
|
||||
last = None
|
||||
# Adim denetimi (jev_denetim.py): Jev'in gecirmedigi adimi yedek model yapar. Yedek ayni gecitteyse ve
|
||||
# secili model zaten yedek degilse acilir; ozet isteginde ve Jev anahtari yokken kapalidir.
|
||||
jev_yedek = None
|
||||
jev_acik, jev_hedef, jev_ayar = jev_denetim.ayar_oku(CFG.get("jev_denetim"))
|
||||
jev_esleme = MODEL_MAP.get(jev_hedef)
|
||||
if (jev_acik and not ozet_tetik and slug != jev_hedef and jev_esleme and jev_esleme[0] == route_name
|
||||
and rewriter is not None and jev_denetim.anahtar()):
|
||||
jev_istek = jev_denetim.son_istek(req)
|
||||
|
||||
def jev_yedek():
|
||||
yedek_req = json.loads(data)
|
||||
yedek_req["model"] = jev_esleme[1]
|
||||
yanit = urllib.request.urlopen(urllib.request.Request(
|
||||
route["url"], data=json.dumps(yedek_req, ensure_ascii=False).encode("utf-8"), headers=hdrs, method="POST"),
|
||||
timeout=UPSTREAM_IDLE_TIMEOUT)
|
||||
return (yanit, SseRewriter(custom, hedef=jev_esleme[1], tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren),
|
||||
f"[{n} {route_name.upper()} {slug}=>{jev_esleme[1]}]")
|
||||
if not jev_istek:
|
||||
jev_yedek = None
|
||||
# A 9Router combo (Fusion-*, no provider prefix) runs a whole panel upstream. Once it may
|
||||
# have started (timeout, 504, cut stream) resending it runs and bills it twice (28 Sep 2026);
|
||||
# only failures before the work starts (no connection, 500/502/503/529) are retried.
|
||||
@@ -1543,6 +1634,8 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
sonuc = self._stream(resp, resp.status, tag, akis)
|
||||
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
|
||||
return sonuc
|
||||
if jev_yedek is not None and resp.status == 200:
|
||||
return self._denetimli_akis(resp, tag, rewriter, izle, jev_istek, jev_ayar, jev_yedek)
|
||||
return self._stream(resp, resp.status, tag, rewriter, izle)
|
||||
self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}})
|
||||
|
||||
|
||||
Reference in new issue
Block a user